/*****************************************************************************
 * $Id: dirent2.c,v 1.10 1995/04/28 22:40:36 ak Exp $
 *****************************************************************************
 * $Log: dirent2.c,v $
 * Revision 1.10  1995/04/28  22:40:36  ak
 * Cleanup. Make dirent2 layout compatible to EMX to avoid trouble
 * with include-files. Moved struct defs to implementation.
 *
 * Revision 1.9  1994/11/18  22:39:48  ak
 * Use a request count of 100 to avoid LS20 bug.
 *
 * Revision 1.8  1994/02/17  11:57:06  ak
 * Bugfix upper/lowercase conversion.
 *
 * Revision 1.7  1994/02/14  14:07:26  edvkai
 * Added abs_path (like dirent.c), it's needed in some places.
 *
 * Revision 1.6  1994/02/08  14:59:51  edvkai
 * fstype caching.
 * different buffer sizes.
 * no double slashes
 *
 * Revision 1.5  1994/01/05  13:46:36  edvkai
 * Old openxdir included directories by default, so dirent2 should do as well.
 *
 * Revision 1.4  1994/01/03  12:30:46  edvkai
 * *** empty log message ***
 *
 * Revision 1.3  1994/01/03  12:30:21  edvkai
 * *** empty log message ***
 *
 * Revision 1.2  1993/12/30  18:11:37  edvkai
 * opendir omitted directories.
 *
 * Revision 1.1  1993/12/30  17:58:15  edvkai
 * Initial revision
 *
 *****************************************************************************/

#ifdef OS2

static char *rcsid = "$Id: dirent2.c,v 1.10 1995/04/28 22:40:36 ak Exp $";

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

#include <dirent.h>
#include <errno.h>

#ifndef __EMX__
#include <libx.h>
#endif

#define INCL_DOSFILEMGR
#define INCL_DOSERRORS
#include <os2.h>

#if OS2 >= 2
# define FFBUF	FILEFINDBUF3
# define Word	ULONG
  /*
   * LS20 recommends a request count of 100, but according to the
   * APAR text it does not lead to missing files, just to funny
   * numbers of returned entries.
   *
   * LS30 HPFS386 requires a count greater than 2, or some files
   * are missing (those starting with a character less that '.').
   *
   * Novell looses entries which overflow the buffer. In previous
   * versions of dirent2, this could have lead to missing files
   * when the average length of 100 directory entries was 40 bytes
   * or more (quite unlikely for files on a Novell server).
   *
   * Conclusion: Make sure that the entries all fit into the buffer
   * and that the buffer is large enough for more than 2 entries
   * (each entry is at most 300 bytes long). And ignore the LS20
   * effect.
   */
# define Count	25
# define BufSz	(25 * (sizeof(FILEFINDBUF3)+1))
#else
# define FFBUF	FILEFINDBUF
# define Word	USHORT
# define BufSz	1024
# define Count	3
#endif

#if defined(__IBMC__)
  #define error(rc) _doserrno = rc, errno = EOS2ERR
#elif defined(MICROSOFT)
  #define error(rc) _doserrno = rc, errno = 255
#else
  #define error(rc) errno = 255
#endif

struct _dirdescr {
	HDIR		handle;		/* DosFindFirst handle */
	char		fstype;		/* filesystem type */
	Word		count;		/* valid entries in <ffbuf> */
	long		number;		/* absolute number of next entry */
	int		index;		/* relative number of next entry */
	FFBUF *		next;		/* pointer to next entry */
	char		name[MAXPATHLEN+3]; /* directory name */
	unsigned	attrmask;	/* attribute mask for seekdir */
	struct dirent	entry;		/* buffer for directory entry */
	BYTE		ffbuf[BufSz];
};

/*
 * Return first char of filesystem type, or 0 if unknown.
 */
static char
getFSType(const char *path)
{
	static char cache[1+26];
	char drive[3], info[512];
	Word unit, infolen;
	char r;

	if (isalpha(path[0]) && path[1] == ':') {
		unit = toupper(path[0]) - '@';
		path += 2;
	} else {
		ULONG driveMap;
#if OS2 >= 2
		if (DosQueryCurrentDisk(&unit, &driveMap))
#else
		if (DosQCurDisk(&unit, &driveMap))
#endif
			return 0;
	}

	if ((path[0] == '\\' || path[0] == '/')
	 && (path[1] == '\\' || path[1] == '/'))
		return 0;

	if (cache [unit])
		return cache [unit];

	drive[0] = '@' + unit;
	drive[1] = ':';
	drive[2] = '\0';
	infolen = sizeof info;
#if OS2 >= 2
	if (DosQueryFSAttach(drive, 0, FSAIL_QUERYNAME, (PVOID)info, &infolen))
		return 0;
	if (infolen >= sizeof(FSQBUFFER2)) {
		FSQBUFFER2 *p = (FSQBUFFER2 *)info;
		r = p->szFSDName[p->cbName];
	} else
#else
	if (DosQFSAttach((PSZ)drive, 0, FSAIL_QUERYNAME, (PVOID)info, &infolen, 0))
		return 0;
	if (infolen >= 9) {
		char *p = info + sizeof(USHORT);
		p += sizeof(USHORT) + *(USHORT *)p + 1 + sizeof(USHORT);
		r = *p;
	} else
#endif
		r = 0;
	return cache [unit] = r;
}

char *
abs_path(const char *name, char *buffer, int len)
{
	char buf[4];
	if (isalpha(name[0]) && name[1] == ':' && name[2] == '\0') {
		buf[0] = name[0];
		buf[1] = name[1];
		buf[2] = '.';
		buf[3] = '\0';
		name = buf;
	}
#if OS2 >= 2
	if (DosQueryPathInfo(name, FIL_QUERYFULLNAME, buffer, len))
#else
	if (DosQPathInfo((PSZ)name, FIL_QUERYFULLNAME, (PBYTE)buffer, len, 0L))
#endif
		return NULL;
	return buffer;
}

