[comp.sources.amiga] v02i097: ls - unix-like directory lister v2.0

page@swan.ulowell.edu (Bob Page) (12/13/88)

Submitted-by: nop@cup.portal.com
Posting-number: Volume 2, Issue 97
Archive-name: unix/ls20.1

Here is a new version of Justin McCormick's "ls" program.
Hope the net enjoys the program -- I sure do!

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#	Run the following text with /bin/sh to create:
#	c2.a
#	ls.c
#	ls.doc
#	ls.h
#	ls.lnk
#	lssup.a
#	makefile
# This archive created: Mon Dec 12 17:01:16 1988
cat << \SHAR_EOF > c2.a
*
* C initial startup procedure under AmigaDOS
* 
* Use the following command line to make c.o
* asm -u -iINCLUDE: c.a
*
* Use the following command line to make cres.o
* asm -u -dRESIDENT -iINCLUDE: -ocres.o c.a

	IDNT	"c2.a"

	OPTION	L
	LISTSYMS
	BASEREG	B
	SMALLOBJ
	OPTIMON
	ADDSYM
	DEBUG

* --------------------------------------------------------------------- *
* Macros:
* --------------------------------------------------------------------- *
SYS	MACRO	*
	IFGT	NARG-2
	FAIL	!!!
	ENDC
	IFEQ	NARG-2
	MOVE.L	\2,a6
	ENDC
	JSR	_LVO\1(a6)
	ENDM

XLVO	MACRO	*
	XREF	_LVO\1
	ENDM

* --------------------------------------------------------------------- *
* Equates:
* --------------------------------------------------------------------- *
RESIDENT	EQU	1

MEMF_PUBLIC	EQU	$1
MEMF_CLEAR	EQU	$10000
MEMFLAGS	EQU	MEMF_CLEAR+MEMF_PUBLIC
AbsExecBase	EQU	$0004
cli_CommandName	EQU	$10
pr_CLI		EQU	$AC
pr_CurrentDir	EQU	$98
ThisTask	EQU	$114

_LVOCloseLibrary EQU	$FE62
_LVOAllocMem	EQU	$FF3A
_LVOFreeMem	EQU	$FF2E
_LVOSetSignal	EQU	$FECE
_LVOOpenLibrary	EQU	$FDD8

	XREF	_DOSBase
	XREF	_LinkerDB		; linker defined base value
	XREF	__BSSBAS		; linker defined base of BSS
	XREF	__BSSLEN		; linker defined length of BSS

	 IFD	RESIDENT
	XREF	_RESLEN
	XREF	_RESBASE
	XREF	_NEWDATAL
	 ENDC
	
	XREF	__main			; Name of C program to start with.
	XREF	_MemCleanup		; Free all allocated memory
	XREF	___fpinit		; initialize floating point
	XREF	___fpterm		; terminate floating point

* --------------------------------------------------------------------- *
	SECTION	TEXT,CODE
* --------------------------------------------------------------------- *
	XDEF	zzstart
zzstart:
	move.l	a0,a2			; save command pointer
	move.l	d0,d2			; and command length
	lea	_LinkerDB,a4		; load base register

	 IFND	RESIDENT
	lea	__BSSBAS,a3		; get base of BSS
	moveq	#0,d1
	move.l	#__BSSLEN,d0		; get length of BSS in longwords
	 bra.b	clr_lp		 	; and clear for length given
clr_bss move.l	d1,(a3)+
clr_lp	dbf	d0,clr_bss
	 ENDC

	 IFD	RESIDENT
	movea.l	AbsExecBase,a6

	movem.l	d0-d1/a0-a2,-(sp)
	sub.l	#_RESBASE,a4
	move.l	#_RESLEN,d0
	move.l	#MEMFLAGS,d1
	SYS	AllocMem
	tst.l	d0
	 beq.w	abort
	move.l	d0,a0
	move.l 	d0,a2

;a2 now has difference
	move.l	d0,a1
	move.l	#_NEWDATAL,d0
;copy data over
cpy:	move.l	(a4)+,(a0)+
	subq.l	#1,d0
	 bne	cpy
;a4 now points at number of relocs
	move.l	(a4)+,d0
reloc:	 beq.b	nreloc
	move.l	a1,a0
	add.l	(a4)+,a0		; a0 now has add of reloc
	add.l	(a0),a2
	move.l	a2,(a0) 
	move.l	a1,a2			; restore offset
	subq.l	#1,d0
	bra	reloc
	
nreloc: move.l	a1,a4			; set up new base register
	add.l	#_RESBASE,a4
	movem.l	(sp)+,d0-d1/a0-a2
	 ENDC

	movea.l	AbsExecBase,a6
	move.l	a6,_SysBase(a4)
	move.l	sp,__StackPtr(a4)	; Save stack ptr
	clr.l	_WBenchMsg(a4)

* get the address of our task
	move.l	ThisTask(a6),a3

*-	clear any pending signals
	moveq	#0,d0
	move.l	#$00003000,d1
	SYS	SetSignal
	
* are we running as a son of Workbench?
	move.l	pr_CurrentDir(a3),_curdir(a4)

	IFD	WBENCH
	tst.l	pr_CLI(a3)
	 beq	fromWorkbench
	ENDC

*=======================================================================
*====== CLI Startup Code ===============================================
*=======================================================================
*
; Entry: d2 = command length
;	 a2 = Command pointer
fromCLI:
	move.l	sp,d0		 	; get top of stack
	sub.l	4(sp),d0		; compute bottom 
	add.l	#128,d0	 		; allow for parms overflow
	move.l	d0,__base(a4)		; save for stack checking

* attempt to open DOS library:
	bsr.w	openDOS

* find command name:
	move.l	pr_CLI(a3),a0
	add.l	a0,a0		 	; bcpl pointer conversion
	add.l	a0,a0
	move.l	cli_CommandName(a0),a1
	add.l	a1,a1		 	; bcpl pointer conversion
	add.l	a1,a1

* collect parameters:
	move.l	d2,d0			; get command line length
	moveq.l	#0,d1
	move.b	(a1)+,d1
	move.l	a1,__ProgramName(a4)
	add.l	d1,d0			; add length of command name
	addq.l	#1,d0			; allow for space after command 

	clr.w	-(sp)			; set null terminator for command line
	addq.l	#1,d0			; force to even number of bytes
	andi.w	#$fffe,d0		; (round up)
	sub.l	d0,sp			; make room on stack for command line
	subq.l	#2,d0
	clr.w	0(sp,d0)

* copy command line onto stack
	move.l	d2,d0			; get command line length
	subq.l	#1,d0
	add.l	d1,d2

copy_line:
	move.b	0(a2,d0.w),0(sp,d2.w)	; copy command line to stack
	subq.l	#1,d2
	dbf	d0,copy_line
	move.b	#' ',0(sp,d2.w)	 	; add space between command and parms
	subq.l	#1,d2

copy_cmd:
	move.b	0(a1,d2.w),0(sp,d2.w)	; copy command name to stack
	dbf	d2,copy_cmd
	move.l	sp,a1
	move.l	a1,-(sp)		; push command line address

	IFD	WBENCH
	bra	main			; call C entrypoint
* --------------------------------------------------------------------- *
* Workbench Startup Code
* --------------------------------------------------------------------- *
fromWorkbench:
	move.l	TC_SPLOWER(a3),__base(a4) ; set base of stack
	moveq 	#127,d0
	addq.l	#1,d0			; Efficient way of getting in 128
	add.l	d0,__base(a4)		; allow for parms overflow

* open the DOS library:
	bsr.w	openDOS

* we are now set up. wait for a message from our starter
	lea	pr_MsgPort(a3),a0	; our process base
	SYS	WaitPort
	lea	pr_MsgPort(a3),a0	; our process base
	SYS	GetMsg
	move.l	d0,_WBenchMsg(a4)
	move.l	d0,-(sp)

	move.l	d0,a2			; get first argument
	move.l	sm_ArgList(a2),d0
	 beq	do_cons
	move.l	_DOSBase(a4),a6
	move.l	d0,a0
	move.l	wa_Lock(a0),d1
	move.l	d1,_curdir(a4)
	SYS	CurrentDir
do_cons:
	move.l	sm_ToolWindow(a2),d1	; get the window argument
	 beq	do_main
	move.l	#MODE_OLDFILE,d2
	SYS	Open
	move.l	d0,_stdin(a4)
	 beq	do_main
	lsl.l	#2,d0
	move.l	d0,a0
	move.l	fh_Type(a0),pr_ConsoleTask(a3)
do_main:
	move.l	_WBenchMsg(a4),a0	; get address of workbench message
	move.l	a0,-(sp)		; push argv
	pea	_NULL(a4)		; push argc
	move.l	sm_ArgList(a0),a0	; get address of arguments
	move.l	wa_Name(a0),__ProgramName(a4)	 ; get name of program
	ENDC

* --------------------------------------------------------------------- *
* Common CLI/WBENCH code
* --------------------------------------------------------------------- *
main:
	 IFD	FLOAT
	jsr	___fpinit(pc)		; Initialize floating point
	 ENDC
	jsr	__main(pc)		; call C entrypoint
	moveq.l	#0,d0			; set successful status
	bra.b	exit2

	XDEF	_XCEXIT
_XCEXIT:
	move.l	4(sp),d0		; extract return code

	XDEF	xXCEXIT
xXCEXIT:
exit2:
	 IFD	ERRTRAPS
	move.l	d0,-(sp)
	move.l	__ONEXIT(a4),d0		; exit trap function?
	 beq.b	exit3
	move.l	d0,a0
	jsr	(a0)
