/*
 *
 * HiLandR's new list command
 *
 */

#define __USE_SYSBASE

#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <libraries/multiuser.h>
#include <utility/utility.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/multiuser.h>

#include <clib/alib_stdio_protos.h>

#include <dos.h>
#include <strings.h>
#include <stdlib.h>

#define DOSLIB "dos.library"
#ifndef DOSVER
#define DOSVER 39L    /*  we require AT LEAST release 3.0 of OS */
#else
#ifdef LIBPOOL
APTR __stdargs LibAllocPooled(APTR, unsigned long);
APTR __stdargs LibCreatePool(unsigned long, unsigned long, unsigned long);
void __stdargs LibDeletePool(APTR);
void __stdargs LibFreePooled(APTR, APTR, unsigned long);
#define AllocPooled LibAllocPooled
#define CreatePool LibCreatePool
#define DeletePool LibDeletePool
#define FreePooled LibFreePooled
#endif
#endif


void _XCEXIT(long dummy);

LONG __regargs readmatchdir(STRPTR);
LONG __regargs printdir(struct FileInfoBlock **);
void __regargs readsoftlink(STRPTR, STRPTR, LONG);
void __regargs itok(ULONG, STRPTR);
UWORD __regargs max(UWORD, UWORD);
void __regargs dirinfo(ULONG, ULONG, ULONG, ULONG);
BOOL __regargs scanrecurse(STRPTR, LONG *, LONG *);

STRPTR Template = "Dir/M,ViewDir=VD/S,"
	"Icons=IC/S,Comments=CM/S,Sizes=SZ/S,Dates=DT/S,Secs=SC/S,Full=FL/S,NoLinks=NL/S,"
	"NoSort=NS/S,SortDate=SD/S,SortSize=SS/S,FirstDirs=FD/S,SortBack=SB/S";

#define OPT_DIR       0
#define OPT_VIEWDIRS  1

#define OPT_ICONS     2
#define OPT_COMMS     3
#define OPT_SIZES     4
#define OPT_DATES     5
#define OPT_SECS      6
#define OPT_FULL      7
#define OPT_NOLINKS   8

#define OPT_NOSORT    9
#define OPT_SORTDATE  10
#define OPT_SORTSIZE  11
#define OPT_FIRSTDIRS 12
#define OPT_SORTBACK  13

#define OPT_COUNT     14

struct ExecBase *SysBase;
struct DosLibrary *DOSBase = NULL;
struct Library *UtilityBase = NULL;
struct muBase *muBase = NULL;

LONG opts[OPT_COUNT];

UBYTE Verstag[ ] = "\0$VER: ls 0.99 (20.7.95)\r\n";

UBYTE buf[256];

struct muExtOwner *taskowner;
struct muUserInfo *userinfo = NULL;
struct muGroupInfo *groupinfo = NULL;
struct AnchorPath *ua;

ULONG total = 0, totalsize = 0, totalblocks = 0, totalfiles = 0, totaldirs = 0;

int (*cmp_funct)(struct FileInfoBlock *, struct FileInfoBlock *);

#ifndef DEBUG
#define DEBUG
#endif

#ifndef DEBUG
#define DFUNC(x)
#else
#define DFUNC(x) (x)
#endif

