/*
 *  LS 4.5ljr
 *
 *  This software is Copyright © 1989, 1990, 1991, 1992  Loren J. Rittle.
 *  This software is Copyright for the sole purpose of protecting
 *  the fact that this is indeed free software.
 *
 *  This software is wholly, but at this point loosely, based upon
 *  Justin V. McCormick's PD LS ``V3.1  July 29, 1989'' with
 *  enhancements and bug fixes by the copyright holder, Loren J. Rittle.
 */

/* This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 4.5ljr, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

See COPYING and ls.doc for copyright and copying information. */

/* If you generate a new version for release to the public,
please replace my initials 'ljr' with your own (so the public
knows who to blame... :-).  Your version should be marked
(at least in the source and documentation) as having been based
upon the 'ljr' strain version <current version number>.

On the other hand, you can send bug reports and patches to
l-rittle@uiuc.edu for me to incorporate into my next major
release.

Loren J. Rittle
l-rittle@uiuc.edu */

#ifndef LS_INCLUDE
/* To allow for precompiled header usage... */
#include "ls.h"
#endif

#define MAXARG 100
#define PADTABSIZE 100
#define WORKSIZE 300

BYTE version[] = "$VER: ls-(ljr_strain) 4.5 (30.1.92)";
BYTE shortusage[] = "LS Version 4.5ljr, Thu Jan 30 20:00:58 1992\n\
Usage: ls [-h?acdefklnrst1ADFHIMNOPRTX <args>] [path] ...\n";
BYTE fullusage[] = "\
	a  List all entries	A  Display entries across line\n\
	c  Show filenotes	D  Show dirs last\n\
	d  About dirs		F <format> Format output\n\
	e  Sort by extension	H  No headings and subtotals\n\
	f  Only show files	I <pattern> Ignore pattern\n\
	k  Kill ANSI codes	M  Mixed output files and dirs\n\
	l  Long listing		N <date> Show newer than date\n\
	n  No sort		O <date> Show older than date\n\
	r  Reverse sort		P  Show full pathnames\n\
	s  Sort by size		R  Recursive listing\n\
	t  Sort by date		T  Total all subtotals\n\
	1  One entry per line	X <wide> Set output columns\n";
BYTE deffmtstr[] = "%p %5b %8s %d %n\\n";
BYTE deffullstr[] = "%n\\n";
BYTE LongFmtStr[] = "%ld";
BYTE totalfmtstr[] = "Dirs:%-4ld Files:%-4ld Blocks:%-5ld Bytes:%-8ld\n";
BYTE TotHeaderMsg[] = "Totals:\n";
BYTE ErrFmtStr[] = "%s\n";
BYTE BadOptFmtStr[] = "ls: Unknown option -%lc\n";
BYTE OptNeedsArgStr[] = "ls: Option -%lc needs argument\n";
BYTE NoWildPathMsg[] = "ls: Unable to pattern match paths";
BYTE NoExamFmtStr[] = "ls: Cannot examine file or directory, Error #%ld\n";
BYTE NoFindFmtStr[] = "ls: '%s' not found\n";
BYTE NotGoodDateStr[] = "ls: '%s' not a date\n";
BYTE NoRAMMsg[] = "ls: No RAM";

/* these are basically related to fib_DirEntryType after being
   remaped in FillFibEntry () or DirIt (). */
struct highlight highlight_tabx[12] = 
{
  /* -7 DEFAULT      */ {"", "", 0},
  /* -6 LINKFILE_EXE */ {"\x9b" "1m", "\x9b" "22m", 0},
  /* -5 FILE_EXE     */ {"", "", 0},
  /* -4 LINKFILE     */ {"\x9b" "1m", "\x9b" "22m", 0},
  /* -3 FILE         */ {"", "", 0},
  /* -2 SOFTLINK_EXE */ {"\x9b" "3m", "\x9b" "23m", 0},
  /* -1 SOFTLINK     */ {"\x9b" "3m", "\x9b" "23m", 0},
  /*  0 COMMENT      */ {"\x9b" "2m/* ", " */\x9b" "22m\n", 6},
  /*  1 ROOT         */ {"\x9b" "2m", "\x9b" "22m", 0},
  /*  2 USERDIR      */ {"\x9b" "2m", "\x9b" "22m", 0},
  /*  3 LABEL        */ {"\x9b" "2m", "\x9b" "22m", 0},
  /*  4 LINKDIR      */ {"\x9b" "2;1m", "\x9b" "22m", 0},
}, *highlight_tab = &highlight_tabx[7];
#define HIGHLIGHT_MAX 4
#define HIGHLIGHT_MIN -7

#define HI_DEFAULT -7
#define HI_LINKFILE_EXE -6
#define HI_FILE_EXE -5
#define HI_LINKFILE -4
#define HI_FILE -3
#define HI_SOFTLINK_EXE -2
#define HI_SOFTLINK -1
#define HI_COMMENT 0
#define HI_ROOT 1
#define HI_USERDIR 2
#define HI_LABEL 3
#define HI_LINKDIR 4

struct highlight highlight_null = {"", "", 0};
struct highlight highlight_cursor = {"\x9b" " p", "\x9b" "0 p", 0};

struct DateStamp theolddate;	/* Show files older than this date */
struct DateStamp thenewdate;	/* Show files newer than this date */
struct FileLock *CurFLock;	/* Global Directory lock */

/* Don't change order of next four entries.  Order needs to be preserved
   for VPrintf calls. */
LONG gdircount;			/* Grand total # of dirs for Recursive */
LONG gfilecount;		/* Grand total # of files found */
LONG gtotblocks;		/* Grand total # of blocks */
LONG gtotbytes;			/* Grand total # of bytes */
/* end of important order */

/* Don't change order of next four entries.  Order needs to be preserved
   for VPrintf calls. */
LONG dircount;			/* Number of directories found */
LONG filecount;			/* Number of files found */
LONG totblocks;			/* total # of blocks */
LONG totbytes;			/* total # of bytes */
/* end of important order */

LONG maxnamlen;			/* Longest name found */
LONG sortkey;			/* 0=alpha, 1=size, 2=date, 3=extension */
BYTE padtab[PADTABSIZE];	/* Column table tab amounts */
BYTE thePath[WORKSIZE * 2 + 4];	/* Current filename space */
BYTE theDirPat[WORKSIZE];	/* Dirname pattern workspace */
BYTE theFilePat[WORKSIZE];	/* Filename pattern workspace */
BYTE theDirPatParsed[WORKSIZE*2+2]; /* Dirname pattern parsed workspace */
BYTE theFilePatParsed[WORKSIZE*2+2]; /* Filename pattern parsed workspace */
BYTE workstr[WORKSIZE + 64];	/* Temp string space */
BYTE theblksstr[20];
BYTE thedatestr[32];
BYTE theprotstr[12];
BYTE thesizestr[20];
BYTE thehexsizestr[20];
BYTE thetimesizestr[20];
LONG CurWinCols;		/* Window bounds, determined at runtime */
BYTE *curpath;
BYTE *thefmtstr = deffmtstr;	/* Format string pointer for long output */
struct AnAntiPattern *TheAntiPatterns; /* Avoid pattern string */

