/* :ts=8 bk=0
 * 
 * eless.c:	An attempt at a reasonable-speed 'ls' program by fiddling
 *		with the DOS.
 *
 *		Compiles under Manx 3.20 and patched 3.40.
 *		cc eless.c
 *		ln eless.c -lc -o eless
 *
 * Leo L. Schwab		8704.26		(415)-456-6565
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <libraries/dosextens.h>

#define	BLOCKSIZE	128
#define	STARTTAB	6
#define ENDTAB		(BLOCKSIZE-51)
#define	TABSIZ		(BLOCKSIZE-56)
#define	FILENAME	(BLOCKSIZE-20)
#define	HASHCHAIN	(BLOCKSIZE-4)
#define	SECONDARY	(BLOCKSIZE-1)
#define	ST_DIR		2
#define	ST_FILE		-3

/*
 * Note:  I usually declare Amiga functions this way to disable the compiler's
 * type checking.  This allows me to assign values to variables without
 * explicitly casting them to the appropriate type.  In reality, these
 * functions return things entirely different from the way I've declared
 * them.  For example, CreatePort() should really be declared as:
 *
 * struct MsgPort *CreatePort();
 *
 * Caveat Programmer.
 */
extern void	*AllocMem(), *CreatePort(), *RemTail(), *FindTask();
extern long	Lock(), Examine();

long		dopacket();
int		longcmp(), namecmp();


struct StandardPacket	*packet;
struct FileInfoBlock	*fib;
struct FileLock		*lock;
struct InfoData		*id;
struct MsgPort		*dosreply;
BPTR			lok;
long			*buf, hashtab[TABSIZ];


main (ac, av)
char **av;
{
	int flag;

	openstuff ();

	if (ac < 2)		/*  No arguments; do current directory  */
		printdir (((struct Process *) FindTask (0L)) -> pr_CurrentDir);

	else {			/*  Arguments; treat as directories  */
		flag = (ac > 2);
		while (++av, --ac) {
			if (!(lok = Lock (*av, ACCESS_READ))) {
				printf ("%s: Not found.\n", *av);
				if (ac > 1)
					putchar ('\n');
				continue;
			}

			if (!Examine (lok, fib))
				die ("Examine failed.");
			if (fib -> fib_DirEntryType < 0) {
				printf ("%s: Not a directory.\n", *av);
				if (ac > 1)
					putchar ('\n');
				continue;
			}

			if (flag)
				printf ("%s:\n", *av);
			printdir (lok);
			if (ac > 1)
				putchar ('\n');
			UnLock (lok);  lok = NULL;
		}
	}

	closestuff ();
}


/*
 * This function prints the directory in a nice, pretty columnar format.
 */
printdir (dirlock)
BPTR dirlock;
{
	struct List		namelist;
	register struct Node	*name, **namearray;
	long			args[2];
	register int		i, n;
	int			len = 0, ncols, nlines, nfiles = 0;
	char			fmt1[16], fmt2[16];
	char			*fn = (char *) (&buf[FILENAME]) + 1;

	lock = (struct FileLock *) (dirlock << 2);
	args[0] = lock -> fl_Key;	/*  Block number  */
	args[1] = (long) buf >> 2;	/*  BPTR to buffer  */

	/*  Attempt to get raw directory block.  */
	if (!dopacket (lock -> fl_Task, ACTION_GET_BLOCK, args, 2)) {
		if (packet -> sp_Pkt.dp_Res2 == ERROR_ACTION_NOT_KNOWN)
			printf ("Not a block device.\n");
		else
			printf ("Error %ld while getting dirblock.\n",
				packet -> sp_Pkt.dp_Res2);
		return;
	}

	/*  Copy DOS's hash table into our array.  */
	for (i=0; i<TABSIZ; i++)
		hashtab[i] = buf[i+STARTTAB];

	NewList (&namelist);
	while (1) {
		/*  Sort hash table.  */
		qsort (hashtab, TABSIZ, sizeof (long), longcmp);
		if (!hashtab[0])	/*  Empty hash table  */
			break;

		/*
		 * Retrieve disk blocks according to sorted hash table and
		 * collect filenames.
		 */
		for (i=0; hashtab[i] && i<TABSIZ; i++) {
			args[0] = hashtab[i];
			dopacket (lock -> fl_Task, ACTION_GET_BLOCK, args, 2);

			if (!(name = AllocMem (sizeof (*name) + *(fn-1) + 1L,
					       MEMF_CLEAR))) {
				puts ("Node memory allocation failure.");
				goto bombout;
			}

			name -> ln_Name = (char *) name + sizeof (*name);
			name -> ln_Type = (buf[SECONDARY] == ST_DIR);
			fn[*(fn-1)] = '\0';	/*  Force null-termination  */
			strcpy (name -> ln_Name, fn);
			AddTail (&namelist, name);
			nfiles++;	/*  Number of files found  */

			hashtab[i] = buf[HASHCHAIN];
		}
	}
	if (!nfiles)	/*  No files  */
		return;

	/*  Create array that qsort can deal with.  */
	if (!(namearray = AllocMem
			   ((long) nfiles * sizeof (char *), MEMF_CLEAR))) {
		puts ("Name array allocation failure.");
		goto bombout;
	}

	/*  Load up the array.  */
	for (name = namelist.lh_Head, i=0;
	     name -> ln_Succ;
	     name = name -> ln_Succ, i++)
	     	namearray[i] = name;

	/*  Alphabetize filenames.  */
	qsort (namearray, nfiles, sizeof (struct Node *), namecmp);

	/*  Find longest string so we can format intelligently.  */
	for (i=0; i<nfiles; i++)
		if ((n = strlen (namearray[i] -> ln_Name)) > len)
			len = n;
	len += 2;	/*  Inter-name spacing  */

	/*  Print them suckers out  */
	ncols = 77 / len;		/*  Assume CON: is 77 columns  */
	nlines = nfiles/ncols + (nfiles%ncols != 0);
	sprintf (fmt1, "%%-%ds", len);		/*  Normal format string  */
	sprintf (fmt2, "\033[33m%%-%ds\033[m", len);	/* For directories */
	for (i=0; i<nlines; i++) {
		for (n=i; n<nfiles; n+=nlines)
			printf (namearray[n] -> ln_Type ? fmt2 : fmt1,
				namearray[n] -> ln_Name);
		putchar ('\n');
	}

	/*  Phew!  Now free all the stuff we allocated.  */
bombout:
	freenamelist (&namelist);
	if (namearray)
		FreeMem (namearray, (long) nfiles * sizeof (struct Node *));
}