exit3:
	 ENDC

	 IFD	MALLOC
	jsr	_MemCleanup(pc)		; cleanup leftover memory alloc.
	 ENDC

	movea.l	AbsExecBase,a6
	move.l	_DOSBase(a4),a1
	SYS	CloseLibrary		; close Dos library

	 IFD	FLOAT
	jsr	___fpterm(pc)		; clean up any floating point
	 ENDC

	 IFD	WBENCH
done_1c:
* if we ran from CLI, skip workbench cleanup:
	tst.l	_WBenchMsg(a4)
	 beq	exitToDOS
	move.l	_stdin(a4),d1
	 beq.b	done_4
	SYS	Close
done_4:
* return the startup message to our parent
*	 we forbid so workbench can't UnLoadSeg() us
*	 before we are done:
	movea.l	AbsExecBase,a6
	SYS	Forbid
	move.l 	_WBenchMsg(a4),a1
	SYS	ReplyMsg
	 ENDC

* this rts sends us back to DOS:
exitToDOS:
	 IFD	RESIDENT
	move.l	#_RESLEN,d0
	move.l 	a4,a1
	sub.l	#_RESBASE,a1
	movea.l	AbsExecBase,a6
	SYS	FreeMem
	 ENDC
	
	move.l	(sp)+,d0
	movea.l	__StackPtr(a4),sp	; restore stack ptr

	XDEF	xchkabort
xchkabort:
	rts

	 IFD	RESIDENT
abort:
	movem.l	(sp)+,d0-d1/a0-a2
	rts
	 ENDC

* --------------------------------------------------------------------- *
noDOS:
	moveq.l	#100,d0
	bra	exit2

* --------------------------------------------------------------------- *
*	Open the DOS library:
* --------------------------------------------------------------------- *
openDOS:
	lea	DOSName(pc),a1
	moveq.l #0,d0
	SYS	OpenLibrary
	move.l	d0,_DOSBase(a4)
	 beq	noDOS
	rts

DOSName:	dc.b	'dos.library',0

* --------------------------------------------------------------------- *
	section __MERGED,BSS
* --------------------------------------------------------------------- *

	XDEF	_NULL,_SysBase,_WBenchMsg
	XDEF	_curdir,__mbase,__mnext,__msize,__tsize
	XDEF	__oserr,__OSERR
	 IFD	FLOAT
	XDEF	__FPERR
	XDEF	__SIGFPE
	 ENDC
	 IFD	ERRTRAPS
	XDEF	__ONERR,__ONEXIT,__ONBREAK
	XDEF	__SIGINT
	 ENDC
	XDEF	_stdin
	XDEF	__ProgramName,__StackPtr,__base

* --------------------------------------------------------------------- *
_NULL		ds.l	1		 ; Huh?
__base		ds.l	1		 ; base of stack
__mbase		ds.l	1		 ; base of memory pool
__mnext		ds.l	1		 ; next available memory location
__msize		ds.l	1		 ; size of memory pool
__tsize		ds.l	1		 ; total size?
__oserr
__OSERR		ds.l	1
	 IFD	FLOAT
__FPERR		ds.l	1
__SIGFPE	ds.l	1
	 ENDC
	 IFD	ERRTRAPS
__SIGINT	ds.l	1
__ONERR		ds.l	1
__ONEXIT	ds.l	1
__ONBREAK	ds.l	1
	 ENDC
_curdir		ds.l	1
_SysBase	ds.l	1
_WBenchMsg	ds.l	1
__StackPtr	ds.l	1
_stdin		ds.l	1
__ProgramName	ds.l	1

* --------------------------------------------------------------------- *
	END
* --------------------------------------------------------------------- *
SHAR_EOF
cat << \SHAR_EOF > ls.c
/* --------------------------------------------------------------------- *
  LS.C -- an "improved" directory listing utility to replace the
  AmigaDOS DIR and LIST commands.

  V1.0   August 1986 Written from scratch by Justin V. McCormick.
  V2.0 November 1988 Revised for Lattice 5.0 and made 1.3 compatible.

Notice:

  This program is placed in the public domain with the understanding
that the author makes no claims or guarantees with regard to its
suitability for any given application, and that the author assumes no
responsibility for damages incurred by its usage.  Any resemblance
of this program to any other program, either living or dead, is
purely coincidental.

  Feel free to steal this code and make millions of dollars from its sale
or commercial use, but please give credit where credit is due.

Synopsis:

  Features adaptive columnar listing, versatile sort options,
UNIX-style pattern matching, recursive subdirectory listing, etc!

Usage:
	ls [options] [path1] [path2] ...

Options:
	-?	Help!
	-c	Show file comment info, -c implies -l
	-d	Show directory names only
	-f	Show filenames only
	-l      Long verbose listing showing filesizes and dates
	-n	No sort, just spit them out in the order ExNext() returns
	-r	Reverse sort direction
	-s	Sorted by size smallest to largest
	-t	Sorted by date oldest to newest
	-R	Recursive descent of subdirectories

  All arguments are optional.  Default is to give short columnar listing,
sorted alphabetically, using the current directory.  Alphabetizing is case
insensitive.

  Patterns may be matched in the given names, using the UNIX-style '*'
to wildcard any number of characters, and '?' to wildcard a single
character.  If you need to specify a pathname with spaces in it like
"Wombat Soup", you need to put quotes around it.  LS can process up to 30
separate pathname patterns in one command line.

Bugs:

  Redirecting the shortlist output to PRT: gives undesirable results,
since I am using relative cursor positioning commands to format the
screen output.  I thought about using an array to store a virtual
screen, but my primary goals were to keep the size down and display
speed at a maxiumum.  Also, LS cannot pattern match devices (like "dh*:")
or support multiple levels of pattern matching (like "dh0:?/L*.info").
This would involve another level of recursion and groking the Device List.

Changes From 1.0 to 2.0:

 o Source code prototyped, linted, traced, optimized, tweaked, etc.
 o Made resident ("pseudo-pure") by linking with cres.o from LC 5.0.
 o High-volume routines recoded in assembly (lssup.a).
 o Now handles multiple paths/files on a command line, up to 30.
 o New sort flags, including no sort.
 o Enhanced wildcards, understands complex *.?*.* expressions now.
 o More efficient ExNext() performance, less ram used for recursion.
 o SIGBREAKF_CTRL_C signal (Ctrl-C) cleanly aborts at any point now.
 o Command line parser handles quoted pathnames now (LC 5.0 benefit).
 o Short listing finally auto-adjusts to new console window sizes!
 o Pen color escape codes bypassed when redirecting long output.
 o Sorting by size or date is also subsorted alphabetically now.
 o Long listing shows new 1.3 file attributes, plus comment indicator.
 o File dates are now in international format, YY-MM-DD.
 o Fixed listings with files datestamped after 99-12-31 (overflow).
 o Fixed listings with files datestamped before 78-01-01 (time < 0).

* --------------------------------------------------------------------- */

#include "ls.h"

/* Structure used to hold file info in a linked list */
struct FibEntry
{
  struct FibEntry *NextFib;
  struct FibEntry *LastFib;
  struct FileInfoBlock *Fibp;
};

/* Externs from lssup.a */
/*lint +fva2 */
extern int __stdargs asprintf (char *, char *,...);
/*lint -fva2 */

extern __asm void SortFibs (R_D0 long, R_D1 long, R_A0 struct FibEntry *);
extern __stdargs long iswild (char *);
extern __stdargs long wildmatch (char *, char *);
extern __stdargs char *FibFileDate (struct DateStamp *);
extern __stdargs void GetWinBounds(long *, long *);

/* Local CODE */
struct FibEntry *GetDir (BPTR, struct FileInfoBlock *);
struct FibEntry *AllocFib (void);
void CleanUp (char *, long);
void ColorPen2 (void);
void CursorOff (void);
void CursorOn (void);
void DirIt (BPTR, char *);
void FreeAllFibs (struct FibEntry *);
void FreeFib (struct FibEntry *);
void LListDir (struct FibEntry *);
void LListEntry (struct FileInfoBlock *);
void LongList (long *, long *, struct FibEntry *);
void main (int, char **);
void PagePrompt (long);
void ResetPen (void);
void SListDir (struct FibEntry *);
void TestBreak (void);
void Usage (void);
void WCHR (char *);
void WSTR (char *);

BPTR Out = 0L;
BPTR In = 0L;
BPTR lockp = 0L;
struct FileInfoBlock *GFibp = 0L;

long BREAKFLAG = 0;
long CONSOLE = 0;
long dircount = 0;
long DIRFILEFLAG = 0;
long errstat = 0;
long filecount = 0;
long LISTALL = 0;
long LONGLIST = 0;
long maxnamlen = 0;
long NOSORTFLAG = 0;
long NOTEFLAG = 0;
long PATHNAMED = 0;
long REVFLAG = 0;
long sortkey = 0;
long WILDCARD = 0;
long CurWinRows = 20L;
long CurWinCols = 77L;

char filename[160];
char pattern[160];
char workstr[160];

static char Author[] = "\23333mLS 2.0\2330m by Justin V. McCormick 1988";
static char usage[] = "\nUsage: ls [-cdflnrstR] [path1] [path2] ...\n";