struct
{
  int BREAKFLAG;
  int CONSOLE;
  int HIDEDIRS;
  int LISTALL;
  int LONGLIST;
  int NOTEFLAG;
  int PATHNAMED;
  int REVFLAG;
  int ABOUTDIRS;
  int FULLPATHNAMES;
  int TOTALIZE;
  int NOHEADERS;
  int FILESFIRST;
  int MIXFILESDIRS;
  int SHOWOLDERTHAN;
  int SHOWNEWERTHAN;
  int SHOWHIDDEN;
  int ACROSSLIST;
} LSFlags;

int CompareExtensions (char *, char *);
int CmpDateBounds (struct DateStamp *);
int CompFibs (int, struct FileInfoBlock *, struct FileInfoBlock *);
void WriteErrorString (BYTE *, ...);
void TestBreak (void);
void DateStr (BYTE *, struct DateStamp *);
int StrDate (BYTE *, struct DateStamp *);
void CleanUp (BYTE *, LONG, LONG);
void AddAntiPattern (BYTE *);
void Usage (BYTE);
void kput1 (void);
void asprintf (BYTE *, BYTE *, ...);
void GetWinBounds (LONG *, LONG *, LONG);
int FillFibEntry (struct MinList *, struct FileInfoBlock *);
struct FibEntry *ModNextFib (struct FibEntry *, LONG);
void SListDir (struct MinList *);
void LListEntry (struct FileInfoBlock *);
void LListDir (struct MinList *);
void FreeAllFibs (struct MinList *);
struct MinList *GetDir (struct FileLock *, struct FileInfoBlock *);
void DirIt (struct FileLock *, BYTE *);
void GetCLIArgs (BYTE *, LONG *, BYTE **);
LONG ParseCmdOptions (LONG, LONG, BYTE **);

int CompareExtensions (char *namea, char *nameb)
{
  char *taila = strrchr (namea, '.');
  char *tailb = strrchr (nameb, '.');

  if (taila)
    return tailb ? stricmp (taila, tailb) : 1;
  else
    return tailb ? -1 : 0;
}

int CmpDateBounds (struct DateStamp *tdate)
{
  if ((LSFlags.SHOWNEWERTHAN) && (CompareDates (&thenewdate, tdate) <= 0))
    return 0;
  if ((LSFlags.SHOWOLDERTHAN) && (CompareDates (tdate, &theolddate) <= 0))
    return 0;
  return 1;
}

int CompFibs (int keytype, struct FileInfoBlock *a, struct FileInfoBlock *b)
{
  int rc;

  if (((a->fib_DirEntryType < 0) && (b->fib_DirEntryType > 0)) ||
      ((a->fib_DirEntryType > 0) && (b->fib_DirEntryType < 0)))
    if (!(LSFlags.MIXFILESDIRS))
      return (LSFlags.FILESFIRST) ?
	     (a->fib_DirEntryType < 0) :
	     (a->fib_DirEntryType > 0);

  switch (keytype)
    {
    case 1:
      rc = b->fib_Size - a->fib_Size;
      goto like;
    case 2:
      rc = CompareDates (&a->fib_Date, &b->fib_Date);
      goto like;
    case 3:
      rc = CompareExtensions (b->fib_FileName, a->fib_FileName);
    like:
      if (rc > 0)
	goto bad;
      if (rc < 0)
	goto good;
    case 0:
      if (stricmp (b->fib_FileName, a->fib_FileName) > 0)
	goto bad;
    }
good:
  return LSFlags.REVFLAG;
bad:
  return !LSFlags.REVFLAG;
}

void WriteErrorString (BYTE *tstring, ...)
{
  struct Process *procp = (struct Process *) FindTask (0L);
  BPTR StdErr = procp->pr_CES;

  VFPrintf (StdErr ? StdErr : Output (), tstring, (LONG *) &tstring + 1);
}

void TestBreak (void)
{
  if (CheckSignal (SIGBREAKF_CTRL_C))
    {
      PutStr (highlight_cursor.on);
      PutStr ("*** Break: ls\n");
      LSFlags.BREAKFLAG = 1;
    }
}

void DateStr(BYTE *s, struct DateStamp *ds)
{
  char StrDate[LEN_DATSTRING], StrTime[LEN_DATSTRING];
  struct DateTime dt;
  struct DateStamp cds;

  dt.dat_Stamp = *ds;
  dt.dat_Format = FORMAT_DOS;
  dt.dat_Flags = 0;
  dt.dat_StrDay = NULL;
  dt.dat_StrDate = StrDate;
  dt.dat_StrTime = StrTime;

  if (!DateToStr (&dt))
    {
      strcpy (s, "Bad  1  Date");
      return;
    }

  s[0] = StrDate[3];
  s[1] = StrDate[4];
  s[2] = StrDate[5];
  s[3] = ' ';
  s[4] = (StrDate[0] == '0') ? ' ': StrDate[0];
  s[5] = StrDate[1];
  s[6] = ' ';
  DateStamp (&cds);
  if (((cds.ds_Days - ds->ds_Days) > 180) || ((cds.ds_Days - ds->ds_Days) < 0))
    {
      s[7] = ' ';
      if (ds->ds_Days < 8035)
	{
	  s[8] = '1';
	  s[9] = '9';
	}
      else
	{
	  s[8] = '2';
	  s[9] = '0';
	}
      s[10] = StrDate[7];
      s[11] = StrDate[8];
    }
  else
    {
      s[7] = StrTime[0];
      s[8] = StrTime[1];
      s[9] = ':';
      s[10] = StrTime[3];
      s[11] = StrTime[4];
    }
  s[12] = '\0';
}

int StrDate (char *s, struct DateStamp *ds)
{
  char StrDate[LEN_DATSTRING], StrTime[LEN_DATSTRING];
  struct DateTime dt;

  switch (strlen (s))
    {
    case 17:
      /* Standard "MMM DD HH:MM YYYY" 'ls' format */
      asprintf (StrDate, "%2.2s-%3.3s-%s", &s[4], &s[0], &s[15]);
      asprintf (StrTime, "%5.5s:00", &s[7]);

      if (StrDate[0] == ' ')
	StrDate[0] = '0';
      break;
    case 18:
      /* Standard "DD-MMM-YY HH:MM:SS" AmigaDOS 'date' and internal format */
      asprintf (StrDate, "%9.9s", &s[0]);
      asprintf (StrTime, "%s", &s[10]);
      break;
    default:
      return 1;
    }

  dt.dat_Format = FORMAT_DOS;
  dt.dat_Flags = 0;
  dt.dat_StrDay = NULL;
  dt.dat_StrDate = StrDate;
  dt.dat_StrTime = StrTime;

  if (!StrToDate (&dt))
    return 1;

  *ds = dt.dat_Stamp;
  return 0;
}

