/* opendir/readdir/closedir routines */

/* under MiNT (v0.9 or better) these use the appropriate system calls.
 * under TOS or older versions of MiNT, they use Fsfirst/Fsnext
 *
 * Written by Eric R. Smith and placed in the public domain
 */

#include <stdlib.h>
#include <string.h>
#include <types.h>
#include <limits.h>
#include <dirent.h>
#include <errno.h>
#include <osbind.h>
#include <mintbind.h>
#include "lib.h"

extern int __mint;
extern ino_t __inode;	/* in stat.c */

DIR *
opendir(uname)
	const char *uname;
{
	DIR *d;
	long r;
	_DTA *olddta;
	char name[PATH_MAX];
	char dirpath[PATH_MAX];
	char *p;

	_unx2dos(uname, name);

	if (__mint > 8) {
		r = Dopendir(name, 0);
		if ( (r & 0xff000000L) == 0xff000000L ) {
			errno = (int) -r;
			return 0;
		}
		return (DIR *)r;
	}

/* TOS emulation routines */

	d = malloc(sizeof(DIR));
	if (!d) {
		errno = ENOMEM;
		return d;
	}

	p = name;
	if (p) {
	/* find the end of the string */
		for (p = name; *p; p++) ;

	/* make sure the string ends in '\' */
		if (*(p-1) != '\\') {
			*p++ = '\\';
		}
	}

	strcpy(p, "*.*");
	olddta = Fgetdta();
	Fsetdta(&(d->dta));
	r = Fsfirst(name, 0x17);
	Fsetdta(olddta);

	if (r == 0) {
		d->status = _STARTSEARCH;
	} else if (r == -ENOENT) {
		d->status = _NMFILE;
	} else {
		free(d);
		errno = (int) -r;
		return 0;
	}
	d->buf.d_off = 0;
/* for rewinddir: if necessary, build a relative path */
	if (name[1] == ':') {	/* absolute path, no problem */
		dirpath[0] = 0;
	} else {
		dirpath[0] = Dgetdrv() + 'A';
		dirpath[1] = ':';
		dirpath[2] = 0;
		if (*name != '\\')
			(void)Dgetpath(dirpath+2, 0);
	}
	d->dirname = malloc(strlen(dirpath)+strlen(name)+1);
	if (d->dirname) {
		strcpy(d->dirname, dirpath);
		strcat(d->dirname, name);
	}
	return d;
}

struct dirent *
readdir(d)
	DIR *d;
{
	struct dbuf {
		long ino;
		char name[NAME_MAX + 1];
	} dbuf;
	long r;
	_DTA *olddta;
	struct dirent *dd = &d->buf;

	if (__mint > 8) {
		r = (int)Dreaddir((int)(NAME_MAX+1+sizeof(long)), d, (char *) &dbuf);
		if (r == -ENMFIL)
			return 0;
		else if (r) {
			errno = (int) -r;
			return 0;
		}
		dd->d_ino = dbuf.ino;
		dd->d_off++;
		dd->d_reclen = strlen(dbuf.name);
		strcpy(dd->d_name, dbuf.name);
		return dd;
	}
/* ordinary TOS search, using Fsnext. Note that the first time through,
 * Fsfirst has already provided valid data for us; for subsequent
 * searches, we need Fsnext.
 */
	if (d->status == _NMFILE)
		return 0;
	if (d->status == _STARTSEARCH) {
		d->status = _INSEARCH;
	} else {
		olddta = Fgetdta();
		Fsetdta(&(d->dta));
		r = Fsnext();
		Fsetdta(olddta);
		if (r == -ENMFIL) {
			d->status = _NMFILE;
			return 0;
		} else if (r) {
			errno = (int)-r;
			return 0;
		}
	}
	dd->d_ino = __inode++;
	dd->d_off++;
	_dos2unx(d->dta.dta_name, dd->d_name);
	dd->d_reclen = strlen(dd->d_name);
	return dd;
}

void
rewinddir(dirp)
	DIR *dirp;
{
	long r;
	_DTA *olddta;

	if (__mint >= 9) {
		(void)Drewinddir(dirp);
		return;
	}

/* I wish POSIX had allowed an error to occur here! */
	if (!dirp->dirname) {
		return;
	}

	olddta = Fgetdta();
	Fsetdta(&(dirp->dta));
	r = Fsfirst(dirp->dirname, 0x17);
	Fsetdta(olddta);
	if (r == 0) {
		dirp->status = _STARTSEARCH;
	} else {
		dirp->status = _NMFILE;
	}
	dirp->buf.d_off = 0;
}

int
closedir(dirp)
	DIR *dirp;
{
	if (__mint > 8) {
		return (int)Dclosedir(dirp);
	}
	free(dirp);
	return 0;
}

/* the next two aren't POSIX, but lots of people have them */

off_t
telldir(dirp)
	DIR *dirp;
{
	return dirp->buf.d_off;
}

void
seekdir(dirp, loc)
	DIR *dirp;
	off_t loc;
{
	rewinddir(dirp);
	while (dirp->buf.d_off != loc) {
		if (!readdir(dirp))
			break;
	}
}