int __saveds cmd_ls(void)
	{
	struct RDArgs *rdargs;
	static STRPTR defaultname[ ] = {"", NULL};
	STRPTR *argptr, curarg;
	LONG err;

	SysBase = *((struct ExecBase **)4);
	if ((DOSBase = (struct DosLibrary *)OpenLibrary(DOSLIB, DOSVER)) &&
	 (UtilityBase = OpenLibrary(UTILITYNAME, 0)))
		{
	   muBase = (struct muBase *)OpenLibrary(MULTIUSERNAME, MULTIUSERVERSION);
	   if (muBase)
			taskowner = muGetTaskExtOwner(NULL);
		if (rdargs = ReadArgs(Template, opts, NULL))
			{
			if (!muBase || ((userinfo = muAllocUserInfo()) &&
			   (groupinfo = muAllocGroupInfo())))
				{
				if (opts[OPT_DIR])
					argptr = (STRPTR *)opts[OPT_DIR];
				else
					argptr = defaultname;
				if (ua = (struct AnchorPath *)AllocMem(sizeof(struct AnchorPath), MEMF_ANY))
					{
					err = 0;
					while ((err == 0) && (curarg = *argptr++))
						{
						memset(ua, 0, sizeof(struct AnchorPath));
						ua->ap_BreakBits = SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D;
						err = readmatchdir(curarg);
						}
					if (err == ERROR_BREAK)
						Printf("***Break\n");
					else if (err)
						PrintFault(err, "***ls");
					else if (total > 1)
						{
						Printf("\nTotal: ");
						dirinfo(totalsize, totalblocks, totalfiles, totaldirs);
						}
					FreeMem(ua, sizeof(struct AnchorPath));
					}
				}
			if (muBase)
				{
				muFreeUserInfo(userinfo);
				muFreeGroupInfo(groupinfo);
				}
			FreeArgs(rdargs);
			}
		else
			PrintFault(IoErr(), NULL);
		if (muBase)
			muFreeExtOwner(taskowner);
		}
	else
		((struct Process *)(SysBase->ThisTask))->pr_Result2 = ERROR_INVALID_RESIDENT_LIBRARY;
	CloseLibrary((struct Library *)muBase);
	CloseLibrary(UtilityBase);
	CloseLibrary((struct Library *)DOSBase);

	return 0;
	}

#define DIRLEN 64

LONG __regargs readmatchdir(STRPTR name)
	{
	APTR pool = NULL;
	struct FileInfoBlock **fibs = NULL, **fibptr;
	LONG temprc = 0, rc, dirpt = 0, dirlen = 0;
	BPTR lock, oldlock;

	MatchFirst(name, ua);
	if (temprc = IoErr())
		{
		if (temprc == ERROR_NO_MORE_ENTRIES)
			{
			Printf("%sNo match.\n", total ? "\n" : NULL);
			total++;
			return 0;
			}
		return temprc;
		}
	if (!(ua->ap_Flags & APF_ITSWILD) && (ua->ap_Info.fib_DirEntryType >= 0))
		{
		ua->ap_Flags |= APF_DODIR;
		MatchNext(ua);
		if (ua->ap_Flags & APF_DIDDIR)
			{
			if (NameFromLock(ua->ap_Last->an_Lock, buf, 256))
				Printf("%sDirectory %s is empty\n", total ? "\n" : NULL, buf);
			total++;
			temprc = ERROR_NO_MORE_ENTRIES;
			}
		}
	lock = DupLock(ua->ap_Last->an_Lock);
	while (temprc == 0)
		{
		 /* re-allocate larger buffer is current one is full
		    or if no buffer allocated yet */
		if (dirpt+2 >= dirlen)
			{
			if (((pool == NULL) &&
			 ((pool = CreatePool(MEMF_ANY, 1000, 1000)) == NULL)) ||
			 ((fibptr = (struct FileInfoBlock **)
			 AllocPooled(pool, 4*(dirlen+DIRLEN))) == NULL))
				{
				temprc = ERROR_NO_FREE_STORE;
				break;
				}
			if (fibs)
				{
				CopyMemQuick((ULONG *)fibs, (ULONG *)fibptr, 4*dirlen);
				FreePooled(pool, fibs, 4*dirlen);
				}
			dirlen += DIRLEN;
			fibs = fibptr;
			fibptr = fibs+dirpt;
			}

		 /* show entry */
		if ((*fibptr = AllocPooled(pool, sizeof(struct FileInfoBlock))) == NULL)
			{
			temprc = ERROR_NO_FREE_STORE;
			break;
			}
		CopyMemQuick(&ua->ap_Info, *fibptr++, sizeof(struct FileInfoBlock));
		dirpt++;

		 /* read next entry */
		MatchNext(ua);
		temprc = IoErr();
		if (temprc && temprc != ERROR_NO_MORE_ENTRIES)
			{
			DeletePool(pool);
			break;
			}
		if (ua->ap_Flags & APF_DIDDIR)
			temprc = ERROR_NO_MORE_ENTRIES;

		 /* dir changed? */
		if (temprc == ERROR_NO_MORE_ENTRIES || ua->ap_Flags & APF_DirChanged)
			{
			*fibptr++ = NULL;

			oldlock = CurrentDir(lock);
			rc = printdir(fibs);
			CurrentDir(oldlock);

			if (!temprc)
				{
				UnLock(lock);
				lock = DupLock(ua->ap_Last->an_Lock);
				}

			DeletePool(pool);

			pool = fibs = NULL;

			dirpt = dirlen = 0;

			if (temprc || (temprc = rc))
				break;
			}
		}
	if (temprc == ERROR_NO_MORE_ENTRIES)
		{
		 /* the dir was entry */
		}
	DeletePool(pool);
	UnLock(lock);
	MatchEnd(ua);

	if (temprc == ERROR_NO_MORE_ENTRIES)
		return 0;
	return temprc;
	}