void CleanUp (BYTE *exit_msg, LONG exit_status, LONG result2)
{
  struct Process *procp;

  /* this cleanup routine assumes that the C library will free all
     malloc'd memory.  This is true with SAS/C with standard exit(). */

  if (CurFLock)
    UnLock ((BPTR) CurFLock); /* Make sure we unlock any locks we created! */

  if (*exit_msg)
    WriteErrorString (ErrFmtStr, exit_msg);

  procp = (struct Process *) FindTask (0L);
  procp->pr_Result2 = result2;

  exit ((int) exit_status);
}

void AddAntiPattern (BYTE *pattern)
{
  struct AnAntiPattern *AnAntiPattern = malloc (sizeof (struct AnAntiPattern));
  int len = strlen (pattern) * 2 + 2;

  if (!AnAntiPattern)
    CleanUp (NoRAMMsg, RETURN_FAIL, ERROR_NO_FREE_STORE);
  AnAntiPattern->next = TheAntiPatterns;
  AnAntiPattern->pattern = pattern;
  if (!(AnAntiPattern->parsedpattern = malloc (len)))
    CleanUp (NoRAMMsg, RETURN_FAIL, ERROR_NO_FREE_STORE);
  if (ParsePatternNoCase (pattern, AnAntiPattern->parsedpattern, len) == -1)
    CleanUp ("ls: ParsePattern Error", RETURN_FAIL, ERROR_NO_MORE_ENTRIES);
  TheAntiPatterns = AnAntiPattern;
}

void Usage (BYTE full)
{
  PutStr (shortusage);
  if (full)
    PutStr (fullusage);
  CleanUp ("", full ? RETURN_WARN : RETURN_FAIL, ERROR_BAD_TEMPLATE);
}

#ifdef __SASC
void kput1 (void)
{
  __builtin_emit (0x16C0);	/* MOVE.B  D0,(A3)+ */
}
#else
#error Fix kput1() for your compiler.
#endif

void asprintf (BYTE *s, BYTE *fmt, ...)
{
  RawDoFmt (fmt, &fmt + 1, kput1, s);
}

void GetWinBounds (LONG *width, LONG *height, LONG have_console)
{
  BPTR output = Output ();
  char buffer[16];

  if (have_console && output && IsInteractive (output) &&
      SetMode (output, 1) && (Write (output, "\x9b" "0 q", 4) == 4) &&
      WaitForChar (output, 10000L) &&
      (Read (output, buffer, sizeof (buffer)) > 9) &&
      (buffer[0] == '\x9b'))
    {
      int y = StrToLong (buffer+5, height);
      int x = StrToLong (buffer+5+y+1, width);
      if ((x == -1) || (y == -1))
	goto TRY_ENV;
    }
  else
    {
    TRY_ENV:
      if ((GetVar ("console_width", buffer, sizeof (buffer), LV_VAR | GVF_LOCAL_ONLY) == -1) ||
	  (StrToLong (buffer, width) == -1))
	*width = 77;
      if ((GetVar ("console_height", buffer, sizeof (buffer), LV_VAR | GVF_LOCAL_ONLY) == -1) ||
	  (StrToLong (buffer, height) == -1))
	*height = 23;
    }
  if (have_console && output && IsInteractive (output))
    SetMode (output, 0);
}

int FillFibEntry (struct MinList *headfib, struct FileInfoBlock *fibp)
{
  struct FibEntry *afib, *tfibp = malloc (sizeof (struct FibEntry));

  if (!tfibp)
    {
      LSFlags.BREAKFLAG = 1;
      return 0;
    }

  tfibp->fe_Fib = *fibp;

  /* Remap fib_DirEntryType to allow for easier usage later. */
  /* See order in highlight_tab for more details. */
  switch (tfibp->fe_Fib.fib_DirEntryType)
    {
    case ST_LINKFILE:
    case ST_FILE:
      if ((tfibp->fe_Fib.fib_Protection & FIBF_SCRIPT) ||
	  (~tfibp->fe_Fib.fib_Protection & FIBF_EXECUTE))
	tfibp->fe_Fib.fib_DirEntryType -= 2;
      break;
    case ST_ROOT:
    case ST_USERDIR:
    case ST_LINKDIR:
      break;
    case ST_SOFTLINK:
      if ((tfibp->fe_Fib.fib_Protection & FIBF_SCRIPT) ||
	  (~tfibp->fe_Fib.fib_Protection & FIBF_EXECUTE))
	tfibp->fe_Fib.fib_DirEntryType = HI_SOFTLINK_EXE;
      else
	tfibp->fe_Fib.fib_DirEntryType = HI_SOFTLINK;
      break;
    default:
      tfibp->fe_Fib.fib_DirEntryType = HI_DEFAULT;
      break;
    }

  for (afib = (struct FibEntry *) headfib->mlh_Head ; afib->fe_Node.mln_Succ;
       afib = (struct FibEntry *) afib->fe_Node.mln_Succ)
    if (CompFibs (sortkey, &(tfibp->fe_Fib), &(afib->fe_Fib)))
      break;
  Insert ((struct List *) headfib, (struct Node *) tfibp, (struct Node *) afib->fe_Node.mln_Pred);

  return 1;
}

struct FibEntry *ModNextFib (struct FibEntry *tfibp, LONG rows)
{
  LONG i;

  for (i = 0; i < rows && tfibp->fe_Node.mln_Succ; i++)
      tfibp = (struct FibEntry *) tfibp->fe_Node.mln_Succ;
  return (tfibp);
}

