[comp.os.minix] ls for minix - part 1 of 2

cechew@bruce.OZ (Earl Chew) (07/27/89)

Here is my version of ls for minix. This version of ls uses the directory
access routines from the library rather than attempting to interpret the
directory file itself. There is also no limit on the number of entries in the
directory to be scanned other than that imposed by the memory available for the
program. This ls supports most of the options available with bsd and sysv
version of ls (it will print in columns, sort entries across the page, sort
entries down the page, print groups, print owners, sort by modification time,
indicate file type, etc).

Bruce Evans and I have been using this for some time now. This is
version 1.6 --- some previous versions have been posted before.

-------------------------------------------------------------------------------
#! /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 shell archive."
# Contents:  ls.c
# Wrapped by cechew@bruce on Thu Jul 27 14:29:32 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'ls.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'ls.c'\"
else
echo shar: Extracting \"'ls.c'\" \(30602 characters\)
sed "s/^X//" >'ls.c' <<'END_OF_FILE'
X/*
X *			List Directory Entries
X *
X * (C) Copyright C E Chew
X *
X * Feel free to copy and use this software provided:
X *
X *	1. you do not pretend that you wrote it
X *	2. you leave this copyright notice intact.
X *
X * This is an implementation of a BSD style ls(1) for Minix. This
X * implementation is faster than the original ls(1) for Minix. There
X * are no restrictions with regard to the size of the directory file
X * since memory is allocated dynamically.
X *
X * Patchlevel 1.6
X *
X * Edit History:
X *
X * 16-May-1989	Option -n shouldn't look in /etc/passwd. Use new
X *		strerror in string.h, add prototype for itoa().
X * 30-Apr-1989	Changed stat failure message to not found. Include
X *		dirent.h after stdio.h so that NULL is defined.
X * 22-Mar-1989	Incompatible options processing, long sizes instead
X *		of int and removed TURBOC conditional.
X * 02-Mar-1989	Sort command line arguments.
X * 22-Feb-1989	Fixed up bug regarding device number printing.
X * 10-Sep-1988	Cleaned up for lint.
X * 05-Sep-1988	Tidied up for network release.
X */
X
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <errno.h>
X#include <stdio.h>
X#include <dirent.h>
X#include <string.h>
X#include <time.h>
X#include <ctype.h>
X#include <grp.h>
X#include <pwd.h>
X
X#ifdef	MINIX
X#include <minix/const.h>
X#include <minix/type.h>
X#include <fs/const.h>
X#include <fs/type.h>
X#undef printf
X#endif
X
X#define DEFAULTDIR	"."		/* default for ls without args */
X#define PARENT		".."		/* cd back to parent */
X#define ENVNAME		"LSOPTS"	/* ls options */
X#define COLUMNS		80		/* columns on terminal */
X#define INTERSPACE	2		/* spacing between columns */
X#define INODEWIDTH	5		/* width of field for inode */
X#define BLOCKWIDTH	4		/* width of field for blocks */
X#define PATH_MAX	128		/* space for path */
X
X#define HALFYEAR	((long) 60*60*24*365/2) /* half year in seconds */
X#define BASEYEAR	1900		/* struct tm.tm_year base */
X
X#define NIL		((void *) 0)	/* nil pointer */
X
X/*
X * Flags are maintained in a bitmap. Access should be fast
X * because of the inline nature.
X */
X#define BPC		8		/* bits per character */
X#define BITTEST(m,b)	(m[b/BPC] & (1<<(b%BPC)))
X#define BITSET(m,b)	(m[b/BPC] |= (1<<(b%BPC)))
X#define BITCLEAR(m,b)	(m[b/BPC] &= ~(1<<(b%BPC)))
X#define BITCHARS(b)	((b+BPC-1)/BPC)
X
X#define TEST(b)		BITTEST(flags,b)
X#define SET(b)		BITSET(flags,b)
X#define CLEAR(b)	BITCLEAR(flags,b)
X
X/*
X * These macros permit the shortens the short circuiting of
X * complicated boolean expressions. This is particularly
X * useful when working with the flags since these are
X * read-only.
X */
X#define BOOL(b)		static char b = 0
X#define EVAL(b,a)	((b ? b : (b = (a)+1)) > 1)
X
X/*
X * A descriptor is kept for each file in the directory. The
X * descriptors are linked together in a linked list.
X */
Xstruct direntry {
X  struct direntry *next;		/* link list */
X  char *name;				/* name of entry */
X  struct direntry *parent;		/* pointer to parent */
X  struct stat *status;			/* pointer to status */
X};
X
Xtypedef struct direntry DIRENTRY;	/* entry */
Xtypedef DIRENTRY *DIRLIST[2];		/* list of entries */
X
X#define EMPTY_dl(d)	(d[0]=d[1]=NIL)
X#define APPEND_dl(d,p)	if (d[0]) d[1]->next=p,d[1]=p;else d[0]=d[1]=p
X#define TIE_dl(d,p)	APPEND_dl(d,p); while (d[1]->next) d[1]=d[1]->next
X#define HEAD_dl(d)	d[0]
X
X#define LINKOFFSET	((char *)&((DIRENTRY *)NIL)->next - \
X			 (char *)((DIRENTRY *)NIL))
X
X/*
X * External Functions.
X */
X 
Xtime_t time();				/* get system time */
Xchar *itoa();				/* asciify integer */
Xchar *getenv();				/* get environment variable */
Xint stat();				/* get status of file */
Xstruct passwd *getpwuid();		/* get password from uid */
Xstruct group *getgrgid();		/* get group from gid */
Xint getuid();				/* get uid of this process */
Xint getopt();				/* parse the options */
Xvoid *malloc();				/* memory allocator */
Xvoid free();				/* free memory */
Xint isatty();				/* stdout is a terminal */
Xvoid exit();				/* terminate */
X
X/*
X * External Variables.
X */
X
Xextern int optind;			/* next argument */
X
X/*
X * Forward declarations.
X */
X
Xvoid acrosspage();			/* list across the page */
Xvoid downpage();			/* list down the page */
Xvoid streamoutput();			/* stream output */
Xvoid dolsopts();			/* do environment variable */
Xvoid parse();				/* parse argument list */
Xvoid setflag();				/* set flag */
Xvoid checkflag();			/* check flag for incompatibilies */
Xvoid init();				/* initialise globals */
Xint stringlength();			/* length of string with feeling */
Xunsigned long bytestoblocks();		/* convert bytes to blocks */
Xvoid date();				/* make date readable */
Xvoid makepath();			/* form the path */
Xvoid longprint();			/* long format */
Xvoid printentry();			/* print this entry */
XDIRENTRY *newentry();			/* malloc a new entry */
Xvoid freelist();			/* free entries */
Xvoid freeentry();			/* free an entry */
Xint alphacmp();				/* alphabetic comparison */
Xint mtimecmp();				/* compare by modification time */
Xvoid list();				/* file listing routine */
Xvoid filestat();			/* get status of file */
Xvoid *safemalloc();			/* malloc with feeling */
XDIRENTRY *makelist();			/* create list of entries */
Xvoid *lsort();				/* linked list sort */
X
X/*
X * Unitialised Globals.
X */
X 
XDIRLIST rlist;				/* temporary recursive list */
XDIRLIST dlist;				/* list of directories */
Xtime_t today;				/* today's date */
X
X/*
X * Initialised Globals.
X */
X 
Xunsigned char flags[BITCHARS(128)] = {0};
Xchar pathname[PATH_MAX] = {0};		/* current path */
Xchar *endpath = pathname;		/* end of path */
Xint (*compare)() = alphacmp;		/* sort criteria */
Xint extrawidth = INTERSPACE;		/* implied extra space to add */
Xchar *incompatible[] = {		/* mutually exclusive flags */
X  "aA", "mx1C", "glno", "bq", "pF", NIL
X};
X
Xint main(argc, argv)
X
Xint argc;				/* number of arguments */
Xchar *argv[];				/* list of arguments */
X
X{
X  DIRENTRY *dp;				/* directory scanner */
X  DIRLIST nlist;			/* list of files */
X  int showdir = 0;			/* print directory name */
X  
X/* Initialise by emptying the directory lists */
X  EMPTY_dl(dlist), EMPTY_dl(nlist);
X
X/* Parse the command line */
X  dolsopts();
X  parse(argc, argv);
X  init();
X
X/* No arguments defaults to a list of the current directory */
X  if (optind >= argc) {
X    dp = newentry(DEFAULTDIR);
X    APPEND_dl(dlist, dp);
X  }
X
X/* Insert arguments into the current list */
X  else {
X    int number, width;			/* width and number */
X    int thiswidth;			/* width of this filename */
X    
X    for (width=number=0; optind < argc; optind++) {
X
X      dp = newentry(argv[optind]);
X
X      if (TEST('f') || ! TEST('d')) {
X
X        if (! TEST('f')) {
X	  dp->status = (struct stat *) safemalloc(sizeof(struct stat));
X          if (stat(argv[optind], dp->status)) {
X	    if (errno == ENOENT)
X	      (void) fprintf(stderr, "%s not found\n", argv[optind]);
X	    else
X	      (void) fprintf(stderr, "%s stat failure: %s\n",
X	                     argv[optind], strerror(errno));
X	    freeentry(dp);
X	    continue;
X	  }
X
X	  if ((dp->status->st_mode & S_IFMT) != S_IFDIR)
X	    goto normalfile;
X
X	  free(dp->status);
X	  dp->status = NIL;
X	}
X	
X        if (HEAD_dl(dlist))
X	  showdir = 1;
X	APPEND_dl(dlist, dp);
X      }
X      else {
X
Xnormalfile:
X
X	APPEND_dl(nlist, dp);
X        number++;
X        if (width < (thiswidth = stringlength(argv[optind])+1))
X	  width = thiswidth;
X      }
X    }
X
X    if (number)
X      showdir = 1;
X
X    HEAD_dl(nlist) = (DIRENTRY *) lsort(HEAD_dl(nlist), LINKOFFSET, compare);
X    list(NIL, nlist, width, number);
X  }
X
X  if (! TEST('f'))
X    HEAD_dl(dlist) = (DIRENTRY *) lsort(HEAD_dl(dlist), LINKOFFSET, compare);
X    
X  for (dp = HEAD_dl(dlist); dp; showdir = 1, dp = dp->next) {
X    int width, number;			/* width and number */
X    unsigned long blocks;		/* blocks in total */
X
X    freelist(nlist);
X    makepath(dp);
X
X    if (showdir)
X      (void) printf("\n%.*s:\n", endpath-pathname-1, pathname);
X
X    HEAD_dl(nlist) = makelist(pathname, &width, &number, &blocks);
X
X    {
X      BOOL(dototal);			/* show totals */
X
X      if (EVAL(dototal, TEST('g') || TEST('o') || TEST('s')))
X        (void) printf("total %lu\n", blocks);
X    }
X
X    list(dp, nlist, width, number);
X  }
X
X  return 0;
X}
X
X/*
X * Scan the options from the environment variable. This is
X * done in a similar fashion to the command line option
X * scan but no errors are flagged.
X */
X
Xvoid dolsopts()
X
X{
X   char *env = getenv(ENVNAME);		/* environment options */
X
X/* Output to tty allows environment settable options */
X  if (isatty(fileno(stdout)) && env) {
X    while (*env && isascii(*env)) {
X      setflag(*env);
X      env++;
X    }
X  }
X}
X
X/*
X * Parse the command line arguments. This function will set
X * the switches in the global variable flags. No interpretation
X * of the switches is performed at this point.
X */
X
Xvoid parse(argc, argv)
X
Xint argc;				/* number of arguments */
Xchar *argv[];				/* argument vector */
X
X{
X  int swch;				/* switch character */
X  
X  while ((swch = getopt(argc, argv, "abdfgilmnopqrstx1ACFR")) != EOF) {
X    if (swch != '?')
X      setflag(swch);
X    else {
X      (void) fputs("Unrecognised option\n", stderr);
X      exit(1);
X    }
X  }
X}
X
X/*
X * Set the specified option switch. This function knows about
X * mutually exclusive options and will turn off options which
X * are not compatible with the current one.
X */
X
Xvoid setflag(ch)
X
Xint ch;					/* option to set */
X
X{
X  char **p;				/* scanner */
X
X  for (p = incompatible; *p != NIL; p++)
X    checkflag(ch, *p);
X  SET(ch);
X}
X
X/*
X * Check the specified flag against the list of mutually exclusive
X * flags. If the flag appears, then all other flags in the list are
X * turned off. If not, then nothing is done.
X */
X
Xvoid checkflag(ch, p)
X
Xint ch;					/* flag */
Xchar *p;				/* strings */
X
X{
X  if (strchr(p, ch) != NIL) {
X    while (*p != 0) {
X      if (*p++ != ch)
X        CLEAR(p[-1]);
X    }
X  }
X}
X
X/*
X * Scan the switches and initialise globals. This function will
X * do some preliminary work on the switches to determine which
X * globals will be needed and also which switches need to be
X * set or cleared in addition to the current settings.
X */
X 
Xvoid init()
X
X{
X/* Get today's date */
X  today = time(NIL);
X
X/* Turn on -A if we're the super user */
X  if (! TEST('a') && getuid() == SUPER_USER)
X    SET('A');
X
X/* Force raw directory listing */
X  if (TEST('f')) {
X    CLEAR('l'); CLEAR('n'); CLEAR('o'); CLEAR('g');
X    CLEAR('t');
X    CLEAR('s');
X    CLEAR('r');
X    CLEAR('F'); CLEAR('p');
X    CLEAR('A');
X    SET('a');
X  }
X  
X/* Sort criterion */
X  if (TEST('t'))
X    compare = mtimecmp;
X  
X/* Open the password and group files if required */
X  if (TEST('l') || TEST('n')) {
X    SET('o');
X    SET('g');
X  }
X
X/* Long format forces output to be single column */
X  if (TEST('g') || TEST('o')) {
X    CLEAR('m');
X    CLEAR('x');
X    CLEAR('C');
X  }
X
X/* Accumulate extra width if required */
X  if (TEST('i'))
X    extrawidth += INODEWIDTH+1;
X  if (TEST('s'))
X    extrawidth += BLOCKWIDTH+1;
X}
X
X/*
X * Make a linked list of entries using specified directory. The
X * directory is rewound before being scanned. The function
X * returns a pointer to the head of the list of entries. The
X * function gathers two important statistics as the list
X * is created. It will return the width required to print
X * the files, and also the number of files in the list.
X *
X * The list will be sorted according to the current sorting
X * criteria.
X */
X
XDIRENTRY *makelist(dirname, width, number, blocks)
X
Xchar *dirname;				/* name of directory to scan */
Xint *width, *number;			/* width and number */
Xunsigned long *blocks;			/* number of blocks */
X
X{
X  DIR *dirp;				/* directory to scan */
X  struct dirent *cp;			/* current entry */
X  DIRLIST nlist;			/* files in directory */
X  DIRENTRY *p;				/* new entry */
X  int colwidth;				/* width output columns */
X
X  EMPTY_dl(nlist);
X  *width  = 0;
X  *number = 0;
X  *blocks = 0;
X
X  if ((dirp = opendir(dirname)) == NIL) {
X    (void) fprintf(stderr, "Cannot access %s: %s\n", pathname, strerror(errno));
X    return NIL;
X  }
X    
X  while ((cp = readdir(dirp)) != NIL) {
X    if (cp->d_name[0]  != '.' || TEST('a')   ||
X        (cp->d_name[1] != 0   && cp->d_name[1] != '.' && TEST('A'))) {
X
X      if (*width < (colwidth = stringlength(cp->d_name)))
X        *width = colwidth;
X      (*number)++;
X      p = newentry(cp->d_name);
X
X      {
X        BOOL(dostat);			/* perform a stat */
X	
X	if (EVAL(dostat, TEST('t') || TEST('g') || TEST('o') || TEST('s')))
X          filestat(p->name, p->status = (struct stat *)
X	                                safemalloc(sizeof(struct stat)));
X      }
X
X      {
X        BOOL(doblock);			/* check block sizes */
X
X	if (EVAL(doblock, TEST('g') || TEST('o') || TEST('s')))
X	  *blocks += bytestoblocks(p->status->st_size);
X      }
X      
X      APPEND_dl(nlist, p);
X    }
X  }
X
X  if (closedir(dirp)) {
X    (void) fprintf(stderr, "Cannot close directory: %s\n", strerror(errno));
X    exit(1);
X  }
X
X  return TEST('f') ? HEAD_dl(nlist)
X                   : (DIRENTRY *) lsort(HEAD_dl(nlist), LINKOFFSET, compare);
X}
X
X/*
X * This function performs does the formatting and output for ls(1).
X * The function is passed a list of files (not necessarily sorted)
X * to list. All files will be listed. . and .. removal should have
X * been done BEFORE calling this function.
X */
X
Xvoid list(parent, lp, width, number)
X
XDIRENTRY *parent;			/* parent to this entry */
XDIRLIST lp;				/* files to list */
Xint width, number;			/* width and number */
X
X{
X  if (number) {
X
X/* Empty recursive directory list */
X    EMPTY_dl(rlist);
X
X/* Select the correct output format */
X    if (TEST('m'))
X      streamoutput(parent, lp);
X    else if (! TEST('C'))
X      acrosspage(parent, lp, width);
X    else
X      downpage(parent, lp, width, number);
X      
X    (void) putchar('\n');
X
X/* Check recursive list */
X    if (HEAD_dl(rlist)) {
X      if (! TEST('f'))
X        HEAD_dl(rlist) = (DIRENTRY *)
X	                 lsort(HEAD_dl(rlist), LINKOFFSET, compare);
X      TIE_dl(dlist, HEAD_dl(rlist));
X    }
X  }
X}
X
X/*
X * List the entries across the page. Single column output is
X * treated as a special case of this. This is the easiest
X * since the list of files can be scanned and dumped.
X */
X
Xvoid acrosspage(parent, lp, width)
X
XDIRENTRY *parent;			/* parent to this entry */
XDIRLIST lp;				/* files to list */
Xint width;				/* maximum width */
X
X{
X  DIRENTRY *p;			/* entries to print */
X  int columns;			/* columns to print in */
X  int colno;			/* which column we're printing */
X
X  columns = TEST('x') ? COLUMNS/(width+extrawidth) : 1;
X
X  for (colno = 0, p = HEAD_dl(lp); p; p = p->next) {
X    if (++colno > columns) {
X      colno = 1;
X      (void) putchar('\n');
X    }
X    printentry(parent, p, width, colno != columns);
X  }
X}
X
X/*
X * Print the entries down the page. This is the default format
X * for multicolumn listings. It's unfortunate that this is
X * the most acceptable to the user, but it causes the program
X * a little grief since the list of files is not in the
X * most convenient order. Most of this code is taken up
X * with rearranging the list to suit the output format.
X */
X
Xvoid downpage(parent, lp, width, number)
X
XDIRENTRY *parent;			/* parent to this entry */
XDIRLIST lp;				/* files to list */
Xint width, number;			/* width and number */
X
X{
X  DIRENTRY *c[(COLUMNS+INTERSPACE-1)/INTERSPACE]; /* column pointers */
X  DIRENTRY *p;				/* list scanner */
X  int columns;				/* columns to print in */
X  int rows;				/* number of rows per column */
X  int i, j;				/* general counters */
X
X/* Nominal number of columns - targeted number of rows */
X  columns = COLUMNS/(width+extrawidth);
X  rows    = (number+columns-1)/columns;
X
X/* Scan and fill in pointers adjusting columns */
X  for (columns = 0, p = HEAD_dl(lp); p; ) {
X
X    c[columns++] = p;
X    for(i = rows; i-- && p; p = p->next)
X      ;
X  }
X
X/* Scan and print the listing */
X  for (i = 0; i < rows; i++) {
X    if (i)
X      (void) putchar('\n');
X
X    for (j = 0; j < columns; j++) {
X      if (c[j]) {
X        printentry(parent, c[j], width, j != columns-1);
X        c[j] = c[j]->next;
X      }
X    }
X  }
X}
X
X/*
X * List the files using stream format. This code looks a bit
X * kludgy because it is necessary to KNOW how wide the next
X * entry is going to be. If the width would case the printout
X * to exceed the width of the display, the entry must be printed
X * on the next line.
X */
X 
Xvoid streamoutput(parent, lp)
X
XDIRENTRY *parent;			/* parent to this entry */
XDIRLIST lp;				/* files to list */
X
X{
X  DIRENTRY *p;				/* entries to print */
X  int colno;				/* current column */
X  int midline;				/* in middle of line */
X  int tw;				/* width of this entry */
X
X  for (midline = colno = 0, p =HEAD_dl(lp); p; p = p->next) {
X
X    {
X      BOOL(dostat);			/* stat the file */
X
X      if (EVAL(dostat, TEST('s') || TEST('i') ||
X		       TEST('F') || TEST('p')))
X        if (p->status == NIL)
X	  filestat(p->name, p->status = (struct stat *)
X				        safemalloc(sizeof(struct stat)));
X    }
X
X/* Nominal length of the name */
X    tw = stringlength(p->name);
X
X/* Pretty printing adds an extra character */
X    {
X      BOOL(dopretty);		/* pretty print */
X
X      if (EVAL(dopretty, TEST('F') || TEST('p'))) {
X        if ((p->status->st_mode & S_IFMT) == S_IFDIR)    tw++;
X        else if (TEST('F') && p->status->st_mode & 0111) tw++;
X      }
X    }
X
X/* Size will add to the length */
X    {
X      unsigned long blocks;		/* size in blocks */
X
X      if (TEST('s')) {
X	blocks = bytestoblocks(p->status->st_size);
X	do {
X	  tw++;
X	} while ((blocks /= 10) != 0);
X        tw++;
X      }
X    }
X
X/* Inode number adds to the length */
X    if (TEST('i'))
X      tw += strlen(itoa(p->status->st_ino)) + 1;
X
X/* There will be a separating comma */
X    if (p->next)
X      tw++;
X
X/* There will be a separating space */
X    if (midline)
X      tw++;
X
X/* Begin a new line if necessary in which case there is no separating space */
X    if (colno + tw >= COLUMNS) {
X      (void) putchar('\n');
X      midline = 0;
X      colno   = 0;
X      tw--;
X    }
X
X/* Separate entries if required */
X    if (midline)
X      (void) putchar(' ');
X
X/* Now the entry proper */
X    printentry(parent, p, 0, 0);
X
X/* Now the separating comma */
X    if (p->next)
X      (void) putchar(',');
X
X    midline = 1;
X    colno += tw;
X  }
X}
X
X/*
X * Print this entry on stdout. No newline is appended to the
X * output. This function localises all the transformations
X * which have to be carried out to make the output readable.
X * Columnar magic is done elsewhere.
X */
X
Xvoid printentry(parent, p, w, padout)
X
XDIRENTRY *parent;			/* parent to this entry */
XDIRENTRY *p;				/* entry to print */
Xint w;					/* width of field */
Xint padout;				/* pad this entry */
X
X{
X  char *q;				/* pointer into name */
X  unsigned int c;			/* current character */
X  int pad;				/* pad count */
X
X  {
X    BOOL(dostat);			/* do stat on file */
X
X    if (EVAL(dostat, TEST('F') || TEST('g') || TEST('o') ||
X                     TEST('p') || TEST('R') || TEST('i') ||
X                     TEST('s'))) {
X      if (p->status == NIL)
X        filestat(p->name, p->status = (struct stat *)
X	                              safemalloc(sizeof(struct stat)));
X    }
X  }
X
X  if (TEST('i'))
X    (void) printf("%*d ", w ? INODEWIDTH : 0, p->status->st_ino);
X
X  if (TEST('s'))
X    (void) printf("%*lu ", w ? BLOCKWIDTH : 0, bytestoblocks(p->status->st_size));
X
X  {
X    BOOL(dolong);			/* long print */
X    
X    if (EVAL(dolong, TEST('o') || TEST( 'g')))
X      longprint(p->status);
X  }
X
X/* Print the name of this file */
X  for (q = p->name; *q; ) {
X    if (isprint(c = *q++))
X      (void) putchar(c);
X    else if (TEST('q'))
X      (void) putchar('?');
X    else if (TEST('b'))
X      (void) printf("\\%03d", c & 0377);
X    else
X      (void) putchar(c);
X  }
X
X/* Determine the pad width */
X  pad = w - (q - p->name) + 1;
X
X  {
X    BOOL(dopretty);			/* do pretty printing */
X
X    if (!EVAL(dopretty, TEST('F') || TEST('p')))
X      pad++;
X    else {
X      if ((p->status->st_mode & S_IFMT) == S_IFDIR)    (void) putchar('/');
X      else if (TEST('F') && p->status->st_mode & 0111) (void) putchar('*');
X      else pad++;
X    }
X  }
X
X/* Only pad it if the caller requires it */
X  if (padout)
X    while (pad-- > 0)
X      (void) putchar(' ');
X
X/* If recursion is required, add to list if it's a directory */
X  if (TEST('R') && (p->status->st_mode & S_IFMT) == S_IFDIR) {
X    DIRENTRY *np = newentry(p->name);
X    np->parent   = parent;
X    APPEND_dl(rlist, np);
X  }
X}
X
X/*
X * Format and print out the information for a long listing.
X * This function handles the conversion of the mode bits
X * owner, etc. It assumes that the status has been obtained.
X */
X
Xvoid longprint(sp)
X
Xstruct stat *sp;			/* pointer to status */
X
X{
X  unsigned int filetype;		/* type of file */
X  char filecode;			/* code for type of file */
X  unsigned int mode;			/* current mode */
X  char *prot[3];			/* protection ogu */
X  static struct passwd *pwent = NIL;	/* user's password entry */
X  static struct group *grent = NIL;	/* group entry */
X  static char *protmode[16] = {		/* protection modes */
X    "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx",
X    "--S", "--s", "-wS", "-ws", "r-S", "r-s", "rwS", "rws"
X  };
X  static unsigned int setbits[3] = {	/* set on execution bits */
X    0, S_ISGID, S_ISUID
X  };
X  int i;				/* general counter */
X
X  filetype = sp->st_mode & S_IFMT;
X  if      (filetype == S_IFDIR) filecode = 'd';
X  else if (filetype == S_IFBLK) filecode = 'b';
X  else if (filetype == S_IFCHR) filecode = 'c';
X  else                          filecode = '-';
X
X  for (i = 0, mode = sp->st_mode; i < 3; mode >>= 3, i++)
X    prot[i] = protmode[(mode & 07) + (sp->st_mode & setbits[i] ? 8 : 0)];
X
X  (void) printf("%c%s%s%s %2d ",
X                filecode, prot[2], prot[1], prot[0], sp->st_nlink);
X
X  if (TEST('o')) {
X    if (! TEST('n') && ((pwent && pwent->pw_uid == sp->st_uid) ||
X                        (pwent = getpwuid(sp->st_uid)) != NIL))
X      (void) printf("%6s ", pwent->pw_name);
X    else
X      (void) printf("%6d ", sp->st_uid);
X  }
X
X  if (TEST('g')) {
X    if (! TEST('n') && ((grent && grent->gr_gid == sp->st_gid) ||
X                        (grent = getgrgid(sp->st_gid)) != NIL))
X      (void) printf("%6s ", grent->gr_name);
X    else
X      (void) printf("%6d ", sp->st_gid);
X  }
X
X/* Now show how big the file is */
X  if (filetype != S_IFCHR && filetype != S_IFBLK)
X    (void) printf("%7ld ", sp->st_size);
X  else
X    (void) printf("%3d,%3d ", (sp->st_rdev >> 8) & 0377, sp->st_rdev & 0377);
X  
X/* Now the date */
X  date(sp->st_mtime);
X}
X
X/*
X * Given the time convert it into human readable form. The month and
X * day are always printed. If the time is within about the last half year,
X * the hour and minute are printed, otherwise the year.
X */
X
Xvoid date(t)
X
Xtime_t t;				/* time to print */
X
X{
X  struct tm *tmbuf;			/* output time */
X  
X  tmbuf = localtime(&t);
X  (void) printf("%.3s %2d ",
X                "JanFebMarAprMayJunJulAugSepOctNovDec"+tmbuf->tm_mon*3,
X                tmbuf->tm_mday);
X  if (t < today && (today-t) <= HALFYEAR)
X    (void) printf("%2d:%02d ", tmbuf->tm_hour, tmbuf->tm_min);
X  else
X    (void) printf("%5d ", tmbuf->tm_year+BASEYEAR);
X}
X
X/*
X * Convert the size of a file (in bytes) to the number of
X * blocks used. This figure will include the number of
X * indirect blocks used to store the file. This routine
X * was lifted from the original Minix ls.
X */
X
Xunsigned long bytestoblocks(size)
X
Xlong size;				/* size in bytes */
X
X{
X#ifdef	MINIX
X  unsigned long blocks;			/* accumulated blocks */
X  unsigned long fileb;			/* size of file in blocks */
X
X/* Compute raw file size and checking for direct zones */
X  fileb = (size + (long) BLOCK_SIZE - 1)/BLOCK_SIZE;
X  blocks = fileb;
X  if (fileb <= NR_DZONE_NUM) return blocks;
X
X/* Single indirect zones */
X  blocks++;
X  fileb -= NR_DZONE_NUM;
X  if (fileb <= NR_INDIRECTS) return blocks;
X
X/* Doubly indirect zones */
X  blocks++;
X  fileb -= NR_INDIRECTS;
X  blocks += (fileb + NR_INDIRECTS - 1)/NR_INDIRECTS;
X  return blocks;
X#endif
X}
X
X/*
X * Chase the parent pointers and make a path. This path is
X * used later to stat the files in each directory.
X */
X
Xvoid makepath(p)
X
XDIRENTRY *p;				/* directory */
X
X{
X  char tmppath[PATH_MAX];		/* build it here */
X  char *cp;				/* pointer to tmppath */
X  char *cq;				/* pointer into name */
X  char *cr;				/* temporary pointer */
X  
X/* Work your way back up to the root */
X  for (cp = tmppath, *cp++ = 0; p; p = p->parent) {
X    for (cq = p->name; *cq; *cp++ = *cq++)
X      ;
X    *cp++ = 0;
X  }
X
X/* Now flip the order */
X  for (cq = pathname, --cp; cp != tmppath; ) {
X    while (*--cp)
X      ;
X    for (cr = cp+1; *cr; *cq++ = *cr++)
X      ;
X    if (cq[-1] != '/')
X      *cq++ = '/';
X  }
X  *(endpath = cq) = 0;
X}
X
X/*
X * Allocate memory for a new entry. Memory is allocated and
X * filled in. The next, parent and status pointers are set
X * to null. The function returns a pointer to the new entry.
X */
X
XDIRENTRY *newentry(name)
X
Xchar *name;				/* name of entry */
X
X{
X  DIRENTRY *p;				/* pointer to entry */
X
X  p = (DIRENTRY *) safemalloc(sizeof(DIRENTRY));
X  (void) strcpy(p->name = (char *) safemalloc((unsigned int) strlen(name)+1),
X		name);
X  p->next     = NIL;
X  p->parent   = NIL;
X  p->status   = NIL;
X  return p;
X}
X
X/*
X * Free the memory associated with list of elements. The function
X * assumes all memory has been allocated using malloc(), so that
X * free() will work without suffering a heart attack. The list
X * header is set to null before returning.
X */
X
Xvoid freelist(lp)
X
XDIRLIST lp;				/* list to free */
X
X{
X  while (lp[0]) {
X    lp[1] = lp[0]->next;
X    freeentry(lp[0]);
X    lp[0] = lp[1];
X  }
X  EMPTY_dl(lp);
X}
X
X/*
X * Free the memory associated with a directory entry. Remember that
X * all the memory should have been allocated using malloc().
X */
X
Xvoid freeentry(p)
X
XDIRENTRY *p;				/* pointer to entry */
X
X{
X  if (p) {
X    if (p->name)
X      free(p->name);
X    if (p->status)
X      free(p->status);
X    free(p);
X  }
X}
X
X/*
X * Compare entries in the file list. Pointers to two entries are
X * expected as arguments (non null pointers). Compare the entries
X * and return -1, 0 or 1 depending on whether the first argument
X * is less than, equal to or greater than the second.
X */
X
Xint alphacmp(p, q)
X
XDIRENTRY *p, *q;
X
X{
X  register int rv = strcmp(p->name, q->name);
X
X  return TEST('r') ? -rv : rv;
X}
X
X/*
X * Compare entries on the basis of modification time. Pointers to
X * two entries are expected as arguments. It is assumed that the status
X * has been obtained. The routine will return -1, 0 or 1 depending
X * on whether the first argument is later than, equal to or earlier
X * than the second.
X */
X
Xint mtimecmp(p, q)
X
XDIRENTRY *p, *q;			/* entries to compare */
X
X{
X  register int rv;			/* comparison result */
X  long delta = p->status->st_mtime - q->status->st_mtime;
X
X  rv = delta > 0 ? -1 : delta ? 1 : 0;
X
X  return TEST('r') ? -rv : rv;
X}
X
X/*
X * Get the status of a file prepending the path before calling
X * stat. It accepts the same arguments as stat.
X */
X
Xvoid filestat(name, statbuf)
X
Xchar *name;				/* name of file */
Xstruct stat *statbuf;			/* stat buffer */
X
X{
X  (void) strcpy(endpath, name);
X  if (stat(pathname, statbuf)) {
X    if (errno == ENOENT)
X      (void) fprintf(stderr, "%s not found\n", pathname);
X    else
X      (void) fprintf(stderr, "%s stat failure: %s\n",
X                     pathname, strerror(errno));
X    exit(1);
X  }
X  *endpath = 0;
X}
X
X/*
X * Compute the length of the string taking into account the
X * form of output. String lengths will increase if the
X * visible output flag has been specified.
X */
X
Xint stringlength(s)
X
Xchar *s;				/* file name */
X
X{
X  register int i = 0;			/* length */
X  register int c;			/* character */
X  
X  while ((c = *s++) != 0)
X    if (TEST('b') && !isprint(c))
X      i += 4;
X    else
X      i++;
X  return i;
X}
X
X/*
X * This function does a malloc() with feeling. If malloc() returns
X * null, indicating no more memory, the function will fail with
X * an error message. The function will return a pointer to the
X * allocated memory.
X */
X
Xvoid *safemalloc(n)
X
Xunsigned int n;				/* amount to malloc */
X
X{
X  void *p;				/* malloc'ed region */
X  
X  if ((p = malloc(n)) == NIL) {
X    (void) fputs("Insufficient memory to complete operation\n", stderr);
X    exit(1);
X  }
X  return p;
X}
X
X/*
X * Sort a linked list using a natural merge sort with automatic
X * run merging. Logical run boundaries between runs are not maintained
X * allowing runs to naturally coalesce.
X *
X * This function accepts three arguments. The first is a pointer to
X * the first element of the list. The second is the number of elements
X * in the list. If this is given as -1, the list will be scanned first
X * to find the number of elements (a null pointer indicating the final
X * element). The third argument is the byte offset from the element
X * pointer to find the pointer to the next element in the list.
X */
X
Xtypedef void *LIST[2];
X
X#define EMPTYLIST(t)	(t[0] = t[1] = (void *) 0)
X#define NEXT(p)		(* (void **) ((char *) p + offset))
X#define HEAD(t)		(t[0])
X#define APPEND(a,p)	if (a[0]) NEXT(a[1])=p,a[1]=p; else a[0]=a[1]=p
X#define MOVE(a,p,q)	APPEND(a,p); NEXT(p) = (void *) 0; p = q;
X
Xvoid *lsort(head, offset, cmp)
X
Xvoid *head;				/* head of list */
Xint offset;				/* byte offset to link */
Xint (*cmp)();				/* comparison */
X
X{
X  LIST pile[2];				/* distributions */
X  void *p, *q;				/* tape head */
X  void *ap, *aq;			/* first tape head */
X  void *bp, *bq;			/* second tape head */
X  int tapeno;				/* tape number */
X  int eora, eorb;			/* end of run */
X
X/* Distribute the runs from the initial list */
X  EMPTYLIST(pile[0]), EMPTYLIST(pile[1]);
X
X  for (p = head, tapeno = 0; p; tapeno ^= 1) {
X    do {
X      eora = (q = NEXT(p)) != (void *) 0 ? (*cmp)(p,q) > 0 : 1;
X      MOVE(pile[tapeno], p, q);
X    } while (!eora);
X  }
X
X/* Now merge the runs from the two piles - distribute as we go */
X  while (HEAD(pile[1])) {
X
X    ap = HEAD(pile[0]); aq = NEXT(ap);
X    bp = HEAD(pile[1]); bq = NEXT(bp);
X
X    EMPTYLIST(pile[0]), EMPTYLIST(pile[1]);
X
X    for (tapeno = 0;
X	 eora = (ap == 0), eorb = (bp == 0), !eora || !eorb;
X	 tapeno ^= 1) {
X
X      while (!eora && !eorb) {
X        if ((*cmp)(ap,bp) < 0) {
X	  eora = aq == (void *) 0 || (*cmp)(ap,aq) > 0;
X	  MOVE(pile[tapeno], ap, aq);
X	  if (ap)
X	    aq = NEXT(ap);
X        }
X        else {
X	  eorb = bq == (void *) 0 || (*cmp)(bp,bq) > 0;
X	  MOVE(pile[tapeno], bp, bq);
X	  if (bp)
X	    bq = NEXT(bp);
X        }
X      }
X
X/* At least one of the lists has finished - copy out the other */
X      while (!eora) {
X        eora = aq == (void *) 0 || (*cmp)(ap,aq) > 0;
X        MOVE(pile[tapeno], ap, aq);
X        if (ap)
X	  aq = NEXT(ap);
X      }
X
X      while (!eorb) {
X        eorb = bq == (void *) 0 || (*cmp)(bp,bq) > 0;
X        MOVE(pile[tapeno], bp, bq);
X        if (bp)
X	  bq = NEXT(bp);
X      }
X    }
X  }
X
X  return HEAD(pile[0]);
X}
END_OF_FILE
if test 30602 -ne `wc -c <'ls.c'`; then
    echo shar: \"'ls.c'\" unpacked with wrong size!
fi
# end of 'ls.c'
fi
echo shar: End of shell archive.
exit 0
-------------------------------------------------------------------------------