/* -------------------------------------------------------------------- */
void main (argc, argv)
  int argc;
  char **argv;
{
  struct Process *procp;
  long cnt, i;

  if (argc == 0)			/* son of Workbench -- no go! */
    exit (0);

/* Grab FileHandles for input and output to console (or redirection file) */
  In = Input ();
  Out = Output ();
  CONSOLE = (IsInteractive (Out) == 0L) ? 0L : 1L; /* Is this console output? */

/* Allocate a global FileInfoBlock for ExNext() */
  if ( (GFibp = (struct FileInfoBlock *)AllocMem((long)sizeof(struct FileInfoBlock), MEMF_PUBLIC | MEMF_CLEAR)) == 0L)
    CleanUp("No RAM?", 103L);

/* Parse command line arguments <ugh> */
  cnt = 1;
  if (argc >= 2)
  {
    if (argv[1][0] == '-')
    {
      cnt++;
      for (i = strlen(argv[1]) - 1; i > 0; i--)
      {
        switch (argv[1][i])
        {
	  case 'c': 
	    LONGLIST = 1;
	    NOTEFLAG = 1;
	    break;
          case 'd':
            DIRFILEFLAG |= 1;
            break;
          case 'f':
            DIRFILEFLAG |= 2;
            break;
	  case 'l': 
	    LONGLIST = 1;
	    break;
	  case 'n':
	    NOSORTFLAG = 1;
	    break;
	  case 'r':
	    REVFLAG = 1;
	    break;
	  case 's':
	    sortkey = 1;
	    break;
	  case 't': 
	    sortkey = 2;
	    break;
	  case 'R': 
	    LISTALL = 1;
	    break;
	  case '?': 
	    Usage ();
	    break;
	  default: 
	    (void) asprintf (workstr, "Unknown option \'%c\'\n", argv[1][i]);
	    WSTR (workstr);
	    Usage ();
	    break;
	}
      }
    }
  }

/* Clean up the state flags */
  if ( (argc - cnt) > 1)
    LISTALL |= 2;

  if (DIRFILEFLAG == 0)
    DIRFILEFLAG = 3;

/* Loop through the remaining args now */
  do
  {
    PATHNAMED = 0;
    if (cnt < argc)
    {
      (void) stpcpy (pattern, argv[cnt]);
      PATHNAMED = 1;
    }

    if (PATHNAMED)			/* If user specified a pathname	*/
    {
      WILDCARD = iswild (pattern); 	/* check for wildcards	 	*/
 
      if (WILDCARD)			/* If wildcards, separate	*/
      {					/* pattern from pathname	*/
        for (i = (strlen (pattern) - 1); i >= 0; i--)
        {
          if (pattern[i] == '/' || pattern[i] == ':')
          {
	    (void) strncpy (filename, pattern, (UWORD) (i + 1));
	    filename[i + 2] = (BYTE) 0;
	    (void) stpcpy (workstr, &pattern[i + 1]);
	    (void) stpcpy (pattern, workstr);
	    break;
          }
        }
/* Disallow wildcards in pathname */
        if (iswild (filename))
          CleanUp ("Sorry, can't pattern match paths", 5L);
      }
      else				/* No wildcards, use filename as is */
      {
        (void) stpcpy (filename, pattern);
      }

/* If the user specified a pathname, try to grab a FileLock on it */
/* Discard trailing slash if silly Joe User put one there */
      if (filename[1] != 0 && filename[strlen (filename) - 1] == '/')
        filename[strlen (filename) - 1] = (BYTE) 0;

      lockp = Lock (filename, ACCESS_READ);

      if (!lockp)			/* Can't Lock it! 		*/
      {
        errstat = IoErr ();
        (void) strcat (filename, " not found");
        CleanUp (filename, (long)errstat);
      }
    }
    else
    {
/*
 * If no filename was specified, steal Lock on current directory from
 * CLI process task info.  We durn well better get something useful back;
 * we don't do any error checking on the stolen Lock.
 */
      procp = (struct Process *) FindTask (0L);
      lockp = procp->pr_CurrentDir;
      filename[0] = 0;			/* Tell DirIt() to use current dir */
    }

/* Get the directory for this path, display it */
    DirIt (lockp, filename);

/* Release the lock, bump our arg counter */
    if (lockp != 0 && PATHNAMED != 0)
      UnLock (lockp);
    lockp = 0L;
    cnt++;

    if (cnt < argc)
      WCHR("\n");
  } while (cnt < argc && BREAKFLAG == 0);

  CleanUp ("", 0L);
}

/* -------------------------------------------------------------------- */
/* Deallocate and close everything 					*/
/* -------------------------------------------------------------------- */
void CleanUp (exit_msg, exit_status)
  char *exit_msg;
  long exit_status;
{
  if (lockp && PATHNAMED)
    UnLock (lockp);

  if (GFibp != 0L)
    FreeMem(GFibp, (long)sizeof(struct FileInfoBlock));

  if (exit_status)
  {
    (void) asprintf (workstr, "ls: %s, Error #%ld\n", exit_msg, exit_status);
    WSTR (workstr);
  }
  exit ((int) exit_status);
}

/* -------------------------------------------------------------------- */
void DirIt (lockp, dirname)
  BPTR lockp;
  char *dirname;
{
  BPTR  tlockp;
  struct FibEntry *fibheadp;
  struct FibEntry *tfibp;
  char *subdir;
  long strsize;

/* Try to fill FileInfoBlock, bomb if not readable for some reason */
  if (!Examine (lockp, GFibp))
  {
    (void) asprintf (workstr, "Can't examine file or directory\n");
    WSTR (workstr);
    return;
  }

/* Put directory header if this is a recursive listing */
  if (dirname[0] && LISTALL > 0)
  {
    if (CONSOLE != 0)
     (void)asprintf(workstr, "\23330;41m %s \2330m\n", dirname);
    else
     (void)asprintf(workstr, "%s\n", dirname);
    WSTR(workstr);
  }

/* If this is a single file list it verbosely */
  if (GFibp->fib_EntryType < 0 && (DIRFILEFLAG & 2) != 0)
  {
    LListEntry (GFibp);
  }
  else
  {
/* Otherwise do a directory 	*/
/* Allocate and initialize a FibEntry head node */
    if ( (fibheadp = GetDir (lockp, GFibp)) != 0L && BREAKFLAG == 0)
    {
      if (NOSORTFLAG == 0)
        SortFibs ((long)sortkey, REVFLAG, fibheadp);

      if (LONGLIST == 0)
	SListDir (fibheadp);
      else
	LListDir (fibheadp);

      if ( (LISTALL & 1) != 0)
      {
        tfibp = fibheadp;
	do
	{
	  if (tfibp->Fibp->fib_EntryType > 0)
	  {
	    strsize = (strlen (dirname) + strlen (tfibp->Fibp->fib_FileName) + 2);
	    subdir = (char *) AllocMem ((LONG)strsize, 0L);
	    if (strlen (dirname) != 0)
	    {
	      (void) stpcpy (subdir, dirname);
	      if (dirname[strlen (dirname) - 1] != ':')
		(void) strcat (subdir, "/");
	    }
	    (void) strcat (subdir, tfibp->Fibp->fib_FileName);
	    tlockp = Lock (subdir, ACCESS_READ);
	    if (tlockp == 0L)
	    {
	      WSTR (subdir);
	      WSTR (" -- can't lock it!\n");
	      break;
	    }
	    else
	    {
	      WCHR ("\n");		/* Put a blank line between directories */
	      DirIt (tlockp, subdir);
	      UnLock (tlockp);
	      FreeMem (subdir, (LONG)strsize);
	    }
	  }
	  tfibp = tfibp->NextFib;
	} while (tfibp != fibheadp && BREAKFLAG == 0);
      }
    }
/* Clean up */
    FreeAllFibs (fibheadp);
  }
}

/* -------------------------------------------------------------------- */
/* Allocate and fill a linked list of FileInfoBlocks 			*/
/* -------------------------------------------------------------------- */
struct FibEntry *GetDir (lockp, fibp)
  BPTR lockp;
  struct FileInfoBlock *fibp;
{
  long nextstat;
  long tempnamlen;
  struct FibEntry *tfibp;
  struct FibEntry *headfib;

  maxnamlen = dircount = filecount = 0L;
  headfib = 0L;

  do
  {
    TestBreak();
    if (BREAKFLAG != 0)
      return(headfib);
    nextstat = ExNext (lockp, fibp);

    if (nextstat != 0)	/* We got something */
    {
/* See if it matches our pattern */
      if (!WILDCARD || wildmatch (fibp->fib_FileName, pattern))
      {

/* Bump count of files or directories */
	if (fibp->fib_EntryType > 0)
        {
          if ((DIRFILEFLAG & 1) != 0)
            dircount++;
          else
            goto ALLOCFIB;
        }
        else
        {
          if ((DIRFILEFLAG & 2) != 0)
            filecount++;
          else
            continue;
        }

/* See if this is the longest filename for later use in listing */
	tempnamlen = strlen (fibp->fib_FileName);
	if (tempnamlen > maxnamlen)
	  maxnamlen = tempnamlen;

        ALLOCFIB:
/* Allocate another FibEntry to put the info in */
        if (headfib == 0L)
        {
          headfib = AllocFib();
          if (headfib == 0L)
            return(headfib);
          headfib->NextFib = headfib;
          headfib->LastFib = headfib;
          *(headfib->Fibp) = *(fibp);
          tfibp = headfib;
        }
        else
        {
	  tfibp->NextFib = AllocFib ();
	  if (tfibp->NextFib == 0L)
	    return(0L);

/* Copy FIB contents to next entry for ExNext to work with */
	  *(tfibp->NextFib->Fibp) = *(fibp);

/* Link it into the list */
	  tfibp->NextFib->LastFib = tfibp;
	  tfibp = tfibp->NextFib;
	  tfibp->NextFib = headfib;
	}
      }
    }
  } while (nextstat);

/* Return TRUE if entries found, else print message and return FALSE */
  if ( (dircount + filecount) != 0)
  {
    return (headfib);
  }
  else
  {
    if (WILDCARD == 0 && DIRFILEFLAG == 3)
      WSTR ("Volume or directory is empty.\n");
    else
      WSTR ("No match.\n");
    return (0L);
  }
}