#define ICON_SKIP 1
#define ICON_ON   2
#define ICON_OFF  0
#define fib_Icon  fib_Reserved[0]

/*
 *
 * dichspa rwedrwedrwed hilandr/dev 368K Yesterday 18:07 Text/ -> Work:Text/ :scripts & docs
 * ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ ŻŻŻŻŻŻŻ ŻŻŻ ŻŻŻŻ ŻŻŻŻŻŻŻŻŻ ŻŻŻŻŻ ŻŻŻŻŻ ŻŻŻŻŻŻŻŻŻŻŻŻŻ ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ
 *       bits            user group size    date   time  name      link          comment
 *
 */

#define FIELDS  20
#define BITSLEN 40
#define DATELEN 9
#define TIMELEN 8

LONG __regargs __saveds printdir(struct FileInfoBlock **fibs)
	{
	int cmpfunct(const void *, const void *);
	int cmp_names(struct FileInfoBlock *, struct FileInfoBlock *);
	int cmp_dates(struct FileInfoBlock *, struct FileInfoBlock *);
	int cmp_sizes(struct FileInfoBlock *, struct FileInfoBlock *);

	UWORD owners = 0, hil, i, j;
	struct FileInfoBlock **fibptr, *curfib, **fibptricon;
	ULONG bits, owner, flags;
	APTR pool;
	STRPTR *dstream, *dsptr, *dptr, ptr, s;
	const static STRPTR hil_on = "\x9B" "1m", hil_off = "\x9B" "0m";
	struct DateTime dt;
	BPTR dirlock, lock;
	UWORD userwidth, groupwidth, sizewidth, datewidth;
	ULONG dirsize = 0, dirblocks = 1, dirfiles = 0, dirdirs = 0;
	LONG temprc = 0;

	dt.dat_Format = FORMAT_INT;
	dt.dat_Flags = opts[OPT_DATES] ? 0 : DTF_SUBST;
	dt.dat_StrDay = NULL;

	 /* seek for owners, view dirs */
	for (fibptr = fibs; curfib = *fibptr; fibptr++)
		{
		owners |= curfib->fib_OwnerUID | curfib->fib_OwnerGID;

		if (curfib->fib_DirEntryType >= 0)
			{
			curfib->fib_Size = 0;
			curfib->fib_NumBlocks = 1;
			if (opts[OPT_VIEWDIRS])
				scanrecurse(curfib->fib_FileName, &curfib->fib_Size, &curfib->fib_NumBlocks);
			}

		curfib->fib_Icon = ICON_OFF;
		}

	if (!muBase)
		owners = FALSE;

	 /* sort list if needed */
	if (!opts[OPT_NOSORT])
		{
		if (opts[OPT_SORTDATE])
			cmp_funct = cmp_dates;
		else if (opts[OPT_SORTSIZE])
			cmp_funct = cmp_sizes;
		else
			cmp_funct = cmp_names;
		qsort(fibs, fibptr-fibs, 4, cmpfunct);
		}

	if ((pool = CreatePool(MEMF_ANY, 1024, 1024)) == NULL)
		return ERROR_NO_FREE_STORE;
	if ((dptr = dstream = (STRPTR *)AllocPooled(pool, (fibptr-fibs)*FIELDS*4)) == NULL)
		{
		DeletePool(pool);
		return ERROR_NO_FREE_STORE;
		}

	if (!opts[OPT_ICONS])
		{
		 /* remove icons from list */
		for (fibptr = fibs; curfib = *fibptr; fibptr++)
			{
			strcpy(buf, curfib->fib_FileName);
			if (buf[0] == '.')
				curfib->fib_Icon = ICON_SKIP;
			else if ((strlen(buf) >=5 ) && !strcmp(buf+strlen(buf)-5, ".info"))
				{
				 /* mark as icon */
				curfib->fib_Icon = ICON_SKIP;
				buf[strlen(buf)-5] = '\0';
				 /* search for its file */
				for (fibptricon = fibs; curfib = *fibptricon; fibptricon++)
					{
					if (strcmp(buf, curfib->fib_FileName) == 0)
						{
						curfib->fib_Icon = ICON_ON;
						break;
						}
					}
				}
			}
		}

	dirlock = Lock("", ACCESS_READ);

	 /* process all fibs */
	dsptr = dstream;
	for (fibptr = fibs; curfib = *fibptr; fibptr++)
		{
		if ((!opts[OPT_ICONS]) && curfib->fib_Icon == ICON_SKIP)
			continue;

		temprc = ERROR_NO_FREE_STORE;

		 /* count dir */
		dirsize   += curfib->fib_Size;
		dirblocks += curfib->fib_NumBlocks;
		if (curfib->fib_DirEntryType < 0)
			dirfiles++;
		else
			dirdirs++;

		dptr = dsptr;
		dsptr += FIELDS;

		 /* allocate puddle for bits */
		if ((ptr = *dptr++ = AllocPooled(pool,
		 BITSLEN+20*(opts[OPT_FULL] ? 1 : 0))) == NULL)
			break;

		 /* show type of entry */
		*ptr++ = (curfib->fib_DirEntryType < -5 || curfib->fib_DirEntryType > 4) ?
		 '?' : "PL-???ddsl"[curfib->fib_DirEntryType+5];

		if (!opts[OPT_ICONS])
			 /* indicate icon */
			*ptr++ = (curfib->fib_Icon == ICON_ON) ? 'i' : '-';

		if (!opts[OPT_COMMS])
			 /* indicate comment */
			 *ptr++ = curfib->fib_Comment[0] ? 'c' : '-';

		 /* show protection bits (hspa) */
		bits = curfib->fib_Protection;
		for (i=0; i<4; i++)
			*ptr++ = ((bits <<= 1) & 0x100) ? "uspa"[i] : '-';
		*ptr++ = ' ';

		 /* show right bits (rwed) */
		bits = curfib->fib_Protection;
		if (owners)
			{
			owner = *(ULONG *)&curfib->fib_OwnerUID;
			flags = muGetRelationship(taskowner, owner, TAG_DONE);
			if (!opts[OPT_FULL])
				{
				 /* the user is owner of file? */
				if ((flags & muRelF_UID_MATCH) == 0)
					{
					 /* the user belongs to file owner's group? */
					if (flags & muRelF_GID_MATCH)
						bits = (bits>>8)^0x0F;
					 /* other user */
					else
						bits = (bits>>12)^0x0F;
					}
				 /* show right bits for current user */
				for (i=0; i<4; i++)
					*ptr++ = ((bits <<= 1) & 0x10) ? '-' : "rwed"[i];
				}
			else
				{
				hil = 0;
				 /* the user is owner of file? */
				if ((flags & muRelF_UID_MATCH) == 0)
					hil = (flags & muRelF_GID_MATCH) ? 1 : 2;
				for (j = 0; j < 3; j++)
					{
					bits = curfib->fib_Protection;
					switch (j)
						{
						case 1:
							bits = (bits>>8)^0x0F;
							break;

						case 2:
							bits = (bits>>12)^0x0F;
						}
					if (hil == j)
						for(s = hil_on; *ptr = *s++; ptr++);
					 /* show right bits for current user */
					for (i=0; i<4; i++)
						*ptr++ = ((bits <<= 1) & 0x10) ? '-' : "rwed"[i];
					if (hil == j)
						for(s = hil_off; *ptr = *s++; ptr++);
					}
				}
			*ptr++ = ' ';
			*ptr++ = ' ';
			*ptr = '\0';

			 /* show owner user/group */
			if (curfib->fib_OwnerUID == 0)
				{
				for (i=0; i<7; i++)
					*dptr++ = NULL;
				dptr[-4] = " ";
				}
			else
				{
				*dptr++ = flags & muRelF_UID_MATCH ? hil_on : NULL;
				userinfo->uid = curfib->fib_OwnerUID;
				muGetUserInfo(userinfo, muKeyType_uid);
				if ((ptr = *dptr++ = AllocPooled(pool, strlen(userinfo->UserID)+1)) == NULL)
					break;
				strcpy(ptr, userinfo->UserID);
				*dptr++ = flags & muRelF_UID_MATCH ? hil_off : NULL;
				*dptr++ = "/";
				*dptr++ = flags & muRelF_GID_MATCH ? hil_on : NULL;
				groupinfo->gid = curfib->fib_OwnerGID;
				muGetGroupInfo(groupinfo, muKeyType_gid);
				if ((ptr = *dptr++ = AllocPooled(pool, strlen(groupinfo->GroupID)+1)) == NULL)
					break;
				strcpy(ptr, groupinfo->GroupID);
				*dptr++ = flags & muRelF_GID_MATCH ? hil_off : NULL;
				}
			}
		else
			{
			 /* show standard right bits */
			for (i=0; i<4; i++)
				*ptr++ = ((bits <<= 1) & 0x10) ? '-' : "rwed"[i];
			*ptr = '\0';
			 /* no owner information available */
			for (i=0; i<7; i++)
				*dptr++ = NULL;
			}

		 /* show size */
		if (curfib->fib_DirEntryType >= 0 && (!opts[OPT_VIEWDIRS]))
			*dptr++ = "Dir";
		else
			{
			if (opts[OPT_SIZES])
				{
				if ((ptr = *dptr++ = AllocPooled(pool, 12)) == NULL)
					break;
				sprintf(ptr, "%ld", curfib->fib_Size);
				}
			else
				{
				if ((ptr = *dptr++ = AllocPooled(pool, 6)) == NULL)
					break;
				itok(curfib->fib_Size, ptr);
				}
			}

		 /* show date/time */
		dt.dat_Stamp = curfib->fib_Date;
		if ((dt.dat_StrDate = *dptr++ = AllocPooled(pool, 10)) == NULL ||
		    (dt.dat_StrTime = *dptr++ = AllocPooled(pool, 10)) == NULL)
			break;
		if (!DateToStr(&dt))
			{
			*dt.dat_StrTime = '\0';
			strcpy(dt.dat_StrDate, "Invalid");
			}
		else if(!opts[OPT_SECS])
			dt.dat_StrTime[5] = '\0';

		 /* show filename */
		*dptr++ = curfib->fib_DirEntryType >= 0 ? hil_on : NULL;
		if ((ptr = *dptr++ = AllocPooled(pool, strlen(curfib->fib_FileName)+3)) == NULL)
			break;
		strcpy(ptr, curfib->fib_FileName);
		if (curfib->fib_DirEntryType >= 0)
			strcat(ptr, "/");
		*dptr++ = curfib->fib_DirEntryType >= 0 ? hil_off : NULL;

		 /* show hardlink */
		if (!opts[OPT_NOLINKS])
			{
			if (curfib->fib_DirEntryType == ST_LINKDIR ||
			    curfib->fib_DirEntryType == ST_LINKFILE)
				{
				if (lock = Lock(curfib->fib_FileName, ACCESS_READ))
					{
					if (NameFromLock(lock, buf, 250))
						{
						if ((ptr = *dptr++ = AllocPooled(pool, strlen(buf)+6)) == NULL)
							break;
						strcpy(ptr, " -> ");
						strcat(ptr, buf);
						if (curfib->fib_DirEntryType == ST_LINKDIR)
							strcat(ptr, "/");
						}
					else
						*dptr++ = NULL;
					UnLock(lock);
					}
				else
					*dptr++ = NULL;
				}
			/* show softlink */
			else if (curfib->fib_DirEntryType == ST_SOFTLINK)
				{
				readsoftlink(curfib->fib_FileName, buf, 250);
				if ((ptr = *dptr++ = AllocPooled(pool, strlen(buf)+6)) == NULL)
					break;
				strcpy(ptr, " -> ");
				strcat(ptr, buf);
				}
			else
				*dptr++ = NULL;
			}
		else
			*dptr++ = NULL;

		if (opts[OPT_COMMS] && *curfib->fib_Comment)
			{
			if ((ptr = *dptr = AllocPooled(pool, strlen(curfib->fib_Comment)+4)) == NULL)
				break;
			strcpy(ptr, "\n:");
			strcat(ptr, curfib->fib_Comment);
			}
		else
			*dptr = NULL;

		temprc = 0;
		}

	if (temprc)
		{
		DeletePool(pool);
		UnLock(dirlock);
		return temprc;
		}

	 /* determine field width for owner user, owner group, file length and date */
	userwidth = groupwidth = sizewidth = datewidth = 0;
	for (dptr = dstream; dptr < dsptr; dptr += FIELDS)
		{
		userwidth = max(strlen(dptr[2]), userwidth);
		groupwidth = max(strlen(dptr[6]), groupwidth);
		sizewidth = max(strlen(dptr[8]), sizewidth);
		datewidth = max(strlen(dptr[9]), datewidth);
		}

	 /* print header */
	if (NameFromLock(dirlock, buf, 255))
		Printf("%sDirectory of %s\n", total ? "\n" : NULL, buf);
	UnLock(dirlock);

	 /* create format string */
	sprintf(buf, " %%s%%s%%%lds%%s%%s%%s%%-%lds%%s  %%%lds  %%%lds %%s  %%s%%s%%s%%s%%s\n",
	 userwidth, groupwidth, sizewidth, datewidth);
	 /* print entries */
	for (dptr = dstream; dptr < dsptr; dptr += FIELDS)
		{
		VPrintf(buf, dptr);
		if (SetSignal(0L, SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D) &
		 (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D))
			{
			DeletePool(pool);
			return ERROR_BREAK;
			}
		}

	 /* print dir information */
	dirinfo(dirsize, dirblocks, dirfiles, dirdirs);

	 /* if listing more dirs, count total information */
	totalsize   += dirsize;
	totalblocks += dirblocks;
	totalfiles  += dirfiles;
	totaldirs   += dirdirs;
	total++;

	 /* free all allocated memory */
	DeletePool(pool);

	return 0L;
	}

