/* 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, olddta;
	char name[PATH_MAX];
	char dirpath[PATH_MAX];

	_unx2dos(uname, name);

	if (__mint > 8) {
		r = Dopendir(name, 0);
		if ( (r & 0xff000000) == 0xff000000 ) {
			errno = -r;
			return 0;
		}
		return (DIR *)r;
	}

/* TOS emulation routines */

	d = malloc(sizeof(DIR));
	if (!d) {
		errno = ENOMEM;
		return d;
	}

	strcat(name, "\\*.*");
	olddta = Fgetdta();
	Fsetdta(d->dta);
	r = Fsfirst(name, 0xff);
	Fsetdta(olddta);

	if (r == 0) {
		d->status = _STARTSEARCH;
	} else if (r == -ENOENT) {
		d->status = _NMFILE;
	} else {
		free(d);
		errno = -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, olddta;
	struct dirent *dd = &d->buf;

	if (__mint > 8) {
		r = Dreaddir(NAME_MAX+1+sizeof(long), d, &dbuf);
		if (r == -ENMFIL)
			return 0;
		else if (r) {
			errno = -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 = -r;
			return 0;
		}
	}
	dd->d_ino = __inode++;
	dd->d_off++;
	_dos2unx(d->dta+30, dd->d_name);
	dd->d_reclen = strlen(dd->d_name);
	return dd;
}

void
rewinddir(dirp)
	DIR *dirp;
{
	long r, 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, 0xff);
	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 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;
	}
}