/* -------------------------------------------------------------------- */
/* List a FibEntry list in a compact fashion 				*/
/* -------------------------------------------------------------------- */
void SListDir (fibheadp)
  struct FibEntry *fibheadp;
{
  struct FibEntry *tfibp;
  long tabsize;
  long maxtab;
  long totrows;
  long maxwinrow;
  long colcnt;
  long rowcnt;
  long tabcnt;
  long pagecnt = 1;

  CursorOff ();		/* Turn the cursor off since it will blink anyway */
  GetWinBounds(&CurWinCols, &CurWinRows);

  tfibp = fibheadp;
  tabcnt = rowcnt = colcnt = 0;

  tabsize = maxnamlen + 2;
  if (CurWinCols < tabsize)
    maxtab = 1;
  else
    maxtab = CurWinCols / tabsize;
  maxwinrow = CurWinRows - 3;
  if (maxwinrow <= 0)
    maxwinrow = 1;
  totrows = (dircount + filecount) / maxtab;
  if ((dircount + filecount) % maxtab != 0)
    totrows++;

  do
  {
    if (tfibp->Fibp->fib_EntryType > 0)
    {
      if ( (DIRFILEFLAG & 1) == 0)
        goto GETNEXTFIB;
      else
        ColorPen2 ();
    }
    if (tabcnt)
      (void) asprintf (workstr, "\233%ldC", (long)tabcnt);
    else
      workstr[0] = (BYTE) 0;
    (void) strcat (workstr, tfibp->Fibp->fib_FileName);
    (void) strcat (workstr, "\n");
    WSTR (workstr);
    if (tfibp->Fibp->fib_EntryType > 0)
      ResetPen ();
    rowcnt++;

    if (rowcnt == maxwinrow || rowcnt == totrows)
    {
      colcnt++;				/* Start a new column		 */
/* Check to see if we have used the last column up and are about to run
 * off the screen entirely.  If so, give the user a chance to read it first.
 */
      if (colcnt == maxtab && rowcnt == maxwinrow && CONSOLE != 0)
      {
        if (maxwinrow > 1)
        {
         ++pagecnt;			/* Advance page number count	*/
	 PagePrompt (pagecnt);		/* Print it, wait for user	*/
	}
	totrows -= maxwinrow;
	colcnt = tabcnt = rowcnt = 0L;
      }
      else	/* Just move over one row and back up to the top */
      {
	(void) asprintf (workstr, "\233%ldA", (long)rowcnt);
	WSTR (workstr);
	tabcnt += tabsize;
	rowcnt = 0L;
      }
    }
    GETNEXTFIB:
    tfibp = tfibp->NextFib;
  } while (tfibp != fibheadp);

  if (totrows - rowcnt > 0)		/* Cursor down till level with	 */
  {					/* lowest line on screen	 */
    (void) asprintf (workstr, "\233%ldE", (long)(totrows - rowcnt));
    WSTR (workstr);
  }
  CursorOn ();
}

/* -------------------------------------------------------------------- */
/* Reset character color to default Pen1 colors 			*/
/* -------------------------------------------------------------------- */
void ResetPen ()
{
  if (CONSOLE != 0)
    WSTR ("\2330m");
}

/* -------------------------------------------------------------------- */
/* Turn the cursor on							*/
/* -------------------------------------------------------------------- */
void CursorOn ()
{
  if (CONSOLE != 0)
    WSTR ("\233 p");
}

/* -------------------------------------------------------------------- */
/* Turn the cursor off for faster text output 				*/
/* -------------------------------------------------------------------- */
void CursorOff ()
{
  if (CONSOLE != 0)
    WSTR ("\2330 p");
}

/* -------------------------------------------------------------------- */
/* Make Pen2 <usually yellow/orange> the current charcter color 	*/
/* -------------------------------------------------------------------- */
void ColorPen2 ()
{
  if (CONSOLE != 0)
    WSTR ("\23333m");
}

/* -------------------------------------------------------------------- */
/* Prompt the user to hit return, wait till return is hit 		*/
/* -------------------------------------------------------------------- */
void PagePrompt (page)
  long page;
{
  WSTR ("\2337m -- MORE -- Press Return: \2330m");
  (void) Read (In, workstr, 1L);
  (void) asprintf (workstr, "\233F\233K\2334;33mPage %ld:\2330m\n", (long)page);
  WSTR (workstr);
}

/* -------------------------------------------------------------------- */
/* List a directory in a verbose informative manner 			*/
/* -------------------------------------------------------------------- */
void LListDir (fibheadp)
  struct FibEntry *fibheadp;
{
  long totblocks = 0L;
  long totbytes = 0L;

  CursorOff ();
  LongList (&totblocks, &totbytes, fibheadp);

  (void) asprintf (workstr, "Dirs:%-3ld Files:%-4ld Blocks:%-5ld Bytes:%-8ld\n",
      (long)dircount, (long)filecount, totblocks, totbytes);
  WSTR (workstr);
  CursorOn ();
}

/* -------------------------------------------------------------------- */
void LongList (totblocks, totbytes, fibheadp)
  long *totblocks, *totbytes;
  struct FibEntry *fibheadp;
{
  struct FibEntry *tfibp;

  tfibp = fibheadp;
  do
  {
    LListEntry (tfibp->Fibp);
    if (tfibp->Fibp->fib_EntryType < 0)
    {
      *totblocks += tfibp->Fibp->fib_NumBlocks;
      *totbytes += tfibp->Fibp->fib_Size;
    }
    tfibp = tfibp->NextFib;
  } while (tfibp != fibheadp);
}

/* -------------------------------------------------------------------- */
/* Verbosely list a particular FibEntry 				*/
/* -------------------------------------------------------------------- */
void LListEntry (fib)
  struct FileInfoBlock *fib;
{
  long i, pmodes;
  char *cp1;
  char entry[160];

  pmodes = fib->fib_Protection & 0xff;
  cp1 = stpcpy (entry, "chsparwed ");
  for (i = 0; i < 4; i++)
  {
    if ((pmodes & (1 << i)) != 0)
      entry[8 - i] = '-';
  }
  for (; i < 8; i++)
  {
    if ((pmodes & (1 << i)) == 0)
      entry[8 - i] = '-';
  }
  if (fib->fib_Comment[0] == 0)
    entry[0] = '-';

  cp1 = stpcpy (cp1, FibFileDate (&fib->fib_Date));
  if (fib->fib_EntryType > 0)
  {
    if ( (DIRFILEFLAG & 1) == 0)
      return;

    if (CONSOLE)
      cp1 = stpcpy (cp1, "\23333m");
    cp1 = stpcpy (cp1, "   Directory   ");
    if (CONSOLE)
      cp1 = strcat (cp1, "\2330m");
  }
  else
    (void) asprintf (&entry[27], " %4ld %8ld ", fib->fib_NumBlocks, fib->fib_Size);
  (void)strcat(entry, fib->fib_FileName);
  (void)strcat(cp1, "\n");
  WSTR (entry);

  if (NOTEFLAG != 0 && fib->fib_Comment[0] != 0)
  {
    if (CONSOLE)
      (void)asprintf(cp1, "\23333m/* %s */\2330m\n", fib->fib_Comment);
    else
      (void)asprintf(cp1, "/* %s */\n", fib->fib_Comment);
    WSTR (workstr);
  }
}

#ifdef NOASM
/* -------------------------------------------------------------------- */
/* Calculate date based on DateStamp structure, return a string pointer */
/* -------------------------------------------------------------------- */
char *CalcDate (fib)
  struct FileInfoBlock *fib;
{
  static long days[12] =
  {
    31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  };

  static char *months[12] =
  {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  };
  char datestr[25];
  long i, mdays;
  long day, hour;
  long minute, sec;
  long ldays;
  long year;

  ldays = 1461L;
  year = 78L;
  day = fib->fib_Date.ds_Days;
  minute = fib->fib_Date.ds_Minute;
  sec = fib->fib_Date.ds_Tick / 50L;
  year += (day / ldays) * 4L;
  day %= ldays;

  while (day)
  {
    mdays = 365;
    if ((year & 3) == 0)
      mdays++;
    if (day < mdays)
      break;
    day -= mdays;
    year++;
  }

  for (i = 0L, day++; i < 12; i++)
  {
    mdays = days[i];
    if (i == 1 && (year & 3) == 0)
      mdays++;
    if (day <= mdays)
      break;
    day -= mdays;
  }
  hour = minute / 60;
  minute -= hour * 60;
  (void) asprintf (datestr, "%02ld-%s-%02ld %02ld:%02ld:%02ld ", day, (long)months[i], year, hour, minute, sec);
  return (datestr);
}
#endif

/* -------------------------------------------------------------------- */
/* Use AmigaDos to put a string on the stdout 				*/
/* -------------------------------------------------------------------- */
void WSTR (tstring)
  char *tstring;
{
  (void) Write (Out, tstring, (long) strlen (tstring));
}

/* -------------------------------------------------------------------- */
/* Use AmigaDos to put a character on the stdout 			*/
/* -------------------------------------------------------------------- */
void WCHR (ch)
  char *ch;
{
  (void) Write (Out, ch, 1L);
}

/* -------------------------------------------------------------------- */
/* Check to see if the user hit ^C 					*/
/* -------------------------------------------------------------------- */
void TestBreak ()
{
  unsigned long oldsig;

  oldsig = SetSignal (0L, SIGBREAKF_CTRL_C);
  if ( (oldsig & SIGBREAKF_CTRL_C) != 0L)
  {
    WSTR ("\2330m\233 p**BREAK\n");
    BREAKFLAG = 1;
  }
}