/*
 * thanks for this to Carsten Heyl and AMK
 */
void __regargs readsoftlink(STRPTR path, STRPTR buf, LONG buflen)
	{
	BPTR lock;
	UBYTE *bname;
	struct DevProc *devproc;
	LONG pathlen;

	strcpy(buf, "???");
	if ((pathlen = strlen(path)) > 255)
		return;

	 /* build BSTR path */
	if (bname = AllocMem(pathlen+2, MEMF_CLEAR|MEMF_PUBLIC))
		{
		bname[0] = pathlen;
		CopyMem(path, bname+1, pathlen);
		if (devproc = GetDeviceProc(path, NULL))
			{
			if (lock = DoPkt(devproc->dvp_Port, ACTION_LOCATE_OBJECT, devproc->dvp_Lock,
			 MKBADDR(bname), ACCESS_READ, NULL, NULL))
				{
				if ((lock == NULL) && (IoErr() == ERROR_IS_SOFT_LINK))
					ReadLink(devproc->dvp_Port, devproc->dvp_Lock, path, buf, buflen);
				UnLock(lock);
				}
			FreeDeviceProc(devproc);
			}
		FreeMem(bname, pathlen+2);
		}
	}

void __regargs itok(ULONG num, STRPTR buf)
	{
	UBYTE *exp = " KMG";
	ULONG m = 1, m2, res, mod;

	if (num<1024)
		{
		sprintf(buf, "%ld", num);
		return;
		}
	while ((res = (num+m/2)/m) >= 1000)
		{
		m2 = m;
		m *= 1024;
		exp++;
		}
	if (res < 11)
		{
		res = num/m;
		if (res < 1)
			{
			sprintf(buf, "1.0%lc", *exp);
			return;
			}
		mod = ((num/m2)%1024*10+512)/1024;
		if (mod > 9)
			{
			res++;
			mod = 0;
			}
		if (res < 10)
			{
			sprintf(buf, "%ld.%ld%lc", res, mod, *exp);
			return;
			}
		}
	sprintf(buf, "%ld%lc", res, *exp);
	}

