/*
 *  dirent.c - POSIX directory access routines for MS-DOS and OS/2
 *
 *  Author: Frank Whaley (few@well.sf.ca.us)
 *
 *  Copyright Frank Whaley 1992.  All rights reserved.
 *
 *  Permission to use, copy, modify, distribute, and sell this software
 *  and its documentation for any purpose is hereby granted without fee,
 *  provided that the above copyright notice appears in all copies of the
 *  source code.  The name of the author may not be used to endorse or
 *  promote products derived from this software without specific prior
 *  written permission.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 *  CAVEATS:
 *    The associated 'dirent.h' file should be copied into your system
 *    include directory if the '#include <dirent.h>' syntax will be used,
 *    otherwise a '-I.' switch must be added to command lines.
 *
 *    This code was originally developed with Turbo C, and continues to
 *    use TC's function and structure names.  Numerous macros make the
 *    code palatable to MSC 5.1/6.0 for MS-DOS and OS/2.  The macros
 *    depend on four defines: __TURBOC__, __MSC__, __MSDOS__, and __OS2__.
 *    The TC and BC compilers provide __TURBOC__ and __MSDOS__ as
 *    appropriate; MSC doesn't provide any of these flags so they must
 *    be given on the command line.  Sample commands for building test
 *    programs (see '#ifdef TEST' below):
 *      tcc -DTEST dirent.c
 *      cl -DTEST -D__MSC__ -D__MSDOS__ dirent.c
 *      cl -Lp -DTEST -D__MSC__ -D__OS2__ dirent.c
 *
 *    This code reads an entire directory into memory, and thus is not
 *    a good choice for scanning very large directories.
 *
 *    POSIX requires that the rewinddir() function re-scan the directory,
 *    so this code must preserve the original directory name.  If the
 *    name given is a relative path (".", "..", etc.) and the current
 *    directory is changed, moved, or deleted between opendir() and
 *    rewinddir(), a different directory will be scanned by rewinddir().
 *    The directory name could be qualified by opendir(), but this process
 *    yields unusable names for network drives.
 *
 *    This code provides only filenames, as that is all that is required
 *    by POSIX.  Considerable other information is available from the
 *    MS-DOS and OS/2 directory search functions.  This package should not
 *    be considered as a general-purpose directory scanner, but rather as
 *    a tool to simplify porting other programs.
 */

#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifndef S_ISDIR
#define S_ISDIR(n) ((n)&S_IFDIR)
#endif

#ifdef __TURBOC__
#	include <alloc.h>
#	include <dir.h>
#	include <mem.h>
	typedef struct ffblk FIND_T;
#	define findclose(f)
#	define FA_RDONLY	0x01
#	define FA_HIDDEN	0x02
#	define FA_SYSTEM	0x04
#	define FA_LABEL		0x08
#	define FA_DIREC		0x10
#	define FA_ARCH		0x20
#endif

#if defined(__MSC__) && defined(__MSDOS__)
#	include <dos.h>
#	include <malloc.h>
#	include <memory.h>
	typedef struct find_t FIND_T;
#	define findfirst(n,f,a)	_dos_findfirst(n,a,f)
#	define findnext(f)	_dos_findnext(f)
#	define findclose(f)
#	define ff_name		name
#	define FA_RDONLY	_A_RDONLY
#	define FA_HIDDEN	_A_HIDDEN
#	define FA_SYSTEM	_A_SYSTEM
#	define FA_LABEL		_A_VOLID
#	define FA_DIREC		_A_SUBDIR
#	define FA_ARCH		_A_ARCH
#endif

#if defined(__MSC__) && defined(__OS2__)
#	define INCL_DOS
#	include <os2.h>
#	include <malloc.h>
#	include <memory.h>
	typedef struct {
		HDIR ft_handle;
		FILEFINDBUF ft_ffb;
		int ft_count;
	} FIND_T;
#	define findfirst(n,f,a)	\
		( \
			(f)->ft_handle = HDIR_CREATE, \
			(f)->ft_count = 1, \
			DosFindFirst(n, &(f)->ft_handle, a, &(f)->ft_ffb, \
				sizeof((f)->ft_ffb), &(f)->ft_count, 0L \
			) \
		)
#	define findnext(f) \
		DosFindNext( \
			(f)->ft_handle, &(f)->ft_ffb, \
			sizeof((f)->ft_ffb), &(f)->ft_count \
		)