void SListDir (struct MinList *fibheadp)
{
  LONG avglen;
  LONG colcnt;
  LONG dfcount;
  LONG i, j, wlen;
  LONG maxcol;
  LONG maxrow;
  LONG rowcnt;
  LONG tlen;
  LONG totlen;
  struct FibEntry *hfibp, *tfibp;

  PutStr (highlight_cursor.off);		/* Turn the cursor off since it will blink anyway */

/* Make a average-case WxH estimate for # of display columns */
  for (totlen = dfcount = 0, hfibp = (struct FibEntry *) fibheadp->mlh_Head;
       hfibp->fe_Node.mln_Succ;
       hfibp = (struct FibEntry *) hfibp->fe_Node.mln_Succ)
    {
      totlen += strlen (hfibp->fe_Fib.fib_FileName) +
		highlight_tab[hfibp->fe_Fib.fib_DirEntryType].printable_len;
      dfcount++;
    }

/* Calc average length of all entries */
  avglen = totlen / dfcount;	/* Avg name length = totlen/numentries */
  if (totlen % dfcount)
    avglen++;

  if ((CurWinCols) <= maxnamlen)/* Longest name wider than window? */
    maxcol = 1;			/* Yep, just print one column */
  else
    for (maxcol = 0, colcnt = CurWinCols; colcnt >= avglen; maxcol++)
      colcnt -= avglen + 2;

/* Dry run output avg-case WxH table to see if it needs adjusting */
  for (;;)
    {
      /* Clear out previous padtab */
      memset (padtab, 0, PADTABSIZE);

      if (!(LSFlags.ACROSSLIST))
      {
      /* Number of rows = total entries / entries per row */
      maxrow = dfcount / maxcol;
      if (dfcount % maxcol)	/* Round up if non-integral */
	maxrow++;
        for (rowcnt = 0, hfibp = (struct FibEntry *) fibheadp->mlh_Head;
	     rowcnt < maxrow && hfibp->fe_Node.mln_Succ;
	     rowcnt++, hfibp = (struct FibEntry *) hfibp->fe_Node.mln_Succ)
	  {
	    for (colcnt = 0, tfibp = hfibp;
	         colcnt < maxcol && tfibp->fe_Node.mln_Succ;
	         colcnt++, tfibp = ModNextFib (tfibp, maxrow))
	      {
	        tlen = strlen (tfibp->fe_Fib.fib_FileName) +
		       highlight_tab[tfibp->fe_Fib.fib_DirEntryType].printable_len;
	        if (tlen > padtab[colcnt])
		  padtab[colcnt] = tlen;
	      }  

	    /* If this is the first row, calc actual maxcol/maxrow for this dfcount */
	    if (!rowcnt)
	      {
	        maxcol = colcnt;
	        maxrow = dfcount / maxcol;
	        if (dfcount % maxcol)	/* Round up if non-integral */
	  	  maxrow++;
	      }
	  }
      }
      else
      {
      /* Number of rows = total entries / entries per row */
      maxrow = dfcount / maxcol;
      if (dfcount % maxcol)	/* Round up if non-integral */
	maxrow++;

        for (rowcnt = 0, tfibp = (struct FibEntry *) fibheadp->mlh_Head;
	     rowcnt < maxrow && tfibp->fe_Node.mln_Succ;
	     rowcnt++)
	  {
	    for (colcnt = 0;
	         colcnt < maxcol && tfibp->fe_Node.mln_Succ;
	         colcnt++, tfibp = (struct FibEntry *) tfibp->fe_Node.mln_Succ)
	      {
	        tlen = strlen (tfibp->fe_Fib.fib_FileName) +
		       highlight_tab[tfibp->fe_Fib.fib_DirEntryType].printable_len;
	        if (tlen > padtab[colcnt])
		  padtab[colcnt] = tlen;
	      }
	  }
      }

      /* Calculate actual total width by adding up width of all columns */
      for (colcnt = totlen = 0; (colcnt + 1) < maxcol; colcnt++)
	  totlen += (LONG) padtab[colcnt] + 2;
      totlen += (LONG) padtab[colcnt];

      /* if More than one column and
       * total width of all columns is greater > our window width,
       * then decrease number of display columns
       */
      if (maxcol > 1 && totlen > CurWinCols)
	  maxcol--;
      else
	break;
    }

/* Do actual output scan */
  if (!(LSFlags.ACROSSLIST))
  {
  for (rowcnt = 0, hfibp = (struct FibEntry *) fibheadp->mlh_Head;
       !(LSFlags.BREAKFLAG) && rowcnt < maxrow && hfibp->fe_Node.mln_Succ;
       TestBreak (), rowcnt++, hfibp = (struct FibEntry *) hfibp->fe_Node.mln_Succ)
    {
      for (colcnt = 0, tfibp = hfibp;
	   colcnt < maxcol && tfibp->fe_Node.mln_Succ;
	   colcnt++)
	{
	  workstr[0] = 0;
	  wlen = strlen (tfibp->fe_Fib.fib_FileName) +
		 highlight_tab[tfibp->fe_Fib.fib_DirEntryType].printable_len;
	  strcat (workstr, highlight_tab[tfibp->fe_Fib.fib_DirEntryType].on);
	  strcat (workstr, tfibp->fe_Fib.fib_FileName);
	  strcat (workstr, highlight_tab[tfibp->fe_Fib.fib_DirEntryType].off);

	  /* Move along list to the next entry, mod maxcol, print this entry */
	  tfibp = ModNextFib (tfibp, maxrow);

	  /* If this is not the last column, pad with spaces till we get to next column */
	  if ((colcnt + 1) < maxcol && tfibp->fe_Node.mln_Succ)
	    {
	      for (i = (LONG) padtab[colcnt] + 1, j = strlen (workstr); i >= wlen; i--, j++)
		workstr[j] = ' ';
	      workstr[j] = 0;
	    }
	  /* Output the final entry */
	  PutStr (workstr);
	}

      /* Filled this row, start next down */
      PutStr ("\n");
    }
  }
  else
  {
  for (rowcnt = 0, tfibp = (struct FibEntry *) fibheadp->mlh_Head;
       !(LSFlags.BREAKFLAG) && rowcnt < maxrow && tfibp->fe_Node.mln_Succ;
       TestBreak (), rowcnt++)
    {
      for (colcnt = 0;
	   colcnt < maxcol && tfibp->fe_Node.mln_Succ;
	   colcnt++)
	{
	  workstr[0] = 0;
	  wlen = strlen (tfibp->fe_Fib.fib_FileName) +
		 highlight_tab[tfibp->fe_Fib.fib_DirEntryType].printable_len;
	  strcat (workstr, highlight_tab[tfibp->fe_Fib.fib_DirEntryType].on);
	  strcat (workstr, tfibp->fe_Fib.fib_FileName);
	  strcat (workstr, highlight_tab[tfibp->fe_Fib.fib_DirEntryType].off);

	  /* Move along list to the next entry, mod maxcol, print this entry */
          tfibp = (struct FibEntry *) tfibp->fe_Node.mln_Succ;

	  /* If this is not the last column, pad with spaces till we get to next column */
	  if ((colcnt + 1) < maxcol && tfibp->fe_Node.mln_Succ)
	    {
	      for (i = (LONG) padtab[colcnt] + 1, j = strlen (workstr); i >= wlen; i--, j++)
		workstr[j] = ' ';
	      workstr[j] = 0;
	    }
	  /* Output the final entry */
	  PutStr (workstr);
	}

      /* Filled this row, start next down */
      PutStr ("\n");
    }
  }

  PutStr (highlight_cursor.on);		/* Turn cursor back on */
}

