
			DIRLIB for MS-DOS
			-----------------

Enclosed is an implementation of the `dirlib' package for MS-DOS.  
The implementation is targeted for MS-C, although any reasonably
competent C compiler should manage.  The package consists of:

	dir.h		the header file
	dir.c		the functions
	testdir.c	a q&d test program
	
The package tries to view directory naming in a Un*x light; in particular,
directories such as '/.' and '/..' (as well as `.' and `..' if your
current directory is root) are understood.   Indefinite paths like 
`/../.././../..' will correctly refer to the root (of the particular disk).
Names such as `a:////./../' are okay too.

I've tried to be as sensible about DTA's as possible, since you never
know who will be using one; they are set before use, and reset afterwards.

There is some cruft in the package, namely the way `seekdir' and
`telldir' are done.  The code was derived from a little experimentation, 
and may not work after a certain point (although I believe the 2.x version
to be solid).  Caveat utilitor.

Documentation for the package is available in the public domain; the
package's functionality was derived from this documentation.

Bug reports and comments are welcome.  Enjoy!

				- Matt

-------
UUCP:	{ucbvax,ihnp4,randvax,trwrb!trwspp,ism780}!ucla-cs!matt
ARPA:	matt@LOCUS.UCLA.EDU
Ph:	(213) 825-2756

----------------------------  dirlib.h -------------------------------

/* DIRLIB.H by M. J. Weinstein   Released to public domain 1-Jan-89 */

#define MAXNAMLEN	15

struct direct {
	long		d_ino;
	unsigned short 	d_reclen;
	unsigned short 	d_namlen;
	char		d_name[MAXNAMLEN+1];
};

typedef struct {
	struct {
		char	fcb[21];
		char	attr;
		short	time;
		short	date;
		long	size;
		char	name[13];
	} dd_dta;
	short	dd_dosver;	/* which version of dos? */
	short	dd_stat;	/* status return from last lookup */
	char	dd_name[1];	/* full name of file -- struct is extended */
} DIR;

#ifdef LINT_ARGS
	DIR *opendir(char *);
	struct direct *readdir(DIR *);
	long telldir(DIR *);
	void seekdir(DIR *, long);
	void closedir(DIR *);
#else
	DIR *opendir();
	struct direct *readdir();
	long telldir();
#endif LINT_ARGS

#define rewinddir(dirp)	seekdir(dirp,0L)

--------------------------- dirlib.c ---------------------------------

/*
    Directory Access Library

                    DIRLIB.C by M. J. Weinstein
		 Released to public domain 1-Jan-89

    The author may be contacted at: 
	matt@cs.ucla.edu -or- POB 84524, L.A., CA  90073
 */


/*
 * revision history:
 *
 *	VER	MM/DD/YY	COMMENTS
 *	----	--------	--------
 *	0.99	02/24/86	Beta release to INTERNET
 */

#define LINT_ARGS

#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <dos.h>
#include <malloc.h>

#include "dir.h"

#ifdef DEBUG
#	define	PRIVATE
#else
#	define	PRIVATE	static
#endif	DEBUG

PRIVATE _err;

PRIVATE DIR *_findfirst(char *, DIR *);
PRIVATE DIR *_findnext(DIR *);
PRIVATE char far *_getsetdta(char far *);

#ifdef DEBUG

#include <stdio.h>

PRIVATE void _dumpdir(DIR *);

PRIVATE void _dumpdir(dirp)
DIR *dirp;
{
	int i;
	char *cp;

	printf("\ndump of DIR at %xH: fcb:", dirp);
	for (i = 0, cp = (char *)&dirp->dd_dta; 
	    i < sizeof(dirp->dd_dta.fcb); i++, cp++) {
		if ((i%16) == 0)
			printf("\n%3.3d (%3.3xH):  ", i);
		printf("%3d ", (unsigned) *cp & 0xff);
	}
	printf("\nattr %xH  time %d  date %d  size %ld  name %s\n", 
		dirp->dd_dta.attr, dirp->dd_dta.time, 
		dirp->dd_dta.date, dirp->dd_dta.size, dirp->dd_dta.name);
	printf("ver %d  stat %d  pattern %s\n\n", 
		dirp->dd_dosver, dirp->dd_stat, dirp->dd_name);
}