DIR *
openxdir(const char *path, unsigned att_mask)
{
	DIR *dir;
	char name[MAXPATHLEN+3];
	Word rc;

	dir = malloc(sizeof(DIR));
	if (dir == NULL) {
		errno = ENOMEM;
		return NULL;
	}

	strncpy(name, path, MAXPATHLEN);
	name[MAXPATHLEN] = '\0';
	switch (name[strlen(name)-1]) {
	default:
		strcat(name, "\\");
	case '\\':
	case '/':
	case ':':
		;
	}
	strcat(name, ".");
	if (!abs_path(name, dir->name, MAXPATHLEN+1))
		strcpy(dir->name, name);
	if (dir->name[strlen(dir->name)-1] == '\\')
		strcat(dir->name, "*");
	else
		strcat(dir->name, "\\*");

	dir->fstype = getFSType(dir->name);
	dir->attrmask = att_mask | A_DIR;

	dir->handle = HDIR_CREATE;
	dir->count = 100;
#if OS2 >= 2
	rc = DosFindFirst(dir->name, &dir->handle, dir->attrmask,
		dir->ffbuf, sizeof dir->ffbuf, &dir->count, FIL_STANDARD);
#else
	rc = DosFindFirst((PSZ)dir->name, &dir->handle, dir->attrmask,
		(PFILEFINDBUF)dir->ffbuf, sizeof dir->ffbuf, &dir->count, 0);
#endif
	switch (rc) {
	default:
		free(dir);
		error(rc);
		return NULL;
	case NO_ERROR:
	case ERROR_NO_MORE_FILES:
		;
	}

	dir->number = 0;
	dir->index = 0;
	dir->next = (FFBUF *)dir->ffbuf;

	return (DIR *)dir;
}

DIR *
opendir(const char *pathname)
{
	return openxdir(pathname, 0);
}

struct dirent *
readdir(DIR *dir)
{
	static int dummy_ino = 2;

	if (dir->index == dir->count) {
		Word rc;
		dir->count = 100;
#if OS2 >= 2
		rc = DosFindNext(dir->handle, dir->ffbuf,
			sizeof dir->ffbuf, &dir->count);
#else
		rc = DosFindNext(dir->handle, (PFILEFINDBUF)dir->ffbuf,
			sizeof dir->ffbuf, &dir->count);
#endif
		if (rc) {
			error(rc);
			return NULL;
		}

		dir->index = 0;
		dir->next = (FFBUF *)dir->ffbuf;
	}

	if (dir->index == dir->count)
		return NULL;

	memcpy(dir->entry.d_name, dir->next->achName, dir->next->cchName);
	dir->entry.d_name[dir->next->cchName] = '\0';
	dir->entry.d_ino = dummy_ino++;
	dir->entry.d_reclen = dir->next->cchName;
	dir->entry.d_namlen = dir->next->cchName;
	dir->entry.d_size = dir->next->cbFile;
	dir->entry.d_attribute = dir->next->attrFile;
	dir->entry.d_time = *(USHORT *)&dir->next->ftimeLastWrite;
	dir->entry.d_date = *(USHORT *)&dir->next->fdateLastWrite;

	switch (dir->fstype) {
	case 'F': /* FAT */
	case 'C': /* CDFS */
		if (dir->next->attrFile & FILE_DIRECTORY)
			strupr(dir->entry.d_name);
		else
			strlwr(dir->entry.d_name);
	}

#if OS2 >= 2
	dir->next = (FFBUF *)((BYTE *)dir->next + dir->next->oNextEntryOffset);
#else
	dir->next = (FFBUF *)((BYTE *)dir->next->achName + dir->next->cchName + 1);
#endif
	++dir->number;
	++dir->index;

	return &dir->entry;
}

long
telldir(DIR *dir)
{
	return dir->number;
}

void
seekdir(DIR *dir, long off)
{
	if (dir->number > off) {
		char name[MAXPATHLEN+2];
		Word rc;

		DosFindClose(dir->handle);

		strcpy(name, dir->name);
		strcat(name, "*");

		dir->handle = HDIR_CREATE;
		dir->count = 32767;
#if OS2 >= 2
		rc = DosFindFirst(name, &dir->handle, dir->attrmask,
			dir->ffbuf, sizeof dir->ffbuf, &dir->count, FIL_STANDARD);
#else
		rc = DosFindFirst((PSZ)name, &dir->handle, dir->attrmask,
			(PFILEFINDBUF)dir->ffbuf, sizeof dir->ffbuf, &dir->count, 0);
#endif
		switch (rc) {
		default:
			error(rc);
			return;
		case NO_ERROR:
		case ERROR_NO_MORE_FILES:
			;
		}

		dir->number = 0;
		dir->index = 0;
		dir->next = (FFBUF *)dir->ffbuf;
	}

	while (dir->number < off && readdir(dir))
		;
}

void
closedir(DIR *dir)
{
	DosFindClose(dir->handle);
	free(dir);
}

/*****************************************************************************/

#ifdef TEST

main(int argc, char **argv)
{
	int i;
	DIR *dir;
	struct dirent *ep;

	for (i = 1; i < argc; ++i) {
		dir = opendir(argv[i]);
		if (!dir)
			continue;
		while (ep = readdir(dir))
			if (strchr("\\/:", argv[i] [strlen(argv[i]) - 1]))
				printf("%s%s\n", argv[i], ep->d_name);
			else
				printf("%s/%s\n", argv[i], ep->d_name);
		closedir(dir);
	}

	return 0;
}

#endif

#endif /* OS2 */