void LListEntry (struct FileInfoBlock *fib)
{
  BYTE *cp1, *cp2, *reps;
  BYTE *pathend, *thenamestr;
  LONG i, spccnt;

  theprotstr[0] = "-L-L-ll-dd-D"[fib->fib_DirEntryType + 7];
  theprotstr[1] = (fib->fib_Protection & 128)		? 'h' : '-';
  theprotstr[2] = (fib->fib_Protection & FIBF_SCRIPT)	? 's' : '-';
  theprotstr[3] = (fib->fib_Protection & FIBF_PURE)	? 'p' : '-';
  theprotstr[4] = (fib->fib_Protection & FIBF_ARCHIVE)	? 'a' : '-';
  theprotstr[5] = (fib->fib_Protection & FIBF_READ)	? '-' : 'r';
  theprotstr[6] = (fib->fib_Protection & FIBF_WRITE)	? '-' : 'w';
  theprotstr[7] = (fib->fib_Protection & FIBF_EXECUTE)	? '-' : 'e';
  theprotstr[8] = (fib->fib_Protection & FIBF_DELETE)	? '-' : 'd';
  theprotstr[9] = ' ';
  theprotstr[10] = fib->fib_Comment[0] ? 'c' : ' ';

  DateStr (thedatestr, &(fib->fib_Date));

  RawDoFmt (LongFmtStr, &fib->fib_NumBlocks, kput1, theblksstr);
  RawDoFmt (LongFmtStr, &fib->fib_Size, kput1, thesizestr);

  i = strlen (curpath);
  pathend = curpath + i;
  if (i > 1 && *(curpath + i - 1) != ':')
    {
      *(curpath + i) = '/';
      i++;
      *(curpath + i) = 0;
    }
  thenamestr = curpath + strlen (curpath);
  cp2 = thenamestr;
  if ((fib->fib_DirEntryType == HI_SOFTLINK) ||
      (fib->fib_DirEntryType == HI_SOFTLINK_EXE))
    {
	struct DevProc *devproc = GetDeviceProc (curpath, NULL);
	UBYTE buffer[512];
	BOOL success;

        cp2 = stpcpy (cp2, fib->fib_FileName);

	if (devproc)
	  {
	    cp2 = stpcpy (cp2, " -> ");
	    success = ReadLink (devproc->dvp_Port, devproc->dvp_Lock,
				fib->fib_FileName, buffer, 512);
	    if (success)
	      strcpy (cp2, buffer);
	    else
	      strcpy (cp2, "!ERROR READING LINK!");
	    FreeDeviceProc (devproc);
	  }
	else
	  strcpy (cp2, "!ERROR LOCKING LINK!");
    }
  else if ((fib->fib_DirEntryType == HI_LINKDIR) ||
	   (fib->fib_DirEntryType == HI_LINKFILE) ||
	   (fib->fib_DirEntryType == HI_LINKFILE_EXE))
    {
	UBYTE buffer[512];
	BPTR plock, lock;

	plock = Lock (curpath, ACCESS_READ);
	plock = CurrentDir (plock);
	cp2 = stpcpy (cp2, fib->fib_FileName);
	cp2 = stpcpy (cp2, " -> ");
	lock = Lock (fib->fib_FileName, ACCESS_READ);
	if (lock)
	  {
	    NameFromLock (lock, buffer, 512);
	    UnLock (lock);
	    strcpy (cp2, buffer);
	  }
	else
	  strcpy (cp2, "!ERROR LOCKING LINK!");
	plock = CurrentDir (plock);
	UnLock (plock);
    }
  else
    strcpy (cp2, fib->fib_FileName);

  for (cp1 = workstr, cp2 = thefmtstr; *cp2; cp2++)
    {
      if (*cp2 != '%' && *cp2 != '\\')
	*cp1++ = *cp2;
      else
	{
	  if (*cp2++ == '%')
	    {
	      if ((*cp2 >= '0') && (*cp2 <= '9'))
		{
		  cp2 += StrToLong (cp2, &spccnt);
		  if (spccnt > 99)
		    spccnt = 99;
		}
	      else
		spccnt = 0;

	      switch (*cp2)
		{
		case 'p':
		  reps = theprotstr;
		  break;
		case 'd':
		  reps = thedatestr;
		  break;
		case 'b':
		  reps = theblksstr;
		  break;
		case 's':
		  reps = thesizestr;
		  break;
		case 'h':
		  RawDoFmt ("0x%lx", &fib->fib_Size, kput1, thehexsizestr);
		  reps = thehexsizestr;
		  break;
		case 'S':
		{
		  int xfer_rate;
		  int time[2];

		  cp2++;
		  if ((*cp2 >= '0') && (*cp2 <= '9'))
		    cp2 += StrToLong (cp2, &xfer_rate);
		  else
		    xfer_rate = 0;
		  cp2--;
		  if (!xfer_rate)
		    xfer_rate = 230;

		  time[0] = fib->fib_Size / xfer_rate / 60;
		  time[1] = fib->fib_Size / xfer_rate % 60;
		  RawDoFmt ("%ld:%02ld", time, kput1, thetimesizestr);
		  reps = thetimesizestr;
		  break;
		}
		case 'n':
		  if (LSFlags.FULLPATHNAMES)
		    reps = curpath;
		  else
		    reps = thenamestr;
		  break;
		case '%':
		  *cp1++ = '%';
		  *cp1++ = 0;
		default:
		  reps = "";
		  break;
		}
	      for (i = strlen (reps); i < spccnt; i++)
		*cp1++ = ' ';
	      cp1 = stpcpy (cp1, reps);
	    }
	  else
	    {
	      switch (*cp2)
		{
		case 'n':
		  *cp1++ = '\n';
		  break;
		case 't':
		  *cp1++ = '\t';
		  break;
		case '\\':
		  *cp1++ = '\\';
		  break;
		default:
		  break;
		}
	      *cp1 = 0;
	    }
	}
    }
  PutStr (workstr);

  if ((LSFlags.NOTEFLAG) && fib->fib_Comment[0])
    {
      PutStr (highlight_tab[HI_COMMENT].on);
      PutStr (fib->fib_Comment);
      PutStr (highlight_tab[HI_COMMENT].off);
    }
  *pathend = 0;
}

void LListDir (struct MinList *fibheadp)
{
  struct FibEntry *tfibp;

  PutStr (highlight_cursor.off);

  totblocks = totbytes = 0;
  for (tfibp = (struct FibEntry *) fibheadp->mlh_Head;
       tfibp->fe_Node.mln_Succ;
       tfibp = (struct FibEntry *) tfibp->fe_Node.mln_Succ)
    {
      TestBreak ();
      if (LSFlags.BREAKFLAG)
	return;
      LListEntry (&(tfibp->fe_Fib));
      if (tfibp->fe_Fib.fib_DirEntryType < 0)
	{
	  totblocks += tfibp->fe_Fib.fib_NumBlocks;
	  totbytes += tfibp->fe_Fib.fib_Size;
	}
    }

  if (!(LSFlags.BREAKFLAG || LSFlags.NOHEADERS))
    VPrintf (totalfmtstr, &dircount); /* note that parameters are in order in memory */

  PutStr (highlight_cursor.on);
}