#endif DEBUG

/*
 * return dos major version #
 */

PRIVATE	int dosver()
{
	union REGS r;
	r.h.ah = 0x30;
	intdos(&r, &r);
	return (int)r.h.al;
}

/*
 * get/set dta address
 */

PRIVATE char far *_getsetdta(newdta)
char far *newdta;
{
	char far *olddta;
	union REGS r;
	struct SREGS s;

	/* get old dta */     	
	r.h.ah = 0x2f;
	intdos(&r, &r);
	segread(&s);
	FP_SEG(olddta) = s.es;
	FP_OFF(olddta) = r.x.bx;

	/* conditionally set new dta */
	if (newdta) {
		r.h.ah = 0x1a;
		s.ds	= FP_SEG(newdta);
		r.x.dx	= FP_OFF(newdta);	
		intdosx(&r, &r, &s);
	}

	return olddta;
}

/*
 * dos findfirst
 */

PRIVATE DIR *_findfirst(name, dirp)
char *name;
DIR *dirp;
{
	union REGS r;  
	struct SREGS s;
	char far *dtasave;

	dtasave = _getsetdta((char far *)dirp);
	
	/* do directory lookup */
	segread(&s);
	r.h.ah	= 0x4e;
	r.x.cx	= 0x10;
	r.x.dx	= FP_OFF((char far *)name);
	s.es	= FP_SEG((char far *)name);
	intdosx(&r, &r, &s);
	/* restore dta */
	_getsetdta(dtasave);
	_err = r.x.ax;
	if (r.x.cflag)
		return (DIR *) 0;
#ifdef	DEBUG
	_dumpdir(dirp);
#endif	DEBUG
	return dirp;
}

/*
 * dos findnext
 */

PRIVATE DIR *_findnext(dirp)
DIR *dirp;
{
	union REGS r;  
	struct SREGS s;
	char far *dtasave;

	dtasave = _getsetdta((char far *)dirp);

	/* do directory lookup */
	r.h.ah = 0x4f;
	intdos(&r, &r);
	/* restore old dta */
	_getsetdta(dtasave);
	_err = r.x.ax;
	if (r.x.cflag)
		return (DIR *) 0;
#ifdef	DEBUG
	_dumpdir(dirp);
#endif	DEBUG
	return dirp;
}

/*
 * get working directory for a particular drive
 */

PRIVATE char *getdcwd(drive)
int drive;
{
	union REGS r;
	struct SREGS s;
	static char xcwd[64];
	char far *cwd = xcwd;

	r.h.ah = 0x47;
	r.h.dl = drive;
	r.x.si = FP_OFF(cwd);
	s.ds = FP_SEG(cwd);
	intdosx(&r, &r, &s);
	_err = r.x.ax;
	if (r.x.cflag)
		return (char *) 0;
	return xcwd;
}

/*
 * opendir
 */

#define SUFFIX	"\\*.*"
#define	SLASH	"\\"
#define streq(a,b)	(strcmp(a,b)==0)

