[comp.os.minix] ls Version 2 - Part 2 of 2

cechew@bruce.OZ (Earl Chew) (10/09/89)

#! /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 2 (of 2)."
# Contents:  ls.c
# Wrapped by cechew@bruce on Mon Oct  9 19:57:51 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'\" \(36040 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 2.0
X *
X * Edit History:
X * 21-Sep-1989	Changed names to _BSD and _MINIX. Use #ifdef for
X *		portability.
X * 19-Sep-1989	Sizes in kb rather than `blocks'.
X * 14-Sep-1989	No longer need to define MINIX. Get rid of itoa().
X *		Pyramid BSD coercions. Symbolic links and sockets.
X * 27-Aug-1989	Added declaration of errno for old errno.h and
X *		char *malloc() for old include files. Added
X *		support for named FIFO's. Changed user and group
X *		name format to %-6.6s so that long names won't
X *		muck up columns. Later will have to add
X *		symbolic link support for -l and -L.
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#ifndef		_SYSV
X# ifndef	_BSD
X#  ifndef	_MINIX
X#   define 	_MINIX
X#  endif
X# endif
X#endif
X
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <errno.h>
X#include <stdio.h>
X#ifdef		_BSD
X# include <sys/dir.h>
X# define dirent direct
X# include <sys/param.h>
X# define STD_BLK	DEV_BSIZE
X# define PATH_MAX	MAXPATHLEN
X# include <strings.h>
X# define strchr		index
X# define strerror(n)	((n) < 1 || (n) > sys_nerr \
X			 ? "Unknown" : sys_errlist[n])
X# include <sys/time.h>
X#else
X# include <limits.h>
X# include <dirent.h>
X# include <string.h>
X# include <time.h>
X#endif
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# undef T
X# undef D
X# undef P
X#endif
X
X#ifndef		SUPER_USER
X# define SUPER_USER	(0)
X#endif
X
X#ifdef		__STDC__
X# define T(x,y)		x
X# define D(x)
X# define P(x)		x
X#else
X# define T(x,y)		y
X# define D(x)		x;
X# define P(x)		()
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
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		(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  char *suffix;				/* entry suffix */
X  int length;				/* length of name and suffix */
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]=(DIRENTRY *)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 P((time_t *));		/* get system time */
Xchar *getenv P((char *));		/* get environment variable */
Xint stat P((char *, struct stat *));	/* get status of file */
Xstruct passwd *getpwuid P((uid_t));	/* get password from uid */
Xstruct group *getgrgid P((gid_t));	/* get group from gid */
Xint getuid P((void));			/* get uid of this process */
Xint getopt P((int, char **, char *));	/* parse the options */
Xvoid free P((void *));			/* free memory */
Xint isatty P((int));			/* stdout is a terminal */
Xvoid exit P((int));			/* terminate */
Xchar *malloc P((unsigned));		/* memory allocator */
Xstruct tm *localtime P((time_t *));	/* convert to local time */
XDIR *opendir P((char *));		/* open directory */
Xstruct dirent *readdir P((DIR *));	/* read directory */
X
X#ifdef	_BSD
Xvoid closedir P((DIR *));		/* close directory */
X#else
Xint closedir P((DIR *));		/* close directory */
X#endif
X
X#ifdef		S_IFLNK
X  int lstat P((char *, struct stat *));	/* stat() with opaque links */
X  int readlink P((char *, char *, int));/* read symbolic link */
X# define STAT		lstat
X#else
X# define STAT		stat
X#endif
X
X/*
X * External Variables.
X */
X
Xextern int optind;			/* next argument */
Xextern int errno;			/* error code */
Xextern int sys_nerr;			/* errors */
Xextern char *sys_errlist[];		/* errors by name */
X
X/*
X * Forward declarations.
X */
X
Xvoid acrosspage P((DIRENTRY *, DIRLIST, int)); /* list across the page */
Xvoid downpage P((DIRENTRY *, DIRLIST, int, int)); /* list down the page */
Xvoid streamoutput P((DIRENTRY *, DIRLIST)); /* stream output */
Xvoid dolsopts P((void));		/* do environment variable */
Xvoid parse P((int, char **));		/* parse argument list */
Xvoid setflag P((int));			/* set flag */
Xvoid checkflag P((int, char *));	/* check flag for incompatibilies */
Xvoid init P((void));			/* initialise globals */
Xint stringlength P((char *));		/* length of string with feeling */
Xvoid printstring P((char *));		/* print string with feeling */
Xunsigned long bytestokb P((struct stat *)); /* convert bytes to kb */
Xvoid date P((time_t));			/* make date readable */
Xvoid makepath P((DIRENTRY *));		/* form the path */
Xvoid longprint P((struct stat *));	/* long format */
Xvoid printentry P((DIRENTRY *, DIRENTRY *, int, int)); /* print this entry */
XDIRENTRY *newentry P((char *));		/* malloc a new entry */
Xvoid freelist P((DIRLIST));		/* free entries */
Xvoid freeentry P((DIRENTRY *));		/* free an entry */
Xint alphacmp P((DIRENTRY *, DIRENTRY *)); /* alphabetic comparison */
Xint mtimecmp P((DIRENTRY *, DIRENTRY *)); /* compare by modification time */
Xvoid list P((DIRENTRY *, DIRLIST, int, int)); /* file listing routine */
Xvoid suffix P((DIRENTRY *));		/* do suffixes */
Xint filestat P((int,
X                int (*)(char *, struct stat *),
X                char *, struct stat *)); /* get status of file */
Xvoid *safemalloc P((unsigned int));	 /* malloc with feeling */
XDIRENTRY *makelist P((char *, int *, int *,
X                      unsigned long *)); /* create list of entries */
Xvoid *lsort P((void *, int,
X               int (*)(void *, void *)));/* linked list sort */
X
X#ifdef	S_IFLNK
Xchar *followlink P((char *));		/* follow symbolic link */
X#endif
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+1] = {0};	/* current path */
Xchar *endpath = pathname;		/* end of path */
Xint (*compare)() = alphacmp;		/* sort criteria */
Xint (*status)() = STAT;			/* opaque status */
Xint extrawidth = INTERSPACE;		/* implied extra space to add */
Xchar *incompatible[] = {		/* mutually exclusive flags */
X  "aA", "mx1C", "glno", "bq", "pF", (char *) 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  BOOL(dototal);			/* show totals */
X  BOOL(dostat);				/* need to do a stat */
X  BOOL(dodir);				/* check for directory */
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    
X    for (width=number=0; optind < argc; optind++) {
X
X      dp = newentry(argv[optind]);
X
X      if (EVAL(dostat, TEST('t') || TEST('g') ||
X		       TEST('o') || TEST('s') ||
X		       TEST('F') || TEST('p') ||
X		       TEST('R') || TEST('i') ||
X		       ! TEST('f') && ! TEST('d')) &&
X	  dp->status == (struct stat *) NIL        &&
X	  filestat(0, stat, dp->name,
X		   dp->status =
X		     (struct stat *) safemalloc(sizeof(*dp->status)))) {
X	freeentry(dp);
X	continue;
X      }
X
X      if (EVAL(dodir, TEST('f') || ! TEST('d')) &&
X	  (dp->status->st_mode & S_IFMT) == S_IFDIR) {
X
X	free(dp->status);
X	dp->status = (struct stat *) NIL;
X      
X	if (HEAD_dl(dlist))
X	  showdir = 1;
X	APPEND_dl(dlist, dp);
X      }
X      else {
X	suffix(dp);
X	number++;
X	if (width < dp->length)
X	  width = dp->length;
X
X	APPEND_dl(nlist, dp);
X      }
X    }
X
X    if (number)
X      showdir = 1;
X
X    HEAD_dl(nlist) = (DIRENTRY *) lsort((void *) HEAD_dl(nlist),
X					LINKOFFSET, compare);
X    list((DIRENTRY *) NIL, nlist, width, number);
X  }
X
X  if (! TEST('f'))
X    HEAD_dl(dlist) = (DIRENTRY *) lsort((void *) HEAD_dl(dlist),
X					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 kb;			/* kb 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, &kb);
X
X    if (EVAL(dototal, TEST('g') || TEST('o') || TEST('s')))
X      (void) printf("total %lu\n", kb);
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, "abdfgilmnopqrstx1ACFLR")) != 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 != (char *) 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) != (char *) 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((long *) 0);
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#ifdef	S_IFLNK
X/* File status */
X  if (TEST('L'))
X    status = stat;
X#endif
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, kb)
X
Xchar *dirname;				/* name of directory to scan */
Xint *width, *number;			/* width and number */
Xunsigned long *kb;			/* number of kb */
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  BOOL(dostat);				/* perform a stat */
X  BOOL(doblock);			/* check block sizes */
X
X  EMPTY_dl(nlist);
X  *width  = 0;
X  *number = 0;
X  *kb     = 0;
X
X  if ((dirp = opendir(dirname)) == (DIR *) NIL) {
X    (void) fprintf(stderr, "Cannot access %s: %s\n", pathname, strerror(errno));
X    return (DIRENTRY *) NIL;
X  }
X    
X  while ((cp = readdir(dirp)) != (struct dirent *) NIL) {
X    if (cp->d_name[0] != '.' || TEST('a') ||
X	cp->d_name[1] != 0   && (cp->d_name[1] != '.' || cp->d_name[2] != 0)
X	                     && TEST('A')) {
X
X      p = newentry(cp->d_name);
X
X      if (EVAL(dostat, TEST('t') || TEST('g') ||
X		       TEST('o') || TEST('s') ||
X		       TEST('F') || TEST('p') ||
X		       TEST('R') || TEST('i')) &&
X	  filestat(0, status, p->name,
X		   p->status =
X		     (struct stat *) safemalloc(sizeof(*p->status)))) {
X	freeentry(p);
X	continue;
X      }
X
X      suffix(p);
X
X      if (*width < p->length)
X        *width = p->length;
X      (*number)++;
X
X      if (EVAL(doblock, TEST('g') || TEST('o') || TEST('s')))
X	*kb += bytestokb(p->status);
X      
X      APPEND_dl(nlist, p);
X    }
X  }
X
X#ifndef		_MINIX
X  (void) closedir(dirp);
X#else
X  if (closedir(dirp)) {
X    (void) fprintf(stderr, "Cannot close directory: %s\n", strerror(errno));
X    exit(1);
X  }
X#endif
X
X  return TEST('f') ? HEAD_dl(nlist)
X                   : (DIRENTRY *) lsort((void *) HEAD_dl(nlist),
X					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 *) lsort((void *) HEAD_dl(rlist),
X					    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  int x;				/* inode calculation */
X  BOOL(dopretty);			/* pretty print */
X  unsigned long kb;			/* size in kb */
X
X  for (midline = colno = 0, p =HEAD_dl(lp); p; p = p->next) {
X
X/* Nominal length of the name */
X    tw = p->length;
X
X/* Pretty printing adds an extra character */
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/* Size will add to the length */
X    if (TEST('s')) {
X      kb = bytestokb(p->status);
X      do {
X	tw++;
X      } while ((kb /= 10) != 0);
X      tw++;
X    }
X
X/* Inode number adds to the length (plus the separating space) */
X    if (TEST('i')) {
X      x = p->status->st_ino;
X      tw++;
X      do
X	tw++;
X      while ((x /= 10) != 0);
X    }
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  int pad;				/* pad count */
X  BOOL(dolong);				/* long print */
X
X  if (TEST('i')) {
X    (void) printf(sizeof(p->status->st_ino) == sizeof(unsigned int)
X                  ? "%*u " : "%*lu ",
X		  w ? INODEWIDTH : 0,
X		  p->status->st_ino);
X  }
X
X  if (TEST('s'))
X    (void) printf("%*lu ", w ? BLOCKWIDTH : 0, bytestokb(p->status));
X
X  if (EVAL(dolong, TEST('o') || TEST('g')))
X    longprint(p->status);
X
X/* Print the name of this file */
X  printstring(p->name);
X  printstring(p->suffix);
X
X/* Only pad it if the caller requires it */
X  if (padout)
X    for (pad = w - p->length + INTERSPACE; pad-- > 0; (void) putchar(' '))
X      ;
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      (p->name[0] != '.' ||
X       p->name[1] != 0   && (p->name[1] != '.' || p->name[2] != 0))) {
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 = (struct passwd *) NIL; /* password entry */
X  static struct group *grent  = (struct group *)  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#ifdef		S_IFIFO
X  else if (filetype == S_IFIFO)  filecode = 'p';
X#endif
X#ifdef		S_IFLNK
X  else if (filetype == S_IFLNK)  filecode = 'l';
X#endif
X#ifdef		S_IFSOCK
X  else if (filetype == S_IFSOCK) filecode = 's';
X#endif
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)) != (struct passwd *) NIL))
X      (void) printf("%-8.8s ", pwent->pw_name);
X    else
X      (void) printf("%-8d ", 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)) != (struct group *) NIL))
X      (void) printf("%-9.9s ", grent->gr_name);
X    else
X      (void) printf("%-9d ", sp->st_gid);
X  }
X
X/* Now show how big the file is */
X  if (filetype != S_IFCHR && filetype != S_IFBLK)
X    (void) printf("%6ld ", sp->st_size);
X  else
X    (void) printf("%2d,%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 * kilobytes of storage used. This figure will include the
X * number of indirect blocks used to store the file.
X * The Minix code was lifted from the original Minix ls.
X */
X
Xunsigned long bytestokb(sp)
X
Xstruct stat *sp;			/* status buffer */
X
X{
X#ifdef		_BSD
X  return sp->st_blocks * STD_BLK / 1024;
X#endif
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 = (sp->st_size + (long) STD_BLK - 1)/STD_BLK;
X  blocks = fileb;
X  if (fileb > NR_DZONE_NUM) {
X
X/* Single indirect zones */
X    blocks++;
X    fileb -= NR_DZONE_NUM;
X    if (fileb > NR_INDIRECTS) {
X
X/* Doubly indirect zones */
X    blocks++;
X    fileb -= NR_INDIRECTS;
X    blocks += (fileb + NR_INDIRECTS - 1)/NR_INDIRECTS;
X    }
X  }
X  return blocks * STD_BLK / 1024;
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+1];		/* 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(*p));
X  (void) strcpy(p->name = (char *) safemalloc((unsigned int) strlen(name)+1),
X		name);
X  p->suffix   = (char *) NIL;
X  p->next     = (DIRENTRY *) NIL;
X  p->parent   = (DIRENTRY *) NIL;
X  p->status   = (struct stat *) 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->suffix && p->suffix[0] != 0 && p->suffix[1] != 0)
X      free(p->suffix);
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 * Append file name suffix. The suffix can be a simple file type
X * indicator or can be the full path name if it is a symbolic
X * link.
X */
X
Xvoid suffix(p)
X
XDIRENTRY *p;				/* directory entry */
X
X{
X  struct stat lsb;			/* link stat buffer */
X  char *type;				/* file type */
X  BOOL(showtype);			/* show file type */
X#ifdef		S_IFLNK
X  BOOL(dolink);				/* follow link */
X  char *link;				/* link to */
X#endif
X
X  p->length = 0;
X
X#ifdef		S_IFLNK
X  if (EVAL(dolink, TEST('o') || TEST('g')) &&
X      (p->status->st_mode & S_IFMT) == S_IFLNK) {
X    if (! filestat(1, stat, link = followlink(p->name), &lsb)) {
X      *p->status = lsb;
X      p->length += 4 + stringlength(link);
X      *(p->suffix = (char *) safemalloc((unsigned int) p->length+2)) = 0;
X      (void) strcat(p->suffix, " -> ");
X      (void) strcat(p->suffix, link);
X    }
X  }
X#endif
X
X  type = "";
X  if (EVAL(showtype, TEST('F') || TEST('p'))) {
X    if ((p->status->st_mode & S_IFMT) == S_IFDIR)    type = "/";
X    else if (TEST('F')) {
X      if ((p->status->st_mode & 0111))               type = "*";
X#ifdef		S_IFLNK
X      if ((p->status->st_mode & S_IFMT) == S_IFLNK)  type = "@";
X#endif
X#ifdef		S_IFSOCK
X      if ((p->status->st_mode & S_IFMT) == S_IFSOCK) type = "=";
X#endif
X    }
X  }
X
X  p->length += stringlength(p->name) + strlen(type);
X
X  if (p->suffix == NIL)
X    p->suffix = type;
X  else if (*type != 0)
X    (void) strcat(p->suffix, type);
X}
X
X/*
X * Follow a symbolic link and return the name of the file
X * to which it points. The function will return a pointer
X * to a static area.
X */
X
X#ifdef		S_IFLNK
Xchar *followlink(name)
X
Xchar *name;				/* name of link to follow */
X
X{
X  static char linkto[PATH_MAX+1];	/* link to name */
X  int c;				/* size of link name */
X
X  (void) strcpy(endpath, name);
X  if ((c = readlink(pathname, linkto, sizeof(linkto)-1)) < 0) {
X    (void) fprintf(stderr, "%s readlink failure: %s\n",
X		   pathname, strerror(errno));
X    exit(1);
X  }
X  linkto[c] = 0;
X  return linkto;
X}
X#endif
X
X/*
X * Get the status of a file prepending the path before calling
X * stat. The function pointer should indicate which status function
X * to call. Return 0 if successful, non-zero if the name cannot
X * be found.
X */
X
Xint filestat(T(int silent, silent),
X             T(int (*status)(char *, struct stat *), status),
X	     T(char *name, name),
X	     T(struct stat *statbuf, statbuf))
X
XD(int silent)				/* say no evil */
XD(int (*status)())			/* file status */
XD(char *name)				/* name of file */
XD(struct stat *statbuf)			/* stat buffer */
X
X{
X  if (*name != '/') {
X    (void) strcpy(endpath, name);
X    name = pathname;
X  }
X
X  if ((*status)(name, statbuf)
X#ifdef		S_IFLNK
X      && (status == lstat  ||
X	  errno  != ENOENT ||
X          lstat(name, statbuf))
X#endif
X     ) {
X    if (errno == ENOENT) {
X      if (! silent)
X	(void) fprintf(stderr, "%s not found\n", name);
X      return 1;
X    }
X    else {
X      (void) fprintf(stderr, "%s stat failure: %s\n", name, strerror(errno));
X      exit(1);
X    }
X  }
X  return *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 * Print a string converting invisible characters into visible sequences
X * if necessary.
X */
X
Xvoid printstring(s)
X
Xchar *s;				/* string to print */
X
X{
X  int c;				/* temporary */
X
X  for (; *s; ) {
X    if (isprint(c = *s++))
X      (void) putchar(c);
X    else if (TEST('s'))
X      (void) putchar('?');
X    else if (TEST('b'))
X      (void) printf("\\%03d", c & 0377);
X    else
X      (void) putchar(c);
X  }
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 = (void *) malloc(n)) == (void *) 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(T(void *head, head),
X            T(int offset, offset),
X	    T(int (*cmp)(void *, void *), cmp))
X
XD(void *head)				/* head of list */
XD(int offset)				/* byte offset to link */
XD(int (*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 36040 -ne `wc -c <'ls.c'`; then
    echo shar: \"'ls.c'\" unpacked with wrong size!
fi
# end of 'ls.c'
fi
echo shar: End of archive 2 \(of 2\).
cp /dev/null ark2isdone
MISSING=""
for I in 1 2 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked both 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
-- 
Earl Chew, Dept of Computer Science, Monash University, Australia 3168
ARPA: cechew%bruce.cs.monash.oz.au@uunet.uu.net  ACS : cechew@bruce.oz
----------------------------------------------------------------------