void FreeAllFibs (struct MinList *fibheadp)
{
  struct FibEntry *tfibp;

  if (fibheadp)
    {
      while (fibheadp->mlh_Head->mln_Succ)
	{
	  tfibp = (struct FibEntry *) RemTail ((struct List *) fibheadp);
	  free (tfibp);
	}
    }
}

struct MinList *GetDir (struct FileLock *lockp, struct FileInfoBlock *fibp)
{
  struct AnAntiPattern *theanti; /* Pattern pointer to anti pattern */
  LONG matchstat;		/* Result of wildmatch() */
  LONG nextstat;		/* Status of ExNext() */
  LONG tempnamlen;		/* Compare length for strings */
  struct MinList *fibhead;	/* Temp list of Fibs created */
  struct MinList *dirhead;	/* Temp list of dirs if HIDEDIRS == 1 */

  maxnamlen = dircount = filecount = 0L;

/* Initialize an exec list of nodes, zero entries */
  if (!(fibhead = malloc (sizeof (struct MinList))))
    return (0L);
  NewList ((struct List *) fibhead);

/* Allocate an separate list for directories that don't match specs */
  if (!(dirhead = malloc (sizeof (struct MinList))))
    goto BADALLOC;
  NewList ((struct List *) dirhead);

  do
    {
      TestBreak ();
      if (LSFlags.BREAKFLAG)
	goto GOODRET;

      /* If we got something */
      if (nextstat = ExNext ((BPTR) lockp, fibp))
	{
	  /* If the entry is wanted bump count of files or directories */
	  if (CmpDateBounds (&fibp->fib_Date))
	    {
	      if ((fibp->fib_DirEntryType == ST_ROOT) ||
		  (fibp->fib_DirEntryType == ST_USERDIR) ||
		  (fibp->fib_DirEntryType == ST_LINKDIR)) /* It's a directory */
		matchstat = MatchPatternNoCase (theDirPatParsed, fibp->fib_FileName);
	      else
		matchstat = MatchPatternNoCase (theFilePatParsed, fibp->fib_FileName);

	      if (!(LSFlags.SHOWHIDDEN) && matchstat)
		matchstat = !(fibp->fib_Protection & 0x80);
	      theanti = TheAntiPatterns;
	      while (matchstat && theanti)
		{
		  matchstat = !MatchPatternNoCase (theanti->parsedpattern, fibp->fib_FileName);
		  theanti = theanti->next;
		}

	      if (!matchstat)	/* No match? Then move on */
		continue;

	      if ((fibp->fib_DirEntryType == ST_ROOT) ||
		  (fibp->fib_DirEntryType == ST_USERDIR) ||
		  (fibp->fib_DirEntryType == ST_LINKDIR)) /* It's a directory */
		{
		  if (!FillFibEntry (dirhead, fibp))
		    goto BADALLOC;
		  if (LSFlags.HIDEDIRS)
		    continue;
		  dircount++;
		  gdircount++;
		}
	      else
		/* It's a file entry */
		{
		  filecount++;
		  gfilecount++;
		  gtotblocks += fibp->fib_NumBlocks;
		  gtotbytes += fibp->fib_Size;
		}

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

	      /* Allocate another FibEntry, put the info inside */
	      if (!FillFibEntry (fibhead, fibp))
		goto BADALLOC;
	    }
	}
  } while (nextstat);

  if (dircount + filecount)
    {
      if (!(LSFlags.LONGLIST))
	SListDir (fibhead);
      else
	LListDir (fibhead);
    }

GOODRET:
  FreeAllFibs (fibhead);
  return (dirhead);

BADALLOC:
  FreeAllFibs (fibhead);
  FreeAllFibs (dirhead);
  return (0L);
}

void DirIt (struct FileLock *curlock, BYTE *dirname)
{
  BYTE *subdir;
  LONG dnamlen;
  struct FibEntry *tfibp;
  struct FileLock *sublock;
  struct MinList *fibheadp;
  struct FileInfoBlock __aligned GFibp;

/* Try to fill FileInfoBlock, bomb if not readable for some reason */
  if (!Examine ((BPTR) curlock, &GFibp))
    {
      WriteErrorString (NoExamFmtStr, IoErr ());
      return;
    }

/* Put directory header for each listing, if we know the name && recursive */
  if (dirname[0] && (LSFlags.LISTALL) && !(LSFlags.NOHEADERS))
    {
      PutStr (highlight_tab[HI_LABEL].on);
      PutStr (dirname);
      PutStr ("\n");
      PutStr (highlight_tab[HI_LABEL].off);
    }

/* If this is a single file list it verbosely */
  if ((GFibp.fib_DirEntryType == ST_SOFTLINK) ||
      (GFibp.fib_DirEntryType == ST_LINKFILE) ||
      (GFibp.fib_DirEntryType == ST_FILE) ||
      (LSFlags.ABOUTDIRS))
    {
  /* Remap fib_DirEntryType to allow for easier usage later. */
  /* See order in highlight_tab for more details. */
  switch (GFibp.fib_DirEntryType)
    {
    case ST_LINKFILE:
    case ST_FILE:
      if ((GFibp.fib_Protection & 64) ||
	  (~GFibp.fib_Protection & 2))
	GFibp.fib_DirEntryType -= 2;
      break;
    case ST_ROOT:
    case ST_USERDIR:
    case ST_LINKDIR:
      break;
    case ST_SOFTLINK:
      if ((GFibp.fib_Protection & 64) ||
	  (~GFibp.fib_Protection & 2))
	GFibp.fib_DirEntryType = HI_SOFTLINK_EXE;
      else
	GFibp.fib_DirEntryType = HI_SOFTLINK;
      break;
    default:
      GFibp.fib_DirEntryType = HI_DEFAULT;
      break;
    }

      *(PathPart (thePath)) = '\0';
      LListEntry (&GFibp);
      filecount++;
      gfilecount++;
      gtotblocks += GFibp.fib_NumBlocks;
      gtotbytes += GFibp.fib_Size;
    }
  else
    /* It is a directory entry */
    {
	  /* Allocate, fill, and display a dir of entries, check for no ram or abort */
	  if (fibheadp = GetDir (curlock, &GFibp))
	    {
	      /* Recursively descend any subdirs in this list, if wanted */
	      if (LSFlags.LISTALL)
		{
		  for (tfibp = (struct FibEntry *) fibheadp->mlh_Head;
		       tfibp->fe_Node.mln_Succ;
		       tfibp = (struct FibEntry *) tfibp->fe_Node.mln_Succ)
		    {
		      TestBreak ();
		      if (LSFlags.BREAKFLAG)
			break;

		      if ((tfibp->fe_Fib.fib_DirEntryType > 0) &&
			  (tfibp->fe_Fib.fib_DirEntryType != ST_SOFTLINK))
			{
			  /* Alloc length of path + 1 + newdir name +1 + length of maxname + 1 for NULL
		           * + 1 for null + 3 for rounding.
		           */
			  dnamlen = (LONG) (strlen (dirname) + strlen (tfibp->fe_Fib.fib_FileName) + 36);
			  if (subdir = malloc (dnamlen))
			    {
			      if (dirname[0])
				{
				  strcpy (subdir, dirname);
				  dnamlen = strlen (dirname) - 1;
				  if (dirname[dnamlen] != ':' && dirname[dnamlen] != '/')
				    strcat (subdir, "/");
				}
				  strcat (subdir, tfibp->fe_Fib.fib_FileName);

				  if (sublock = (struct FileLock *) Lock (subdir, (LONG) ACCESS_READ))
				    {
				      if (!(LSFlags.NOHEADERS))
					PutStr ("\n");	/* Put a blank line between directories */

				      curpath = subdir;

				      DirIt (sublock, subdir);	/* Recurse into this subdirectory */

				      UnLock ((BPTR) sublock);	/* Unlock our sublock */
				    }
			      free (subdir);	/* Free the current namespace */
			    }
			}
		    }
		}
	      /* Free up this fib list */
	      FreeAllFibs (fibheadp);
	    }
    }
}