DIR *opendir(name)
char *name;
{
	register DIR *nd;
	char *cwd;
	char drive[3];
	int atroot = 0;
	int rooted = 0;

	/*
	 * hack off drive designator if present
	 */

	if (name[1] == ':') {
		cwd = getdcwd(toupper(name[0]) - 'A' + 1);
		drive[0] = name[0]; drive[1] = ':'; drive[2] = '\0';
		name += 2;
	}
	else {
		cwd = getdcwd(0);
		drive[0] = '\0';
	}

#ifdef	DEBUG
	printf("working on drive %s = /%s\n", drive, cwd);
#endif	DEBUG

	/* is the name 'rooted'? */
	if ((*name == '/') || (*name == '\\')) ++rooted;

	/* see if we are at the root directory for this device */
	if (!*cwd) ++atroot;

	/* 
	 * MSDOS '/' doesn't have a '.' or '..'
	 * also, double '/' sequences don't make sense.
	 * many ported programs expect them to work, so we fix it up...
	 */

	/* chop off leading . and .. if at root */
	if (atroot && (*name == '.')) {
		switch (*++name) {
		case '\0': case '/': case '\\':
			break;
		case '.':
			switch (*++name) {
			case '\0': case '/': case '\\':
				break;
			default:
				--name; 
				--name;
			}
			break;
		default:
			--name;
		}
	}

#ifdef DEBUG
	printf("after chopping leading .'s: %s\n", name);
#endif DEBUG

	/* chop off leading /'s, /.'s and /..'s to make naming sensible */
	while (*name && ((*name == '/') || (*name == '\\'))) {
		if (*++name == '.') {
			switch (*++name) {
			case '\0': case '/': case '\\':
				break;
			case '.':
				switch (*++name) {
				case '\0': case '/': case '\\':
					break;
				default:
					--name; 
					--name;
				}
				break;
			default:
				--name;
			}
		}
	}

#ifdef DEBUG
	printf("after chopping /'s: %s\n", name);
#endif DEBUG

	/*
	 * name should now look like: path/path/path 
	 * we must now construct name based on whether or not it
	 * was 'rooted' (started with a /)
	 */

	if (rooted) cwd = "";

	/* construct DIR */
	if (!(nd = (DIR *)malloc(
	    sizeof(DIR)+strlen(drive)+strlen(cwd)+strlen(SLASH)+
	    strlen(name)+strlen(SUFFIX))))
		return (DIR *) 0;
	/* create long name */
	strcpy(nd->dd_name, drive);
	if (*cwd) {
		strcat(nd->dd_name, SLASH);
		strcat(nd->dd_name, cwd);
	}
	if (*name) {
		strcat(nd->dd_name, SLASH);
		strcat(nd->dd_name, name);
	}
	strcat(nd->dd_name, SUFFIX);

#ifdef	DEBUG
	printf("calling findfirst(%s)\n", nd->dd_name);
#endif	DEBUG

	/* search */
	if (!_findfirst(nd->dd_name, nd)) {
		free((char *)nd);
		errno = ENOENT;
		return (DIR *) 0;
	}
	nd->dd_stat = 0;
	nd->dd_dosver = dosver();
	return nd;
}

struct direct *readdir(dirp)
DIR *dirp;
{
	static struct direct dir;

	if (dirp->dd_stat)
		return (struct direct *) 0;

	/* format structure */
	dir.d_ino = 0; /* not valid for DOS */
	dir.d_reclen = 0;
	strcpy(dir.d_name, dirp->dd_dta.name);
	dir.d_namlen = strlen(dir.d_name);
	strlwr(dir.d_name); /* DOSism */

	/* read ahead */
	if (_findnext(dirp))
		dirp->dd_stat = 0;
	else		
		dirp->dd_stat = _err;

	return &dir;
}

void closedir(dirp)
DIR *dirp;
{
	free((char *)dirp);
}

/*
 * fake seek for DOS 2.x
 */

PRIVATE void dos2seek(dirp, pos)
DIR *dirp;
long pos;
{
	/*
	 * check against DOS limits
	 */

	if ((pos < 0) || (pos > 4095)) {
		dirp->dd_stat = 1;
		return;
	}

	if (pos == 0) {
		if (_findfirst(dirp->dd_name, dirp))
			dirp->dd_stat = 0;
		else
			dirp->dd_stat = _err;
	}
	else {
		pos--;
		dirp->dd_dta.fcb[1] = 2 - (pos % 3);
		*(short *)&dirp->dd_dta.fcb[13] = pos;
		/* read ahead */
		if (_findnext(dirp))
			dirp->dd_stat = 0;
		else		
			dirp->dd_stat = _err;
	}
}

/*
 * fake seek for DOS 3.x
 */

PRIVATE void dos3seek(dirp, pos)
DIR *dirp;
long pos;
{
	/*
	 * check against DOS limits
	 */

	if ((pos < 0) || (pos > 4095)) {
		dirp->dd_stat = 1;
		return;
	}

	*(short *)&dirp->dd_dta.fcb[13] = pos + 1;
	/* read ahead */
	if (_findnext(dirp))
		dirp->dd_stat = 0;
	else		
		dirp->dd_stat = _err;
}