/* -------------------------------------------------------------------- */
/* Explain how to use 							*/
/* -------------------------------------------------------------------- */
void Usage ()
{
  WSTR (Author);
  WSTR (usage);
  WSTR (" c > Show comments\n");
  WSTR (" d > Dirs only\n");
  WSTR (" f > Files only\n");
  WSTR (" l > Long listing\n");
  WSTR (" n > No sort\n");
  WSTR (" r > Reverse sort\n");
  WSTR (" s > Sort by size\n");
  WSTR (" t > Sort by date\n");
  WSTR (" R > Recursive listing\n");
  CleanUp ("", 0L);
}

/* -------------------------------------------------------------------- */
/* Allocate a FibEntry structure and associated FileInfoBlock 		*/
/* -------------------------------------------------------------------- */
struct FibEntry *AllocFib ()
{
  struct FibEntry *tfibp;

  tfibp = (struct FibEntry *) AllocMem \
((long)( sizeof (struct FibEntry) + sizeof(struct FileInfoBlock) ), 0L);
  if (tfibp != 0L)
  {
    tfibp->Fibp = (struct FileInfoBlock *)((ULONG)tfibp + sizeof(struct FibEntry));
  }
  else
    BREAKFLAG = 1;
  return (tfibp);
}

/* -------------------------------------------------------------------- */
/* Free up memory allocated to a linked list of FibEntrys 		*/
/* -------------------------------------------------------------------- */
void FreeAllFibs (fibheadp)
  struct FibEntry *fibheadp;
{
  struct FibEntry *fibp;
  struct FibEntry *tfibp;

  if (fibheadp != 0)
  {
    fibp = fibheadp;
    do
    {
      tfibp = fibp->NextFib;
      FreeFib (fibp);
      fibp = tfibp;
    } while (tfibp != fibheadp);
  }
}

/* -------------------------------------------------------------------- */
/* Deallocate a single FibEntry structure 				*/
/* -------------------------------------------------------------------- */
void FreeFib (fibp)
  struct FibEntry *fibp;
{
  if (fibp != 0L)
    FreeMem (fibp, (long)(sizeof (struct FibEntry) + sizeof(struct FileInfoBlock)));
}

SHAR_EOF
cat << \SHAR_EOF > ls.doc

  LS.C -- an "improved" directory listing utility to replace the
  AmigaDOS DIR and LIST commands.

  V1.0   August 1986 Written from scratch by Justin V. McCormick.
  V2.0 November 1988 Revised for Lattice 5.0 and made 1.3 compatible.

Notice:

  This program is placed in the public domain with the understanding
that the author makes no claims or guarantees with regard to its
suitability for any given application, and that the author assumes no
responsibility for damages incurred by its usage.  Any resemblance
of this program to any other program, either living or dead, is
purely coincidental.

  Feel free to steal this code and make millions of dollars from its sale
or commercial use, but please give credit where credit is due.

Synopsis:

  Features adaptive columnar listing, versatile sort options,
UNIX-style pattern matching, recursive subdirectory listing, etc!

Usage:
	ls [options] [path1] [path2] ...

Options:
	-?	Help!
	-c	Show file comment info, -c implies -l
	-d	Show directory names only
	-f	Show filenames only
	-l      Long verbose listing showing filesizes and dates
	-n	No sort, just spit them out in the order ExNext() returns
	-r	Reverse sort direction
	-s	Sorted by size smallest to largest
	-t	Sorted by date oldest to newest
	-R	Recursive descent of subdirectories

  All arguments are optional.  Default is to give short columnar listing,
sorted alphabetically, using the current directory.  Alphabetizing is case
insensitive.

  Patterns may be matched in the given names, using the UNIX-style '*'
to wildcard any number of characters, and '?' to wildcard a single
character.  If you need to specify a pathname with spaces in it like
"Wombat Soup", you need to put quotes around it.  LS can process up to 30
separate pathname patterns in one command line.

Bugs:

  Redirecting the shortlist output to PRT: gives undesirable results,
since I am using relative cursor positioning commands to format the
screen output.  I thought about using an array to store a virtual
screen, but my primary goals were to keep the size down and display
speed at a maxiumum.  Also, LS cannot pattern match devices (like "dh*:")
or support multiple levels of pattern matching (like "dh0:?/L*.info").
This would involve another level of recursion and groking the Device List.

Changes From 1.0 to 2.0:

 o Source code prototyped, linted, traced, optimized, tweaked, etc.
 o Made resident ("pseudo-pure") by linking with cres.o from LC 5.0.
 o High-volume routines recoded in assembly (lssup.a).
 o Now handles multiple paths/files on a command line, up to 30.
 o New sort flags, including no sort.
 o Enhanced wildcards, understands complex *.?*.* expressions now.
 o More efficient ExNext() performance, less ram used for recursion.
 o SIGBREAKF_CTRL_C signal (Ctrl-C) cleanly aborts at any point now.
 o Command line parser handles quoted pathnames now (LC 5.0 benefit).
 o Short listing finally auto-adjusts to new console window sizes!
 o Pen color escape codes bypassed when redirecting long output.
 o Sorting by size or date is also subsorted alphabetically now.
 o Long listing shows new 1.3 file attributes, plus comment indicator.
 o File dates are now in international format, YY-MM-DD.
 o Fixed listings with files datestamped after 99-12-31 (overflow).
 o Fixed listings with files datestamped before 78-01-01 (time < 0).

  Don't send me money for this one!  Its a Christmas present 8-)
However, if you find any bugs I'd like to hear about them!

        Justin V. McCormick
	8330 E. Quincy Ave., C-312
	Denver, CO 80237

        Home: 303-290-8429
	Work: 303-825-4144
SHAR_EOF
cat << \SHAR_EOF > ls.h
#include <dos.h>
#include <libraries/dosextens.h>

/*lint -save	*/
/*lint -library */
#include <proto/exec.h>
#include <proto/dos.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*lint -restore	*/

extern int tolower (char);

/* Prevent Lint from complaining about ANSI prototype extensions */
#ifdef _lint

#define __asm
#define __stdargs
#define R_D0
#define R_D1
#define R_A0

#else

#define R_D0	register __d0
#define R_D1	register __d1
#define R_A0	register __a0

#endif

#define MEMF_PUBLIC (1L<<0)
#define MEMF_CHIP   (1L<<1)
#define MEMF_FAST   (1L<<2)
#define MEMF_CLEAR  (1L<<16)
SHAR_EOF
cat << \SHAR_EOF > ls.lnk
DEFINE @_main = @_tinymain
DEFINE @XCEXIT = xXCEXIT
DEFINE @chkabort = xchkabort
DEFINE @write = xchkabort
DEFINE @_dclose = xchkabort
SC SD ND
SHAR_EOF
cat << \SHAR_EOF > lssup.a
* --------------------------------------------------------------------- *
* LSSUP.A	- Assembly support routines for ls.c			*
* Copyright (c) 1988 by Justin V. McCormick. All Rights Reserved.	*
* --------------------------------------------------------------------- *
	IDNT	"lssup.a"

	include "asm:inc/macros.i"

; Assembler options for CAPE
	BASEREG	B
	SMALLOBJ
	OPTIMON
	ADDSYM
	DEBUG

; Equates
fib_FileName	equ	$8
fib_Size	equ	$7C
fib_NumBlocks	equ	$80
fib_DateStamp	equ	$84

ds_Days		equ	$0
ds_Minute	equ	$4
ds_Tick		equ	$8

_LVOAddPort	equ	$FFFFFE9E
_LVOAllocMem	equ	$FFFFFF3A
_LVOAllocSignal	equ	$FFFFFEB6
_LVODebug	equ	$FFFFFF8E
_LVOFindTask	equ	$FFFFFEDA
_LVOFreeMem	equ	$FFFFFF2E
_LVOFreeSignal	equ	$FFFFFEB0
_LVOGetMsg	equ	$FFFFFE8C
_LVOPutMsg	equ	$FFFFFE92
_LVORawDoFmt	equ	$FFFFFDF6
_LVORemPort	equ	$FFFFFE98
_LVORead	equ	$FFFFFFD6
_LVOWaitPort	equ	$FFFFFE80
_LVOWrite	equ	$FFFFFFD0

pr_ConsoleTask		EQU	$A4 
MEMF_CLEAR		EQU	$10000 
MEMF_PUBLIC		EQU	$1 
sp_Msg			EQU	$0 
sp_Pkt			EQU	$14 
sp_SIZEOF		EQU	$44 
dp_Link			EQU	$0 
dp_Port			EQU	$4 
dp_Arg1			EQU	$14 
dp_Type			EQU	$8 
ACTION_SCREEN_MODE	EQU	$3E2 
LN_NAME			EQU	$A 
LN_PRI			EQU	$9 
LN_TYPE			EQU	$8 
MP_FLAGS		EQU	$E 
MP_MSGLIST		EQU	$14 
MP_SIGBIT		EQU	$F 
MP_SIGTASK		EQU	$10 
MP_SIZE			EQU	$22 
NT_MSGPORT		EQU	$4 
PA_SIGNAL		EQU	$0 

* External constants
	XREF	_DOSBase
	XREF	_Out
	XREF	_In
* --------------------------------------------------------------------- *
	SECTION	lssup,CODE

* ------------------------------------------------------------------------- *
* void asprintf(wstr, formatstring, args)
*   char *wstr;
*   char *formatstring;
*   char **args;
* 
* Synopsis: Given formatstring and args to format, formats output to wstr.
* Similar to sprintf(), except doesn't handle floats.
* ------------------------------------------------------------------------- *
	XDEF	_asprintf
_asprintf:
	link	a5,#0
	movem.l	d0-d2/a0-a3,-(sp)	;Save everything we might clobber