void GetCLIArgs (BYTE *line, LONG *argc, BYTE **argv)
{
  BYTE **pargv, *qarg;

  *argc = 0;
  while (*argc < MAXARG)
    {
      while (*line == ' ' || *line == '\t' || *line == '\n')
	line++;
      if (!(*line))
	break;
      pargv = &argv[*argc];
      *argc += 1;

      if (*line == '"')
	{
	  qarg = line;
	  line += 1;
	  *pargv = line;	/* ptr inside quoted string */
	  while (*line && *line != '"')
	    line++;
	  if (!(*line))	/* Hit end of string without quote! */
	    {
	      *pargv = qarg;	/* Must be okay */
	      break;
	    }
	  else
	    *line++ = 0;	/* terminate arg ontopof quote */
	}
      else
	/* non-quoted arg */
	{
	  *pargv = line;
	  while (*line && !(*line == ' ' || *line == '\t' || *line == '\n'))
	    line++;
	  if (*line)
	    *line++ = 0;	/* terminate arg */
	  else
	    break;
	}
    }				/* while */
}

LONG ParseCmdOptions (LONG ncnt, LONG argc, BYTE **argv)
{
  LONG i, cnt, len;

  cnt = ncnt;		/* Current arg number that contains the options */
  ncnt += 1;		/* Advance to next arg */

  for (i = 1, len = strlen (argv[cnt]); i < len; i++)
    {
      switch (argv[cnt][i])
	{
	case '?':
	case 'h':
	  Usage (1);
	  break;
	case 'D':
	  LSFlags.FILESFIRST = 1;
	  break;
	case 'F':
	  if (argc < (ncnt + 1))  /* Missing required format string */
	    goto missing_arg_error;
	  thefmtstr = argv[ncnt]; /* Else our format string is the next arg */
	  ncnt += 1;		  /* Bump arg counter */
	  LSFlags.LONGLIST = 1;
	  break;
	case 'H':
	  LSFlags.NOHEADERS = 1;
	  break;
	case 'A':
	  LSFlags.ACROSSLIST = 1;
	  break;
	case 'M':
	  LSFlags.MIXFILESDIRS = 1;
	  break;
	case 'N':
	  if (argc < (ncnt + 1))  /* Missing required name string */
	    goto missing_arg_error;
	  if (StrDate (argv[ncnt], &thenewdate))
	    goto date_arg_error;
	  ncnt += 1;		  /* Bump arg counter */
	  LSFlags.SHOWNEWERTHAN = 1;
	  break;
	case 'O':
	  if (argc < (ncnt + 1))  /* Missing required name string */
	    goto missing_arg_error;
	  if (StrDate (argv[ncnt], &theolddate))
	    goto date_arg_error;
	  ncnt += 1;		  /* Bump arg counter */
	  LSFlags.SHOWOLDERTHAN = 1;
	  break;
	date_arg_error:
	  WriteErrorString (NotGoodDateStr, argv[ncnt]);
	  CleanUp ("", RETURN_FAIL, ERROR_BAD_TEMPLATE);
	case 'P':
	  if ((thefmtstr == deffmtstr) && !(LSFlags.LONGLIST))
	    thefmtstr = deffullstr;
	  LSFlags.FULLPATHNAMES = 1;
	  LSFlags.LONGLIST = 1;
	  LSFlags.NOHEADERS = 1;
	  break;
	case 'R':
	  LSFlags.LISTALL = 1;
	  break;
	case 'T':
	  LSFlags.TOTALIZE = 1;
	  break;
	case 'X':
	  if (argc < (ncnt + 1))  /* Missing required width number */
	    goto missing_arg_error;
	  StrToLong (argv[ncnt], &CurWinCols);
	  ncnt += 1;		  /* Bump arg counter */
	  break;
	case '1':
	  CurWinCols = 1;
	  break;
	case 'Y':
	  if (argc < (ncnt + 1))  /* Missing required height number */
	    goto missing_arg_error;
	  ncnt += 1;		  /* Bump arg counter */
	  break;
	case 'a':
	  LSFlags.SHOWHIDDEN = 1;
	  break;
	case 'c':
	  LSFlags.LONGLIST = 1;
	  LSFlags.NOTEFLAG = 1;
	  break;
	case 'd':
	  LSFlags.ABOUTDIRS = 1;
	  break;
	case 'e':
	  sortkey = 3;
	  break;
	case 'f':
	  LSFlags.HIDEDIRS = 1;
	  break;
	case 'k':
	  LSFlags.CONSOLE = 0;
	  break;
	case 'l':
	  if ((thefmtstr == deffullstr) && (LSFlags.FULLPATHNAMES))
	    thefmtstr = deffmtstr;
	  LSFlags.LONGLIST = 1;
	  break;
	case 'n':
	  sortkey = 4;
	  break;
	case 'r':
	  LSFlags.REVFLAG = 1;
	  break;
	case 's':
	  sortkey = 1;
	  break;
	case 't':
	  sortkey = 2;
	  break;
	case 'I':
	  if (argc < (ncnt + 1))  /* Missing required pattern string */
	    goto missing_arg_error;
	  AddAntiPattern (argv[ncnt]);
	  ncnt += 1;		  /* Skip over arg */
	  break;
	default:
	  WriteErrorString (BadOptFmtStr, argv[cnt][i]);
	  Usage (0);
	missing_arg_error:
	  WriteErrorString (OptNeedsArgStr, argv[cnt][i]);
	  Usage (1);
	}
    }
  return (ncnt);
}