void seekdir(dirp, newpos)
DIR *dirp;
long newpos;
{
	switch (dirp->dd_dosver) {
	case 2:
		dos2seek(dirp, newpos);
		break;
	case 3:
		dos3seek(dirp, newpos);
		break;
	default:
		abort();
		break;
	}
}

PRIVATE long dos2tell(dirp)
DIR *dirp;
{
	return (long) *(short *)&dirp->dd_dta.fcb[13];

}

PRIVATE long dos3tell(dirp)
DIR *dirp;
{
	return (long) (*(short *)&dirp->dd_dta.fcb[13] - 2);
}

long telldir(dirp)
DIR *dirp;
{
	switch (dirp->dd_dosver) {
	case 2:
		return dos2tell(dirp);
	case 3:
		return dos3tell(dirp);
	default:
		abort();
	}
}

----------------------- testdir.c follows ------------------
/*
    Directory Access Library Test Program

	           TESTDIR.C by M. J. Weinstein
		Released to public domain 1-Jan-89

    The author may be contacted at: 
	matt@cs.ucla.edu -or- POB 84524, L.A., CA  90073
 */

#define	LINT_ARGS

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "dir.h"

#define	streqi(a,b)	(strcmpi(a,b) == 0)

main()
{
	char *token;
	char buff[128];
	DIR *dir = 0;
	struct direct *dp;

	while (1) {
		printf("> ");
		if (!gets(buff)) break;
		if (!(token = strtok(buff, " \t\n"))) {
			printf("eh?\n");
		}
		else if (streqi("open", token)) {
			if (dir) {
				closedir(dir);
				dir = 0;
				printf("previous directory closed\n");
			}
			if (!(token = strtok(NULL, "\n"))) {
				printf("please specify directory name\n");
			}
			else if (!(dir = opendir(token))) {
				perror(token);
			}
			else {
				printf("directory %s opened successfully\n", token);
			}
		}
		else if (streqi("close", token)) {
			if (!dir) {
				printf("no directory open\n");
			}
			else {
				closedir(dir);
				dir = 0;
				printf("directory closed\n");
			}
		}
		else if (streqi("rew", token)) {
			if (!dir) {
				printf("no directory open\n");
			}
			else {
				rewinddir(dir);
				printf("directory rewind completed\n");
			}
		}
		else if (streqi("seek", token)) {
			if (!dir) {
				printf("no directory open\n");
			}
			else if (!(token = strtok(NULL, "\n"))) {
				printf("specify an entry number\n");
			}
			else {
				seekdir(dir, atol(token));
				printf("seek completed\n");
			}
		}
		else if (streqi("read", token)) {
			if (!dir) {
				printf("no directory open\n");
			}
			else if (!(dp = readdir(dir))) {
				perror("couldn't read entry");
			}
			else {
				printf("ino=%2ld reclen=%2d namlen=%2d name=%s\n",
					dp->d_ino, dp->d_reclen, dp->d_namlen,
					dp->d_name);
			}
		}
		else if (streqi("tell", token)) {
			if (!dir) {
				printf("no directory open\n");
			}
			else {
				printf("positioned at %ld\n", telldir(dir));
			}
		}
		else if (streqi("dir", token)) {
			if (!dir) {
				printf("no directory open\n");
			}
			else {
				while (dp = readdir(dir)) {
					printf("%-16.16s", dp->d_name);
				}
				putchar('\n');
			}
		}
		else if (streqi("cd", token)) {
			if (!(token = strtok(NULL, "\n"))) {
				printf("specify a new directory\n");
			}
			else if (chdir(token) < 0) {
				perror(token);
			}
			else {
				char buff[128];
				printf("new directory is %s\n", getcwd(buff, sizeof(buff)));
			}
		}
		else if (streqi("quit", token) || streqi("exit", token)) {
			break;
		}
		else {
			printf("unknown command: %s\n", token);
		}
	}
}