#	define findclose(f)	DosFindClose((f)->ft_handle);
#	define ff_name		ft_ffb.achName
#	define FA_RDONLY	0x01
#	define FA_HIDDEN	0x02
#	define FA_SYSTEM	0x04
#	define FA_LABEL		0x08
#	define FA_DIREC		0x10
#	define FA_ARCH		0x20
#endif

	/*  mask for all interesting files  */
#define ALL	(FA_RDONLY+FA_HIDDEN+FA_SYSTEM+FA_DIREC)

struct dirnames {
	struct dirnames *next;	/* next name in directory */
	char name[1];		/* null terminated name; may be larger */
};

struct __DIRENT {
	struct dirnames *names;	/* list of names */
	struct dirnames const *current;	/* position (0 if at directory end) */
	char path[1];		/* null terminated pathname; may be larger */
};

static char const pattern[] = "\\*.*";

/* forward declarations */
static int loadDir(DIR *dir);
static void freenames(DIR *dir);

/* Open the directory NAME for reading.  */
	DIR *
opendir(char const *name)
{
	DIR *dir;

	if (!*name) {
		errno = ENOENT;
		return 0;
	}

	if (!(dir = malloc(sizeof(DIR) + strlen(name) + sizeof(pattern)-1))) {
		errno = ENOMEM;
		return 0;
	}

	strcpy(dir->path, name);
	if (loadDir(dir) != 0) {
		int e = errno;
		free(dir);
		errno = e;
		return 0;
	}

	return dir;
}

/* Close the directory DIR.  */
	int
closedir(DIR *dir)
{
	freenames(dir);
	free(dir);
	return 0;
}

/* Yield the next entry in the directory DIR.  */
	struct dirent *
readdir(DIR *dir)
{
	struct dirnames const *c = dir->current;
	if (!c)
		return 0;
	dir->current = c->next;
	return (struct dirent *) c->name;
}

/* Rewind the directory DIR; this requires rereading it.  */
	void
rewinddir(DIR *dir)
{
	freenames(dir);
	loadDir(dir);
}

/* Load the directory DIR from disk.  */
	static int
loadDir(DIR *dir)
{
	char *path = dir->path,  *pathend = path + strlen(path);
	int mode;
	FIND_T ff;
	struct stat statb;

	/* Is it a directory?  */
	if (stat(path, &statb) != 0)
		return -1;
	if (! S_ISDIR(statb.st_mode)) {
		errno = ENOTDIR;
		return -1;
	}

	/* Build pattern string.  */
	strcpy(pathend,  pattern  +  !!strchr("\\/:", pathend[-1]));

	if (findfirst(path, &ff, ALL) == 0) {
		struct dirnames **np = &dir->names;
		do {
			/*  Add name if not "." or ".."  */
			if (ff.ff_name[0]!='.'  ||
			    ff.ff_name[1] && (ff.ff_name[1]!='.'||ff.ff_name[2])
			) {
				struct dirnames *p = malloc(
					sizeof(struct dirnames)+strlen(ff.ff_name)
				);
				if (!p) {
					freenames(dir);
					errno = ENOMEM;
					*pathend = 0;
					return -1;
				}
				strcpy(p->name, ff.ff_name);
				p->next = 0;
				*np = p;
				np = &p->next;
			}
		} while (findnext(&ff) == 0);
	}
	findclose(&ff);
	dir->current = dir->names;
	*pathend = 0;
	return 0;
}

/* Free all names in the directory DIR.  */
	static void
freenames(DIR *dir)
{
	struct dirnames *o = dir->names, *n;
	for (o = dir->names;  o;  o = n) {
		n = o->next;
		free(o);
	}
}

#ifdef TEST
	int
main(int argc, char *argv[])
{
	DIR *dir;
	struct dirent const *d;
	long pos;

	/* Check arguments.  */
	if (argc != 2) {
		fprintf(stderr, "Usage: dirent <directory>\n");
		return 1;
	}

	/* Try to open the given directory.  */
	if (!(dir = opendir(argv[1]))) {
		fprintf(stderr, "cannot open %s\n", argv[1]);
		return 1;
	}

	/* Walk the directory once forward.  */
	while ((d = readdir(dir)))
		printf("%s\n", d->d_name);

	/* Rewind.  */
	rewinddir(dir);

	/* Scan to the end again.  */
	while ((d = readdir(dir)))
		continue;

	/* Close and exit.  */
	printf("closedir() returns %d\n", closedir(dir));
	return 0;
}
#endif
