home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1993 #1 / NN_1993_1.iso / spool / comp / os / minix / 5076 / ls.c.Z / ls.c
Encoding:
C/C++ Source or Header  |  1993-01-07  |  51.9 KB  |  1,897 lines

  1. /* ls - list directory            Author: C. E. Chew */
  2.  
  3. /*            List Directory Entries
  4.  *
  5.  * (C) Copyright C E Chew
  6.  *
  7.  * Feel free to copy and use this software provided:
  8.  *
  9.  *    1. you do not pretend that you wrote it
  10.  *    2. you leave this copyright notice intact.
  11.  *
  12.  * This is an implementation of a BSD style ls(1) for Minix. This
  13.  * implementation is faster than the original ls(1) for Minix. There
  14.  * are no restrictions with regard to the size of the directory file
  15.  * since memory is allocated dynamically.
  16.  */
  17.  
  18. #ifndef        PASS1        /* hack for small compilers */
  19. # ifndef    PASS2
  20. #   define    PASS1
  21. #   define    PASS2
  22. # endif
  23. #endif
  24.  
  25. #ifdef    PASS1
  26. char Version_[] = "@(#)els 3.14 06-Apr-1991 (C) C E Chew";
  27. #endif
  28.  
  29. /* Edit History:
  30.  * 14-Apr-1991    Incorporated mntent library source.
  31.  * 06-Apr-1991    Fix `ls -ld' problem.
  32.  * 30-Mar-1991    Fix uninitialised cw pointer in acrosspage().
  33.  * 12-Mar-1991    Strip out non-Minix conditional code into ls.h
  34.  * 08-Mar-1991    Need const qualifier in stringlength().
  35.  *        Fix for Mips gcc complaint about wrong printf format.
  36.  *        Don't initialise blanks[] at runtime.
  37.  * 06-Mar-1991    Fix date initialisation.
  38.  * 05-Mar-1991    Fix typos and avoid getcwd() and time() if possible.
  39.  * 03-Mar-1991    Add -0 option to reset options (to unset LSOPTS).
  40.  *        Add usage information.
  41.  * 22-Feb-1991    Add -S option to squeeze column widths.
  42.  * 21-Feb-1991    Use STDC locally to avoid __STDC__ brain damage.
  43.  *        Change order of dolsopts() and docmdname() scan.
  44.  * 20-Feb-1991    Some Coherent port problems.
  45.  * 15-Feb-1991    Coherent port included.
  46.  * 27-Dec-1990    Upgrade for Minix 2.0 filesystem.
  47.  * 30-Nov-1990    Fix nil dereference problem.
  48.  * 21-Nov-1990    Use lstat() on target of symlink so that multilevel
  49.  *        links are visible. Don't give up on failed readlink().
  50.  * 23-Jul-1990    POSIXfication.
  51.  * 10-May-1990    Miscellaneous patches.
  52.  * 23-Apr-1989    Reorder includes for 1.5.8. Support sticky and locking
  53.  *        bits. Support st_ctime and st_atime. Support for name
  54.  *        aliases.
  55.  * 20-Oct-1989    Change suffix for failed symbolic links.
  56.  * 21-Sep-1989    Changed names to _BSD and _MINIX. Use #ifdef for
  57.  *        portability.
  58.  * 19-Sep-1989    Sizes in kb rather than `blocks'.
  59.  * 14-Sep-1989    No longer need to define MINIX. Get rid of itoa().
  60.  *        Pyramid BSD coercions. Symbolic links and sockets.
  61.  * 27-Aug-1989    Added declaration of errno for old errno.h and
  62.  *        char *malloc() for old include files. Added
  63.  *        support for named FIFO's. Changed user and group
  64.  *        name format to %-6.6s so that long names won't
  65.  *        muck up columns. Later will have to add
  66.  *        symbolic link support for -l and -L.
  67.  * 16-May-1989    Option -n shouldn't look in /etc/passwd. Use new
  68.  *        strerror in string.h, add prototype for itoa().
  69.  * 30-Apr-1989    Changed stat failure message to not found. Include
  70.  *        dirent.h after stdio.h so that NULL is defined.
  71.  * 22-Mar-1989    Incompatible options processing, long sizes instead
  72.  *        of int and removed TURBOC conditional.
  73.  * 02-Mar-1989    Sort command line arguments.
  74.  * 22-Feb-1989    Fixed up bug regarding device number printing.
  75.  * 10-Sep-1988    Cleaned up for lint.
  76.  * 05-Sep-1988    Tidied up for network release.
  77.  */
  78.  
  79. #ifndef        _SYSV
  80. # ifndef    _BSD
  81. #   ifndef    COHERENT
  82. #     ifndef    _MINIX
  83. #       define     _MINIX
  84. #     endif
  85. #   endif
  86. # endif
  87. #endif
  88.  
  89. #define        TERMCAP        /* consult termcap for columns */
  90. /*efine        USESTDLIB*/    /* stdlib.h available for use */
  91. /*efine        USESTDDEF*/    /* stddef.h available for use */
  92.  
  93. #ifdef __STDC__            /* fix brain damage */
  94. # if __STDC__ != 0
  95. #   define STDC
  96. # endif
  97. #endif
  98. #ifdef    STDC
  99. # define CONST        const
  100. # define P(x)        x
  101. # define F4(t1,n1,t2,n2,t3,n3,t4,n4)    (t1 n1,t2 n2,t3 n3,t4 n4)
  102. # define F3(t1,n1,t2,n2,t3,n3)        (t1 n1,t2 n2,t3 n3)
  103. # define F2(t1,n1,t2,n2)        (t1 n1,t2 n2)
  104. # define F1(t1,n1)            (t1 n1)
  105. # define F0()                (void)
  106. #else
  107. # define CONST
  108. # define P(x)        ()
  109. # define F4(t1,n1,t2,n2,t3,n3,t4,n4)    (n1,n2,n3,n4)t1 n1;t2 n2;t3 n3;t4 n4;
  110. # define F3(t1,n1,t2,n2,t3,n3)        (n1,n2,n3)t1 n1;t2 n2;t3 n3;
  111. # define F2(t1,n1,t2,n2)        (n1,n2)t1 n1;t2 n2;
  112. # define F1(t1,n1)            (n1)t1 n1;
  113. # define F0()                ()
  114. #endif
  115.  
  116. #include <sys/types.h>
  117. #include <sys/stat.h>
  118.  
  119. #ifndef        _MINIX
  120. # include "ls.h"
  121. #else
  122. # ifndef    _POSIX_SOURCE
  123. #   define    _POSIX_SOURCE
  124. #   define    _POSIX_1_SOURCE 2
  125. # endif
  126. # include <stdlib.h>
  127. # define FREE_T void *
  128. # include <limits.h>
  129. # ifdef    PASS1
  130. #   include <ctype.h>
  131. #   include <grp.h>
  132. #   include <pwd.h>
  133. #   include <errno.h>
  134. #   include <dirent.h>
  135. #   include <string.h>
  136. #   include <time.h>
  137. #   include <stddef.h>
  138. #   define OFFSETOF(t,n)    offsetof(t,n)
  139. #   define USRGRPOTH(m)        (m)
  140. # endif
  141. # ifdef    PASS2
  142. #   include <limits.h>
  143. #   include <minix/config.h>
  144. #   include <minix/const.h>
  145. #   include <minix/type.h>
  146. #   include "../fs/const.h"
  147. #   include "../fs/type.h"
  148. #   undef printf
  149. #   define STD_BLK    BLOCK_SIZE
  150. #   define BYTESPERBLK    512
  151. #   ifndef V1_NR_DZONES
  152. #     define V1_NR_TZONES NR_ZONE_NUMS
  153. #     define V1_NR_DZONES NR_DZONE_NUM
  154. #     define V1_INDIRECTS NR_INDIRECTS
  155. #   else
  156. #     define USEMTAB
  157. #     ifdef USEMNTENTLIB
  158. #    include <mntent.h>
  159. #     else
  160. #    define MTAB        "/etc/mtab"
  161. struct mntent {
  162.   char *mnt_fsname;            /* device */
  163.   char *mnt_dir;            /* mount point */
  164.   char *mnt_type;            /* filesystem type */
  165.   char *mnt_opts;            /* options */
  166. };
  167. #   include <stdio.h>            /* we are trying to include this last
  168.                      * for Minix header braindamage
  169.                      * (multiple definitions of NULL, but
  170.                      * FILE * is needed now */
  171. #   undef NULL                /* fix the header braindamage */
  172. FILE *setmntent P((char *_file, char *));
  173. struct mntent *getmntent P((FILE *_mf));
  174. void endmntent P((FILE *_mf));
  175. #    define setmntent(n,t)    (fopen((n),(t)))
  176. #    define endmntent(f)    ((void) fclose(f))
  177. #     endif
  178. #   endif
  179. # endif
  180. # include <unistd.h>
  181. # include <stdio.h>
  182. #endif
  183.  
  184. #ifdef PASS2
  185. # ifdef    _POSIX_SOURCE
  186. #   undef BYTESPERBLK
  187. #   define BYTESPERBLK    512
  188. # endif
  189. #endif
  190.  
  191. /***********************************************************************\
  192.  *                            Common Definitions                       *
  193. \***********************************************************************/
  194.  
  195. #define NIL        (0)    /* nil pointer */
  196.  
  197. /* Mounted file system parameter table */
  198.  
  199. typedef struct filesystem {
  200.   struct filesystem *next;        /* link to next */
  201.   dev_t dev;                /* mounted device */
  202.   unsigned long *fs;            /* structure */
  203. } FILESYSTEM;
  204.  
  205. /***********************************************************************\
  206.  *                             Portable Code                           *
  207. \***********************************************************************/
  208. #ifdef        PASS1
  209.  
  210. #ifndef        SUPER_USER
  211. # define SUPER_USER    (0)
  212. #endif
  213.  
  214. #define DEFAULTDIR    "."    /* default for ls without args */
  215. #define ENVNAME        "LSOPTS"/* ls options */
  216. #define COLNAME        "COLUMNS"/* columns option */
  217. #define TERMNAME    "TERM"    /* name of terminal */
  218. #define LINKPOINTER    " -> "    /* symlink pointer indicator */
  219. #define COLUMNS        80    /* columns on terminal */
  220. #define INTERSPACE    2    /* spacing between columns */
  221. #define INODEWIDTH    5    /* width of field for inode */
  222. #define BLOCKWIDTH    6    /* width of field for blocks */
  223.  
  224. #define HALFYEAR    ((long) 60*60*24*365/2)    /* half year in seconds */
  225. #define BASEYEAR    1900    /* struct tm.tm_year base */
  226.  
  227. /* Flags are maintained in a bitmap. */
  228. #define BPC        CHAR_BIT/* bits per character */
  229. #define BITTEST(m,b)    (m[b/BPC] & (1<<(b%BPC)))
  230. #define BITSET(m,b)    (m[b/BPC] |= (1<<(b%BPC)))
  231. #define BITCLEAR(m,b)    (m[b/BPC] &= ~(1<<(b%BPC)))
  232. #define BITCHARS(b)    ((b+BPC-1)/BPC)
  233.  
  234. #define TEST(b)        BITTEST(flags,b)
  235. #define SET(b)        BITSET(flags,b)
  236. #define CLEAR(b)    BITCLEAR(flags,b)
  237. #define FLAGS        (1<<BPC)
  238.  
  239. /* These macros permit the shortens the short circuiting of
  240.  * complicated boolean expressions. This is particularly
  241.  * useful when working with the flags since these are
  242.  * read-only.
  243.  */
  244. #define BOOL(b)        static char b = 0
  245. #define EVAL(b,a)    ((b ? b : (b = (a)+1)) > 1)
  246.  
  247. /* A descriptor is kept for each file in the directory. The
  248.  * descriptors are linked together in a linked list.
  249.  */
  250. struct direntry {
  251.   struct direntry *next;    /* link list */
  252.   char *name;            /* name of entry */
  253.   char *suffix;            /* entry suffix */
  254.   int length;            /* length of name and suffix */
  255.   struct direntry *parent;    /* pointer to parent */
  256.   struct stat *status;        /* pointer to status */
  257. };
  258.  
  259. typedef struct direntry DIRENTRY;    /* entry */
  260. typedef struct {
  261.   DIRENTRY *head;        /* head of list */
  262.   DIRENTRY **tail;        /* insertion point at tail */
  263. } DIRLIST;            /* list of entries */
  264.  
  265. #define EMPTY_dl(d)    ((d).head=(DIRENTRY *)NIL,(d).tail=(&(d).head))
  266. #define APPEND_dl(d,p)    (*(d).tail=(p),(d).tail=(&(p)->next))
  267. #define TIE_dl(d,p)    APPEND_dl(d,p); \
  268.         while (*(d).tail) (d).tail=(&(*(d).tail)->next)
  269. #define HEAD_dl(d)    (d).head
  270. #define LINKOFFSET    (OFFSETOF(DIRENTRY, next))
  271.  
  272. /* Name statistics are required to support multi-column output.
  273.  * The statistics are used to compute the optimal number of
  274.  * columns required for output.
  275.  */
  276. typedef struct namestats {
  277.   int n;        /* number of entries */
  278.   int minwidth;        /* minimum width seen */
  279.   int maxwidth;        /* maximum width seen */
  280. } NAMESTATS;
  281.  
  282. #define INIT_NS(x)    ((x).n = 0, (x).minwidth = INT_MAX, (x).maxwidth = 0)
  283. #define MAX_NS(x,y)    ((x)>(y)?(x):(y))
  284. #define MIN_NS(x,y)    ((x)<(y)?(x):(y))
  285. #define UPDATE_NS(x,w)    ((x).n++, \
  286.              (x).minwidth = MIN_NS((x).minwidth, (w)), \
  287.                          (x).maxwidth = MAX_NS((x).maxwidth, (w)))
  288.  
  289. /* Function Pointers */
  290.  
  291. typedef int (*statfunc) P((const char *, struct stat *));
  292. typedef int (*cmpfunc) P((void *, void *));
  293. typedef int (*strlenfunc) P((CONST char *));
  294. typedef void (*putstrfunc) P((char *));
  295.  
  296. /* External Functions */
  297.  
  298. int getopt P((int, char **, char *));    /* parse the options */
  299.  
  300. #ifdef    TERMCAP
  301. int tgetent P((char *, char *));/* get entry from termcap database */
  302. int tgetnum P((char *));    /* get numeric capability */
  303. #endif
  304.  
  305. /* Symbolic Links. */
  306.  
  307. #ifdef        S_IFLNK
  308. # define OPAQUESTAT    ((statfunc) lstat)
  309. #else
  310. # define OPAQUESTAT    ((statfunc) stat)
  311. #endif
  312. #define TRANSPARENTSTAT    ((statfunc) stat)
  313.  
  314. /* Permission Classes */
  315.  
  316. #define    PERMCLASSES    3    /* Number of permission classes */
  317. #define    PERMUSR        2
  318. #define    PERMGRP        1
  319. #define    PERMOTH        0
  320.  
  321. #define    PERMBITS    3    /* Number of bits per class */
  322.  
  323. /* Permission Strings */
  324.  
  325. #define PERM_NORMAL    (0 * (1 << PERMBITS) * sizeof(permstrings[0]))
  326. #define PERM_SUID    (1 * (1 << PERMBITS) * sizeof(permstrings[0]))
  327. #define PERM_STICKY    (2 * (1 << PERMBITS) * sizeof(permstrings[0]))
  328. #ifdef    _SYSV
  329. # define PERM_LOCK    (3 * (1 << PERMBITS) * sizeof(permstrings[0]))
  330. #endif
  331.  
  332. static char permstrings[][PERMBITS + 1] = {    /* permission strings */
  333.           "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"
  334.          ,"--S", "--s", "-wS", "-ws", "r-S", "r-s", "rwS", "rws"
  335.          ,"--T", "--t", "-wT", "-wt", "r-T", "r-t", "rwT", "rwt"
  336. #ifdef    _SYSV
  337.          ,"--l", "--s", "-wl", "-ws", "r-l", "r-s", "rwl", "rws"
  338. #endif
  339. };
  340.  
  341. /* Symbolic link indicator. */
  342.  
  343. char linkpointer[] = LINKPOINTER;/* common link pointer string */
  344. #define ARROW        (linkpointer)
  345. #define ARROWLENGTH    (sizeof(ARROW)-1)
  346.  
  347. /* External Variables. */
  348.  
  349. extern int optind;        /* next argument */
  350. extern int errno;        /* error code */
  351. extern int sys_nerr;        /* errors */
  352. extern char *sys_errlist[];    /* errors by name */
  353.  
  354. /* Forward declarations. */
  355.  
  356. void acrosspage P((DIRENTRY *, DIRLIST *, NAMESTATS *));/* list across */
  357. void downpage P((DIRENTRY *, DIRLIST *, NAMESTATS *));    /* list down */
  358. void streamoutput P((DIRENTRY *, DIRLIST *));    /* stream output */
  359. void dolsopts P((void));    /* do environment variable */
  360. void docmdname P((char *));    /* do command name aliasing */
  361. void parse P((int, char **));    /* parse argument list */
  362. void setflag P((int));        /* set flag */
  363. void checkflag P((int, char *));/* check flag for incompatibilies */
  364. void init P((void));        /* initialise globals */
  365. void initpath P((DIRENTRY *));    /* initialise pathname */
  366. void initcols P((void));    /* initialise columns */
  367. int stringlength P((CONST char *));/* length of string with feeling */
  368. void printstring P((char *));    /* print string without feeling */
  369. void bprintstring P((char *));    /* print string with feeling */
  370. void qprintstring P((char *));    /* print string with feeling */
  371. unsigned long bytestoblk P((struct stat *));    /* convert bytes to blks */
  372. void error P((char *));        /* error message with prefix */
  373. FILESYSTEM *scanmtab P((void));    /* create file system database */
  374. void date P((time_t));        /* make date readable */
  375. char *makepath P((DIRENTRY *));    /* form the path */
  376. void longprint P((struct stat *));    /* long format */
  377. void printentry P((DIRENTRY *, DIRENTRY *, int, int));    /* print this entry */
  378. int optcols P((DIRLIST *, NAMESTATS *, DIRENTRY **, int **));
  379.                 /* optimise number of columns */
  380. DIRENTRY *newentry P((char *));    /* malloc a new entry */
  381. void freelist P((DIRLIST *));    /* free entries */
  382. void freeentry P((DIRENTRY *));    /* free an entry */
  383. int alphacmp P((DIRENTRY *, DIRENTRY *));/* alphabetic comparison */
  384. int mtimecmp P((DIRENTRY *, DIRENTRY *));/* compare by modification time */
  385. int ctimecmp P((DIRENTRY *, DIRENTRY *));/* compare by change time */
  386. int atimecmp P((DIRENTRY *, DIRENTRY *));/* compare by access time */
  387. void list P((DIRENTRY *, DIRLIST *, NAMESTATS *));/* file listing routine */
  388. void suffix P((DIRENTRY *, int));    /* do suffixes */
  389. int filestat P((int, statfunc, char *, struct stat *));    /* get status of file */
  390. void *safemalloc P((size_t));    /* malloc with feeling */
  391. DIRENTRY *makelist P((char *, NAMESTATS *,
  392.            unsigned long *));    /* create list of entries */
  393. void *lsort P((void *, int, cmpfunc));    /* linked list sort */
  394.  
  395. #ifdef    S_IFLNK
  396. char *followlink P((char *, int *));    /* follow symbolic link */
  397. #endif
  398.  
  399. /* Uninitialised Globals. */
  400.  
  401. DIRLIST rlist;            /* temporary recursive list */
  402. DIRLIST dlist;            /* list of directories */
  403. time_t today;            /* today's date */
  404. cmpfunc compare;        /* sort criteria */
  405. strlenfunc strlength;        /* (visible) string length */
  406. putstrfunc putstring;        /* (visible) string output */
  407. int columns;            /* number of columns */
  408. char *pathname;            /* current path name */
  409. FILESYSTEM *filesystems;    /* file system data base */
  410. FILESYSTEM *cfs;        /* current file system */
  411.  
  412. /* Initialised Globals. */
  413.  
  414. unsigned char flags[BITCHARS(FLAGS)] = { 0 }; /* switches */
  415. int exitstatus = EXIT_SUCCESS;                /* exit status */
  416. char cwd[2 * PATH_MAX + 1] = {0};             /* cwd and current pathname */
  417. int extrawidth = INTERSPACE;    /* implied extra space to add */
  418. char blanks[] =            /* blanks for quick padding */
  419. "                                                               ";
  420. char *incompatible[] = {    /* mutually exclusive flags */
  421.         "aA", "mx1Cglno", "bq", "pF", "cu", (char *) NIL
  422. };
  423. char optlist[] = "abcdfgilmnopqrstux01ACFLRS";/* list of options */
  424. char *summary[] = {        /* summary of options */
  425. "",
  426. "a  All files             A  All except . and ..   R  Recursive",
  427. "d  No directory scan     f  Force directory scan  L  Opaque symlink",
  428. "0  Reset options",
  429. "",
  430. "l  Long format           g  Print group only      o  Print owner only",
  431. "x  Multicolumn across    C  Multicolumn down      S  Squeeze columns",
  432. "1  Single column         m  Stream format",
  433. "",
  434. "t  Sort by time          r  Reverse sort",
  435. "c  Status change time    u  Last access time",
  436. "",
  437. "i  Print inode number    s  Print size in blocks  n  Numeric uid and gid",
  438. "p  Suffix /              F  Suffix / @ = | *",
  439. "",
  440. "b  Non-graphic \\ddd      q  Non-graphic ?",
  441. (char *) NIL
  442. };
  443.  
  444. int main F2(int, argc, char **, argv)
  445. {
  446.   DIRENTRY *dp;            /* directory scanner */
  447.   DIRLIST nlist;        /* list of files */
  448.   int showdir;            /* print directory name */
  449.   int notfirst;            /* printing first directory */
  450.   BOOL(dototal);        /* show totals */
  451.   BOOL(dostat);            /* need to do a stat */
  452.   BOOL(dodir);            /* check for directory */
  453.   int symlinked;        /* file a symbolic link */
  454.   char *absname;        /* absolute current path name */
  455.   NAMESTATS ns;            /* statistics */
  456.  
  457.   unsigned long blk;        /* blks in total */
  458. #ifdef    S_IFLNK
  459.   struct stat lsb;        /* stat of link */
  460. #endif
  461.  
  462. /* Initialise by emptying the directory lists */
  463.   EMPTY_dl(dlist), EMPTY_dl(nlist);
  464.   showdir = 0;
  465.   notfirst = 0;
  466.  
  467. /* Parse the command line */
  468.   dolsopts();
  469.   docmdname(argv[0]);
  470.   parse(argc, argv);
  471.   init();
  472.   if (TEST('C') || TEST('x') || TEST('m')) initcols();
  473.  
  474. /* Insert arguments into the current list */
  475.   INIT_NS(ns);
  476.   do {
  477.  
  478.     dp = newentry((optind >= argc) ? DEFAULTDIR : argv[optind]);
  479.     symlinked = 0;
  480.  
  481.     if (EVAL(dostat, (!TEST('f') && !TEST('d')) ||
  482.               TEST('t') ||  TEST('g') ||
  483.               TEST('o') ||  TEST('s') ||
  484.               TEST('F') ||  TEST('p') ||
  485.               TEST('R') ||  TEST('i'))) {
  486.         dp->status = (struct stat *)
  487.                  safemalloc(sizeof(*dp->status));
  488.         if (filestat(0, OPAQUESTAT, dp->name, dp->status)) {
  489.             freeentry(dp);
  490.             continue;
  491.         }
  492.     }
  493.     if (EVAL(dodir, TEST('f') || !TEST('d'))) {
  494.  
  495. #ifdef    S_IFLNK
  496.         if (!TEST('L') && S_ISLNK(dp->status->st_mode) &&
  497.             !filestat(1, TRANSPARENTSTAT, dp->name, &lsb) &&
  498.             S_ISDIR(lsb.st_mode)) {
  499.             symlinked = 1;
  500.             *dp->status = lsb;
  501.         }
  502. #endif
  503.  
  504.         if (S_ISDIR(dp->status->st_mode)) {
  505.             if (compare == (cmpfunc) alphacmp) {
  506.                 free((FREE_T) dp->status);
  507.                 dp->status = NIL;
  508.             }
  509.  
  510.             if (HEAD_dl(dlist)) showdir = 1;
  511.             APPEND_dl(dlist, dp);
  512.  
  513.             continue;
  514.         }
  515.     }
  516.     suffix(dp, symlinked);
  517.     UPDATE_NS(ns, dp->length);
  518.     APPEND_dl(nlist, dp);
  519.  
  520.   } while (++optind < argc);
  521.  
  522. /* List all the non-directory files */
  523.   if (ns.n) {
  524.     showdir = 1;
  525.     notfirst = 1;
  526.   }
  527.   HEAD_dl(nlist) = (DIRENTRY *) lsort((void *) HEAD_dl(nlist),
  528.                       LINKOFFSET, compare);
  529.   list((DIRENTRY *) NIL, &nlist, &ns);
  530.  
  531. /* List all directories */
  532.   if (!TEST('f'))
  533.     HEAD_dl(dlist) = (DIRENTRY *) lsort((void *) HEAD_dl(dlist),
  534.                         LINKOFFSET, compare);
  535.  
  536.   dp = HEAD_dl(dlist);
  537.   initpath(dp);
  538.   while (dp) {
  539.     freelist(&nlist);
  540.     absname = makepath(dp);
  541.  
  542.     if (showdir) {
  543.         if (notfirst) (void) putchar('\n');
  544.         (void) printf("%s:\n", pathname);
  545.     }
  546.     HEAD_dl(nlist) = makelist(absname, &ns, &blk);
  547.  
  548.     if (EVAL(dototal, TEST('g') || TEST('o') || TEST('s')))
  549.         (void) printf("total %lu\n", blk);
  550.  
  551.     list(dp, &nlist, &ns);
  552.  
  553.     notfirst = showdir = 1;
  554.     dp = dp->next;
  555.   }
  556.  
  557.   return exitstatus;
  558. }
  559.  
  560. /* Scan the options from the environment variable. This is
  561.  * done in a similar fashion to the command line option
  562.  * scan but no errors are flagged.
  563.  */
  564.  
  565. void dolsopts F0()
  566. {
  567.   register char *env;        /* environment options */
  568.   register int sw;        /* switch from environment */
  569.  
  570. /* Output to tty allows environment settable options */
  571.   if (isatty(fileno(stdout))) {
  572.     setflag('q');
  573.     if ((env = getenv(ENVNAME)) != NIL) {
  574.         while ((sw = *env) > 0 && sw < FLAGS) {
  575.             setflag(sw);
  576.             env++;
  577.         }
  578.     }
  579.   }
  580. }
  581.  
  582. /* Look at the command name. The following options are set
  583.  * according to the name:
  584.  *
  585.  *    Name        Option
  586.  *    ls        NONE
  587.  *    l        -m
  588.  *    ll        -l
  589.  *    lx        -x
  590.  *    lc        -C
  591.  *    lf        -F
  592.  *    lr        -R
  593.  */
  594.  
  595. void docmdname F1(char *, name)
  596. {
  597.   register char *bp;        /* point to basename */
  598.  
  599.   if ((bp = strrchr(name, '/')) == NIL)
  600.     bp = name;
  601.   else
  602.     bp++;
  603.  
  604.   if (bp[0] != 0) {
  605.     switch (bp[1]) {
  606.         case '\0':    setflag('m');    break;
  607.         case 'l':    setflag('l');    break;
  608.         case 'x':    setflag('x');    break;
  609.         case 'c':    setflag('C');    break;
  610.         case 'f':    setflag('F');    break;
  611.         case 'r':    setflag('R');    break;
  612.     }
  613.   }
  614. }
  615.  
  616. /* Determine the number of columns on the output terminal.
  617.  * The environment variable COLUMNS is preferred. If this
  618.  * is not set, and output is to a terminal, the termcap database
  619.  * is consulted. Failing this, a default assumed.
  620.  */
  621. void initcols F0()
  622. {
  623.   register char *env;        /* environment variable */
  624.   register int cols;        /* columns */
  625. #ifdef    TERMCAP
  626.   char tspace[1024];        /* termcap work space */
  627. #endif
  628.  
  629. /* Check environment first */
  630.   if ((env = getenv(COLNAME)) != NIL && (cols = atoi(env)) > 0)
  631.     columns = cols;
  632.  
  633. #ifdef    TERMCAP
  634. /* Try termcap library */
  635.   else if ((env = getenv(TERMNAME)) != NIL &&
  636.      tgetent(tspace, env) > 0 &&
  637.      (cols = tgetnum("co")) > 0)
  638.     columns = cols;
  639. #endif
  640.  
  641.   else
  642.     columns = COLUMNS;
  643. }
  644.  
  645. /* Parse the command line arguments. This function will set
  646.  * the switches in the global variable flags. No interpretation
  647.  * of the switches is performed at this point.
  648.  */
  649.  
  650. void parse F2(int, argc, char **, argv)
  651. {
  652.   register int swch;        /* switch character */
  653.   register int i;        /* index */
  654.  
  655.   while ((swch = getopt(argc, argv, optlist)) != EOF) {
  656.       if (swch == '0') {
  657.           for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++)
  658.               flags[i] = 0;
  659.       } else if (swch != '?')
  660.         setflag(swch);
  661.     else {
  662.         fprintf(stderr, "Usage: %s [-%s] [names]\n", argv[0], optlist);
  663.         for (i = 0; summary[i] != NIL; i++)
  664.             fprintf(stderr, "%s\n", summary[i]);
  665.         exit(EXIT_FAILURE);
  666.     }
  667.   }
  668. }
  669.  
  670. /* Set the specified option switch. This function knows about
  671.  * mutually exclusive options and will turn off options which
  672.  * are not compatible with the current one.
  673.  */
  674.  
  675. void setflag F1(int, ch)
  676. {
  677.   register char **p;        /* scanner */
  678.  
  679.   for (p = incompatible; *p != NIL; p++) checkflag(ch, *p);
  680.   SET(ch);
  681. }
  682.  
  683. /* Check the specified flag against the list of mutually exclusive
  684.  * flags. If the flag appears, then all other flags in the list are
  685.  * turned off. If not, then nothing is done.
  686.  */
  687.  
  688. void checkflag F2(int, ch, register char *, p)
  689. {
  690.   if (strchr(p, ch) != NIL) {
  691.     while (*p != 0) {
  692.         if (*p++ != ch) CLEAR(p[-1]);
  693.     }
  694.   }
  695. }
  696.  
  697. /* Scan the switches and initialise globals. This function will
  698.  * do some preliminary work on the switches to determine which
  699.  * globals will be needed and also which switches need to be
  700.  * set or cleared in addition to the current settings.
  701.  */
  702.  
  703. void init F0()
  704. {
  705. /* Turn on -A if we're the super user */
  706.   if (!TEST('a') && getuid() == SUPER_USER) SET('A');
  707.  
  708. /* Visible string length */
  709.   strlength = TEST('b') ? stringlength : (strlenfunc) strlen;
  710.  
  711. /* Visible string output */
  712.   putstring = TEST('q') ? qprintstring : TEST('b') ? bprintstring : printstring;
  713.  
  714. /* Force raw directory listing */
  715.   if (TEST('f')) {
  716.     CLEAR('l');
  717.     CLEAR('n');
  718.     CLEAR('o');
  719.     CLEAR('g');
  720.     CLEAR('t');
  721.     CLEAR('s');
  722.     CLEAR('r');
  723.     CLEAR('F');
  724.     CLEAR('p');
  725.     CLEAR('A');
  726.     SET('a');
  727.   }
  728.  
  729. /* Sort criterion */
  730.   compare = (cmpfunc) (TEST('t') ? TEST('u') ? atimecmp
  731.                                      : TEST('c') ? ctimecmp : mtimecmp
  732.                          : alphacmp);
  733.  
  734. /* Open the password and group files if required */
  735.   if (TEST('l') || TEST('n')) {
  736.     SET('o');
  737. /* Use -DAST to suppress groups on ls -l  (V7 style). */
  738. #ifndef AST
  739.     SET('g');
  740. #endif
  741.   }
  742.  
  743. /* Accumulate extra width if required */
  744.   if (TEST('i')) extrawidth += INODEWIDTH + 1;
  745.   if (TEST('s')) extrawidth += BLOCKWIDTH + 1;
  746.  
  747. /* Get today's date */
  748.   if (TEST('o') || TEST('g')) today = time((time_t *) 0);
  749.  
  750. /* Initialise file system data base */
  751.   if (TEST('g') || TEST('o') || TEST('s'))
  752.     cfs = filesystems = scanmtab();
  753. }
  754.  
  755. /* Form the name of the current directory. Before each directory
  756.  * is scanned, its absolute name is formed. The name of the
  757.  * current directory is prefixed to relative names to obtain
  758.  * this.
  759.  */
  760. void initpath F1(DIRENTRY *, dp)
  761. {
  762.   if (dp == NIL || (dp->next == NIL && !TEST('R')))
  763.     pathname = cwd;
  764.   else {
  765.     if (getcwd(cwd, sizeof(cwd)) == NIL) {
  766.           error("cannot locate cwd");
  767.           exit(EXIT_FAILURE);
  768.     }
  769.     pathname = strchr(cwd, 0);
  770.     pathname[0] = '/';
  771.     pathname[1] = 0;
  772.     pathname++;
  773.   }
  774. }
  775.  
  776. /* Make a linked list of entries using specified directory. The
  777.  * directory is rewound before being scanned. The function
  778.  * returns a pointer to the head of the list of entries. The
  779.  * function gathers two important statistics as the list
  780.  * is created. It will return the width required to print
  781.  * the files, and also the number of files in the list.
  782.  *
  783.  * The list will be sorted according to the current sorting
  784.  * criteria.
  785.  */
  786.  
  787. DIRENTRY *makelist F3(char *, dirname, NAMESTATS *, np, unsigned long *, blk)
  788. {
  789.   DIR *dirp;            /* directory to scan */
  790.   register struct dirent *cp;    /* current entry */
  791.   DIRLIST nlist;        /* files in directory */
  792.   register DIRENTRY *p;        /* new entry */
  793.   BOOL(dostat);            /* perform a stat */
  794.   BOOL(doblock);        /* check block sizes */
  795.  
  796.   EMPTY_dl(nlist);
  797.   INIT_NS(*np);
  798.   *blk = 0;
  799.  
  800.   if ((dirp = opendir(dirname)) == NIL || chdir(dirname)) {
  801.     error(dirname);
  802.     return NIL;
  803.   }
  804.   while ((cp = readdir(dirp)) != NIL) {
  805.     if (cp->d_name[0] != '.' || TEST('a') ||
  806.         cp->d_name[1] != 0 && (cp->d_name[1] != '.' ||
  807.         (cp->d_name[2] != 0) && TEST('A'))) {
  808.  
  809.         p = newentry(cp->d_name);
  810.  
  811.         if (EVAL(dostat, TEST('t') || TEST('g') ||
  812.                  TEST('o') || TEST('s') ||
  813.                  TEST('F') || TEST('p') ||
  814.                  TEST('R') || TEST('i'))) {
  815.             p->status = (struct stat *)
  816.                     safemalloc(sizeof(*p->status));
  817.             if (filestat(0, OPAQUESTAT, p->name, p->status)) {
  818.                 freeentry(p);
  819.                 continue;
  820.             }
  821.         }
  822.         suffix(p, 0);
  823.  
  824.         UPDATE_NS(*np, p->length);
  825.  
  826.         if (EVAL(doblock, TEST('g') || TEST('o') || TEST('s')))
  827.             *blk += bytestoblk(p->status);
  828.  
  829.         APPEND_dl(nlist, p);
  830.     }
  831.   }
  832.  
  833. #ifndef        _MINIX
  834.   (void) closedir(dirp);
  835. #else
  836.   if (closedir(dirp)) error(dirname);
  837. #endif
  838.  
  839.   return TEST('f') ? HEAD_dl(nlist)
  840.     : (DIRENTRY *) lsort((void *) HEAD_dl(nlist),
  841.                  LINKOFFSET, compare);
  842. }
  843.  
  844. /* This function performs does the formatting and output for ls(1).
  845.  * The function is passed a list of files (not necessarily sorted)
  846.  * to list. All files will be listed. . and .. removal should have
  847.  * been done BEFORE calling this function.
  848.  */
  849.  
  850. void list F3(DIRENTRY *, parent, DIRLIST *, lp, NAMESTATS *, np)
  851. {
  852.   if (np->n) {
  853.  
  854. /* Empty recursive directory list */
  855.     EMPTY_dl(rlist);
  856.  
  857. /* Select the correct output format */
  858.     if (TEST('m'))
  859.         streamoutput(parent, lp);
  860.     else if (!TEST('C'))
  861.         acrosspage(parent, lp, np);
  862.     else
  863.         downpage(parent, lp, np);
  864.  
  865.     (void) putchar('\n');
  866.  
  867. /* Check recursive list */
  868.     if (HEAD_dl(rlist)) {
  869.         if (!TEST('f'))
  870.             HEAD_dl(rlist) = (DIRENTRY *)
  871.                      lsort((void *) HEAD_dl(rlist),
  872.                            LINKOFFSET, compare);
  873.         TIE_dl(dlist, HEAD_dl(rlist));
  874.     }
  875.   }
  876. }
  877.  
  878. /* List the entries across the page. Single column output is
  879.  * treated as a special case of this. This is the easiest
  880.  * since the list of files can be scanned and dumped.
  881.  */
  882.  
  883. void acrosspage F3(DIRENTRY *, parent, DIRLIST *, lp, NAMESTATS *, np)
  884. {
  885.   register DIRENTRY *p;        /* entries to print */
  886.   int cols;            /* columns to print in */
  887.   int *cw;            /* column widths */
  888.   register int colno;        /* which column we're printing */
  889.  
  890.   if (!TEST('x')) {
  891.     cols = 1;
  892.     cw = NIL;
  893.   } else {
  894.     cols = optcols(lp, np, (DIRENTRY **) NIL, &cw);
  895.   }
  896.  
  897.   for (colno = 0, p = HEAD_dl(*lp); p; p = p->next) {
  898.     if (++colno > cols) {
  899.         colno = 1;
  900.         (void) putchar('\n');
  901.     }
  902.     printentry(parent, p, cw ? cw[colno-1] : np->maxwidth, colno != cols);
  903.   }
  904. }
  905.  
  906. /* Print the entries down the page. This is the default format
  907.  * for multicolumn listings. It's unfortunate that this is
  908.  * the most acceptable to the user, but it causes the program
  909.  * a little grief since the list of files is not in the
  910.  * most convenient order. Most of this code is taken up
  911.  * with rearranging the list to suit the output format.
  912.  */
  913.  
  914. void downpage F3(DIRENTRY *, parent, DIRLIST *, lp, NAMESTATS *, np)
  915. {
  916.   static DIRENTRY **c = NIL;    /* column pointers */
  917.   int *cw;            /* column width vector */
  918.   int cols;            /* columns to print in */
  919.   int rows;            /* number of rows per column */
  920.   register int i, j;        /* general counters */
  921.  
  922.   if (c == NIL) {
  923.     cols = (columns+INTERSPACE-1) / INTERSPACE;
  924.     c = (DIRENTRY **) safemalloc(sizeof(*c) * cols);
  925.   }
  926.  
  927.   cols = optcols(lp, np, c, &cw);
  928.   rows = (np->n + cols - 1) / cols;
  929.  
  930. /* Scan and print the listing */
  931.   for (i = 0; i < rows; i++) {
  932.     if (i) (void) putchar('\n');
  933.  
  934.     for (j = 0; j < cols; j++) {
  935.         if (c[j]) {
  936.             printentry(parent, c[j],
  937.                    cw ? cw[j] : np->maxwidth, j != cols - 1);
  938.             c[j] = c[j]->next;
  939.         }
  940.     }
  941.   }
  942. }
  943.  
  944. /* List the files using stream format. This code looks a bit
  945.  * kludgy because it is necessary to KNOW how wide the next
  946.  * entry is going to be. If the width would case the printout
  947.  * to exceed the width of the display, the entry must be printed
  948.  * on the next line.
  949.  */
  950.  
  951. void streamoutput F2(DIRENTRY *, parent, DIRLIST *, lp)
  952. {
  953.   register DIRENTRY *p;        /* entries to print */
  954.   int colno;            /* current column */
  955.   int midline;            /* in middle of line */
  956.   register int tw;        /* width of this entry */
  957.   int x;            /* inode calculation */
  958.   BOOL(dopretty);        /* pretty print */
  959.   unsigned long blk;        /* size in blks */
  960.  
  961.   for (midline = colno = 0, p = HEAD_dl(*lp); p; p = p->next) {
  962.  
  963. /* Nominal length of the name */
  964.     tw = p->length;
  965.  
  966. /* Pretty printing adds an extra character */
  967.     if (EVAL(dopretty, TEST('F') || TEST('p')) &&
  968.         (S_ISDIR(p->status->st_mode) ||
  969.          (TEST('F') && (
  970. #ifdef    S_IFLNK
  971.                S_ISLNK(p->status->st_mode) ||
  972. #endif
  973. #ifdef    S_IFIFO
  974.                S_ISFIFO(p->status->st_mode) ||
  975. #endif
  976. #ifdef    S_IFSOCK
  977.                S_ISSOCK(p->status->st_mode) ||
  978. #endif
  979.                (p->status->st_mode & 0111) != 0))))
  980.         tw++;
  981.  
  982. /* Size will add to the length */
  983.     if (TEST('s')) {
  984.         blk = bytestoblk(p->status);
  985.         do {
  986.             tw++;
  987.         } while ((blk /= 10) != 0);
  988.         tw++;
  989.     }
  990.  
  991. /* Inode number adds to the length (plus the separating space) */
  992.     if (TEST('i')) {
  993.         x = p->status->st_ino;
  994.         tw++;
  995.         do
  996.             tw++;
  997.         while ((x /= 10) != 0);
  998.     }
  999.  
  1000. /* There will be a separating comma */
  1001.     if (p->next) tw++;
  1002.  
  1003. /* There will be a separating space */
  1004.     if (midline) tw++;
  1005.  
  1006. /* Begin a new line if necessary in which case there is no separating space */
  1007.     if (colno + tw >= columns) {
  1008.         (void) putchar('\n');
  1009.         midline = 0;
  1010.         colno = 0;
  1011.         tw--;
  1012.     }
  1013.  
  1014. /* Separate entries if required */
  1015.     if (midline) (void) putchar(' ');
  1016.  
  1017. /* Now the entry proper */
  1018.     printentry(parent, p, 0, 0);
  1019.  
  1020. /* Now the separating comma */
  1021.     if (p->next) (void) putchar(',');
  1022.  
  1023.     midline = 1;
  1024.     colno += tw;
  1025.   }
  1026. }
  1027.  
  1028. /* Optimise the number of columns taken to print listing.
  1029.  * Attempt to squeeze as many columns across the page as
  1030.  * possible. Return a pointer to a width vector and also
  1031.  * the number of columns to print. If the -S option is
  1032.  * not in effect, the number of columns returned might
  1033.  * be overoptimistic.
  1034.  */
  1035.  
  1036. int optcols F4(DIRLIST *, lp, NAMESTATS *, np, DIRENTRY **, c, int **, cwp)
  1037. {
  1038.   static int *cw = NIL;        /* column widths */
  1039.   int ccw;            /* width of current column */
  1040.   int scw;            /* sum of column widths */
  1041.   int cols;            /* column count */
  1042.   int rows;            /* row count */
  1043.   register DIRENTRY *p;        /* list scanner */
  1044.   register int i, j;
  1045.  
  1046.   if (! TEST('S')) {
  1047.  
  1048. /* Nominal number of columns */
  1049.     if ((cols = columns / (np->maxwidth + extrawidth)) == 0)
  1050.         cols = 1;
  1051.     *cwp = NIL;
  1052.  
  1053.   } else {
  1054.  
  1055. /* Allocate space for column widths */
  1056.     if (cw == NIL) {
  1057.         cols = (columns+INTERSPACE-1) / INTERSPACE;
  1058.         cw = (int *) safemalloc(sizeof(*cw) * cols);
  1059.     }
  1060.  
  1061. /* Allocate maximum number of columns */
  1062.     if ((cols = columns / (np->minwidth + extrawidth)) == 0)
  1063.         cols = 1;
  1064.     *cwp = cw;
  1065.   }
  1066.  
  1067.   if (cols > np->n) cols = np->n;
  1068.  
  1069. /* Distribute the names and compute the column widths */
  1070.   if (c) {
  1071.     while (1) {
  1072.         rows = (np->n + cols - 1) / cols;
  1073.         cols = (np->n + rows - 1) / rows;
  1074.  
  1075.         scw = cols * extrawidth;
  1076.         for (i = 0, p = HEAD_dl(*lp); p; i++) {
  1077.             c[i] = p;
  1078.             ccw = p->length;
  1079.             for (j = rows; j-- && p; p = p->next)
  1080.                 if (ccw < p->length) ccw = p->length;
  1081.             if ((scw += ccw) > columns)
  1082.                 if (cols > 1) break;
  1083.             if (cw) cw[i] = ccw;
  1084.         }
  1085.         if (cols <= 1 || scw <= columns) break;
  1086.         cols--;
  1087.     }
  1088.   } else if (TEST('S')) {
  1089.     while (1) {
  1090.         for (i = cols; i--; )
  1091.             cw[i] = 0;
  1092.         scw = cols * extrawidth;
  1093.         for (i = 0, p = HEAD_dl(*lp); p; p = p->next) {
  1094.             if (cw[i] < p->length) {
  1095.                 scw -= cw[i];
  1096.                 cw[i] = p->length;
  1097.                 if ((scw += cw[i]) > columns)
  1098.                     if (cols > 1) break;
  1099.             }
  1100.             if (++i == cols) i = 0;
  1101.         }
  1102.         if (cols <= 1 || scw <= columns) break;
  1103.         cols--;
  1104.     }
  1105.   }
  1106.  
  1107.   return cols;
  1108. }
  1109.  
  1110. /* Print this entry on stdout. No newline is appended to the
  1111.  * output. This function localises all the transformations
  1112.  * which have to be carried out to make the output readable.
  1113.  * Columnar magic is done elsewhere.
  1114.  */
  1115.  
  1116. void printentry F4(DIRENTRY *, parent,
  1117.         register DIRENTRY *, p,
  1118.         int, w,
  1119.         register int, padout)
  1120. {
  1121.   int pad;            /* pad count */
  1122.   BOOL(dolong);            /* long print */
  1123.   DIRENTRY *ep;            /* new entry for recursion */
  1124.  
  1125.   if (TEST('i')) {
  1126.     (void) ( sizeof(p->status->st_ino) == sizeof(unsigned int)
  1127.            ? printf("%*u ",  w ? INODEWIDTH : 0,
  1128.                      (unsigned int)  p->status->st_ino)
  1129.            : printf("%*lu ", w ? INODEWIDTH : 0,
  1130.                      (unsigned long) p->status->st_ino));
  1131.   }
  1132.   if (TEST('s'))
  1133.     (void) printf("%*lu ", w ? BLOCKWIDTH : 0, bytestoblk(p->status));
  1134.  
  1135.   if (EVAL(dolong, TEST('o') || TEST('g'))) longprint(p->status);
  1136.  
  1137. /* Print the name of this file */
  1138.   (*putstring)(p->name);
  1139.   (*putstring)(p->suffix);
  1140.  
  1141. /* Only pad it if the caller requires it */
  1142.   if (padout && (pad = w - p->length + INTERSPACE) > 0) {
  1143.     do {
  1144.         padout = pad;
  1145.         if (padout > sizeof(blanks)-1) padout = sizeof(blanks-1);
  1146.         (void) fputs(&blanks[(sizeof(blanks)-1)-padout], stdout);
  1147.     } while ((pad -= padout) != 0);
  1148.   }
  1149.  
  1150. /* If recursion is required, add to list if it's a directory */
  1151.   if (TEST('R') && S_ISDIR(p->status->st_mode) &&
  1152.       (p->name[0] != '.' ||
  1153.        (p->name[1] != 0 && (p->name[1] != '.') || p->name[2] != 0))) {
  1154.     ep = newentry(p->name);
  1155.     ep->parent = parent;
  1156.     APPEND_dl(rlist, ep);
  1157.   }
  1158. }
  1159.  
  1160. /* Format and print out the information for a long listing.
  1161.  * This function handles the conversion of the mode bits
  1162.  * owner, etc. It assumes that the status has been obtained.
  1163.  */
  1164.  
  1165. void longprint F1(register struct stat *, sp)
  1166. {
  1167.   register unsigned int permbits;    /* file access permissions */
  1168.   char filecode;        /* code for type of file */
  1169.   static struct passwd *pwent = NIL;    /* password entry */
  1170.   static struct group *grent = NIL;    /* group entry */
  1171.   char *perm[PERMCLASSES];    /* permissions ogu */
  1172.  
  1173.   if (S_ISREG(sp->st_mode))       filecode = '-';
  1174.   else if (S_ISDIR(sp->st_mode))  filecode = 'd';
  1175.   else if (S_ISBLK(sp->st_mode))  filecode = 'b';
  1176.   else if (S_ISCHR(sp->st_mode))  filecode = 'c';
  1177. #ifdef        S_IFIFO
  1178.   else if (S_ISFIFO(sp->st_mode)) filecode = 'p';
  1179. #endif
  1180. #ifdef        S_IFLNK
  1181.   else if (S_ISLNK(sp->st_mode))  filecode = 'l';
  1182. #endif
  1183. #ifdef        S_IFSOCK
  1184.   else if (S_ISSOCK(sp->st_mode)) filecode = 's';
  1185. #endif
  1186.   else                            filecode = '?';
  1187.  
  1188.   permbits = USRGRPOTH(sp->st_mode);
  1189.  
  1190.   perm[PERMUSR] = permstrings[(permbits >> 6) & 7];
  1191.   perm[PERMGRP] = permstrings[(permbits >> 3) & 7];
  1192.   perm[PERMOTH] = permstrings[(permbits)      & 7];
  1193.  
  1194.   if (sp->st_mode & S_ISUID) perm[PERMUSR] += PERM_SUID;
  1195.   if (sp->st_mode & S_ISGID)
  1196. #ifdef    _SYSV
  1197.     perm[PERMGRP] += PERM_LOCK;
  1198. #else                /* _SYSV */
  1199.     perm[PERMGRP] += PERM_SUID;
  1200. #endif
  1201. #ifdef    S_ISVTX
  1202.   if (sp->st_mode & S_ISVTX) perm[PERMOTH] += PERM_STICKY;
  1203. #endif
  1204.  
  1205.   (void) printf("%c%s%s%s%c %2d ",
  1206.           filecode,
  1207.           perm[PERMUSR], perm[PERMGRP], perm[PERMOTH],
  1208.           ' ', sp->st_nlink);
  1209.  
  1210.   if (TEST('o')) {
  1211.     if (!TEST('n') && ((pwent && pwent->pw_uid == sp->st_uid) ||
  1212.                (pwent = getpwuid(sp->st_uid)) != NIL))
  1213.         (void) printf("%-8.8s\t", pwent->pw_name);
  1214.     else
  1215.         (void) printf("%-8d\t", (int) sp->st_uid);
  1216.   }
  1217.   if (TEST('g')) {
  1218.     if (!TEST('n') && ((grent && grent->gr_gid == sp->st_gid) ||
  1219.                (grent = getgrgid(sp->st_gid)) != NIL))
  1220.         (void) printf("%-8.8s\t", grent->gr_name);
  1221.     else
  1222.         (void) printf("%-8d\t", (int) sp->st_gid);
  1223.   }
  1224.  
  1225. /* Now show how big the file is */
  1226.   if (!S_ISCHR(sp->st_mode) && !S_ISBLK(sp->st_mode))
  1227.     (void) printf("%7lu ", (unsigned long) sp->st_size);
  1228.   else
  1229.     (void) printf(" %2d,%3d ", (int) (sp->st_rdev >> 8) & 0377,
  1230.               (int) sp->st_rdev & 0377);
  1231.  
  1232. /* Now the date */
  1233.   date(TEST('u') ? sp->st_atime : TEST('c') ? sp->st_ctime : sp->st_mtime);
  1234. }
  1235.  
  1236. /* Given the time convert it into human readable form. The month and
  1237.  * day are always printed. If the time is within about the last half year,
  1238.  * the hour and minute are printed, otherwise the year.
  1239.  */
  1240.  
  1241. void date F1(time_t, t)
  1242. {
  1243.   struct tm *tmbuf;        /* output time */
  1244.  
  1245.   tmbuf = localtime(&t);
  1246.   (void) printf("%.3s %2u ",
  1247.      "JanFebMarAprMayJunJulAugSepOctNovDec" + tmbuf->tm_mon * 3,
  1248.           (unsigned) tmbuf->tm_mday);
  1249.   if (t <= today && (today - t) <= HALFYEAR)
  1250.     (void) printf("%02u:%02u ", (unsigned) tmbuf->tm_hour,
  1251.               (unsigned) tmbuf->tm_min);
  1252.   else
  1253.     (void) printf("%5d ", tmbuf->tm_year + BASEYEAR);
  1254. }
  1255.  
  1256. /* Chase the parent pointers and make a path. This path is
  1257.  * used to locate the current directory. The function returns
  1258.  * the absolute pathname to the caller. The pathname relative
  1259.  * to the directory specified by the user is available from
  1260.  * the global variable pathname.
  1261.  */
  1262.  
  1263. char *makepath F1(DIRENTRY *, p)
  1264. {
  1265.   char tmppath[PATH_MAX + 1];    /* build it here */
  1266.   register char *cp;        /* pointer to tmppath */
  1267.   register char *cq;        /* pointer into name */
  1268.   char *cr;            /* temporary pointer */
  1269.   char absolute;        /* / for absolute file names */
  1270.  
  1271. /* Work your way back up to the root */
  1272.   for (cp = tmppath, *cp++ = 0, absolute = 0; p; p = p->parent) {
  1273.     for (cq = p->name, absolute = *cq; *cq; *cp++ = *cq++);
  1274.     *cp++ = 0;
  1275.   }
  1276.  
  1277. /* Now flip the order */
  1278.   for (cq = pathname, --cp; ; cp = cr) {
  1279.     while (*--cp);
  1280.     for (cr = cp++; *cp; *cq++ = *cp++);
  1281.     if (cr == tmppath) break;
  1282.     if (cq[-1] != '/') *cq++ = '/';
  1283.   }
  1284.   *cq = 0;
  1285.   return absolute == '/' ? pathname : cwd;
  1286. }
  1287.  
  1288. /* Allocate memory for a new entry. Memory is allocated and
  1289.  * filled in. The next, parent and status pointers are set
  1290.  * to null. The function returns a pointer to the new entry.
  1291.  */
  1292.  
  1293. DIRENTRY *newentry F1(char *, name)
  1294. {
  1295.   register DIRENTRY *p;        /* pointer to entry */
  1296.  
  1297.   p = (DIRENTRY *) safemalloc(sizeof(*p));
  1298.   p->name = (char *) safemalloc(strlen(name) + 1);
  1299.   (void) strcpy(p->name, name);
  1300.   p->suffix = NIL;
  1301.   p->next = NIL;
  1302.   p->parent = NIL;
  1303.   p->status = NIL;
  1304.   return p;
  1305. }
  1306.  
  1307. /* Free the memory associated with list of elements. The function
  1308.  * assumes all memory has been allocated using malloc(), so that
  1309.  * free() will work without suffering a heart attack. The list
  1310.  * header is set to null before returning.
  1311.  */
  1312.  
  1313. void freelist F1(DIRLIST *, lp)
  1314. {
  1315.   register DIRENTRY *ep, *nep;
  1316.  
  1317.   for (ep = HEAD_dl(*lp); ep; ep = nep) {
  1318.     nep = ep->next;
  1319.     freeentry(ep);
  1320.   }
  1321.   EMPTY_dl(*lp);
  1322. }
  1323.  
  1324. /* Free the memory associated with a directory entry. Remember that
  1325.  * all the memory should have been allocated using malloc().
  1326.  */
  1327.  
  1328. void freeentry F1(register DIRENTRY *, p)
  1329. {
  1330.   if (p) {
  1331.     if (p->name) free((FREE_T) p->name);
  1332.     if (p->suffix && p->suffix[0] != 0 && p->suffix[1] != 0)
  1333.         free((FREE_T) p->suffix);
  1334.     if (p->status) free((FREE_T) p->status);
  1335.     free((FREE_T) p);
  1336.   }
  1337. }
  1338.  
  1339. /* Compare entries in the file list. Pointers to two entries are
  1340.  * expected as arguments (non null pointers). Compare the entries
  1341.  * and return -1, 0 or 1 depending on whether the first argument
  1342.  * is less than, equal to or greater than the second.
  1343.  */
  1344.  
  1345. int alphacmp F2(DIRENTRY *, p, DIRENTRY *, q)
  1346. {
  1347.   register int rv = strcmp(p->name, q->name);
  1348.  
  1349.   return TEST('r') ? -rv : rv;
  1350. }
  1351.  
  1352. /* Compare entries on the basis of access time. Pointers to
  1353.  * two entries are expected as arguments. It is assumed that the status
  1354.  * has been obtained. The routine will return -1, 0 or 1 depending
  1355.  * on whether the first argument is later than, equal to or earlier
  1356.  * than the second.
  1357.  */
  1358.  
  1359. int atimecmp F2(DIRENTRY *, p, DIRENTRY *, q)
  1360. {
  1361.   register int rv;        /* comparison result */
  1362.   long delta = p->status->st_atime - q->status->st_atime;
  1363.  
  1364.   rv = delta > 0 ? -1 : delta ? 1 : 0;
  1365.  
  1366.   return TEST('r') ? -rv : rv;
  1367. }
  1368.  
  1369. /* Compare entries on the basis of status change time. Pointers to
  1370.  * two entries are expected as arguments. It is assumed that the status
  1371.  * has been obtained. The routine will return -1, 0 or 1 depending
  1372.  * on whether the first argument is later than, equal to or earlier
  1373.  * than the second.
  1374.  */
  1375.  
  1376. int ctimecmp F2(DIRENTRY *, p, DIRENTRY *, q)
  1377. {
  1378.   register int rv;        /* comparison result */
  1379.   long delta = p->status->st_ctime - q->status->st_ctime;
  1380.  
  1381.   rv = delta > 0 ? -1 : delta ? 1 : 0;
  1382.  
  1383.   return TEST('r') ? -rv : rv;
  1384. }
  1385.  
  1386. /* Compare entries on the basis of modification time. Pointers to
  1387.  * two entries are expected as arguments. It is assumed that the status
  1388.  * has been obtained. The routine will return -1, 0 or 1 depending
  1389.  * on whether the first argument is later than, equal to or earlier
  1390.  * than the second.
  1391.  */
  1392.  
  1393. int mtimecmp F2(DIRENTRY *, p, DIRENTRY *, q)
  1394. {
  1395.   register int rv;        /* comparison result */
  1396.   long delta = p->status->st_mtime - q->status->st_mtime;
  1397.  
  1398.   rv = delta > 0 ? -1 : delta ? 1 : 0;
  1399.  
  1400.   return TEST('r') ? -rv : rv;
  1401. }
  1402.  
  1403. /* Append file name suffix. The suffix can be a simple file type
  1404.  * indicator or can be the full path name if it is a symbolic
  1405.  * link. If knownlink is nonzero, the entry is assumed to be a symbolic
  1406.  * link. In this case, no further stat is performed and the entry
  1407.  * in p->status is assumed to be that of the real file (not the link).
  1408.  */
  1409.  
  1410. void suffix F2(register DIRENTRY *, p, int, knownlink)
  1411. {
  1412.   char *type;            /* file type */
  1413.   BOOL(showtype);        /* show file type */
  1414. #ifdef        S_IFLNK
  1415.   BOOL(dolink);            /* follow link */
  1416.   char *link;            /* link to */
  1417.   int ltextlen;            /* length of link text */
  1418.   struct stat lsb;        /* link stat buffer */
  1419. #endif
  1420.  
  1421.   p->length = (*strlength)(p->name);
  1422.  
  1423. #ifdef        S_IFLNK
  1424.   link = NIL;
  1425.  
  1426.   if (EVAL(dolink, TEST('o') || TEST('g')) &&
  1427.       (knownlink || S_ISLNK(p->status->st_mode))) {
  1428.  
  1429.     if ((link = followlink(p->name, <extlen)) != NIL) {
  1430.         p->length += ARROWLENGTH + (*strlength)(link);
  1431.         p->suffix = (char *) safemalloc(ltextlen + ARROWLENGTH + 2);
  1432.  
  1433.         if (!knownlink && !TEST('L') &&
  1434.         !filestat(1, OPAQUESTAT, link, &lsb)) {
  1435.         knownlink = 1;
  1436.         *p->status = lsb;
  1437.         }
  1438.  
  1439.         if (knownlink) {
  1440.         (void) strcpy(p->suffix, ARROW);
  1441.         (void) strcpy(p->suffix+ARROWLENGTH, link);
  1442.         }
  1443.     }
  1444.   }
  1445. #endif
  1446.  
  1447.   type = "";
  1448.   if (EVAL(showtype, TEST('F') || TEST('p'))) {
  1449.     if (S_ISDIR(p->status->st_mode))
  1450.         type = "/";
  1451.     else if (TEST('F')) {
  1452.         if (p->status->st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) type = "*";
  1453. #ifdef        S_IFIFO
  1454.         if (S_ISFIFO(p->status->st_mode)) type = "|";
  1455. #endif
  1456. #ifdef        S_IFLNK
  1457.         if (S_ISLNK(p->status->st_mode)) type = "@";
  1458. #endif
  1459. #ifdef        S_IFSOCK
  1460.         if (S_ISSOCK(p->status->st_mode)) type = "=";
  1461. #endif
  1462.     }
  1463.   }
  1464.   p->length += strlen(type);
  1465.  
  1466. #ifdef    S_IFLNK
  1467.   if (link == NIL)
  1468.     p->suffix = type;
  1469.   else if (knownlink)
  1470.     strcpy(p->suffix+ARROWLENGTH+ltextlen, type);
  1471.   else {
  1472.     (void) strcpy(p->suffix, type);
  1473.     (void) strcat(p->suffix+1, ARROW);
  1474.     (void) strcat(p->suffix+1+ARROWLENGTH, link);
  1475.   }
  1476. #else
  1477.   p->suffix = type;
  1478. #endif
  1479. }
  1480.  
  1481. /* Follow a symbolic link and return the name of the file
  1482.  * to which it points. The function will return a pointer
  1483.  * to a static area.
  1484.  */
  1485.  
  1486. #ifdef        S_IFLNK
  1487. char *followlink F2(char *, name, register int *, len)
  1488. {
  1489.   static char linkto[PATH_MAX + 1];    /* link to name */
  1490.  
  1491.   if ((*len = readlink(name, linkto, sizeof(linkto) - 1)) < 0) {
  1492.     error(name);
  1493.     return NIL;
  1494.   }
  1495.   linkto[*len] = 0;
  1496.   return linkto;
  1497. }
  1498.  
  1499. #endif
  1500.  
  1501. /* Get the status of a file prepending the path before calling
  1502.  * stat. The function pointer should indicate which status function
  1503.  * to call. Return 0 if successful, non-zero if the name cannot
  1504.  * be found.
  1505.  */
  1506.  
  1507. int filestat F4(int, silent, statfunc, status, char *, name,
  1508.      struct stat *, statbuf)
  1509. {
  1510.   if ((*status) (name, statbuf)) {
  1511.     if (!silent) {
  1512.         if (errno == ENOENT)
  1513.             (void) fprintf(stderr, "ls: %s not found\n", name);
  1514.         else
  1515.             error(name);
  1516.     }
  1517.     return 1;
  1518.   }
  1519.   return 0;
  1520. }
  1521.  
  1522. /* Compute the length of the string taking into account the
  1523.  * form of output. String lengths will increase if the
  1524.  * visible output flag has been specified.
  1525.  */
  1526.  
  1527. int stringlength F1(register CONST char *, s)
  1528. {
  1529.   register int i;        /* length */
  1530.  
  1531.   for (i = 0; *s != 0; s++)
  1532.     if (TEST('b') && !isprint(*s))
  1533.         i += 4;
  1534.     else
  1535.         i++;
  1536.   return i;
  1537. }
  1538.  
  1539. /* Print a string without any conversion.
  1540.  */
  1541.  
  1542. void printstring F1(char *, s)
  1543. {
  1544.   (void) fputs(s, stdout);
  1545. }
  1546.  
  1547. /* Print a string converting invisible characters into question marks.
  1548.  */
  1549.  
  1550. void qprintstring F1(register char *, s)
  1551. {
  1552.   for (; *s; s++) {
  1553.     if (isprint(*s))
  1554.         (void) putchar(*s);
  1555.     else
  1556.         (void) putchar('?');
  1557.   }
  1558. }
  1559.  
  1560. /* Print a string converting invisible characters into visible sequences.
  1561.  */
  1562.  
  1563. void bprintstring F1(register char *, s)
  1564. {
  1565.   for (; *s; s++) {
  1566.     if (isprint(*s))
  1567.         (void) putchar(*s);
  1568.     else
  1569.         (void) printf("\\%03d", *s & ((1 << CHAR_BIT) - 1));
  1570.   }
  1571. }
  1572.  
  1573. /* This function does a malloc() with feeling. If malloc() returns
  1574.  * null, indicating no more memory, the function will fail with
  1575.  * an error message. The function will return a pointer to the
  1576.  * allocated memory.
  1577.  */
  1578.  
  1579. void *safemalloc F1(size_t, n)
  1580. {
  1581.   register void *p;        /* malloc'ed region */
  1582.  
  1583.   if ((p = (void *) malloc(n)) == NIL) {
  1584.     (void) fputs("ls: insufficient memory\n", stderr);
  1585.     exit(EXIT_FAILURE);
  1586.   }
  1587.   return p;
  1588. }
  1589.  
  1590. /* Wrapper for perror to prefix error codes with program name.
  1591.  */
  1592.  
  1593. void error F1(char *, e)
  1594. {
  1595.   int err;            /* saved error code */
  1596.  
  1597.   err = errno;
  1598.   (void) fprintf(stderr, "ls: %s: ", e);
  1599.   errno = err;
  1600.   perror("");
  1601. }
  1602.  
  1603. /* This is a two way merge sort, but non-recursive and using a binary
  1604.  * comb to combine the sublists. The problem with the straightforward
  1605.  * two way merge is that switching the output from one bin to another
  1606.  * is time consuming. In this approach, bin[i] contains either zero,
  1607.  * or (1<<i) sorted elements (except for the last bin which can hold
  1608.  * any number of elements). When a bin overflows (it will always double
  1609.  * in size), it is emptied and merged with the next bin (hence the
  1610.  * doubling effect). This cascading is forced to stop at the last bin.
  1611.  * When all elements have been placed in bins, all bins are merged
  1612.  * yielding a sorted list. If the number of bins is too small, the
  1613.  * sort collapses, in the limit, into an insertion sort.
  1614.  *
  1615.  * The first is a pointer to the first element of the list.
  1616.  * The second argument is the byte offset from the element pointer to
  1617.  * find the pointer to the next element in the list. The third
  1618.  * argument is a pointer to a comparison function that returns -ve, zero
  1619.  * and +ve respectively if the first element is less than, equal to
  1620.  * or greater that the second element.
  1621.  */
  1622.  
  1623. #define NEXT(p)        (* (void **) ((char *) p + offset))
  1624.  
  1625. void *lsort F3(void *, head, int, offset, cmpfunc, cmp)
  1626. {
  1627.   register void *rp, *rq;        /* merge pointers */
  1628.   register void **lp;            /* merge insertion pointer */
  1629.   register void **bp;            /* list header scanner */
  1630.   void **ep;                /* last header bin */
  1631.   void *p;                /* next element */
  1632.   void *q;                /* current element */
  1633.   void *bin[9+1];            /* list headers */
  1634.  
  1635.   ep = &bin[sizeof(bin)/sizeof(bin[0])-1];
  1636.   for (bp = &bin[0]; bp <= ep; bp++)
  1637.     *bp = 0;
  1638.  
  1639.   for (p = head; p != 0; *bp = p, p = q) {
  1640.     q = NEXT(p);
  1641.     NEXT(p) = 0;
  1642.  
  1643.     for (bp = &bin[0]; ;bp++) {
  1644.           if ((rp = *bp) != 0) {
  1645.             rq  = p;
  1646.             lp  = &p;
  1647.             *bp = 0;
  1648.  
  1649.             for (;;) {
  1650.               if ((*cmp)(rp, rq) < 0) {
  1651.             *lp = rp;
  1652.             lp = &NEXT(rp);
  1653.             if ((rp = NEXT(rp)) == 0) {
  1654.               *lp = rq;
  1655.               break;
  1656.             }
  1657.               } else {
  1658.             *lp = rq;
  1659.             lp = &NEXT(rq);
  1660.             if ((rq = NEXT(rq)) == 0) {
  1661.               *lp = rp;
  1662.               break;
  1663.             }
  1664.               }
  1665.             }
  1666.           } else if (q != 0 || bp == ep)
  1667.             break;
  1668.     }
  1669.     if (bp == ep) bp--;
  1670.   }
  1671.   return ep[-1];
  1672. }
  1673.  
  1674. /***********************************************************************\
  1675.  *                             Portable Code                           *
  1676. \***********************************************************************/
  1677. #endif
  1678.  
  1679. /***********************************************************************\
  1680.  *                            Unportable Code                          *
  1681. \***********************************************************************/
  1682. #ifdef    PASS2
  1683. /* Convert the size of a file (in bytes) to the number of
  1684.  * kilobytes of storage used. This figure will include the
  1685.  * number of indirect blocks used to store the file.
  1686.  * The Minix code was lifted from the original Minix ls.
  1687.  */
  1688.  
  1689. #ifdef        _MINIX
  1690. static unsigned long v1_zone_group[] = {
  1691.   (unsigned long) V1_NR_DZONES,
  1692.   (unsigned long) V1_INDIRECTS,
  1693.   (unsigned long) 0};
  1694. #if V1_NR_TZONES - V1_NR_DZONES != 2
  1695.   << Wrong number of version 1 indirects >>
  1696. #endif
  1697.  
  1698. #ifdef V2_NR_DZONES
  1699. static unsigned long v2_zone_group[] = {
  1700.   (unsigned long) V2_NR_DZONES,
  1701.   (unsigned long) V2_INDIRECTS,
  1702.   (unsigned long) V2_INDIRECTS*V2_INDIRECTS,
  1703.   (unsigned long) 0};
  1704. #if V2_NR_TZONES - V2_NR_DZONES != 3
  1705.   << Wrong number of version 2 indirects >>
  1706. #endif
  1707. #endif
  1708.  
  1709. static unsigned long *filestructure[] = {    /* file structure */
  1710.   v1_zone_group,            /* default version 1 */
  1711.   v1_zone_group,            /* version 1 */
  1712. #ifdef V2_NR_DZONES
  1713.   v2_zone_group,            /* version 2 */
  1714. #endif
  1715. };
  1716. #endif
  1717.  
  1718. unsigned long bytestoblk F1(struct stat *, sp)
  1719. {
  1720. #ifdef        _BSD
  1721.   return (sp->st_blocks * STD_BLK + BYTESPERBLK - 1) / BYTESPERBLK;
  1722. #endif
  1723.  
  1724. #ifdef        _SYSV
  1725.   return (sp->st_blocks * STD_BLK + BYTESPERBLK - 1) / BYTESPERBLK;
  1726. #endif
  1727.  
  1728. #ifdef        _MINIX
  1729.   unsigned long blocks;        /* accumulated blocks */
  1730.   unsigned long fileb;        /* size of file in blocks */
  1731.   unsigned int filetype;    /* type of file */
  1732.   unsigned long *fsp;        /* structure of filesystem for this file */
  1733.   int i, j;            /* zone table scanner */
  1734.  
  1735. #ifndef        USEMTAB
  1736.   fsp = filestructure[0];
  1737. #else
  1738.   static dev_t baddev = NO_DEV;    /* previous bad file system */
  1739.   extern FILESYSTEM *cfs;    /* current file system */
  1740.   extern FILESYSTEM *filesystems; /* list of file systems */
  1741.  
  1742. /* Locate the file system */
  1743.   if (cfs->dev == sp->st_dev)
  1744.     fsp = cfs->fs;
  1745.   else if (sp->st_dev == baddev)
  1746.     fsp = filestructure[0];
  1747.   else {
  1748.     for (cfs = filesystems; cfs != NIL && cfs->dev != sp->st_dev; )
  1749.         cfs = cfs->next;
  1750.     if (cfs != NIL)
  1751.         fsp = cfs->fs;
  1752.     else {
  1753.         (void) fprintf(stderr, "ls: device %d/%d not in /etc/mtab\n",
  1754.                        (sp->st_dev >> MAJOR) & BYTE,
  1755.                        (sp->st_dev >> MINOR) & BYTE);
  1756.         baddev = sp->st_dev;
  1757.         cfs = filesystems;
  1758.         fsp = filestructure[0];
  1759.     }
  1760.   }
  1761. #endif
  1762.  
  1763. /* Compute raw file size */
  1764.   fileb = ((unsigned long) sp->st_size + STD_BLK - 1) / STD_BLK;
  1765.   blocks = fileb;
  1766.   filetype = sp->st_mode;
  1767.  
  1768. /* Compute indirect block overhead */
  1769.   if (fileb > fsp[0] && !S_ISBLK(filetype) && !S_ISCHR(filetype)) {
  1770.     fileb -= fsp[0];
  1771.     for (i = 1; fileb > fsp[i] && fsp[i] != 0; i++) {
  1772.         blocks += (fsp[i] - 1)/(fsp[1] - 1);
  1773.         fileb -= fsp[i];
  1774.     }
  1775.     blocks++;
  1776.     for (j = 1; j < i; j++)
  1777.         blocks += (fileb + fsp[j] - 1)/fsp[j];
  1778.   }
  1779.   return (blocks * STD_BLK + BYTESPERBLK - 1) / BYTESPERBLK;
  1780. #endif
  1781. }
  1782.  
  1783. /* Build a table of mounted files systems. For Minix, each mounted file
  1784.  * system is characterised by the number of zone numbers, the number of
  1785.  * directs and the number of indirects within the inodes. Return a
  1786.  * pointer to a list of file system entries. If /etc/mtab is empty or
  1787.  * doesn't exist, a dummy list is returned.
  1788.  */
  1789.  
  1790. FILESYSTEM *scanmtab F0()
  1791. {
  1792. #ifdef        _BSD
  1793.   return NIL;
  1794. #endif
  1795.  
  1796. #ifdef        _SYSV
  1797.   return NIL;
  1798. #endif
  1799.  
  1800. #ifdef        _MINIX
  1801. #ifndef        USEMTAB
  1802.   return NIL;
  1803. #else
  1804.   struct stat sb;            /* stat buffer */
  1805.   FILESYSTEM *fs;            /* list of file systems */
  1806.   FILESYSTEM *fsp;            /* pointer to file system entry */
  1807.   int version;                /* file system version */
  1808.   struct mntent *mp;            /* mounted file system */
  1809.   FILE *mf;                /* mtab scanner */
  1810.   static FILESYSTEM dummyfs = {NIL, NO_DEV, NIL};
  1811.  
  1812.   if ((mf = setmntent(MTAB, "r")) == NIL) {
  1813.     (void) fputs("ls: cannot access /etc/mtab\n", stderr);
  1814.     return &dummyfs;
  1815.   }
  1816.  
  1817.   fs = NIL;
  1818.   while ((mp = getmntent(mf)) != NIL) {
  1819.     if (stat(mp->mnt_fsname, &sb) < 0) {
  1820.         (void) fprintf(stderr,
  1821.                        "ls: cannot get status of %s\n", mp->mnt_fsname);
  1822.         continue;
  1823.     }
  1824.     if (S_ISBLK(sb.st_mode) == 0) {
  1825.         (void) fprintf(stderr,
  1826.                        "ls: %s not a block device\n", mp->mnt_fsname);
  1827.         exit(EXIT_FAILURE);
  1828.     }
  1829.  
  1830.     version = atoi(mp->mnt_type);
  1831.     if (version < 1 ||
  1832.         version > sizeof(filestructure)/sizeof(filestructure[0])-1) {
  1833.         (void) fprintf(stderr,
  1834.                        "%s has bad filesystem version\n", mp->mnt_fsname);
  1835.         exit(EXIT_FAILURE);
  1836.     }
  1837.     fsp = (FILESYSTEM *) safemalloc(sizeof(*fsp));
  1838.     fsp->dev = sb.st_rdev;
  1839.     fsp->next = fs;
  1840.     fsp->fs = filestructure[version];
  1841.     fs = fsp;
  1842.   }
  1843.   endmntent(mf);
  1844.   return fs == NIL ? &dummyfs : fs;
  1845. #endif
  1846. #endif
  1847. }
  1848.  
  1849. /****************************************************\
  1850.  *                 mntent library                    *
  1851.  *                                                   *
  1852.  * Most Minix systems won't have the mntent library, *
  1853.  * so the code is included here. This library is     *
  1854.  * preferred to the mtab library because the data    *
  1855.  * structures are allocated dynamically and the      *
  1856.  * data space can be re-used later by ls.            *
  1857. \****************************************************/
  1858. #ifdef _MINIX
  1859. #ifndef USEMNTENTLIB
  1860.  
  1861. #include <string.h>
  1862.  
  1863. /* Read the next entry from the mtab file. The entry is parsed and returned
  1864.  * as a struct mntent. A static structure is returned. Return NULL on end
  1865.  * of file or error.
  1866.  */
  1867. struct mntent *getmntent(mf)
  1868. FILE *mf;
  1869. {
  1870.   int c;
  1871.   char *p;
  1872.   static char line[128];        /* local line buffer */
  1873.   static struct mntent mt;        /* local mtab entry */
  1874.   static char mde[] = " ";        /* delimiter */
  1875.  
  1876.   while (fgets(line, sizeof line, mf) != NIL) {
  1877.     p = strchr(line, 0);
  1878.     if (p[-1] == '\n') {
  1879.         *--p = 0;
  1880.     } else {
  1881.         while ((c = getc(mf)) != 'n' && c != EOF)
  1882.             continue;
  1883.     }
  1884.     if (line[0] == '#') continue;
  1885.     if ((mt.mnt_fsname = strtok(line, mde)) != NIL &&
  1886.         (mt.mnt_dir    = strtok((char *) NIL, mde)) != NIL &&
  1887.         (mt.mnt_type   = strtok((char *) NIL, mde)) != NIL &&
  1888.         (mt.mnt_opts   = strtok((char *) NIL, mde)) != NIL)
  1889.         return &mt;
  1890.     break;
  1891.   }
  1892.   return NIL;
  1893. }
  1894. #endif
  1895. #endif
  1896. #endif
  1897.