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
----------------------------------------------------------------------