/* POSIX compatible directory access routines for TOS */
/* written by Eric R. Smith and placed in the public domain */

#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <types.h>
#include <stat.h>
#include <errno.h>
#include <dirent.h>
#include <osbind.h>
#include <unistd.h>
#include "lib.h"
#include "symdir.h"
#ifndef _COMPILER_H
#include <compiler.h>
#endif

static char *shadowed __PROTO((const char *name, SYMDIR *dir));

ino_t	__inode;		/* in stat.c */
DIR	*__opendir_chain;	/* ditto */

static
char *shadowed(name, dir)
	const char *name;
	SYMDIR *dir;
{
	SYMENTRY *ent;

	if (!dir)
		return (char *)0;

	ent = dir->s_dir;
	while (ent) {
		if (!strcmp(name, ent->linkto) && (ent->flags & SD_AUTO))
			return ent->linkname;
		ent = ent->next;
	}
	return (char *)0;
}

DIR *opendir(_dirname)
	const char *_dirname;
{
	char dirname[FILENAME_MAX];
	char tmpnam[_LIB_NAME_MAX];
	char *t;
	DIR  *dd;
	struct dirent *d, *x, **last;
	SYMENTRY *lent;
	SYMDIR *ldir;
	long r;
	short i = 0;
	struct _dta mydta, *olddta;

	_unx2dos(_dirname, dirname);

#if 0
/* we should maybe check to see if we're opening a directory */
/* this code doesn't work, though -- Fattrib is dumb */
	if (dirname[0] && (dirname[1] == ':') && !dirname[2]) {
		/* nothing to do */
	} else {
		r = Fattrib(dirname, 0, FA_DIR|FA_HIDDEN|FA_SYSTEM);
		if (r < 0) {
			errno = -r;
			return NULL;
		}
		else if (!(r & FA_DIR)) {
			errno = EPATH;
			return NULL;
		}
	}
#endif

	if (!(dd = malloc((size_t)sizeof(DIR)))) {
	        errno = ENOMEM;
	        return NULL;
	}

	olddta = (struct _dta *)Fgetdta();
	Fsetdta(&mydta);

	d = x = NULL;
	last = &d;
	dd->D_path = strdup(dirname);

/* check for symbolic links in the directory */
	ldir = _read_symdir(dirname);
	if (!ldir)
		goto skip_symlinks;
	lent = ldir->s_dir;

	while (lent) {
		x = malloc((size_t)(DIRENTSIZ(strlen(lent->linkname))));
		if (!x) {
			errno = ENOMEM;
			free(dd->D_path);
			free(dd);
			while(d) {
				x = d->d_next;
				free(d);
				d = x;
			}
			_free_symdir(ldir);
			Fsetdta(olddta);	/* reset dta ++jrb */
			return NULL;
		}
		strcpy(x->d_name, lent->linkname);
		x->d_reclen = x->d_size = strlen(x->d_name);
		x->d_ino = ++__inode;
		x->d_off = i++;
		x->d_next = NULL;
		x->d_date = x->d_time = 0;
		x->d_attribute = 0xff;	/* mark symbolic link */
		*last = x;
		last = &x->d_next;
		lent = lent->next;
	}

/* OK, so much for the symbolic links. Now for the real directories */

skip_symlinks:

	strcat(dirname, "\\*.*");
	if ((r = Fsfirst(dirname, FA_SYSTEM|FA_HIDDEN|FA_DIR)) != 0) {
	/* report an error unless dirname is a root directory     */
	/* (all other valid directories have '.' and '..' in them */
	        if (!*dirname || strcmp(dirname+1, "\\*.*")) {
	                errno = -r;
			while (d) {
				x = d->d_next;
				free(d);
				d = x;
			}
			free(dd->D_path);
	                free(dd);
	                dd = NULL;
		}
	} else {		/* Fsfirst worked OK */
		do {
		        _dos2unx(mydta.dta_name, tmpnam);
		/* skip the symbolic directory itself if we're supposed to */
			if (_lHIDE && _lOK && !strcmp(_lDIR, tmpnam))
				continue;
		/* check for files being hidden by auto symlinks */
			if (_lAUTO && (t = shadowed(tmpnam, ldir))) {
				for (x = d; x; x = x->d_next)
					if (!strcmp(x->d_name, t))
						break;
				assert(x != 0);
		/* put the real file's info in the structure */
				goto tos_fill;
			}
			x = malloc((size_t)(DIRENTSIZ(strlen(tmpnam))));
	        	if (!x) {
				errno = ENOMEM;
				while (d) {
					x = d->d_next;
					free(d);
					d = x;
				}
				free(dd->D_path);
				free(dd);
				dd = NULL;
				break;
		        }
			strcpy(x->d_name, tmpnam);
	        	x->d_ino = ++__inode; /* make sure no two are equal */
		        x->d_off = i++;
/* I don't know what d_reclen means on Unix, but for TOS we might as well
   stuff the string length in here (so sys/dir.h can be more like BSD) */
		        x->d_reclen = strlen(x->d_name);
			x->d_next = NULL;
			*last = x;
			last = &x->d_next;
tos_fill:
/* fill in TOS specific stuff so stat can find it later */
			x->d_time = mydta.dta_time;
			x->d_date = mydta.dta_date;
			x->d_attribute = mydta.dta_attribute;
			x->d_size = mydta.dta_size;
		} while (!Fsnext());
	}

	Fsetdta(olddta);
	if (dd) {
		dd->D_list = dd->D_curpos = d;
		dd->D_nxtdir = __opendir_chain;
		__opendir_chain = dd;
	}
	_free_symdir(ldir);
	return dd;
}

struct dirent *readdir(dirp)
	DIR *dirp;
{
	struct dirent *x;

	if (!dirp) return NULL;

	x = dirp->D_curpos;
	if (x) dirp->D_curpos = x->d_next;
	return x;
}

off_t telldir(dirp)
       DIR *dirp;
{
	struct dirent *x = dirp->D_curpos;
	if (x)
		return x->d_off;
	else
		return -1;
}

void seekdir(dirp, loc)
	DIR *dirp;
	off_t loc;
{
	struct dirent *x;

	x = dirp->D_list;
	while (x && x->d_off != loc)
		x = x->d_next;
	dirp->D_curpos = x;
}

void rewinddir(dirp)
       DIR *dirp;
{
	dirp->D_curpos = dirp->D_list;
}

int closedir(dirp)
	DIR *dirp;
{
	struct dirent *x, *oldx;
	DIR **old, *nxt;

	if (!dirp) return -1;

	if (dirp->D_path)
		free(dirp->D_path);

	for (x = dirp->D_list; x; ) {
		oldx = x;
		x = x->d_next;
		free(oldx);
	}
/* unlink from __opendir_chain */
	old = &__opendir_chain;
	nxt = *old;
	while (nxt) {
		if (nxt == dirp) {
			*old = nxt->D_nxtdir;
			nxt->D_nxtdir = 0;
			break;
		}
		old = &nxt->D_nxtdir;
		nxt = *old;
	}
	free(dirp);
	return 0;
}