void __stdargs _main (char *line)
{
  BYTE *argv[MAXARG];		/* arg pointers */
  LONG argc;			/* arg count */
  LONG cnt = 1;
  LONG Columns, Rows;
  struct Process *procp;
  SHORT namedPath = FALSE;

  if (DOSBase->dl_lib.lib_Version < 37)
    {
      Write (Output (), "ls: Need AmigaOS Release 2.04\n", 30);
      CleanUp ("", RETURN_FAIL, ERROR_INVALID_RESIDENT_LIBRARY);
    }

  procp = (struct Process *) FindTask (0L);

  GetCLIArgs (line, &argc, argv);

  if (IsInteractive (Output ()))
    LSFlags.CONSOLE = 1;

  while (cnt < argc && argv[cnt][0] == '-')
    {
      if (argv[cnt][1] == '-' || argv[cnt][1] == '\0')
	{
          cnt++;
	  break;
	}
      else
	{
	  cnt = ParseCmdOptions (cnt, argc, argv);
	  continue;
	} 
    }

  {
    int i;
    char a[32], *b;

    if (!(b = malloc (32)))
      CleanUp (NoRAMMsg, RETURN_FAIL, ERROR_NO_FREE_STORE);
    for (i = HIGHLIGHT_MIN; i <= HIGHLIGHT_MAX; i++)
      {
	RawDoFmt ("ls_highlight%ld.on", &i, kput1, a);
	if (GetVar (a, b, 32, LV_VAR | GVF_LOCAL_ONLY) != -1)
	  {
	    highlight_tab[i].on = b;
	    highlight_tab[i].printable_len = 0;
	    for (; *b; b++)
	      {
		if ((*b == '\x9b') || (*b == '\x1b'))
		  {
		    while (*++b)
		      if (*b == 'm')
			break;
		  }
		else
		  highlight_tab[i].printable_len++;
	      }
	    if (!(b = malloc (32)))
	      CleanUp (NoRAMMsg, RETURN_FAIL, ERROR_NO_FREE_STORE);
	    RawDoFmt ("ls_highlight%ld.off", &i, kput1, a);
	    if (GetVar (a, b, 32, LV_VAR | GVF_LOCAL_ONLY) != -1)
	      {
		highlight_tab[i].off = b;
	        for (; *b; b++)
	          {
		    if ((*b == '\x9b') || (*b == '\x1b'))
		      {
		        while (*++b)
		          if (*b == 'm')
			    break;
		      }
		    else
		      highlight_tab[i].printable_len++;
	          }
		if (!(b = malloc (32)))
	          CleanUp (NoRAMMsg, RETURN_FAIL, ERROR_NO_FREE_STORE);
	      }
	  }
      }
    free (b);
  }

  if (!(LSFlags.CONSOLE))
    {
      int i;

      for (i = HIGHLIGHT_MIN; i <= HIGHLIGHT_MAX; i++)
	if ((*highlight_tab[i].on == '\x9b') ||
	    (*highlight_tab[i].on == '\x1b') ||
	    (*highlight_tab[i].off == '\x9b') ||
	    (*highlight_tab[i].off == '\x1b'))
	  highlight_tab[i] = highlight_null;

      highlight_cursor = highlight_null;
      highlight_tab[HI_COMMENT].on = "/* ";
      highlight_tab[HI_COMMENT].off = " */\n";
    }

  if (!(LSFlags.SHOWHIDDEN))
    AddAntiPattern ("(#?.(info|bak)|.#?)");

  if (!CurWinCols)
    {
      GetWinBounds (&Columns, &Rows, (LSFlags.CONSOLE));
      CurWinCols = Columns;
    }

  while (!(LSFlags.BREAKFLAG))
    {
      if (cnt < argc)
	{
	  namedPath = TRUE;

	  theFilePat[0] = 0;
	  theDirPat[0] = 0;
	  strcpy (thePath, argv[cnt]);
	  cnt++;

	  if (ParsePatternNoCase (thePath, theDirPatParsed, sizeof (theDirPatParsed)))
	    {
	      strcpy (theFilePat, FilePart (thePath));
	      strcpy (theDirPat, theFilePat);
	      *(PathPart (thePath)) = '\0';

	      if (ParsePatternNoCase (thePath, theDirPatParsed, sizeof (theDirPatParsed)))
		{
		  strcpy (theDirPat, FilePart (thePath));
		  *(PathPart (thePath)) = '\0';
		}
	    }

	  if (ParsePatternNoCase (thePath, theDirPatParsed, sizeof (theDirPatParsed)))
	    CleanUp (NoWildPathMsg, RETURN_WARN, ERROR_NO_MORE_ENTRIES);

	  if (!(CurFLock = (struct FileLock *) Lock (thePath, (LONG) ACCESS_READ)))
	    {
	      WriteErrorString (NoFindFmtStr, thePath);
	      CleanUp ("", RETURN_FAIL, IoErr ());
	    }
	}
      else if ((!namedPath) && (cnt == argc))
	{
	  cnt++;

	  if (procp->pr_CurrentDir)
	    CurFLock = (struct FileLock *) DupLock (procp->pr_CurrentDir);
	  else
	    CurFLock = (struct FileLock *) Lock ("sys:", ACCESS_READ);
	  if (!CurFLock)
	    CleanUp ("ls: No Current Directory", RETURN_FAIL, ERROR_NO_DEFAULT_DIR);
	}
      else
	break;

      if (!strchr (thePath, ':'))
	if (!NameFromLock ((BPTR) CurFLock, thePath, sizeof (thePath)))
	  CleanUp ("ls: NameFromLock Error", RETURN_FAIL, IoErr ());
      curpath = thePath;

      if (!theDirPat[0])
	strcpy (theDirPat, "#?");

      if (!theFilePat[0])
	strcpy (theFilePat, "#?");

      if (ParsePatternNoCase (theDirPat, theDirPatParsed, sizeof (theDirPatParsed)) == -1)
	CleanUp ("ls: ParsePattern Error", RETURN_FAIL, ERROR_NO_MORE_ENTRIES);
      if (ParsePatternNoCase (theFilePat, theFilePatParsed, sizeof (theFilePatParsed)) == -1)
	CleanUp ("ls: ParsePattern Error", RETURN_FAIL, ERROR_NO_MORE_ENTRIES);

      DirIt (CurFLock, thePath);

      if (CurFLock)
	  UnLock ((BPTR) CurFLock);
      CurFLock = NULL;

      TestBreak ();
  }

  if ((LSFlags.TOTALIZE))
    {
      PutStr (highlight_tab[HI_LABEL].on);
      PutStr (TotHeaderMsg);
      PutStr (highlight_tab[HI_LABEL].off);
      VPrintf (totalfmtstr, &gdircount); /* note that parameters are in order in memory */
    }

  CleanUp ("", RETURN_OK, 0L);
}