* Call format function to convert fmtstring and args to buffer on the stack
	movea.l	12(a5),a0		;Grab format string
	lea	16(a5),a1		;Grab EA of arguments
	lea	kput1,a2		;Grab EA of output subroutine
	movea.l	8(a5),a3		;Grab EA of dest workspace
	SYS	RawDoFmt,4		;Format it into workspace

	movem.l	(sp)+,d0-d2/a0-a3	;Restore registers
	unlk	a5			;And stack frame
	rts

* ------------------------------------------------------------------------- *
* RawDoFmt() output routine for xprintf, called for each formatted char.
* Takes byte in d0 and puts in buffer pointed to by a3, then increments a3.
* ------------------------------------------------------------------------- *
	XDEF	kput1
kput1:
	move.b	d0,(a3)+
	rts

* --------------------------------------------------------------------- *
* void GetWinBounds(width, height)
*   long *width, *height;
*
* Find current console window, determine width and height
* in terms of current font, update width and height VPARMS passed.
* --------------------------------------------------------------------- *
rpstr	equ	-32
rpport	equ	-12
packet	equ	-8
conid	equ	-4

width	equ	8
height	equ	12

	XDEF	_GetWinBounds
_GetWinBounds:
	link	a5,#-32
	movem.l	d2-d4/a2,-(sp)

	suba.l	a1,a1
	SYS	FindTask,4		;d0 = FindTask(0L), our process
	movea.l	d0,a0			;Transfer to address reg
	move.l	pr_ConsoleTask(a0),conid(a5) ;Save proc->pr_ConsoleTask

	moveq	#0,d4			;Clear our success status register

	moveq	#0,d0
	movea.l	d0,a0
	bsr.w	CreatePort
	move.l	d0,rpport(a5)		;rpport = CreatePort(0L, 0L)
	 beq.w	gwbdone			;Oops, no signals or ram available!
	move.l	#MEMF_PUBLIC+MEMF_CLEAR,d1
	moveq	#sp_SIZEOF,d0
	SYS	AllocMem
	move.l	d0,packet(a5)		;packet = AllocMem(sizeof(*packet),MEMF_PUBLIC|MEMF_CLEAR)
	 beq.w	gwbfreeport		;Oops, no ram, free up port

* Okay, we got our process id, reply port, and packet
* Now toggle the console into raw mode
	movea.l	rpport(a5),a2
	movea.l	d0,a1
	movea.l	conid(a5),a0
	moveq	#1,d0
	bsr.w	SetConsoleType		;SetConsoleType(1L, conid, packet, rpport)

* Request a window bounds report
	moveq	#4,d3
	lea	gwbrstr(a4),a0
	move.l	a0,d2
	move.l	_Out(a4),d1
	SYS	Write,_DOSBase(a4)	;Write(Output(), "\2330 q", 4L);
	cmpi.l	#$0004,d0		;Did the console choke on it?
	 bne.w	gwbsetcook		;hmmm, see if we can back out gracefully

* Read the report string into stack buffer
	moveq	#16,d3			;Don't let it get longer than 16 characters
	lea	rpstr(a5),a0		;Point to input string area
	move.l	a0,d2
	move.l	_In(a4),d1
	SYS	Read			;Read(Input(), rpstr, 16L)
	move.l	d0,d4			;Save read length while we close shop

* Turn the console back to cooked mode pronto to avoid cursor blink
gwbsetcook:
	movea.l	rpport(a5),a2
	movea.l	packet(a5),a1
	movea.l	conid(a5),a0
	moveq	#0,d0
	bsr.w	SetConsoleType		;SetConsoleType(0L, conid, packet, rpport)

* Release resources we borrowed
gwbfreepack:
	move.l	packet(a5),d0		;Did we allocate a packet?
	 beq.b	gwbfreeport		;nay, check for port to free
	movea.l	d0,a1
	moveq	#sp_SIZEOF,d0
	SYS	FreeMem			;Else FreeMem(packet, sizeof(*packet))

gwbfreeport:
	move.l	rpport(a5),d0		;if (rpport)...
	 beq.w	gwbdone			;nope
	bsr.w	DeletePort		;Else DeletePort(rpport)

* Finally, sanity check window bounds report string
* d4 = length of report string according to Read()
	cmpi.l	#9,d4			;Less than 8 characters returned?
	 ble.w	gwbdone			;hmmm, phonky bounds report from DOS?
	lea	rpstr(a5),a2		;a2 = rpstr
	cmpi.b	#';',4(a2)		;Matches a typical report template?
	 bne.w	gwbdone			;nope, got some weird junk back?
	cmpi.b	#'r',-1(a2,d4.w)	;Last byte is 'r' for report?
	 bne.w	gwbdone			;Nope, message fubar!

* Parse the height and width variables from the field now
* Our report format looks like this in hex:
* 	9b 31 3b 31 3b y2 y1 3b x2 x1 20 72
* Or in ascii:
*	<0x9b>1;1;20;77 r
* Which would indicate a width of 77 cols and a height of 20 rows for
* the current console device
*
* REGS:	a2 points to beginning of 'r' terminated string

	lea	5(a2),a2		;Point to first char of Y size
	moveq	#0,d1			;Clear out work reg

* Convert ascii rows value to LONG, update host data
	move.b	(a2)+,d1		;Grab a Y
	subi.w	#'0',d1			;Less ascii offset
	cmpi.b	#';',(a2)		;Any more Y digits?
	 beq.b	1$			;Nope
	mulu	#10,d1			;Else shift by 10
	add.b	(a2)+,d1		;Add least significant Y digit
	subi.b	#'0',d1			;Less ascii offset
	cmpi.b	#';',(a2)		;Any more Y digits?
	 beq.b	1$			;Nope
	mulu	#$000a,d1		;Else shift by 10
	add.b	(a2)+,d1		;Add least significant Y digit
	subi.b	#'0',d1			;Less ascii offset
					;We'll assume screen height < 999 rows	
1$
* Convert ascii columns value to LONG, update host data
	addq.w	#1,a2			;Move past the ';' separator
	moveq	#0,d2			;Zap work reg
	move.b	(a2)+,d2		;Grab msd of X
	cmpi.b	#' ',d2			;Premature end?
	 beq.w	gwbdone			;Huh, must be garbage - don't update VPARMS
	cmpi.b	#';',d2			;Also a possible error
	 beq.w	gwbdone
	cmpi.b	#'r',d2			;And what about this?
	 beq.w	gwbdone

	subi.b	#'0',d2			;Okay, adjust ascii offset
	cmpi.b	#' ',(a2)		;Hit end of report?
	 beq.b	2$			;Yep
	mulu	#$000a,d2		;Else shift by 10
	add.b	(a2)+,d2		;Add next digit
	subi.b	#'0',d2			;Ascii adjust
	cmpi.b	#' ',(a2)		;Hit end of report?
	 beq.b	2$			;Yep
	mulu	#$000a,d2		;Else shift by 10
	add.b	(a2),d2			;Add next digit
	subi.b	#'0',d2			;Ascii adjust

2$
* Finally, update parameters by reference
	movea.l	height(a5),a0		;Grab height VPARM
	move.l	d1,(a0)			;*height = d1
	movea.l	width(a5),a0		;Grab width VPARM
	move.l	d2,(a0)			;*width = d2

gwbdone:
	movem.l	(sp)+,d2-d4/a2
	unlk	a5
	rts

* --------------------------------------------------------------------- *
* __asm void SetConsoleType(flag, id, packet, port)
*   register __d0 long flag;
*   register __a0 struct Process *id;
*   register __a1 struct StandardPacket *packet;
*   register __a2 struct MsgPort *port;
*
* Flag = 1L -- Raw mode
*      = 0L -- Cooked mode
* --------------------------------------------------------------------- *
	XDEF	SetConsoleType
SetConsoleType:
	movem.l	a3/a5,-(sp)

	movea.l	a0,a3			;Copy process pointer
	movea.l	a1,a5			;Copy packet pointer
	lea	sp_Pkt(a5),a0		;a0 = &packet->sp_Pkt
	move.l	a0,sp_Msg+LN_NAME(a5)	;p->sp_Msg.mn_Node.ln_Name = &p->sp_Pkt
	lea	sp_Msg(a5),a0		;a0 = &packet->sp_Msg
	move.l	a0,sp_Pkt+dp_Link(a5)	;p->sp_Pkt.dp_Link = &p->sp_Msg
	move.l	a2,sp_Pkt+dp_Port(a5)	;p->sp_Pkt.dp_Port = replyport
	move.l	#ACTION_SCREEN_MODE,sp_Pkt+dp_Type(a5)	;Set function

	tst.w	d0			;On or Off?
	 beq.w	1$
	move.l	#-1,sp_Pkt+dp_Arg1(a5)	;RAW ON
	bra.b	2$
1$
	clr.l	sp_Pkt+dp_Arg1(a5)	;RAW OFF
2$
	movea.l	a3,a0
	movea.l	a5,a1
	SYS	PutMsg,4		;PutMsg(proc, packet)

	movea.l	a2,a0
	SYS	WaitPort		;WaitPort(port)
	movea.l	a2,a0
	SYS	GetMsg			;(void)GetMsg(port)

	movem.l	(sp)+,a3/a5
	rts

* ------------------------------------------------------------------------- *
* struct MsgPort *CreatePort(name, pri) (a0/d0)
* ------------------------------------------------------------------------- *
	XDEF	CreatePort
CreatePort:
	movem.l	d5/d7/a2/a5,-(sp)

	move.l	a0,a5			;Save Name
	move.l	d0,d5			;Save Pri

* Allocate a free signal, crap out if we can't
	moveq	#-1,d0
	SYS	AllocSignal,4
	cmp.l	#-1,d0			;Did we get a signal?
	 bne.b	cpgotsig		;Yep
	moveq	#0,d0			;Otherwise return NULL
	bra.w	cpdone

