[comp.sources.amiga] v90i198: ls 4.0k - yet another unix-like ls command, Part04/04

Amiga-Request@cs.odu.edu (Amiga Sources/Binaries Moderator) (07/04/90)

Submitted-by: kim@uts.amdahl.com (Kim E. DeVaughn)
Posting-number: Volume 90, Issue 198
Archive-name: unix/ls-4.0k/part04

#!/bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 4 (of 4)."
# Contents:  src/ls.c.aa
# Wrapped by tadguy@xanth on Tue Jul  3 15:22:14 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'src/ls.c.aa' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/ls.c.aa'\"
else
echo shar: Extracting \"'src/ls.c.aa'\" \(45212 characters\)
sed "s/^X//" >'src/ls.c.aa' <<'END_OF_FILE'
X/* ----------------------------------------------------------------------
X   ls.c  -  main source code file for the "improved" directory utility.
X
Xls v4.0k is (c) Copyright 1990, Kim E. DeVaughn, all rights reserved.
X
X
XFYI:
X
X  UNIX is a registered trademark of AT&T.  They had nothing to do with any
X  of this code, but did provide the "model" for some of its output formats
X  and options.
X
X
XAuthor:
X
X  Kim E. DeVaughn
X
X  v4.0k  May 11, 1990  Pretty much completely revised the output formats.
X		       Much more like UNIX's "ls" in its output, options,
X		       etc.  Several bugs fixed.  Many new options.
X
X  Justin V. McCormick
X
X  v3.1	July 29, 1989  Bug fixes, new output width and height options.
X  v3.0	July 25, 1989  Instant sorting, best-fit output, new features.
X  v2.2	December 1988  Fixed status return and multiple wildcard pathnames.
X  v2.1	December 1988  Minor size reduction, fixed a few bugs from 2.0.
X  v2.0	November 1988  Revised for Lattice 5.0 and made 1.3 compatible.
X  v1.0	  August 1986  Written from scratch, my first Amiga project.
X
X
XNotice:
X
XThis program is copyrighted.  See the "License" accompanying this distribution
Xfor limitations, and other details.
X
X
XSynopsis:
X
XFeatures intelligent columnar listing, versatile sort options, UNIX-style
Xpattern matching, UNIX-like output formats and flags, recursive subdirectory
Xlisting, and more!
X
X
XUsage:
X	ls [ [ -options <args> ] [ names ] ] ...
X
X
XBugs and Limitations:
X
X"ls" cannot pattern match devices (like "dh*:"), since this would involve
Xanother level of recursion and parsing the Device List.  See the README file
Xfor other limitations, and known bugs.
X
X
XHistory:
X
X  Changes From 3.1 to 4.0k:
X
X  o  New (default) long listing format that *looks* like UNIX's (except for
X     things like owner, group ID, etc).
X  o  Block sizes now include *all* blocks associated with a file, and are
X     finally accurate for FFS devices.
X  o  By default, upper/lower case *is* significant in the output listing,
X     and in the wildcard pattern matching.
X  o  By default, directories and files are intermixed, according to their
X     alphabetic position.
X  o  By default, the short listing format uses fixed width columns, based
X     on the length of the longest filename in the directory.  Variable
X     width columns are still available.
X  o  By default, "hidden", "*.info", and "dot" files (eg, .foo) are not
X     displayed.  There are switches for each, as well as an "all" flag.
X  o  Several new options like the ability to limit the "-R" recursion depth;
X     ability to show absolute and relative path names in the long format
X     listing;  additional control of directory header and totalization
X     lines;  elimination of redundent totalization lines;  showing/sorting
X     on files' "disk keys";  control of the date format;  sort by date/size
X     defaults to newest/biggest first;	and a few other things.
X  o  Improved error handling and error msgs.  Better "break" handling.  No
X     more "memory leaks" (yes, there is one in v3.1 ... it looses 300 bytes
X     on an error exit).
X  o  The assignment of option flags has been "rationalized".  Applicable
X     flags from the UNIX "ls" were assigned 1st, followed by other flags
X     from v3.1, followed by new flags added in this rev.  However ... some
X     flag assignments from previous revs were changed when better mnemonic
X     values could be found, etc.   So ... check the usage, and/or docs.
X     Caveat emptor!
X  o  Flags requiring arguments (eg, -N) may or may not be seperated from
X     the argument by spaces.  So, "-Nfoo" is just as legal as "-N  foo".
X
X
X  Changes From 3.0 to 3.1:
X
X  o Fixed random errors from uninitialized argument count in GetCLIArgs().
X  o New -X and -Y options to specify short format columns and rows.
X
X
X  Changes From 2.2 to 3.0:
X
X  o Added many new command line options!  See Usage descriptions and docs.
X  o "Instant" output sorting, now using an on-the-fly insertion sort.
X  o New Short listing technique is row-by-row, redirects to PRT: or file.
X  o Better columnization of short listings, uses best-fit optimization.
X  o Formatted output parsing system implemented for long listings.
X  o Improved ^C handling, now breaks immediately in mid-output.
X  o New command line parser, now handles multiple options mixed with names.
X  o Added separate pattern matching for directories and files. [ DOESN'T WORK - KED ]
X  o Now inhibits system requesters from popping up by default.
X  o Added custom cres.o module for more size savings.
X  o Now uses Exec List functions for smaller, faster code.
X  o Fixed random cli_ReturnCode and pr_Result2 value CLI returns.
X  o Rewrote many sections, further code cleaning/commenting.
X  o Eliminated main(), LS handles the _main() function for smaller code.
X  o Compiled with 5.03.90 (beta) Lattice compiler, saved a few bytes.
X
X
X  Changes From 2.1 to 2.2:
X
X  o Fixed erroneous Status returns.
X  o Fixed bug with multiple wildcarded pathnames.
X  o Compiled with LC 5.0 Patch 3 and CAPE 2.0, saved another 46 bytes.
X  o Eliminated an extra stpcpy(), saved another few bytes.
X
X
X  Changes From 2.0 to 2.1:
X
X  o Fixed the show comment feature, a last minute bug in 2.0.
X  o Fixed the "Unknown option ''" message problem.
X  o Optimized the assembly branches, reduced code size another few bytes.
X
X
X  Changes From 1.0 to 2.0:
X
X  o Source code prototyped, linted, traced, optimized, tweaked, etc.
X  o Made resident ("pseudo-pure") by linking with cres.o from LC 5.0.
X  o High-volume routines recoded in assembly (lssup.a).
X  o Now handles multiple paths/files on a command line, up to 30.
X  o New sort flags, including no sort.
X  o Enhanced wildcards, understands complex *.?*.* expressions now.
X  o More efficient ExNext() performance, less ram used for recursion.
X  o SIGBREAKF_CTRL_C signal (Ctrl-C) cleanly aborts at any point now.
X  o Command line parser handles quoted pathnames now (LC 5.0 benefit).
X  o Short listing finally auto-adjusts to new console window sizes!
X  o Pen color escape codes bypassed when redirecting long output.
X  o Sorting by size or date is also subsorted alphabetically now.
X  o Long listing shows new 1.3 file attributes, plus comment indicator.
X  o File dates are now in international format, YY-MM-DD.
X  o Fixed listings with files datestamped after 99-12-31 (overflow).
X  o Fixed listings with files datestamped before 78-01-01 (time < 0).
X  -------------------------------------------------------------------- */
X
X/* ---------------------- LS SOURCE ---------------------------------- */
X
X#include "ls.h"
X
X/* Extern CODE from lssup.a */
Xextern BYTE *aindex __ARGS((BYTE *, BYTE));
Xextern LONG  __stdargs asprintf __ARGS((BYTE *, BYTE *,...));
Xextern LONG CompareDateStamps __ARGS((struct DateStamp *, struct DateStamp *));
Xextern LONG CompFibs __ARGS((LONG, struct FileInfoBlock *, struct FileInfoBlock *));
Xextern LONG FillFibEntry __ARGS((struct List *, struct FileInfoBlock *));
Xextern LONG GetFileString __ARGS((BYTE *, BYTE *));
Xextern LONG GetPathString __ARGS((BYTE *, BYTE *));
Xextern LONG iswild __ARGS((BYTE *));
Xextern LONG wildmatch __ARGS((BYTE *, BYTE *));
Xextern VOID *myalloc __ARGS((LONG));
Xextern VOID FibFileDate __ARGS((struct DateStamp *, BYTE *, BYTE *));
Xextern VOID GetWinBounds __ARGS((LONG *, LONG *));
Xextern VOID InsertFibNode __ARGS((struct List *, struct FibEntry *));
Xextern VOID MakePathString __ARGS((struct FileLock *, BYTE *));
Xextern VOID myfree __ARGS((VOID *));
X
X/* Local DATA */
Xstruct DateStamp theolddate;	 /* Show files older than this date */
Xstruct DateStamp thenewdate;	 /* Show files newer than this date */
Xstruct FileHandle *ConOut;	 /* Standard output */
Xstruct FileHandle *ConIn;	 /* Standard input */
Xstruct FileLock *CurFLock   = 0; /* Global Directory lock */
Xstruct FileInfoBlock *GFibp = 0; /* Global FIB for Examine/ExNext */
Xstruct InfoData *CurID	    = 0; /* Global InfoData for Info */
XAPTR   OldWindowPtr;		 /* Copy of what was in pr_WindowPtr */
X
X/* User flags (see ls.h for bit definitions) */
XULONG LSFlags  = SHOWDIRS | SHOWFILES;
XULONG LSFlagsX = 0;
XULONG oldsig   = 0;
X
XLONG gentrycnt; 		/* Grand klugde used to control printing of gtotals */
XLONG gdircount; 		/* Grand total # of dirs for Recursive */
XLONG gfilecount;		/* Grand total # of files found */
XLONG gitemcnt;			/* Grand kludge, the sequel ... for printing BS */
XLONG gtotblocks;		/* Grand total # of blocks */
XLONG gtotbytes; 		/* Grand total # of bytes */
X
XLONG dircount;			/* Number of directories found */
XLONG filecount; 		/* Number of files found */
XLONG totblocks; 		/* total # of blocks */
XLONG totbytes;			/* total # of bytes */
XLONG maxnamlen; 		/* Longest name found */
XLONG sortkey;			/* 0=alpha, 1=size, 2=date, 3=diskkey */
XLONG datefmt = AGE_TO_YEARS;	/* format for date in new long listing */
XLONG recurlevel = 0;
XLONG recurlimit = MAXDEPTH;
XLONG ioerrcode	= 0;
XLONG blksize	= 0;
X
XBYTE padtab[PADTABSIZE];	/* Column table tab amounts */
XBYTE initialpath[WORKSIZE];	/* Path where we were when started */
XBYTE thePath[WORKSIZE]; 	/* Current filename space */
XBYTE theDirPat[WORKSIZE];	/* Dirname pattern workspace */
XBYTE theFilePat[WORKSIZE];	/* Filename pattern workspace */
XBYTE workstr[WORKSIZE+64];	/* Temp string space */
X
XBYTE theblksstr[20];
XBYTE thedatestr[12];
XBYTE theprotstr[12];
XBYTE thesizestr[20];
XBYTE thetimestr[12];
X
XLONG CurWinRows = 0;		/* Window bounds, determined at runtime */
XLONG CurWinCols = 0;
X
X/* Initialized Strings */
XBYTE Author[]  = "\23333mls v4.0k\2330m  \251 Copyright  Kim E. DeVaughn  05/11/90  [non-k revs: J. McCormick]\n";
XBYTE usage[]   = "usage: ls [ [-options <args>]  [names] ] ...\n";
XBYTE usage0[]  = "  a  Show dot files       s  Sort by size           M  Ignore case w/wildcard\n";
XBYTE usage1[]  = "  b  Show data blks       t  Sort by date           N <name> Show newer than\n";
XBYTE usage2[]  = "  c  Show filenotes       u  Usage  [also -?]       O <name> Show older than\n";
XBYTE usage3[]  = "  d  Show dirs only       v  Vari col short list    P  Show full pathnames\n";
XBYTE usage4[]  = "  e  Execute bit is \"e\"   x <pat> Exclude files     Q  Requesters enabled\n";
XBYTE usage5[]  = "  f  Show files only      z  Override blk calc      R  Recursive listing\n";
XBYTE usage6[]  = "  h  Show hidden files    A  Show all  [= -ahi]     S  Show dirs first\n";
XBYTE usage7[]  = "  i  Show *.info files    B <blk> Force blk size    T  Totals for all entries\n";
XBYTE usage8[]  = "  k  Sort by disk key     C  Single column list     V  Show rel pathnames\n";
XBYTE usage9[]  = "  l  Long listing         D  Show dirs last         W  No contents (wild dir)\n";
XBYTE usage10[] = "  m  Mixed case output    E  No ANSI escape codes   X <wide> Set output cols\n";
XBYTE usage11[] = "  n  No sort              G  No subdir totals       Y <high> Set output rows\n";
XBYTE usage12[] = "  o  Old long list fmt    H  No headings            Z  Force ANSI sequences\n";
XBYTE usage13[] = "  p  Append \"/\" to dirs   I  No page prompts       0-6 Date format (new long)\n";
XBYTE usage14[] = "  q  Quit on not found    K  Show disk keys         -  Next arg is filename\n";
XBYTE usage15[] = "  r  Reverse sort         L <n> Limited recursion\n";
XBYTE usage16[] = "  F <format> Format output [-o fmt] (default: \"";
XBYTE usage17[] = "\")\n";
X
X/* Date and output format strings */
XBYTE baddatestr[]     = "00-00-00";
XBYTE badtimestr[]     = "00:00:00";
XBYTE datepat[]	      = "%02ld-%02ld-%02ld";
XBYTE timepat[]	      = "%02ld:%02ld:%02ld";
XBYTE dayspermonth[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
XBYTE deffmtstr[40]    = "%p %d %t %4b %8s %n\\n";
XBYTE LongFmtStr[]     = "%ld";
XBYTE totalfmtstr[]    = "Dirs: %-4ld Files: %-5ld Blocks: %-5ld Bytes: %-8ld\n";
XBYTE filefmtstr[]     = "           Files: %-5ld Blocks: %-5ld Bytes: %-8ld\n";
XBYTE TotHeaderMsg[]   = "\nTotals:\n";
XBYTE ColonStr[]       = ":";
XBYTE SlashStr[]       = "/";
XBYTE RamNameStr[]     = "RAM:";
Xchar *months[] = {"","Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec"};
Xchar exbit = 'x';
X
X/* ANSI color control strings */
XBYTE penstr0[]	      = "\2330m";       /* Reset CON:   */
XBYTE penstr1[]	      = "\2330 p";      /* Cursor off   */
XBYTE penstr2[]	      = "\233 p";       /* Cursor on    */
XBYTE penstr3[]	      = "\23333m";      /* Color 3 NORM */
XBYTE penstr4[]	      = "\2331;33;40m"; /* Color 3 BOLD */
XBYTE penstr5[]	      = "\2331;31;40m"; /* Color 1 BOLD */
XBYTE penstr6[]	      = "\2337;33m\2330 p"; /* Color 3 INV w/cursor off  */
X
X/* CON: command sequence for window bounds report */
XBYTE gwbrstr[4] = {  0x9b, '0', ' ', 'q' };
X
X/* Newline and "" source */
X#define NULLSTR   &NLine[1]
XBYTE	NLine[4]  = {10, 0, 0, 0};
XBYTE	badopt[4] =  {0, 0, 0, 0};
X
X/* Pointers to usage strings for quick easy output */
XBYTE *usagestrs[] =
X{
X  Author,  usage,   usage0,  usage1,	usage2,  usage3,  usage4,  usage5,
X  usage6,  usage7,  usage8,  usage9,	usage10, usage11, usage12, usage13,
X  usage14, usage15, usage16, deffmtstr, usage17,
X  0L
X};
X
XBYTE *thefmtstr = deffmtstr;	/* Format string pointer for long output */
XBYTE *curpath;
XBYTE *theAntiPat;		/* Avoid pattern string */
X
X/* Local CODE */
XBYTE *GetDecNum __ARGS((BYTE *, LONG *));
XLONG CmpDateBounds __ARGS((struct DateStamp *));
XLONG GetFileDate __ARGS((BYTE *, struct DateStamp *));
XLONG ParseCmdOptions __ARGS((LONG, LONG, BYTE **));
Xstruct FibEntry *AllocFib __ARGS((VOID));
Xstruct FibEntry *ModNextFib __ARGS((struct FibEntry *, LONG));
Xstruct List *GetDir __ARGS((struct FileLock *, struct FileInfoBlock *));
XVOID DirIt __ARGS((struct FileLock *, BYTE *));
XVOID FreeAllFibs __ARGS((struct List *));
XVOID GetCLIArgs __ARGS((BYTE *, LONG *, BYTE **));
XVOID LListDir __ARGS((struct List *));
XVOID LListEntry __ARGS((struct FileInfoBlock *));
XVOID LongList __ARGS((LONG *, LONG *, struct List *));
XVOID PagePrompt __ARGS((LONG, LONG));
XVOID ParseFormatOpts __ARGS((struct FileInfoBlock *));
XVOID SetConPen __ARGS((BYTE *));
XVOID SListDir __ARGS((struct List *));
XVOID TestBreak __ARGS((VOID));
XVOID Usage __ARGS((LONG));
XVOID WCHR __ARGS((BYTE *));
XVOID WSTR __ARGS((BYTE *));
XVOID _main __ARGS((BYTE *));
XLONG attrstr __ARGS((register BYTE *, register LONG *, register LONG *));
XVOID fixNumBlocks __ARGS((struct FileLock *, FIB *));
XLONG blkalloc __ARGS((register FIB *));
XLONG datestr __ARGS((BYTE *, struct DateStamp *, LONG));
XVOID cleanup __ARGS((LONG, LONG));
XVOID errmsg __ARGS((BYTE *, LONG));
XVOID llistentry __ARGS((register FIB *));
X
X
X/*
X *  cleanup() - Unlock any locks we may have, free up allocations, turn the
X *		system requesters back on, and get on out ...
X *
X */
X
XVOID cleanup(result2, retcode)
X  LONG result2;
X  LONG retcode;
X{
X  struct Process *procp;
X
X/* Make sure we unlock any locks we created! */
X  if ((CurFLock != 0) && ((LSFlags & PATHNAMED) != 0))
X    UnLock((BPTR)CurFLock);
X
X/* Free our fib and id */
X  if (GFibp != 0) myfree(GFibp);
X  if (CurID != 0) myfree(CurID);
X
X/* Put windowptr back */
X  procp = (struct Process *)FindTask(0L);
X  procp->pr_WindowPtr = OldWindowPtr;
X
X/* Set return status, exit */
X  procp->pr_Result2 = result2;
X  exit((int)retcode);
X}
X
X
X/*
X *  errmsg() - Print meaningful error messages when something screws up.
X *
X */
X
XVOID errmsg(fn, errcode)
X  BYTE *fn;
X  LONG	errcode;
X{
X  LONG len;
X
X/* Make sure we unlock any locks we created! */
X  if ((CurFLock != 0) && ((LSFlags & PATHNAMED) != 0))
X  {
X    UnLock((BPTR)CurFLock);
X    CurFLock = 0;
X  }
X
X  if (errcode == -1L) errcode = ioerrcode = IoErr();
X
X  len = strcspn(fn, ":") + 1;
X  strncpy(workstr, fn, len);
X  *(workstr + len) = '\0';
X
X  switch(errcode)
X  {
X    case ERROR_NO_FREE_STORE:
X      WSTR("ls: Out of memory\n");
X      cleanup(errcode, 20L);
X    case ERROR_OBJECT_IN_USE:
X      asprintf(workstr, "%s: File or directory in use\n", fn);
X      break;
X    case ERROR_DIR_NOT_FOUND:
X    case ERROR_OBJECT_NOT_FOUND:
X    case ERROR_OBJECT_WRONG_TYPE:
X      asprintf(workstr, "%s: No such file or directory\n", fn);
X      break;
X    case ERROR_DISK_NOT_VALIDATED:
X      strcat(workstr, " Filesystem not validated\n");
X      break;
X    case ERROR_DEVICE_NOT_MOUNTED:
X      strcat(workstr, " Device not mounted or assigned\n");
X      break;
X    case ERROR_NOT_A_DOS_DISK:
X      strcat(workstr, " Invalid filesystem format [non-DOS]\n");
X      break;
X    case ERROR_NO_DISK:
X      strcat(workstr, " Device empty\n");
X      break;
X    case PATTERN_ERR:
X      WSTR("ls: Directory or filename pattern too long\n");
X      return;
X    case WILDSPEC_ERR:
X      asprintf(workstr, "%s: Unable to pattern match paths\n", fn);
X      break;
X    default:
X      asprintf(workstr, "ls: %s: Error - %ld\n", fn, errcode);
X      WSTR(workstr);
X      cleanup(errcode, 20L);
X  }
X  WSTR(workstr);
X}
X
X
X/*
X *  datestr() - Converts an AmigaDOS "datestamp" into an ASCII string in one
X *		of several defined formats.
X *
X *		A non-zero return indicates an unknown format was requested,
X *		in which case the return string is null'd.
X *
X */
X
XLONG datestr(s, ds, format)
X  BYTE *s;
X  struct DateStamp *ds;
X  LONG format;
X{
X    register LONG t, mo, yr;
X    LONG y2, day, hr, min, sec;
X    struct  DateStamp  cds;
X
X    t	 =  ds->ds_Days - 2251;   /* This date algorithm by Tom Rokicki.    */
X    yr	 =  (4 * t + 3) / 1461;   /* It was taken from Rob Peck's book:     */
X    t	-=  1461 * yr / 4;	  /*   "Programmer's Guide to the Amiga",   */
X    yr	+=  1984;		  /* and is supposed to be good until 2100; */
X    mo	 =  (5 * t + 2) / 153;    /* doesn't work for dates before 03/01/84 */
X    day  =  t - ((153 * mo + 2) / 5) + 1;
X    mo	+=  3;
X    if (mo > 12) {
X	yr++;
X	mo -= 12;
X    }
X    y2 = (yr < 2000) ? yr-1900 : yr-2000;       /* after 2099 ... punt! */
X
X
X    t	=  ds->ds_Minute * 60 + ds->ds_Tick/TICKS_PER_SECOND;
X    sec =  t % 60; t /= 60;
X    min =  t % 60; t /= 60;
X    hr	=  t % 24;
X
X
X    switch (format) {
X
X    case AGE_TO_YEARS :
X	 DateStamp(&cds);
X	 if (((cds.ds_Days - ds->ds_Days) > TIME_YEAR_THRESHOLD) ||
X	     ((cds.ds_Days - ds->ds_Days) < 0)) {
X	     asprintf(s, "%s %2ld %5ld\0", months[mo], day, yr);
X	 } else {
X	     asprintf(s, "%s %2ld %02ld:%02ld\0", months[mo], day, hr, min);
X	 }
X	 break;
X
X    case FULL_FORM :
X	 asprintf(s, "%s %02ld %4ld  %02ld:%02ld:%02ld\0", months[mo], day, yr, hr, min, sec);
X	 break;
X
X    case DASHA_FORM :
X	 asprintf(s, "%02ld-%s-%02ld %02ld:%02ld:%02ld\0", day, months[mo], y2, hr, min, sec);
X	 break;
X
X    case DASHN_FORM :
X	 asprintf(s, "%02ld-%02ld-%02ld %02ld:%02ld:%02ld\0", mo, day, y2, hr, min, sec);
X	 break;
X
X    case SLASH_FORM :
X	 asprintf(s, "%02ld/%02ld/%02ld %02ld:%02ld:%02ld\0", mo, day, y2, hr, min, sec);
X	 break;
X
X    case EURO_FORM :
X	 asprintf(s, "%02ld/%02ld/%02ld %02ld:%02ld:%02ld\0", day, mo, y2, hr, min, sec);
X	 break;
X
X    case DOT_FORM :
X	 asprintf(s, "%02ld.%02ld.%02ld %02ld:%02ld:%02ld\0", y2, mo, day, hr, min, sec);
X	 break;
X
X    default :
X	 *s = '\0';     /* bad format ... null out the filedate string */
X	 return(1);
X    }
X    return(0);
X}
X
X
X/*
X *  attrstr() - Converts FileInfoBlock DirEntryType & Protection entries into
X *		an ASCII string representation.  Format is:
X *
X *		    dhsparwxd	["h" may possibly be replaced by "H" or "+"]
X *
X *		where a letter is replaced by a "-" if the corresponding
X *		attribute is not associated with the current FIB entry (i.e.,
X *		if the bit is not "true").  [The "H" or "+" form indicates
X *		one or more of the currently reserved/undefined high-order
X *		bits (as of 1.3) are on.  An "H" is used if the "hidden" bit
X *		is also on ... a "+" is used if it is not.]
X *
X *		A non-zero return code indicates the "H" or "+" form was
X *		detected.
X *
X *		Note: Due to historical braindamage in AmigaDOS, bits 0-3
X *		      (the original "protection" bits) are "active low",
X *		      while bits 4-7 (more properly called "attribute" bits)
X *		      are "active high".  At least this is based on the way
X *		      the 1.3 Delete, Resident, Shell, and Protect commands
X *		      interpret the "d", "p", and "s" bits (and how the List
X *		      command displays them).  [ Thanks alot Tim ...! ]
X *
X */
X
XLONG attrstr(s, type, prot)
X  register BYTE *s;
X  register LONG *type;
X  register LONG *prot;
X{
X    BYTE *t;
X
X       t = s;
X    *s++ = (*type > 0)              ? 'd' : '-';
X    *s++ = (*prot & FIBF_HIDDEN)    ? 'h' : '-';
X    *s++ = (*prot & FIBF_SCRIPT)    ? 's' : '-';
X    *s++ = (*prot & FIBF_PURE)      ? 'p' : '-';
X    *s++ = (*prot & FIBF_ARCHIVE)   ? 'a' : '-';
X    *s++ = (*prot & FIBF_READ)      ? '-' : 'r';
X    *s++ = (*prot & FIBF_WRITE)     ? '-' : 'w';
X    *s++ = (*prot & FIBF_EXECUTE)   ? '-' : exbit;
X    *s++ = (*prot & FIBF_DELETE)    ? '-' : 'd';
X    *s	 = '\0';
X
X    if (*prot & FIBF_RESERVED) {
X	if (*prot & FIBF_HIDDEN) {
X	    t[1] = 'H';
X	} else {
X	    t[1] = '+';
X	}
X	return(1);
X    }
X    return(0);
X}
X
X
X/*
X *  fixNumBlocks() - A hack to fix the fib_NumBlocks field so it is correct.
X *
X *  It was busted with the introduction of the FFS in AmigaDOS 1.3.  Don't
X *  ask where this is documented ... as far as I know, it isn't not in the
X *  1.3 includes, autodocs, readme's, user's manual, RKM's, or DevCon notes.
X *  It is possible it may be mentioned in an AmigaMail (that wonderfully
X *  regular publication), and I'm told it was mentioned once in a message on
X *  USENET.
X *
X *  Helluva way to run a ship ...
X *
X *  As may be ... since fib_NumBlocks was in use thruout this code before
X *  this was discovered, it was easiest to just fixup the fib_NumBlocks when
X *  the file/dir gets initially Examine/ExNext'd,  with a call to Info() for
X *  *all* files (which means alot of unnecessary calls get made [for files
X *  all in the same dir, etc]).  The performance penalty ends up being about
X *  1 sec, for a tree with 537 entries (which seems acceptable to me).
X *
X *  If you don't like it ... I suggest you complain to CBM about their, er,
X *  uhmm, "documentation" efforts!
X *
X *  Helluva way to run a ship ...
X *
X *  /kim   /\;;/\
X *
X */
X
XVOID fixNumBlocks(lock, fib)
X  struct FileLock *lock;
X  FIB *fib;
X{
X    LONG *nb;
X    LONG bsize;
X    static int errflag = 0;
X
X    if ((LSFlagsX & NOFIXNUMBLOCKS) != 0) return;
X
X    if (blksize == 0)
X    {
X      /* Try to fill InfoData, bomb if not readable for some reason (like */
X      if (Info((BPTR)lock, CurID) == 0)   /* ls'ing a "pathass'd" assign) */
X      {
X	/* Print error msg only once, and only if we'll be printing block counts */
X	if ((errflag == 0) && ((LSFlags & (LONGLIST | TOTALIZE)) != 0))
X	{
X	  errflag++;
X	  asprintf(workstr, "\nls: warning (%ld): block count(s) may be inaccurate - see ls.doc\n\n", IoErr());
X	  WSTR(workstr);
X	}
X	return;
X      }
X      else
X	bsize = CurID->id_BytesPerBlock;
X    }
X    else
X     bsize = blksize;
X
X     nb = (LONG *)&(fib->fib_NumBlocks);
X    *nb = (fib->fib_Size / bsize) + ((fib->fib_Size % bsize) ? 1 : 0);
X}
X
X
X/*
X *  blkalloc() - Returns the actual number of blocks allocated by a file/dir
X *		 (or just the data blocks, if DATABLKSONLY is set).
X *
X *  Assumes 1.3 original or fast filesystem (and a fixed up fib_NumBlocks).
X *
X */
X
XLONG blkalloc(fib)
X  register FIB *fib;
X{
X    if ((LSFlagsX & DATABLKSONLY) == 0) {
X      return(fib->fib_NumBlocks +
X	    (fib->fib_NumBlocks / MAX_BLKS_PER_EXTENT) +
X	   ((fib->fib_NumBlocks % MAX_BLKS_PER_EXTENT) ? 1 : 0) +
X	   ((fib->fib_Size == 0) ? 1 : 0)  /* kludge for 0-len files (and dirs) */
X	    );
X    } else {
X      return(fib->fib_NumBlocks);
X    }
X}
X
X
X/*
X *  llistentry() - Prints the data in a FIB entry.
X *
X */
X
XVOID llistentry(fib)
X  register FIB *fib;
X{
X    BYTE tbuf[64]; /* shouldn't exceed about 47-53 chars max (but add some pad) */
X    LONG len;
X
X    attrstr(workstr, &(fib->fib_DirEntryType), &(fib->fib_Protection));
X
X    if (fib->fib_Comment[0] == 0) {
X	strcat(workstr, "  ");
X    } else {
X	strcat(workstr, " c");
X    }
X
X    if ((LSFlags & SHOWDISKKEYS) != 0)
X    {
X      asprintf(tbuf, " %6ld", fib->fib_DiskKey);
X      strcat(workstr, tbuf);
X    }
X
X    asprintf(tbuf, " %5ld %8ld  ", blkalloc(fib), fib->fib_Size);
X    strcat(workstr, tbuf);
X
X    datestr(tbuf, &(fib->fib_Date), datefmt);
X    strcat(workstr, tbuf);
X    strcat(workstr, "  ");
X
X    WSTR(workstr);
X
X    if (fib->fib_DirEntryType > 0) SetConPen(penstr3);
X
X    *workstr = '\0';
X    if (((LSFlags & RELPATHNAMES) != 0) || ((LSFlags & FULLPATHNAMES) != 0))
X    {
X      strcat(workstr, curpath);
X      len = strlen(workstr);
X      if ((len > 1) && (*(workstr + len - 1) != ':'))
X	strcat(workstr, "/");
X    }
X
X    strcat(workstr, fib->fib_FileName);
X
X    len = 0;
X    if ((LSFlags & RELPATHNAMES) != 0)
X    {
X      len = strlen(initialpath);
X      if ((strnicmp(workstr, initialpath, len) == 0) && (len < strlen(workstr)))
X      {
X	if (*(workstr + len) == '/')
X	  len++;
X      }
X      else
X	len = 0;
X    }
X
X    if (((LSFlags & ADDDIRSLASH) != 0) && (fib->fib_DirEntryType > 0))
X      strcat(workstr, "/");
X
X/* commented out until there is more support for NOT defaulting to "x" */
X/*  if (workstr[0] == 'd') {
X *	strcat(workstr, "/");
X *  } else if (workstr[7] != '-') {
X *	strcat(workstr, "*");
X *  }
X */
X
X    strcat(workstr, "\n");
X    WSTR(workstr + len);
X
X    if (fib->fib_DirEntryType > 0) SetConPen(penstr0);
X
X    if (((LSFlags & NOTEFLAG) != 0) && (fib->fib_Comment[0] != 0))
X    {
X      SetConPen(penstr3);
X      asprintf(workstr, "\"%s\"\n", fib->fib_Comment);
X      WSTR (workstr);
X      SetConPen(penstr0);
X    }
X}
X
X
X/* --------------------------------------------------------------------
X * Allocate a FibEntry structure and associated FileInfoBlock.
X * -------------------------------------------------------------------- */
Xstruct FibEntry *AllocFib()
X{
X  struct FibEntry *tfibp;
X
X  if ((tfibp = myalloc((LONG)(sizeof(struct FibEntry) + sizeof(struct FileInfoBlock)))) != 0)
X  {
X    tfibp->fe_Fib = (struct FileInfoBlock *)((ULONG)tfibp + sizeof(struct FibEntry));
X  }
X  else
X    LSFlags |= BREAKFLAG;
X  return(tfibp);
X}
X
X
X/* --------------------------------------------------------------------
X * Use AmigaDOS to output a string to the stdout channel
X * -------------------------------------------------------------------- */
XVOID WSTR(tstring)
X  BYTE *tstring;
X{
X  LONG i;
X
X  i = strlen(tstring);
X  if (i > 0)
X  {
X    (VOID) Write((BPTR)ConOut, tstring, i);
X  }
X}
X
X
X/* --------------------------------------------------------------------
X * Use AmigaDOS to put a character to the stdout
X * -------------------------------------------------------------------- */
XVOID WCHR(ch)
X  BYTE *ch;
X{
X  (VOID) Write((BPTR)ConOut, ch, 1L);
X}
X
X
X/* --------------------------------------------------------------------
X * Check to see if the user hit ^C
X *
X * Coded in this arcane fashion since there seems to be a race/etc. between
X * the SetSignal() call, and the WSTR() and or'ing in of the BREAKFLAG.  If
X * the outer "if" is omitted, "**BREAK" prints 3 times; if the outer "else"
X * is omitted, the BREAKFLAG doesn't seem to get or'd in ... grumble, swear!
X *
X * As my be ... this is an ugly hack, but it seems to work ...
X *
X * C'est la vie ... /kim
X *
X * -------------------------------------------------------------------- */
XVOID TestBreak ()
X{
X  if ((oldsig & SIGBREAKF_CTRL_C) == 0)
X  {
X    oldsig = SetSignal (0L, 0L);
X    if ((oldsig & SIGBREAKF_CTRL_C) != 0)
X    {
X      WSTR ("\2330m\233 p**BREAK\n");
X      LSFlags |= BREAKFLAG;
X    }
X  }
X  else
X  {
X    LSFlags |= BREAKFLAG;
X  }
X}
X
X
X/* --------------------------------------------------------------------
X * Prompt the user to hit return, wait till return is hit
X * -------------------------------------------------------------------- */
XVOID PagePrompt (page, maxpage)
X  LONG page, maxpage;
X{
X  if ((CurWinCols > maxnamlen) && (page > 1) && ((LSFlags & NOINTERACT) == 0))
X  {
X    SetConPen(penstr6);
X    asprintf(workstr, " More (%ld of %ld) ... ", (page - 1), maxpage);
X    if (CurWinCols <= strlen(workstr))
X      strcpy(workstr, "More ... ");
X    WSTR(workstr);
X    SetConPen(penstr0);
X    (VOID) Read((BPTR)ConIn, workstr, (long)WORKSIZE);
X    WSTR("\2330 p\233F\233K");
X    TestBreak();
X  }
X}
X
X
X/* -------------------------------------------------------------------- */
Xstruct FibEntry *ModNextFib(tfibp, rows)
X  struct FibEntry *tfibp;
X  LONG rows;
X{
X  LONG i;
X
X  for (i = 0; i < rows && tfibp->fe_Node.mln_Succ != 0; i++)
X  {
X    tfibp = (struct FibEntry *)tfibp->fe_Node.mln_Succ;
X  }
X  return(tfibp);
X}
X
X
X/* --------------------------------------------------------------------
X * set CON: character color to default Pen1 colors
X * -------------------------------------------------------------------- */
XVOID SetConPen (penstr)
X  BYTE *penstr;
X{
X  if ((LSFlags & CONSOLE) != 0)
X    WSTR (penstr);
X}
X
X
X/* #define DEBUGSLD 1 */
X/* --------------------------------------------------------------------
X * List a FibEntry list in a compact fashion
X * -------------------------------------------------------------------- */
XVOID SListDir (fibheadp)
X  struct List *fibheadp;
X{
X  LONG avglen;
X  LONG colcnt;
X  LONG currow;
X  LONG dfcount;
X  LONG i, j, wlen;
X  LONG maxcol;
X  LONG maxpage;
X  LONG maxrow;
X  LONG maxwinrow;
X  LONG minpad = 2;  /* minimum number of blanks between listing entries */
X  LONG pagecnt;
X  LONG rowcnt;
X  LONG tlen;
X  LONG totlen;
X  struct FibEntry *hfibp, *tfibp;
X
X  SetConPen (penstr1);      /* Turn the cursor off since it will blink anyway */
X  GetWinBounds (&colcnt, &currow);  /* Get current window size */
X
X  if (CurWinCols == 0)
X    CurWinCols = colcnt;
X  if (CurWinRows == 0)
X    CurWinRows = currow;
X
X  if ((LSFlags & VARCOLSFORMAT) != 0)
X  {
X  /* Make a average-case WxH estimate for # of display columns */
X    for (totlen = dfcount = 0, hfibp = (struct FibEntry *)fibheadp->lh_Head;
X	 hfibp->fe_Node.mln_Succ != 0;
X	 hfibp = (struct FibEntry *)hfibp->fe_Node.mln_Succ)
X    {
X      if (hfibp->fe_Fib->fib_DirEntryType > 0 && (LSFlags & CONSOLE) == 0)
X	(VOID) strcat (hfibp->fe_Fib->fib_FileName, SlashStr);
X      totlen += strlen (hfibp->fe_Fib->fib_FileName);
X      dfcount++;
X      if (((LSFlags & WILDPATH) != 0) && (hfibp->fe_Fib->fib_DirEntryType > 0))
X      {
X	totlen -= strlen (hfibp->fe_Fib->fib_FileName);
X	dfcount--;
X      }
X    }
X    if (dfcount == 0) dfcount++;
X
X  /* Calc average length of all entries */
X    avglen = totlen / dfcount;		  /* Avg name length = totlen/numentries */
X    if ((totlen % dfcount) != 0)
X      avglen++;
X
X  /* Longest name wider than window, or single column flag set? */
X    if ((CurWinCols <= maxnamlen) || ((LSFlags & ONECOLFORMAT) != 0))
X      maxcol = 1;   /* Yep, just print one column */
X    else
X    {
X    /* Else maxcols = winwidth/namewidth */
X      for (maxcol = 0, colcnt = CurWinCols; colcnt >= avglen; maxcol++)
X      {
X	colcnt -= avglen + 2;
X      }
X    }
X#ifdef DEBUGSLD
X    asprintf(workstr, "avg:%ld max:%ld\n", avglen, maxnamlen); WSTR(workstr);
X#endif
X
X  /* Dry run output avg-case WxH table to see if it needs adjusting */
X    for (;;)
X    {
X    /* Clear out previous padtab */
X      memset(padtab, 0, PADTABSIZE);
X
X    /* Number of rows = total entries / entries per row */
X      maxrow = dfcount / maxcol;
X      if ((dfcount % maxcol) != 0)        /* Round up if non-integral */
X	maxrow++;
X#ifdef DEBUGSLD
X      asprintf(workstr, "avg: %ld rows by %ld cols\n", maxrow, maxcol); WSTR(workstr);
X#endif
X
X      for (rowcnt = 0, hfibp = (struct FibEntry *)fibheadp->lh_Head;
X	   rowcnt < maxrow && hfibp->fe_Node.mln_Succ != 0;
X	   rowcnt++, hfibp = (struct FibEntry *)hfibp->fe_Node.mln_Succ)
X      {
X	for (colcnt = 0, tfibp = hfibp;
X	     colcnt < maxcol && tfibp->fe_Node.mln_Succ != 0;
X	     colcnt++, tfibp = ModNextFib (tfibp, maxrow))
X	{
X	  if (~(((LSFlags & WILDPATH) != 0) && (tfibp->fe_Fib->fib_DirEntryType > 0)))
X	  {
X	    tlen = strlen (tfibp->fe_Fib->fib_FileName);
X	    if (tlen > padtab[colcnt])
X	      padtab[colcnt] = tlen;
X	  }
X	}
X
X      /* If this is the first row, calc actual maxcol/maxrow for this dfcount */
X	if (rowcnt == 0)
X	{
X	  maxcol = colcnt;
X	  maxrow = dfcount / maxcol;
X	  if ((dfcount % maxcol) != 0)    /* Round up if non-integral */
X	    maxrow++;
X	}
X      }
X
X    /* Calculate actual total width by adding up width of all columns */
X      for (colcnt = totlen = 0; (colcnt + 1) < maxcol; colcnt++)
X      {
X	totlen += (LONG)padtab[colcnt] + 2;
X#ifdef DEBUGSLD
X	asprintf(workstr, "padtab[%ld]=%ld\n", colcnt, (LONG)padtab[colcnt]); WSTR(workstr);
X#endif
X      }
X      totlen += (LONG)padtab[colcnt];
X#ifdef DEBUGSLD
X      asprintf(workstr, "padtab[%ld]=%ld\n", colcnt, (LONG)padtab[colcnt]); WSTR(workstr);
X      asprintf(workstr, "totlen %ld\n", totlen); WSTR(workstr);
X#endif
X
X    /* if More than one column and
X     * total width of all columns is greater > our window width,
X     * then decrease number of display columns
X     */
X      if ((maxcol > 1) && (totlen > CurWinCols))
X      {
X	maxcol--;
X#ifdef DEBUGSLD
X	asprintf(workstr, "new maxcol:%ld\n", maxcol); WSTR(workstr);
X#endif
X      }
X      else
X	break;
X    }
X#ifdef DEBUGSLD
X    asprintf(workstr, "adjusted: maxrow:%ld maxcol:%ld (winwidth:%ld totlen:%ld)\n", maxrow, maxcol, CurWinCols, totlen); WSTR(workstr);
X#endif
X
X  }
X  else
X  {
X
X    if ((LSFlags & ADDDIRSLASH) != 0)
X      maxnamlen += 1;
X
X    for (dfcount = 0, hfibp = (struct FibEntry *)fibheadp->lh_Head;
X	 hfibp->fe_Node.mln_Succ != 0;
X	 hfibp = (struct FibEntry *)hfibp->fe_Node.mln_Succ)
X    {
X      if ((LSFlags & ADDDIRSLASH) != 0)
X	if (hfibp->fe_Fib->fib_DirEntryType > 0)
X	  (VOID) strcat (hfibp->fe_Fib->fib_FileName, SlashStr);
X	else
X	  (VOID) strcat (hfibp->fe_Fib->fib_FileName, " ");
X      dfcount++;
X      if (((LSFlags & WILDPATH) != 0) && (hfibp->fe_Fib->fib_DirEntryType > 0))
X	dfcount--;
X    }
X
X    if ((CurWinCols <= maxnamlen) || ((LSFlags & ONECOLFORMAT) != 0))
X      maxcol = 1;
X    else
X      maxcol = CurWinCols / (maxnamlen + minpad);
X    maxrow = dfcount / maxcol;
X    if ((dfcount % maxcol) != 0)
X      maxrow++;
X    memset(padtab, 0, PADTABSIZE);
X    for (colcnt = 0; colcnt < maxcol; colcnt++)
X      padtab[colcnt] = maxnamlen;
X  }
X
X/* Calc number of pages */
X  maxwinrow = CurWinRows - 1;	/* was -3 in v3.1 */
X  if (maxwinrow <= 0)
X    maxwinrow = 1;
X  pagecnt = 1;
X  maxpage = maxrow / maxwinrow;
X  if ((maxrow % maxwinrow) != 0)
X    maxpage++;
X
X/* Do actual output scan */
X  for (rowcnt = 0, currow = maxwinrow, hfibp = (struct FibEntry *)fibheadp->lh_Head;
X       (LSFlags & BREAKFLAG) == 0 && rowcnt < maxrow && hfibp->fe_Node.mln_Succ != 0;
X       TestBreak(), rowcnt++, currow++, hfibp = (struct FibEntry *)hfibp->fe_Node.mln_Succ)
X  {
X    if ((maxpage > 1) && (currow == maxwinrow))
X    {
X      currow = 0;
X      if ((LSFlags & CONSOLE) != 0)
X      {
X	PagePrompt (pagecnt, maxpage);
X	if ((LSFlags & BREAKFLAG) != 0)
X	  break;
X      }
X      pagecnt++;
X    }
X
X    for (colcnt = 0, tfibp = hfibp;
X	 colcnt < maxcol && tfibp->fe_Node.mln_Succ != 0;
X	 colcnt++)
X    {
X      if (tfibp->fe_Fib->fib_DirEntryType < 0)
X      {
X      /* Print a file entry */
X	(VOID) stpcpy (workstr, tfibp->fe_Fib->fib_FileName);
X	wlen = strlen (workstr);
X      }
X      else
X      {
X      /* Print a directory entry */
X	workstr[0] = 0;
X	wlen = 0;
X
X	if ((LSFlags & WILDPATH) == 0)
X	{
X	  wlen = strlen (tfibp->fe_Fib->fib_FileName);
X	  if ((LSFlags & CONSOLE) != 0)
X	    (VOID) strcat (workstr, penstr3);
X	  (VOID) strcat (workstr, tfibp->fe_Fib->fib_FileName);
X	  if ((LSFlags & CONSOLE) != 0)
X	    (VOID) strcat (workstr, penstr0);
X	}
X      }
X
X    /* Move along list to the next entry, mod maxcol, print this entry */
X      tfibp = ModNextFib (tfibp, maxrow);
X
X    /* If this is not the last column, pad with spaces till we get to next column */
X      if ((colcnt + 1) < maxcol && tfibp->fe_Node.mln_Succ != 0)
X      {
X	for (i = (LONG)padtab[colcnt] - 1 + minpad, j = strlen (workstr); i >= wlen; i--, j++)
X	  workstr[j] = ' ';
X	workstr[j] = 0;
X      }
X    /* Output the final entry */
X      WSTR(workstr);
X    }
X
X  /* Filled this row, start next down */
X    WCHR(NLine);
X  }
X  SetConPen(penstr2);         /* Turn cursor back on */
X}
X
X
X/* -------------------------------------------------------------------- */
XBYTE *GetDecNum (cp, spccnt)
X  BYTE *cp;
X  LONG *spccnt;
X{
X  for (*spccnt = 0; *cp >= '0' && *cp <= '9'; cp++)
X  {
X    *spccnt = *spccnt * 10 + (LONG)*cp - '0';
X  }
X  return (cp);
X}
X
X
X/* -------------------------------------------------------------------- */
XVOID ParseFormatOpts (fib)
X  struct FileInfoBlock *fib;
X{
X  BYTE *cp1, *cp2, *reps;
X  BYTE *pathend, *thenamestr;
X  LONG i, spccnt;
X
X  i = strlen (curpath);
X  pathend = curpath + i;
X  if (i > 1 && *(curpath + i - 1) != ':')
X  {
X    *(curpath + i) = '/';
X    i++;
X    *(curpath + i) = 0;
X  }
X  thenamestr = curpath + strlen(curpath);
X  cp2 = thenamestr;
X  if (fib->fib_DirEntryType > 0 && (LSFlags & CONSOLE) != 0)
X    cp2 = stpcpy (cp2, penstr3);
X  cp2 = stpcpy (cp2, fib->fib_FileName);
X  if (fib->fib_DirEntryType > 0 && (LSFlags & CONSOLE) != 0)
X    (VOID) stpcpy (cp2, penstr0);
X
X  for (cp1 = workstr, cp2 = thefmtstr; *cp2 != 0; cp2++)
X  {
X    if (*cp2 != '%' && *cp2 != '\\')
X      *cp1++ = *cp2;
X    else
X    {
X      if (*cp2 == '%')
X      {
X	cp2++;
X	cp2 = GetDecNum (cp2, &spccnt);
X	if (spccnt > 99)
X	{
X	  spccnt = 99;
X	}
X
X	switch (*cp2)
X	{
X	  case 'p':
X	    reps = theprotstr;
X	    break;
X	  case 'd':
X	    reps = thedatestr;
X	    break;
X	  case 't':
X	    reps = thetimestr;
X	    break;
X	  case 'b':
X	    reps = theblksstr;
X	    break;
X	  case 's':
X	    reps = thesizestr;
X	    break;
X	  case 'n':
X	    if (fib->fib_DirEntryType > 0 && (LSFlags & CONSOLE) != 0 && spccnt > 0)
X	      spccnt += 7;
X	    if ((LSFlags & FULLPATHNAMES) != 0)
X	      reps = curpath;
X	    else
X	      reps = thenamestr;
X	    break;
X	  case '%':
X	    *cp1++ = '%';
X	    *cp1++ = 0;
X	  default:
X	    reps = NULLSTR;
X	}
X	for (i = strlen(reps); i < spccnt; i++)
X	  *cp1++ = ' ';
X	cp1 = stpcpy (cp1, reps);
X      }
X      else
X      {
X	cp2++;
X	switch (*cp2)
X	{
X	  case 'n':
X	    *cp1++ = '\n';
X	    break;
X	  case 't':
X	    *cp1++ = '\t';
X	    break;
X	  case '\\':
X	    *cp1++ = '\\';
X	    break;
X	}
X	*cp1 = 0;
X      }
X    }
X  }
X  WSTR(workstr);
X
X  if ((LSFlags & NOTEFLAG) != 0 && fib->fib_Comment[0] != 0)
X  {
X    SetConPen(penstr3);
X    (VOID)asprintf(workstr, "/* %s */\n", fib->fib_Comment);
X    WSTR (workstr);
X    SetConPen(penstr0);
X  }
X  *pathend = 0;
X}
X
X
X/* --------------------------------------------------------------------
X * Verbosely list a particular FibEntry
X * -------------------------------------------------------------------- */
XVOID LListEntry (fib)
X  struct FileInfoBlock *fib;
X{
X  LONG i;
X  LONG	pmodes;
X
X  pmodes = fib->fib_Protection & 0xff;
X  (VOID)stpcpy (theprotstr, "chsparw7d");
X  theprotstr[7] = exbit;
X  for (i = 3; i >=  0; i--)
X  {
X    if ((pmodes & (1 << i)) != 0)
X      theprotstr[8 - i] = '-';
X    if ((pmodes & (1 << (i + 4))) == 0)
X      theprotstr[4 - i] = '-';
X  }
X
X  if (fib->fib_Comment[0] == 0)
X    theprotstr[0] = '-';
X
X  FibFileDate(&fib->fib_Date, thedatestr, thetimestr);
X
X  if (fib->fib_DirEntryType > 0)
X  {
X    if ((LSFlagsX & DATABLKSONLY) != 0)
X      stpcpy(theblksstr, "0");
X    else
X      stpcpy(theblksstr, "1");
X    stpcpy (thesizestr, "Dir");
X  }
X  else
X  {
X    asprintf(theblksstr, LongFmtStr, blkalloc(fib));
X    asprintf(thesizestr, LongFmtStr, fib->fib_Size);
X  }
X  ParseFormatOpts(fib);
X}
X
X
X/* --------------------------------------------------------------------
X * List a directory in a verbose informative manner
X * -------------------------------------------------------------------- */
XVOID LListDir (fibheadp)
X  struct List *fibheadp;
X{
X  struct FibEntry *tfibp;
X
X  SetConPen(penstr1);
X
X  totblocks = totbytes = 0;
X  for (tfibp = (struct FibEntry *)fibheadp->lh_Head;
X       tfibp->fe_Node.mln_Succ != 0;
X       tfibp = (struct FibEntry *)tfibp->fe_Node.mln_Succ)
X  {
X    TestBreak();
X    if ((LSFlags & BREAKFLAG) != 0)
X      return;
X
X    if ((tfibp->fe_Fib->fib_DirEntryType > 0) && ((LSFlags & WILDPATH) != 0))
X    {
X      ;    /* seems to be a Lattice v5.04a bug ... negating the test and putting the "else" here doesn't work */
X    }
X    else
X    {
X      if ((LSFlags & OLDLONGFORMAT) != 0)
X	LListEntry (tfibp->fe_Fib);
X      else
X	llistentry (tfibp->fe_Fib);
X
X      totblocks += blkalloc(tfibp->fe_Fib);
X      totbytes	+= tfibp->fe_Fib->fib_Size;
X    }
X  }
X
X  if ((LSFlags & (BREAKFLAG | NODIRTOTAL)) == 0)
X  {
X    if ((LSFlags & WILDPATH) == 0)
X    {
X      if ((dircount + filecount) > 1)
X      {
X	asprintf(workstr, totalfmtstr, dircount, filecount, totblocks, totbytes);
X	WSTR(workstr);
X      }
X    }
X    else
X    {
X      if (filecount > 1)
X      {
X	asprintf(workstr, filefmtstr, filecount, totblocks, totbytes);
X	WSTR(workstr);
X      }
X    }
X  }
X  SetConPen(penstr2);
X}
X
X
X/* -------------------------------------------------------------------- */
XLONG CmpDateBounds (tdate)
X  struct DateStamp *tdate;
X{
X  if ((LSFlags & SHOWNEWERTHAN) != 0)
X  {
X    if (CompareDateStamps(&thenewdate, tdate) >= 0)
X      return (0L);
X  }
X  if ((LSFlags & SHOWOLDERTHAN) != 0)
X  {
X    if (CompareDateStamps(tdate, &theolddate) >= 0)
X      return (0L);
X  }
X  return (1L);
X}
X
X
X/* --------------------------------------------------------------------
X * Free up memory allocated to a linked list of FibEntrys
X * -------------------------------------------------------------------- */
XVOID FreeAllFibs (fibheadp)
X  struct List *fibheadp;
X{
X  struct FibEntry *tfibp;
X
X  if (fibheadp != 0)
X  {
X    while (fibheadp->lh_Head->ln_Succ != 0)
X    {
X      tfibp = (struct FibEntry *)RemTail(fibheadp);
X      myfree(tfibp);
X    }
X
X  /* Now free the MinList itself */
X    myfree(fibheadp);
X  }
X}
X
X
X/* --------------------------------------------------------------------
X * Allocate and fill a linked list of FileInfoBlocks
X * -------------------------------------------------------------------- */
Xstruct List *GetDir (lockp, fibp)
X  struct FileLock *lockp;
X  struct FileInfoBlock *fibp;
X{
X  BYTE *thepat; 	       /* Pattern pointer to dir or file pattern */
X  LONG dfcount;
X  LONG matchstat;	       /* Result of wildmatch() */
X  LONG nextstat;	       /* Status of ExNext() */
X  LONG tempnamlen;	       /* Compare length for strings */
X  LONG tmpflags;
X  struct List *fibhead;        /* Temp list of Fibs created */
X  struct List *dirhead;        /* Temp list of dirs if SHOWDIRS == 0 */
X
X  maxnamlen = dfcount = dircount = filecount = 0L;
X
X/* Initialize an exec list of nodes, zero entries */
X  if ((fibhead = myalloc ((LONG)sizeof(struct MinList))) == 0)
X    return (0L);
X  NewList (fibhead);
X
X/* Allocate an separate list for directories that don't match specs */
X  if ((dirhead = myalloc((LONG)sizeof(struct MinList))) == 0)
X    goto BADALLOC;
X  NewList (dirhead);
X
X  do
X  {
X    TestBreak ();
X    if ((LSFlags & BREAKFLAG) != 0)
X      goto GOODRET;
X
X  /* If we got something */
X    if ((nextstat = ExNext ((BPTR)lockp, fibp)) != 0)
X    {
X    /* If the entry is wanted bump count of files or directories */
X      if (CmpDateBounds (&fibp->fib_Date) != 0)
X      {
X	if (fibp->fib_DirEntryType > 0) /* It's a directory */
X	  thepat = theDirPat;
X	else
X	  thepat = theFilePat;
X
X	matchstat = wildmatch (fibp->fib_FileName, thepat);
X
X	if ((LSFlags & MATCHINFOFILES) == 0 && matchstat != 0)
X	{
X	  tmpflags  = LSFlags;
X	  LSFlags  |= IGNORECASEWILD;
X	  matchstat = wildmatch (fibp->fib_FileName, "*.info") ^ 1;
X	  LSFlags   = tmpflags;
X	}
X	if ((LSFlags & MATCHDOTFILES) == 0 && matchstat != 0)
X	  matchstat = wildmatch (fibp->fib_FileName, ".*") ^ 1;
X	if ((LSFlags & ANTIMATCH) != 0 && matchstat != 0)
X	  matchstat = wildmatch (fibp->fib_FileName, theAntiPat) ^ 1;
X	if ((fibp->fib_Protection & FIBF_HIDDEN) != 0)
X	  matchstat = (LSFlags & SHOWHIDDEN) ? 1 : 0;
X
X	if (matchstat == 0)     /* No match? Then move on */
X	  continue;
X
X	dfcount++;  /* we found something of interest */
X
X	if (fibp->fib_DirEntryType > 0) /* It's a directory */
X	{
X	  if (FillFibEntry (dirhead, fibp) == 0)
X	    goto BADALLOC;
X	  if ((LSFlags & SHOWDIRS) != 0)
X	  {
X	    dircount++; gdircount++; gitemcnt++;
X
X	    if ((LSFlagsX & DATABLKSONLY) == 0)
X	      gtotblocks++;
X	  }
X	  else
X	    continue;
X	}
X	else				/* It's a file entry */
X	{
X	  if ((LSFlags & SHOWFILES) != 0)
X	  {
X	    filecount++; gfilecount++; gitemcnt++;
X	    fixNumBlocks(lockp, fibp);
X	    gtotblocks += blkalloc(fibp);
X	    gtotbytes  += fibp->fib_Size;
X	  }
X	  else		      /* Don't want this file, move on to next entry */
X	    continue;
X	}
X
X      /* See if this is the longest filename for later use in listing */
X	tempnamlen = strlen (fibp->fib_FileName);
X	if (tempnamlen > maxnamlen)
X	{
X	  if (fibp->fib_DirEntryType < 0)
X	  {
X	    maxnamlen = tempnamlen;
X	  }
X	  else if ((LSFlags & WILDPATH) == 0)
X	    maxnamlen = tempnamlen;
X	}
X
X      /* Allocate another FibEntry, put the info inside */
X	if (FillFibEntry (fibhead, fibp) == 0)
X	  goto BADALLOC;
X      }
X    }
X  } while (nextstat != 0);
X
X/* No entries found? print message and return FALSE */
X  if ((dircount + filecount) == 0)
X  {
X    if ((LSFlags & NOHEADERS) == 0)
X    {
X      if ((dfcount != 0) || ((LSFlags & WILDPATH) != 0))
X	WSTR("No match.\n");
X      else
X      {
X	LSFlagsX |= EMPTYDIRFLAG;
X
X	if ((LSFlags & LONGLIST) != 0)
X	  WSTR("total 0\n");
X      }
X    }
X  }
X  else
X  {
X    if ((LSFlags & (SHOWDIRS | SHOWFILES)) != 0)
X    {
X      if ((LSFlags & LONGLIST) == 0)  /* Short listing wanted */
X      {
X	SListDir (fibhead);
X	gentrycnt++;
X      }
X      else			      /* Full listing */
X	LListDir (fibhead);
X
X      if ((dircount + filecount) > 0) gentrycnt++;
X    }
X  }
END_OF_FILE
if test 45212 -ne `wc -c <'src/ls.c.aa'`; then
    echo shar: \"'src/ls.c.aa'\" unpacked with wrong size!
fi
# end of 'src/ls.c.aa'
fi
echo shar: End of archive 4 \(of 4\).
cp /dev/null ark4isdone
MISSING=""
for I in 1 2 3 4 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 4 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-- 
Mail submissions (sources or binaries) to <amiga@cs.odu.edu>.
Mail comments to the moderator at <amiga-request@cs.odu.edu>.
Post requests for sources, and general discussion to comp.sys.amiga.