freenamelist (list)
struct List *list;
{
	register struct Node *now;

	while (now = RemTail (list))
		FreeMem (now, sizeof (*now) + strlen (now -> ln_Name) + 1L);
}


/*
 * This function assumes the existence of a properly initialized
 * standardpacket structure called "packet" and a reply port called
 * "dosreply".  A more general function would not do this.
 *
 * This function is synchronous i.e. it doesn't return until the specified
 * action has been performed.
 */
long
dopacket (proc, action, args, nargs)
struct MsgPort *proc;
long action;
register long *args;
register int nargs;
{
	register long *argp = &packet -> sp_Pkt.dp_Arg1;

	for (; nargs>0; nargs--)
		*argp++ = *args++;

	/*
	 * AmigaDOS scribbles over the reply port, so we have to initialize
	 * it every time we send a packet.
	 */
	packet -> sp_Pkt.dp_Port = dosreply;
	packet -> sp_Pkt.dp_Type = action;

	PutMsg (proc, packet);
	WaitPort (dosreply);
	GetMsg (dosreply);

	return (packet -> sp_Pkt.dp_Res1);
}


/*
 * These functions are called by qsort().  The sense of camparisons is
 * reversed in longcmp to get a reverse sort (largest elements first).
 */
longcmp (foo, bar)
long *foo, *bar;
{
	/*
	 * We have to do it this way because qsort() expects an int to be
	 * returned.  Subtracting longs directly might overflow a 16-bit int.
	 */
	return ((*foo < *bar) - (*foo > *bar));
}

namecmp (foo, bar)
struct Node **foo, **bar;
{
	return (strcmp ((*foo) -> ln_Name, (*bar) -> ln_Name));
}


/*
 * Resource allocation/deallocation routines.
 */
openstuff ()
{
	if (!(packet = AllocMem ((long) sizeof (*packet), MEMF_CLEAR)))
		die ("Can't allocate packet space.");

	if (!(dosreply = CreatePort (NULL, NULL)))
		die ("Dos reply port create failed.");
	packet -> sp_Msg.mn_Node.ln_Name	= (char *) &packet -> sp_Pkt;
	packet -> sp_Pkt.dp_Link		= &packet -> sp_Msg;

	if (!(fib = AllocMem ((long) sizeof (*fib), MEMF_CLEAR)))
		die ("Can't allocate FileInfoBlock.");

	/*
	 * Note:  This allocation may not work with DMA hard disks.
	 */
	if (!(buf = AllocMem (BLOCKSIZE * 4L, MEMF_CHIP | MEMF_PUBLIC)))
		die ("Couldn't allocate sector buffer.");
}

closestuff ()
{
	if (lok)		UnLock (lok);
	if (buf)		FreeMem (buf, BLOCKSIZE * 4L);
	if (fib)		FreeMem (fib, (long) sizeof (*fib));
	if (dosreply)		DeletePort (dosreply);
	if (packet)		FreeMem (packet, (long) sizeof (*packet));
}

die (str)
char *str;
{
	puts (str);
	closestuff ();
	exit (1);
}