cpgotsig:
	move.l	d0,d7			;Save our signal

* Allocate memory for MsgPort
	moveq.l	#MP_SIZE,d0		;Size of MsgPort
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1	;Type of memory
	SYS	AllocMem		;Allocate it
	tst.l	d0			;Did we get it?
	 bne.b	cpgotport		;Yep

	move.l	d7,d0			;Otherwise crap out, free signal
	SYS	FreeSignal
	moveq	#0,d0			;Return NULL
	bra.w	cpdone

cpgotport:
	move.l	d0,a2			;This is our new port!
	move.l	a5,LN_NAME(a2)		;port->mp_Node.ln_Name = name
	move.b	d5,LN_PRI(a2)		;port->mp_Node.ln_Pri = priority
	move.b	#NT_MSGPORT,LN_TYPE(a2) ;port->mp_Node.ln_Type = NT_MSGPORT
	move.b	#PA_SIGNAL,MP_FLAGS(a2) ;port->mp_Flags = PA_SIGNAL
	move.b	d7,MP_SIGBIT(a2)	;port->mp_SIGBIT = sigBit
	suba.l	a1,a1
	SYS	FindTask
	move.l	d0,MP_SIGTASK(a2)	;port->mp_SIGTASK = FindTask(0L)
	
	cmpa.l	#0,a5			;Is this a new name?
	beq.b	cpnoname		;Nope, add it to the msg list

	movea.l	a2,a1
	SYS	AddPort			;Otherwise add this port
	move.l	a2,d0			;Return port pointer
	bra.b	cpdone

cpnoname:
* Initialized New List head
	lea	MP_MSGLIST(a2),a0 	;a0 = &port->mp_MsgList
	move.l	a0,(a0)			;list->lh_Head = list
	addq.l	#4,(a0)			;list->lh_Head += 4L
	clr.l	4(a0)			;list->lh_Tail = 0L
	move.l	a0,8(a0)		;list->lh_TailPred = list
	move.l	a2,d0			;Return port pointer

cpdone:
	movem.l	(sp)+,d5/d7/a2/a5
	rts

* ------------------------------------------------------------------------- *
* DeletePort(port)(d0)
* ------------------------------------------------------------------------- *
	XDEF	DeletePort
DeletePort:
	move.l	a5,-(sp)

	move.l	d0,a5
	tst.l	LN_NAME(a5)		;Is there a name?
	beq.s	dpnoname
	
	move.l	d0,a1
	SYS	RemPort,4		;RemPort(port)
	
dpnoname:
	move.b	#$ff,LN_TYPE(a5) 	;port->mp_Node.ln_Type = 0xff
	move.l	#-1,MP_MSGLIST(a5) 	;port->mp_MsgList.lh_Head = -1L

	moveq	#0,d0
	move.b	MP_SIGBIT(a5),d0	;d0 = port->mp_SigBit
	SYS	FreeSignal,4		;FreeSignal(d0)

	moveq	#MP_SIZE,d0
	move.l	a5,a1
	SYS	FreeMem			;FreeMem(port, sizeof(*port))

	move.l	(sp)+,a5
	rts

* ------------------------------------------------------------------------
* char *FibFileDate(fib_date)
*   register struct DateStamp *fib_date;
*
*   Calculate date based on DateStamp structure and return a pointer
* to the formatted date string.
* ------------------------------------------------------------------------
	XDEF	_FibFileDate
_FibFileDate:
	link	a5,#0
	movem.l	d3-d7/a4,-(sp)

	movea.l	8(a5),a1		;Grab datestamp pointer
	moveq	#78,d7			;Initial year = 1978

	move.l	(a1),d5			;days = fib_date->ds_Days
	 blt	ffdbaddate		;Hey! you can't be negative! Invalid date...

* Determine what year it is
	divu	#1461,d5
	move.l	d5,d0			;Stash it
	ext.l	d5
	lsl.l	#2,d5
	add.l	d5,d7			;year += (days / 1461) * 4

* Count how many months into that year
ffdgetmo:
	swap	d0			;days %= 1461
	move.w	d0,d5

1$	tst.w	d5			;Out of days yet?
	 beq	3$			;Yep, done here

	move.w	#365,d6			;Else month_days = 365
	move.w	d7,d0			;Grab year
	andi.w	#3,d0			;if (year & 3) == 0 Leap year?
	 bne	2$			;Nope
	addq.w	#1,d6			;Otherwise bump month_days

2$	cmp.w	d6,d5			;is day < month_days?
	 blt	3$			;yep, done here
	sub.w	d6,d5			;otherwise day -= month_days

	addq.l	#1,d7			; year++
	bra	1$
3$

* Count how many days into that month of that year
ffdgetday:
;for (i = 0, day++; i < 12; i++)
	moveq	#0,d4			;current month = 0
	moveq	#0,d6			;Zap hinybs
	addq.w	#1,d5
	lea	_dayspermonth(a4),a0

1$
	move.b	0(a0,d4.w),d6		;month_days = dayspermonth[i]

	cmpi.w	#1,d4			;if (i == 1 && (year & 3) == 0)
	 bne	2$
	move.w	d7,d0
	andi.w	#3,d0
	 bne	2$
	addq.w	#1,d6			;month_days++

2$	cmp.w	d6,d5			;if (day <= month_days)
	 ble	4$			;Break out, found the right month

	sub.w	d6,d5			;Else, day -= month_days

	addq.w	#1,d4			;i++
3$	cmpi.w	#12,d4			;Done all months yet?
	 blt	1$			;Nope

4$
ffdprint:
1$	cmpi.l	#99,d7			;while (year >= 100)
	 ble	2$
	subi.l	#100,d7			;year -= 100
	bra	1$
2$
;asprintf(datestr, "%02d-%02d-%02d %02d:%02d:%02d", i + 1, day, year, hour, min, sec)
	move.l	8(a1),d0		;sec = fib_date->ds_Tick / 50;
	divu	#50,d0
	move.w	d0,-(sp)		;Push secs

	moveq	#0,d0			;Zap reg
	move.w	6(a1),d0		;min = fib_date->ds_Minute
	move.w	d0,d1			;Clone it
	divu	#60,d0
	move.w	d0,d3			;hour = min / 60
	mulu	#60,d0
	sub.w	d0,d1			;min -= hour * 60
	move.w	d1,-(sp)		;Push mins

	move.w	d3,-(sp)		;Push hours
	addq.w	#1,d4			;Push day of month (offset by 1!)
	move.w	d5,-(sp)		;Push month
	move.w	d4,-(sp)
	move.w	d7,-(sp)		;Push year
	pea	_datepat(a4)		;Push the format pattern
	pea	_datestr(a4)		;Push destination buffer
	jsr	_asprintf	
	lea	20(sp),sp
	lea	_datestr(a4),a0
	move.l	a0,d0			;return((char *)&datestr[0])

ffddone:
	movem.l	(sp)+,d3-d7/a4
	unlk	a5
	rts

ffdbaddate:
	lea	_baddatestr(a4),a0	;return (" <Invalid Date> ");
	move.l	a0,d0
	bra	ffddone

*----------------------------------------------------------------------
* LONG iswild(name)
*   char *name;
*
* Search a string for wild characters, return 1 if found
*----------------------------------------------------------------------
	XDEF	_iswild
_iswild:
	movea.l	4(sp),a0		;Grab string pointer
	moveq	#0,d0			;Clear out our character register
ischk1:
	move.b	(a0)+,d0		;Grab a char
	 beq	iwdone			;Might be end of string?
	cmpi.b	#'*',d0			;Is it *?
	 beq	iswdone			;yep, is wild
	cmpi.b	#'?',d0			;Is it a qmark
	 bne	ischk1			;Nope, check next character

iswdone:
	moveq	#1,d0
iwdone:
	rts


* ------------------------------------------------------------------------
; Compare a wild card name with a normal name
; LONG wildmatch (name, wild)
;   char *name, *wild;
* ------------------------------------------------------------------------
	XDEF	_wildmatch
_wildmatch:
	link	a5,#-64
	movem.l	d3/a2-a3,-(sp)

	movea.l	8(a5),a2		;Grab name
	movea.l	12(a5),a3		;Grab pattern
	lea	-64(a5),a0		;back[0][0]
	lea	-60(a5),a1		;back[0][1]

	moveq	#0,d3			;bi = 0

wmloop1:
	tst.b	(a2)			;End of name?
	 bne	wmnoteon
	tst.b	(a3)			;End of pattern?
	 beq	wmmatched		;Yep, we matched

wmnoteon:
	cmpi.b	#'*',(a3)		;Is it a splat?
	 bne	wmnotstar		;Nope, maybe '?'

	cmpi.w	#64,d3			;Have we hit max expression depth?
	 beq	wmnomatch		;Yep, ran out of room in recursion table

;back[bi][0] = w
	move.l	a3,0(a0,d3.w)		;Stash pointer to this '*' in table

;back[bi][1] = n
	move.l	a2,0(a1,d3.w)

	addq.w	#8,d3			;++bi
	addq.w	#1,a3			;++w
	bra	wmloop1			;Check next

wmgoback:
	subq.w	#8,d3			;--bi
	move.l	a0,d0
wmback1:
	tst.w	d3			;while (bi >= 0 && *back[bi][1] == '\x0')
	 blt	wmbacked
	movea.l	0(a1,d3.l),a0
	tst.b	(a0)
	 bne	wmbacked

	subq.w	#8,d3			;--bi
	bra	wmback1