UWORD __regargs max(UWORD a, UWORD b)
	{
	return (UWORD)((a>b) ? a : b);
	}

#define FIB_a (*(struct FileInfoBlock **)a)
#define FIB_b (*(struct FileInfoBlock **)b)

int cmpfunct(const void *a, const void *b)
	{
	int c;

	if (opts[OPT_FIRSTDIRS])
		{
		if (FIB_a->fib_DirEntryType < 0)
			c = FIB_b->fib_DirEntryType >= 0;
		else
			c = -(FIB_b->fib_DirEntryType < 0);
		if (c)
			return c;
		}
	c = cmp_funct(FIB_a, FIB_b);
	return opts[OPT_SORTBACK] ? -c : c;
	}

int cmp_names(struct FileInfoBlock *a, struct FileInfoBlock *b)
	{
	return stricmp(a->fib_FileName, b->fib_FileName);
	}

int cmp_dates(struct FileInfoBlock *a, struct FileInfoBlock *b)
	{
	return CompareDates(&a->fib_Date, &b->fib_Date);
	}

int cmp_sizes(struct FileInfoBlock *a, struct FileInfoBlock *b)
	{
	return b->fib_Size - a->fib_Size; /* dirs sorted first already */
	}

void __regargs dirinfo(ULONG dirsize, ULONG dirblocks, ULONG dirfiles, ULONG dirdirs)
	{
	static UBYTE buf[10];

	 /* print dir information */
	if (!opts[OPT_VIEWDIRS])
		{
		if (dirdirs)
			Printf("%ld dir%s, ", dirdirs, dirdirs > 1 ? "s" : NULL);
		if (dirfiles)
			{
			itok(dirsize, buf);
			Printf("%s %sin %ld file%s, ", buf,
			 dirsize<1000 ? "bytes " : NULL, dirfiles, dirfiles > 1 ? "s" : NULL);
			}
		}
	else
		{
		itok(dirsize, buf);
		Printf("%s %sin ", buf, dirsize<1000 ? "bytes" : NULL);
		if (dirdirs)
			Printf("%ld dir%s%s ", dirdirs, dirdirs > 1 ? "s" : NULL, dirfiles ? " and" : ",");
		if (dirfiles)
			Printf("%ld file%s, ", dirfiles, dirfiles > 1 ? "s" : NULL);
		}
	Printf("%ld blocks used\n", dirblocks);
	}

BOOL __regargs scanrecurse(STRPTR name, LONG *dirsize, LONG *dirblocks)
	{
	struct AnchorPath *ap;
	LONG temprc;
	BOOL rc = TRUE;

	if (ap = (struct AnchorPath *)AllocMem(
	 sizeof(struct AnchorPath), MEMF_ANY|MEMF_CLEAR))
		{
		MatchFirst(name, ap);
		while ((temprc = IoErr()) == 0)
			{
			if (ap->ap_Info.fib_DirEntryType > 0)
				{
				if (!(ap->ap_Flags & APF_DIDDIR))
					{
					ap->ap_Flags |= APF_DODIR;
					++*dirblocks;
					}
				ap->ap_Flags &= ~APF_DIDDIR;
				}
			else
				{
				*dirsize   += ap->ap_Info.fib_Size;
				*dirblocks += ap->ap_Info.fib_NumBlocks;
				}
			if (temprc && (temprc != ERROR_NO_MORE_ENTRIES))
				{
				rc = FALSE;
				break;
				}
			MatchNext(ap);
			}
		MatchEnd(ap);
		FreeMem(ap, sizeof(struct AnchorPath));
		}
	else
		return FALSE;
	return rc;
	}