wmbacked:
	tst.w	d3			;if (bi < 0)
	 blt	wmnomatch		;return (0)

	movea.l	d0,a0
	movea.l	0(a0,d3.w),a3		;w = back[bi][0] + 1
	addq.w	#1,a3	

	addq.l	#1,0(a1,d3.w)
	movea.l	0(a1,d3.l),a2		;n = ++back[bi][1]

	addq.w	#8,d3			;++bi
	bra	wmloop1

wmnotstar:
	cmpi.b	#'?',(a3)		;Is it '?'
	 bne	wmnotqmark

	tst.b	(a2)			;Reached end of string?
	 bne	wmincpoint		;Nope, move on to next char

	tst.w	d3			;Are we at top level of expression?
	 beq	wmnomatch		;Yep, expression didn't match
	bra	wmgoback		;Otherwise pop a level and try to match

wmnotqmark:
	move.b	(a2),d0			;Grab a char from bstr
	cmpi.b	#$40,d0			;less than @ character?
	 bls	1$			;Yep
	cmpi.b	#$5a,d0			;Greater than Z?
	 bhi	1$			;Yep
	addi.b	#$20,d0
1$
	move.b	(a3),d1			;Grab a char from bstr
	cmpi.b	#$40,d1			;less than @ character?
	 bls	2$			;Yep
	cmpi.b	#$5a,d1			;Greater than Z?
	 bhi	2$			;Yep
	addi.b	#$20,d1
2$
	cmp.b	d0,d1			;*n = *w?
	 beq	wmincpoint		;Yep, move on past

	tst.w	d3			;Are we at top expression level?
	 beq	wmnomatch		;Yep, they didn't match
	bra	wmgoback		;Nope, process next part

wmincpoint:
	tst.b	(a2)			;Done with name?
	 beq	wmnamend		;Yep
	addq.w	#1,a2			;Otherwise increment name pointer

wmnamend:
	tst.b	(a3)			;End of pattern?
	 beq	wmmatched		;Yep, we matched
	addq.w	#1,a3			;Otherwise inc wild pointer, match next char
	bra	wmloop1

wmmatched:
	moveq	#1,d0
	bra	wmdone

wmnomatch:
	moveq	#0,d0

wmdone:
	movem.l	(sp)+,d3/a2-a3
	unlk	a5
	rts

* --------------------------------------------------------------------- *
* void SortFibs (keytype, direction, fibheadp)
*		   d0       d1         a0
*  int keytype;
*  struct FibEntry *fibheadp;
*
* Selection sort a linked list of FibEntrys based on a keycode 		*
* --------------------------------------------------------------------- *
	XDEF	_SortFibs
_SortFibs:
	link	a5,#0
	movem.l	d2-d4/a2-a3/a6,-(sp)

	move.l	d0,d2			;Save keytype
	move.l	d1,d4			;Save direction of sort
	movea.l	a0,d3			;Save fibheadp
	movea.l	a0,a2			;a2 = a0 = i

sfILoop:
	cmp.l	(a2),d3			;i->NextFib != fibheadp?
	 beq	sfdone			;Nope, wrapped around to start
	movea.l	a2,a6			;k = i
	movea.l	(a2),a3			;j = i->NextFib

sfJLoop:
	cmp.l	a3,d3			;j != fibheadp?
	 beq	sfJdone			;Nope compared them all, see if swapped any
	movea.l	8(a6),a0		;a0 = k->Fibp
	movea.l	8(a3),a1		;a1 = j->Fibp
	move.l	d2,d0			;d0 = keytype
	jsr	_CompFibs		;d0 = CompFibs(keytype, k->Fibp, j->Fibp)
	tst.w	d4			;Reverse sort?
	 beq	1$			;Nope
	bchg.l	#0,d0			;Else reverse sense of return
1$
	tst.l	d0			;Return != 0?
	 beq	sfNextJ			;Nope, these two are in order
	movea.l	a3,a6			;else k = j, this is new swap

sfNextJ:
	movea.l	(a3),a3			;j = j->NextFib
	bra	sfJLoop			;Check bounds

sfJdone:
	cmpa.l	a6,a2			;k != i, did we swap?
	 beq	sfNextI			;Nope, i was in correct position already
	move.l	8(a6),d0
	move.l	8(a2),8(a6)
	move.l	d0,8(a2)		;Else SwapFibs (k, i)

sfNextI:
	movea.l	(a2),a2			;i = i->NextFib
	bra	sfILoop			;Check bounds

sfdone:
	movem.l	(sp)+,d2-d4/a2-a3/a6
	unlk	a5
	rts

* --------------------------------------------------------------------- *
* int CompFibs (keytype, a, b)
*		 d0     a0 a1
*   int keytype;
*   struct FileInfoBlock *a, *b;
*
* Used by SortFibs to determine precedence of Fibs.
* --------------------------------------------------------------------- *
	XDEF	_CompFibs
_CompFibs:
	tst.w	d0			;Alphabetize?
	 bne	cfnalpha		;Nope

* Compare lexigraphically, ignoring case differences
cfalpha:
	lea	fib_FileName(a0),a0	;a = &Fipb->fib_FileName
	lea	fib_FileName(a1),a1	;b = &Fipb->fib_FileName

;  for(; *a && tolower(*a) == tolower(*b); a++, b++);
lccstart:
	tst.b	(a0)			;Is there a char here at source?
	 beq	lcceostr		;Nope, fell off the end

	move.b	(a1)+,d1		;Grab a char from bstr
	cmpi.b	#$40,d1			;less than @ character?
	 bls	1$			;Yep
	cmpi.b	#$5a,d1			;Greater than Z?
	 bhi	1$			;Yep
	addi.b	#$20,d1
1$
	move.b	(a0)+,d0		;Grab a char from astr
	cmpi.b	#$40,d0			;less than @ character?
	 bls	2$			;Yep
	cmpi.b	#$5a,d0			;Greater than Z?
	 bhi	2$			;Yep
	addi.b	#$20,d0
2$
	cmp.b	d0,d1			;are they the same?
	 beq	lccstart		;Yep, compare next pair of chars

lcceostr:
	sub.b	d1,d0			;return(tolower(*astr) - tolower(*bstr))
	 bgt	cftrue			; > 0?, return TRUE
	bra	cffalse			;Else return FALSE

cfnalpha:
	subq.w	#1,d0			;Size?
	 bne	cfnsize			;Nope

* Compare fib_Sizes
	move.l	fib_Size(a1),d0		;d0 = bfib->fib_Size
	cmp.l	fib_Size(a0),d0		;a->fib_Size > b->fib_Size?
	 blt	cftrue			;Yep, return TRUE
	 bgt	cffalse			;<, return FALSE
	bra	cfalpha			;Else it's a tie, alphabetize

cfnsize:
	subq.w	#1,d0			;Time?
	 bne	cffalse			;Nope, an error!

* Compare fib_DateStamps
	lea	fib_DateStamp(a0),a0	;a = &afib->fib_DateStamp
	lea	fib_DateStamp(a1),a1	;b = &bfib->fib_DateStamp
	move.l	ds_Days(a1),d0		;d0 = bdate->ds_Days
	cmp.l	ds_Days(a0),d0		;a->ds_Days > b->ds_Days?
	 blt	cftrue			;Yep, a is older
	 bgt	cffalse			;Else if a < b, b is older
					;Else they are the same day, check min/tick
	move.l	ds_Minute(a0),d0
	sub.l	ds_Minute(a1),d0	;d0 = amin - bmin
	muls	#3000,d0		;     * 3000
	add.l	ds_Tick(a0),d0
	sub.l	ds_Tick(a1),d0		;     + atick - btick
	 bgt	cftrue			;Hey, a > b
	 blt	cffalse			;a < b, return false
	bra	cfalpha			;Else its the same date, alphabetize

cftrue:
	moveq	#1,d0
	rts
cffalse:
	moveq	#0,d0
	rts

* --------------------------------------------------------------------- *
	SECTION	__MERGED,DATA
* --------------------------------------------------------------------- *
	XDEF	_dayspermonth
_dayspermonth:
	dc.b	31,28,31,30,31,30,31,31,30,31,30,31
	XDEF	_datepat
_datepat:
	dc.b	'%02d-%02d-%02d %02d:%02d:%02d',0
	CNOP	0,2
	XDEF	_baddatestr
_baddatestr:
	dc.b	'00-00-00 00:00:00',0
	CNOP	0,2

	XDEF	gwbrstr
gwbrstr:
	dc.b	$9b,'0 q'

* --------------------------------------------------------------------- *
	SECTION	__MERGED,BSS
* --------------------------------------------------------------------- *
	XDEF	_datestr
_datestr:
	ds.b	40

* --------------------------------------------------------------------- *
	END
* --------------------------------------------------------------------- *
SHAR_EOF
cat << \SHAR_EOF > makefile
# -------------------------------------------
# LS 2.0 lmkfile by Justin V. McCormick 88/11/27
# -------------------------------------------
OBJS	= c2.o ls.o lssup.o
DEST	= lcs

ASM	= casm
ASFLAGS = -cv
AINC	= ainc:
LC	= lc
LC1	= lc1
LC2	= lc2
LCFLAGS = -ccfmsu -d2 -rr -v

LINK	= blink
LIBS 	= lib:lcr.lib
LNMAP	= F H L S X PLAIN WIDTH 84 HEIGHT 0 SWIDTH 20

.a.o:
	$(ASM) -a $*.a -o$*.o -i$(AINC) $(ASFLAGS)

.c.o:
	$(LC) $(LCFLAGS) $*

$(DEST): $(OBJS)
	$(LINK) $(OBJS) to $(DEST) LIB $(LIBS) WITH $(DEST).lnk MAP $(DEST).map $(LNMAP)
SHAR_EOF
#	End of shell archive
exit 0
-- 
Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
Have five nice days.