home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume18 / sls / sls.c < prev   
Encoding:
C/C++ Source or Header  |  1989-03-26  |  33.2 KB  |  1,323 lines

  1. /*
  2.  * "Super" ls program.  Allows complete specification of output contents:
  3.  * field order, width, format, and sort order.
  4.  *
  5.  * Options:
  6.  *   -a        show all files (includes '.' files)
  7.  *   -d        show directories in arg list as files
  8.  *   -l        long listing (uses $SLS_LONGFMT if set, or ls style)
  9.  *   -L        follow symbolic links (show stat of pointed-to file)
  10.  *   -p "ostr"    output key string
  11.  *   -R        recursively list subdirectories
  12.  *   -s "sstr"    sort key string
  13.  *   -u        use ls-style date formatting (<6 mos. vs. >6 mos.)
  14.  *
  15.  * Uses environment vbl SLS_DATEFMT as default format string for dates, if
  16.  * supplied.
  17.  */
  18.  
  19. #include <stdio.h>
  20. #include <string.h>
  21. #include <ctype.h>
  22. #include <pwd.h>
  23. #include <grp.h>
  24. #include <time.h>
  25. #include <sys/types.h>
  26. #include <sys/stat.h>
  27. #include <sys/dir.h>
  28.  
  29. #define ISEXEC(m)    (m & (S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6)))
  30. #define MAXUIDS        200    /* max #of user names from passwd file */
  31. #define MAXGIDS        100    /* max #of group names from group file */
  32.  
  33.     /* defines for sort/print option strings (preceded by a '%'); keys can
  34.      * have field widths; leading '-' indicates left-justify (string fields)
  35.      * or reverse order if it is a sort key; leading '0' indicates zero-fill
  36.      * (numeric fields) */
  37. #define F_ADATE        'a'    /* access date (optional fmtdate string) */
  38. #define F_BLOCKS    'b'    /* #allocated (512-byte) blocks (+ m,k,c|b) */
  39. #define F_CDATE        'c'    /* inode change date */
  40. #define F_DEV        'd'    /* device number inode resides on */
  41. #define F_GROUPA    'g'    /* ascii group name (from group file) */
  42. #define F_GROUPN    'G'    /* numeric group id */
  43. #define F_INODE        'i'    /* inode number */
  44. #define F_BLKSIZE    'k'    /* optimal file system block size */
  45. #define F_NLINKS    'l'    /* number of hard links */
  46. #define F_MDATE        'm'    /* modify date (optional fmtdate string) */
  47. #define F_NAME        'n'    /* file name (+ b=basename,a=ascii,s=suffix) */
  48. #define F_NAMEL        'N'    /* file name with symbolic links shown */
  49. #define F_PERMA        'p'    /* ascii permissions */
  50. #define F_PERMO        'P'    /* octal permissions */
  51. #define F_RDEV        'r'    /* device number file resides on */
  52. #define F_SIZE        's'    /* file size in bytes (mods: m,k,c|b) */
  53. #define F_TYPE        't'    /* file type (coded like 'ls') */
  54. #define F_USERA        'u'    /* ascii user name (from passwd file) */
  55. #define F_USERN        'U'    /* numeric user id */
  56.  
  57. typedef struct {        /* for saving name only */
  58.     char        *f_name;
  59.     } FILESTAT;
  60.  
  61. typedef struct {        /* for saving name and stat info */
  62.     char        *f_name;
  63.     struct stat  f_stat;
  64.     } SFILESTAT;
  65.  
  66. FILESTAT *Pfiles;    /* list of files (no stat buf) to sort/print */
  67. int       Npfiles;    /* number of files in Pfiles */
  68.  
  69. SFILESTAT *Psfiles;    /* list of files to sort/print */
  70. int       Npsfiles;    /* number of files in Psfiles */
  71.  
  72.     /* global options */
  73. int    Follow_links;    /* follow symbolic links */
  74. int    Recursive;    /* follow subdirectories */
  75. int    Stat_dirs;    /* show directories as files */
  76. int    All_files;    /* include '.' files in listing */
  77. int    Use_less;    /* use ls-style date format */
  78. int    Dostat;        /* =0 if stat(2) system call not needed */
  79. time_t Sixmosago;    /* system date for 6 mos. ago (for default dates) */
  80. char  *Defdatefmt;    /* default format for dates */
  81. char  *Sortopt;
  82. char  *Printopt;
  83.  
  84. static char  *fmtdate(), *aperms(), *getfmtstr(), *showname(), *copystr();
  85. static char  *getpwname(), *getgrname();
  86. static int    sortsfile(), sortfile();
  87. static time_t getsixmosago();        /* for emulating 'ls' date silliness */
  88.  
  89. extern char  *getenv(), *malloc(), *realloc();
  90. extern int    errno;
  91. extern time_t time();
  92.  
  93. /******************************************************************************/
  94.  
  95. main (ac, av)
  96.     int            ac;
  97.     char          *av[];
  98. {
  99.     register char *pav, *ptmp, *longfmt;
  100.     register int   i, basename, gotfilearg;
  101.  
  102.     gotfilearg = 0;
  103.     Sortopt = "%n";
  104.     Printopt = "%n";
  105.     Defdatefmt = "%h %d 19%y %H:%M";
  106.     if ((pav = getenv("SLS_DATEFMT")) != NULL && strlen(pav) > 0)
  107.     Defdatefmt = copystr (pav);
  108.     longfmt = "%t%p %2l %-u %s %m %n";
  109.     if ((pav = getenv("SLS_LONGFMT")) != NULL && strlen(pav) > 0)
  110.     longfmt = copystr (pav);
  111.     Use_less = Follow_links = Recursive = Stat_dirs = All_files = 0;
  112.     Dostat = 0;
  113.     Sixmosago = getsixmosago ();    /* in case Use_less==1 */
  114.  
  115.     while (--ac > 0)
  116.     {
  117.     pav = *++av;
  118.     if (*pav == '-')
  119.     {
  120.         while (*++pav) switch (*pav)
  121.         {
  122.         case 'a':
  123.         All_files = 1;
  124.         break;
  125.         case 'd':
  126.         Stat_dirs = 1;
  127.         Dostat = 1;
  128.         break;
  129.         case 'l':
  130.         Printopt = longfmt;
  131.         if (strcmp(Printopt,"%n") != 0 && strcmp(Printopt,"%nb") != 0)
  132.             Dostat = 1;
  133.         break;
  134.         case 'L':
  135.         Follow_links = 1;
  136.         Dostat = 1;
  137.         break;
  138.         case 'p':
  139.         if (--ac == 0)
  140.         {
  141.             fprintf (stderr, "? -p requires an argument\n");
  142.             exit (1);
  143.         }
  144.         Printopt = *++av;
  145.         pav = "-";    /* so while() quits */
  146.         if (strcmp(Printopt,"%n") != 0 && strcmp(Printopt,"%nb") != 0)
  147.             Dostat = 1;
  148.         break;
  149.         case 'R':
  150.         Recursive = 1;
  151.         Dostat = 1;
  152.         break;
  153.         case 's':
  154.         if (--ac == 0)
  155.         {
  156.             fprintf (stderr, "? -s requires an argument\n");
  157.             exit (1);
  158.         }
  159.         Sortopt = *++av;
  160.         pav = "-";    /* so while() quits */
  161.         Dostat = 1;
  162.         break;
  163.         case 'u':        /* the "use-l(es)s format" option */
  164.         Use_less = 1;
  165.         break;
  166.         default:
  167.         fprintf (stderr, "?unknown option '%s'\n", pav);
  168.         exit (1);
  169.         }
  170.     }
  171.     else
  172.     {
  173.         dofile (pav, 1);
  174.         ++gotfilearg;
  175.     }
  176.     }
  177.  
  178.     if (!gotfilearg)
  179.     dofile ("", 1);
  180.  
  181.     if (!Dostat)
  182.     {
  183.     qsort ((char *)Pfiles, Npfiles, sizeof(*Pfiles), sortfile);
  184.     basename = (strcmp(Printopt,"%nb") == 0);
  185.     for (i=0; i<Npfiles; ++i)
  186.     {
  187.         if (basename)
  188.         {
  189.         ptmp = (Pfiles+i)->f_name;
  190.         if ((pav = strrchr (ptmp, '/')) == NULL)
  191.             pav = ptmp;
  192.         else
  193.             ++pav;
  194.         puts (pav);
  195.         }
  196.         else
  197.         puts ((Pfiles+i)->f_name);
  198.     }
  199.     exit (0);
  200.     }
  201.  
  202.     if (strlen(Sortopt) > 0)
  203.     qsort ((char *)Psfiles, Npsfiles, sizeof(*Psfiles), sortsfile);
  204.  
  205.     for (i=0; i<Npsfiles; ++i)
  206.     display (Psfiles+i);
  207.     
  208.     exit (0);
  209.  
  210. }  /* main */
  211.  
  212. /******************************************************************************/
  213.  
  214. dofile (fname, cmdarg)
  215.     register char  *fname;    /* name of file to stat and list */
  216.     int             cmdarg;    /* =1 if fname was command line arg */
  217. {  /* check if file should be displayed (read contents if a directory) */
  218.     struct stat     sbuf;
  219.     register char  *pbase;    /* ptr to basename part of fname */
  220.  
  221.     if ((pbase = strrchr (fname, '/')) == (char *)NULL)
  222.     pbase = fname;
  223.     else
  224.     ++pbase;
  225.  
  226.     /* skip files that start with '.' unless -a */
  227.     if (*pbase == '.' && !All_files && !cmdarg)
  228.     return;
  229.     
  230.     if (!Dostat && !cmdarg)
  231.     {
  232.     selectf (fname);    /* short version - file name only */
  233.     return;
  234.     }
  235.  
  236.     if ((!Follow_links && lstat (fname, &sbuf) != 0)
  237.     || (Follow_links && stat (fname, &sbuf) != 0))
  238.     {
  239.     fprintf (stderr, "?unable to stat file '%s' (errno=%d)\n",
  240.         fname, errno);
  241.     return;
  242.     }
  243.  
  244.     if ((sbuf.st_mode&S_IFMT) == S_IFDIR)    /* it's a directory */
  245.     {
  246.     if (!cmdarg
  247.         && (strcmp (pbase, ".") == 0 || strcmp (pbase, "..") == 0))
  248.     {  /* don't show these unless -a given */
  249.         if (All_files)
  250.         selectfs (fname, &sbuf);
  251.         return;
  252.     }
  253.  
  254.     if (Stat_dirs || (!cmdarg && !Recursive))
  255.         selectfs (fname, &sbuf);
  256.     else
  257.         dirread (fname);
  258.     return;
  259.     }
  260.  
  261.     if (Dostat)
  262.     selectfs (fname, &sbuf);
  263.     else
  264.     selectf (fname);
  265.  
  266. }  /* dofile */
  267.  
  268. /******************************************************************************/
  269.  
  270. dirread (dirname)
  271.     char           *dirname;
  272. {  /* read a directory and everything under it that's on the same device */
  273.     register DIR   *dirp;        /* ptr to directory list */
  274.     register struct direct  *dentp;    /* ptr to directory entry */
  275.     register char  *pfname;
  276.     register int    len;
  277.     char            fname[512];        /* maximum pathname length */
  278.  
  279.     /* open and read a directory */
  280.     if ((dirp = opendir(dirname)) == NULL)
  281.     {
  282.     fprintf (stderr, "?unable to open directory '%s' (errno=%d)\n",
  283.         dirname, errno);
  284.     return;
  285.     }
  286.     strcpy (fname, dirname);
  287.     len = strlen(fname);
  288.     pfname = &fname[len];        /* point to start of filename part */
  289.     if (len > 0 && *(pfname-1) != '/')
  290.     {  /* doesn't end with slash, so add it */
  291.     strcat (fname, "/");
  292.     ++pfname;
  293.     ++len;
  294.     }
  295.  
  296.     /* loop through directory entries */
  297.     for (dentp = readdir(dirp); dentp != NULL; dentp = readdir(dirp))
  298.     {
  299.     if (len + dentp->d_namlen >= sizeof(fname))
  300.     {
  301.         *pfname = '\0';    /* so we don't see previous filename part */
  302.         fprintf (stderr, "?file name too long: '%s%s'\n",
  303.         fname, dentp->d_name);
  304.         continue;
  305.     }
  306.         /* add this file name to the directory path */
  307.     strcpy (pfname, dentp->d_name);
  308.     dofile (fname, 0);
  309.     }
  310.  
  311.     closedir (dirp);
  312.  
  313. }  /* dirread */
  314.  
  315. /******************************************************************************/
  316.  
  317. selectf (fname)
  318.     register char *fname;
  319. {  /* allocate space for this file name for sorting */
  320.     FILESTAT     *pf;
  321.     static int     maxpfiles=0;
  322.     unsigned int   size;
  323.  
  324.     /* check if need more space for FILESTAT ptrs */
  325.     if (Npfiles == maxpfiles)
  326.     {
  327.     maxpfiles += 500;
  328.     size = maxpfiles * sizeof(FILESTAT);
  329.     if (Pfiles == (FILESTAT *) NULL)
  330.         Pfiles = (FILESTAT *) malloc (size);
  331.     else
  332.         Pfiles = (FILESTAT *) realloc ((char *)Pfiles, size);
  333.     if (Pfiles == (FILESTAT *) NULL)
  334.     {
  335.         fprintf (stderr, "?malloc failed (errno=%d) on stat of '%s'\n",
  336.         errno, fname);
  337.         exit (1);
  338.     }
  339.     }
  340.  
  341.     pf = Pfiles + Npfiles;
  342.     pf->f_name = copystr (fname);
  343.     ++Npfiles;
  344.  
  345. }  /* selectf */
  346.  
  347. /******************************************************************************/
  348.  
  349. static int sortfile (pf1, pf2)
  350.     register FILESTAT     *pf1, *pf2;
  351. {  /* qsort comparison routine */
  352.  
  353.     return (strcmp (pf1->f_name, pf2->f_name));
  354.  
  355. }  /* sortfile */
  356.  
  357. /******************************************************************************/
  358.  
  359. selectfs (fname, psbuf)
  360.     register char *fname;
  361.     register struct stat  *psbuf;
  362. {  /* allocate a struct for this file for sorting */
  363.     SFILESTAT      *pf;
  364.     static int     maxpfiles=0;
  365.     unsigned int   size;
  366.  
  367.     /* check if need more space for SFILESTAT ptrs */
  368.     if (Npsfiles == maxpfiles)
  369.     {
  370.     maxpfiles += 100;
  371.     size = maxpfiles * sizeof(SFILESTAT);
  372.     if (Psfiles == (SFILESTAT *) NULL)
  373.         Psfiles = (SFILESTAT *) malloc (size);
  374.     else
  375.         Psfiles = (SFILESTAT *) realloc ((char *)Psfiles, size);
  376.     if (Psfiles == (SFILESTAT *) NULL)
  377.     {
  378.         fprintf (stderr, "?malloc failed (errno=%d) on stat of '%s'\n",
  379.         errno, fname);
  380.         exit (1);
  381.     }
  382.     }
  383.  
  384.     pf = Psfiles + Npsfiles;
  385.     pf->f_name = copystr (fname);
  386.     pf->f_stat = *psbuf;    /* copies entire struct */
  387.     ++Npsfiles;
  388.  
  389. }  /* selectfs */
  390.  
  391. /******************************************************************************/
  392.  
  393. static int sortsfile (pf1, pf2)
  394.     register SFILESTAT     *pf1, *pf2;
  395. {  /* qsort comparison routine */
  396.     register char         *ps, *p1, *p2, *ptmp;
  397.     register struct stat  *psbuf1, *psbuf2;
  398.     long                   l1, l2;
  399.     int                    i, reverse, ascii, basen;
  400.     char                   tbuf1[200], tbuf2[200];
  401.  
  402.     psbuf1 = &(pf1->f_stat);
  403.     psbuf2 = &(pf2->f_stat);
  404.     for (ps=Sortopt; *ps; ++ps)
  405.     {
  406.     if (*ps++ != '%')
  407.     {
  408.         fprintf (stderr, "?unknown sort key at '%.6s'...\n", ps);
  409.         exit (1);
  410.     }
  411.  
  412.         /* check for '-' (for reverse order sort) */
  413.     reverse = 1;        /* NOT a flag - multiplied by strcmp() result */
  414.     if (*ps == '-')
  415.     {
  416.         reverse = -1;
  417.         ++ps;
  418.     }
  419.  
  420.     l1 = l2 = 0;
  421.     p1 = p2 = (char *)NULL;
  422.     switch (*ps)
  423.     {
  424.     case F_TYPE:
  425.         switch (psbuf1->st_mode & S_IFMT)
  426.         {
  427.         case S_IFREG:    p1 = "-"; break;
  428.         case S_IFDIR:    p1 = "d"; break;
  429.         case S_IFCHR:    p1 = "c"; break;
  430.         case S_IFBLK:    p1 = "b"; break;
  431.         case S_IFLNK:    p1 = "l"; break;
  432.         case S_IFSOCK:    p1 = "s"; break;
  433.         case S_IFIFO:    p1 = "p"; break;
  434.         default:        p1 = "?"; break;
  435.         }
  436.         switch (psbuf2->st_mode & S_IFMT)
  437.         {
  438.         case S_IFREG:    p2 = "-"; break;
  439.         case S_IFDIR:    p2 = "d"; break;
  440.         case S_IFCHR:    p2 = "c"; break;
  441.         case S_IFBLK:    p2 = "b"; break;
  442.         case S_IFLNK:    p2 = "l"; break;
  443.         case S_IFSOCK:    p2 = "s"; break;
  444.         case S_IFIFO:    p2 = "p"; break;
  445.         default:        p2 = "?"; break;
  446.         }
  447.         break;
  448.     case F_PERMA:
  449.         p1 = aperms ((int)psbuf1->st_mode, ps+1, &i);
  450.         p2 = aperms ((int)psbuf2->st_mode, ps+1, &i);
  451.         ps += i;
  452.         break;
  453.     case F_PERMO:
  454.         l1 = ((psbuf1->st_mode) & (~S_IFMT));
  455.         l2 = ((psbuf2->st_mode) & (~S_IFMT));
  456.         break;
  457.     case F_NLINKS:
  458.         l1 = psbuf1->st_nlink;
  459.         l2 = psbuf2->st_nlink;
  460.         break;
  461.     case F_INODE:
  462.         l1 = psbuf1->st_ino;
  463.         l2 = psbuf2->st_ino;
  464.         break;
  465.     case F_DEV:
  466.         l1 = psbuf1->st_dev;
  467.         l2 = psbuf2->st_dev;
  468.         break;
  469.     case F_RDEV:
  470.         l1 = psbuf1->st_rdev;
  471.         l2 = psbuf2->st_rdev;
  472.         break;
  473.     case F_USERA:
  474.         l1 = psbuf1->st_uid;
  475.         l2 = psbuf2->st_uid;
  476.         if ((ptmp = getpwname((int)l1)) == (char *)NULL)
  477.         break;
  478.         strcpy (tbuf1, ptmp);
  479.         if ((ptmp = getpwname((int)l2)) == (char *)NULL)
  480.         break;
  481.         strcpy (tbuf2, ptmp);
  482.         p1 = tbuf1;
  483.         p2 = tbuf2;
  484.         break;
  485.     case F_USERN:
  486.         l1 = psbuf1->st_uid;
  487.         l2 = psbuf2->st_uid;
  488.         break;
  489.     case F_GROUPA:
  490.         l1 = psbuf1->st_gid;
  491.         l2 = psbuf2->st_gid;
  492.         if ((ptmp = getgrname((int)l1)) == (char *)NULL)
  493.         break;
  494.         strcpy (tbuf1, ptmp);
  495.         if ((ptmp = getgrname((int)l2)) == (char *)NULL)
  496.         break;
  497.         strcpy (tbuf2, ptmp);
  498.         p1 = tbuf1;
  499.         p2 = tbuf2;
  500.         break;
  501.     case F_GROUPN:
  502.         l1 = psbuf1->st_gid;
  503.         l2 = psbuf2->st_gid;
  504.         break;
  505.     case F_SIZE:
  506.         l1 = psbuf1->st_size;
  507.         l2 = psbuf2->st_size;
  508.         break;
  509.     case F_BLOCKS:
  510.         l1 = psbuf1->st_blocks;
  511.         l2 = psbuf2->st_blocks;
  512.         break;
  513.     case F_BLKSIZE:
  514.         l1 = psbuf1->st_blksize;
  515.         l2 = psbuf2->st_blksize;
  516.         break;
  517.     case F_MDATE:
  518.         l1 = psbuf1->st_mtime;
  519.         l2 = psbuf2->st_mtime;
  520.         break;
  521.     case F_ADATE:
  522.         l1 = psbuf1->st_atime;
  523.         l2 = psbuf2->st_atime;
  524.         break;
  525.     case F_CDATE:
  526.         l1 = psbuf1->st_ctime;
  527.         l2 = psbuf2->st_ctime;
  528.         break;
  529.     case F_NAME:
  530.     case F_NAMEL:
  531.         ascii = basen = 0;
  532.         while (*(ps+1) && strchr("abs",*(ps+1)) != NULL)
  533.         {
  534.         ++ps;
  535.         if (*ps == 'a')
  536.             ascii = 1;
  537.         else if (*ps == 'b')
  538.             basen = 1;
  539.         else if (*ps == 's')    /* ignored for sort purposes */
  540.             ;
  541.         }
  542.         if (basen)    /* basename only */
  543.         {
  544.         if ((p1 = strrchr (pf1->f_name, '/')) == (char *)NULL)
  545.             p1 = pf1->f_name;
  546.         else
  547.             ++p1;
  548.         if ((p2 = strrchr (pf2->f_name, '/')) == (char *)NULL)
  549.             p2 = pf2->f_name;
  550.         else
  551.             ++p2;
  552.         }
  553.         else
  554.         {
  555.         p1 = pf1->f_name;
  556.         p2 = pf2->f_name;
  557.         }
  558.         if (ascii)    /* show non-printing chars */
  559.         {
  560.         strcpy (tbuf1, showname (p1));
  561.         strcpy (tbuf2, showname (p2));
  562.         p1 = tbuf1;
  563.         p2 = tbuf2;
  564.         }
  565.         break;
  566.     default:
  567.         fprintf (stderr, "?unknown sort option char '%%%c'\n", *ps);
  568.         continue;
  569.     }  /* switch */
  570.  
  571.         /* now check for type of comparison for this field */
  572.     if (p1 != (char *)NULL)
  573.     {  /* string comparison */
  574.         i = strcmp (p1, p2);
  575.         if (i == 0)
  576.         continue;    /* need to go to next sort field */
  577.         else
  578.         return (reverse*i);
  579.     }
  580.     else
  581.     {  /* numeric comparison */
  582.         if (l1 == l2)
  583.         continue;    /* need to go to next sort field */
  584.         else if (l1 > l2)
  585.         return (reverse);
  586.         else        /* l1 < l2 */
  587.         return (-reverse);
  588.     }
  589.     }  /* loop through sort options string */
  590.  
  591.     return (strcmp (pf1->f_name, pf2->f_name));
  592.  
  593. }  /* sortsfile */
  594.  
  595. /******************************************************************************/
  596.  
  597. display (pf)
  598.     SFILESTAT       *pf;
  599. {  /* display info about a file */
  600.     register struct stat   *psbuf;
  601.     register char  *popt, *pobuf, *fmtstr;
  602.     register int    n, fwid;
  603.     long            l;
  604.     int             i, zerofill, leftjust, ascii, basen, suffix;
  605.     char            obuf[200], lname[200], *ptmp, *pc, *fname, c;
  606.  
  607.     fname = pf->f_name;
  608.     psbuf = &(pf->f_stat);
  609.     pobuf = obuf;
  610.     for (popt=Printopt; *popt; ++popt)
  611.     {
  612.     if (*popt != '%')
  613.     {
  614.         *pobuf++ = *popt;
  615.         continue;
  616.     }
  617.     if (*(popt+1) == '%')
  618.     {
  619.         *pobuf++ = *popt++;
  620.         continue;
  621.     }
  622.  
  623.         /* check for optional field width and '-' (for left justify) */
  624.     fwid = zerofill = leftjust = 0;
  625.     if (*(popt+1) == '-')
  626.     {
  627.         leftjust = 1;
  628.         ++popt;
  629.     }
  630.     if (isdigit (*++popt))
  631.     {
  632.         zerofill = (*popt == '0');
  633.         for (fwid=0; isdigit(*popt); ++popt)
  634.         fwid = fwid*10 + *popt - '0';
  635.     }
  636.  
  637.     c = *popt;
  638.     switch (c)
  639.     {
  640.     case F_TYPE:
  641.         switch (psbuf->st_mode & S_IFMT)
  642.         {
  643.         case S_IFREG:    ptmp = "-"; break;
  644.         case S_IFDIR:    ptmp = "d"; break;
  645.         case S_IFCHR:    ptmp = "c"; break;
  646.         case S_IFBLK:    ptmp = "b"; break;
  647.         case S_IFLNK:    ptmp = "l"; break;
  648.         case S_IFSOCK:    ptmp = "s"; break;
  649.         case S_IFIFO:    ptmp = "p"; break;
  650.         default:
  651.         ptmp = "?";
  652.         fprintf (stderr, "?unknown file type 0%o for file '%s'\n",
  653.             psbuf->st_mode & S_IFMT, fname);
  654.         break;
  655.         }
  656.         if (fwid == 0)
  657.         {
  658.         *pobuf++ = *ptmp;
  659.         *pobuf = '\0';    /* so 'while' loop below exits */
  660.         }
  661.         else
  662.         {
  663.         fmtstr = getfmtstr ('s', fwid, leftjust, zerofill);
  664.         sprintf (pobuf, fmtstr, ptmp);
  665.         }
  666.         break;
  667.     case F_PERMA:
  668.         if (fwid == 0)
  669.         strcpy (pobuf, aperms ((int)psbuf->st_mode, popt+1, &i));
  670.         else
  671.         {
  672.         fmtstr = getfmtstr ('s', fwid, leftjust, zerofill);
  673.         sprintf (pobuf, fmtstr,
  674.             aperms ((int)psbuf->st_mode, popt+1, &i));
  675.         }
  676.         popt += i;
  677.         break;
  678.     case F_PERMO:
  679.         if (fwid == 0)
  680.         fmtstr = getfmtstr ('o', 3, 0, 1);
  681.         else
  682.         fmtstr = getfmtstr ('o', fwid, leftjust, zerofill);
  683.         sprintf (pobuf, fmtstr, psbuf->st_mode & (~S_IFMT));
  684.         break;
  685.     case F_NLINKS:
  686.         fmtstr = getfmtstr ('d', (fwid?fwid:4), leftjust, zerofill);
  687.         sprintf (pobuf, fmtstr, psbuf->st_nlink);
  688.         break;
  689.     case F_INODE:
  690.         fmtstr = getfmtstr ('d', (fwid?fwid:6), leftjust, zerofill);
  691.         sprintf (pobuf, fmtstr, psbuf->st_ino);
  692.         break;
  693.     case F_DEV:
  694.         fmtstr = getfmtstr ('d', (fwid?fwid:6), leftjust, zerofill);
  695.         sprintf (pobuf, fmtstr, psbuf->st_dev);
  696.         break;
  697.     case F_RDEV:
  698.         fmtstr = getfmtstr ('d', (fwid?fwid:6), leftjust, zerofill);
  699.         sprintf (pobuf, fmtstr, psbuf->st_rdev);
  700.         break;
  701.     case F_USERA:
  702.         n = psbuf->st_uid;
  703.         if ((ptmp = getpwname(n)) == (char *)NULL)
  704.         {
  705.         fmtstr = getfmtstr ('d', (fwid?fwid:8), leftjust, zerofill);
  706.         sprintf (pobuf, fmtstr, n);
  707.         }
  708.         else
  709.         {
  710.         if (fwid == 0)
  711.             fmtstr = getfmtstr ('s', 8, leftjust, 0);
  712.         else
  713.             fmtstr = getfmtstr ('s', fwid, leftjust, zerofill);
  714.         sprintf (pobuf, fmtstr, ptmp);
  715.         }
  716.         break;
  717.     case F_USERN:
  718.         fmtstr = getfmtstr ('d', (fwid?fwid:4), leftjust, zerofill);
  719.         sprintf (pobuf, fmtstr, psbuf->st_uid);
  720.         break;
  721.     case F_GROUPA:
  722.         n = psbuf->st_gid;
  723.         if ((ptmp = getgrname(n)) == (char *)NULL)
  724.         {
  725.         fmtstr = getfmtstr ('d', (fwid?fwid:8), leftjust, zerofill);
  726.         sprintf (pobuf, fmtstr, psbuf->st_uid);
  727.         }
  728.         else
  729.         {
  730.         if (fwid == 0)
  731.             fmtstr = getfmtstr ('s', 8, leftjust, 0);
  732.         else
  733.             fmtstr = getfmtstr ('s', fwid, leftjust, zerofill);
  734.         sprintf (pobuf, fmtstr, ptmp);
  735.         }
  736.         break;
  737.     case F_GROUPN:
  738.         fmtstr = getfmtstr ('d', (fwid?fwid:4), leftjust, zerofill);
  739.         sprintf (pobuf, fmtstr, psbuf->st_gid);
  740.         break;
  741.     case F_SIZE:
  742.         l = (long) psbuf->st_size;
  743.         if (*(popt+1) == 'm')
  744.         {
  745.         fmtstr = getfmtstr ('d', (fwid?fwid:3), leftjust, zerofill);
  746.         sprintf (pobuf, fmtstr, (l+1048575)>>20);
  747.         ++popt;
  748.         }
  749.         else if (*(popt+1) == 'k')
  750.         {
  751.         fmtstr = getfmtstr ('d', (fwid?fwid:6), leftjust, zerofill);
  752.         sprintf (pobuf, fmtstr, (l+1023)>>10);
  753.         ++popt;
  754.         }
  755.         else
  756.         {
  757.         fmtstr = getfmtstr ('d', (fwid?fwid:9), leftjust, zerofill);
  758.         sprintf (pobuf, fmtstr, l);
  759.         if (*(popt+1) == 'c' || *(popt+1) == 'b')
  760.             ++popt;
  761.         }
  762.         break;
  763.     case F_BLOCKS:
  764.         l = (long) psbuf->st_blocks;    /* 512-byte blocks allocated */
  765.         if (*(popt+1) == 'm')
  766.         {
  767.         l = (l+511) >> 11;    /* (* 512 / 1048576) = divide by 2048 */
  768.         fmtstr = getfmtstr ('d', (fwid?fwid:3), leftjust, zerofill);
  769.         sprintf (pobuf, fmtstr, l);
  770.         ++popt;
  771.         }
  772.         else if (*(popt+1) == 'k')
  773.         {
  774.         l = (l+1) >> 1;        /* (* 512 / 1024) = divide by 2 */
  775.         fmtstr = getfmtstr ('d', (fwid?fwid:6), leftjust, zerofill);
  776.         sprintf (pobuf, fmtstr, l);
  777.         ++popt;
  778.         }
  779.         else if (*(popt+1) == 'c' || *(popt+1) == 'b')
  780.         {
  781.         l <<= 9;        /* multiply by 512 */
  782.         fmtstr = getfmtstr ('d', (fwid?fwid:9), leftjust, zerofill);
  783.         sprintf (pobuf, fmtstr, l);
  784.         ++popt;
  785.         }
  786.         else
  787.         {
  788.         fmtstr = getfmtstr ('d', (fwid?fwid:5), leftjust, zerofill);
  789.         sprintf (pobuf, fmtstr, l);
  790.         }
  791.         break;
  792.     case F_BLKSIZE:
  793.         fmtstr = getfmtstr ('d', (fwid?fwid:4), leftjust, zerofill);
  794.         sprintf (pobuf, fmtstr, psbuf->st_blksize);
  795.         break;
  796.     case F_MDATE:
  797.         if (fwid == 0)
  798.         strcpy (pobuf, fmtdate (psbuf->st_mtime, popt+1, &i));
  799.         else
  800.         {
  801.         fmtstr = getfmtstr ('s', fwid, leftjust, zerofill);
  802.         sprintf (pobuf, fmtstr, fmtdate (psbuf->st_mtime, popt+1, &i));
  803.         }
  804.         popt += i;
  805.         break;
  806.     case F_ADATE:
  807.         if (fwid == 0)
  808.         strcpy (pobuf, fmtdate (psbuf->st_atime, popt+1, &i));
  809.         else
  810.         {
  811.         fmtstr = getfmtstr ('s', fwid, leftjust, zerofill);
  812.         sprintf (pobuf, fmtstr, fmtdate (psbuf->st_atime, popt+1, &i));
  813.         }
  814.         popt += i;
  815.         break;
  816.     case F_CDATE:
  817.         if (fwid == 0)
  818.         strcpy (pobuf, fmtdate (psbuf->st_ctime, popt+1, &i));
  819.         else
  820.         {
  821.         fmtstr = getfmtstr ('s', fwid, leftjust, zerofill);
  822.         sprintf (pobuf, fmtstr, fmtdate (psbuf->st_ctime, popt+1, &i));
  823.         }
  824.         popt += i;
  825.         break;
  826.     case F_NAME:
  827.     case F_NAMEL:
  828.         ascii = basen = suffix = 0;
  829.         while (*(popt+1) && strchr ("abs", *(popt+1)) != NULL)
  830.         {
  831.         ++popt;
  832.         if (*popt == 'a')
  833.             ascii = 1;
  834.         else if (*popt == 'b')
  835.             basen = 1;
  836.         else if (*popt == 's')
  837.             suffix = 1;
  838.         }
  839.         if (basen)    /* basename only */
  840.         {
  841.         if ((ptmp = strrchr (fname, '/')) == (char *)NULL)
  842.             ptmp = fname;
  843.         else
  844.             ++ptmp;
  845.         }
  846.         else
  847.         ptmp = fname;
  848.         if (ascii)    /* show non-printing chars */
  849.         ptmp = showname (ptmp);
  850.         pc = "";
  851.         if (suffix)
  852.         {    /* add trailing type char for some files */
  853.         switch (psbuf->st_mode & S_IFMT)
  854.         {
  855.         case S_IFDIR:    pc = "/"; break;
  856.         case S_IFLNK:    pc = "@"; break;
  857.         case S_IFSOCK:    pc = "="; break;
  858.         default:
  859.             if (ISEXEC(psbuf->st_mode))
  860.             pc = "*";
  861.             break;
  862.         }
  863.         }
  864.         if (fwid == 0)
  865.         {
  866.         strcpy (pobuf, ptmp);
  867.         if (c == F_NAMEL && !suffix)
  868.         {    /* add linked-to filename if a symlink */
  869.             if ((psbuf->st_mode & S_IFMT) == S_IFLNK
  870.             && (n = readlink (fname, lname, sizeof(lname))) >= 0)
  871.             {
  872.             lname[n] = '\0';
  873.             strcat (pobuf, " -> ");
  874.             strcat (pobuf, lname);
  875.             }
  876.         }
  877.         }
  878.         else
  879.         {
  880.         fmtstr = getfmtstr ('s', fwid, leftjust, zerofill);
  881.         sprintf (pobuf, fmtstr, ptmp);
  882.         }
  883.         if (*pc)
  884.         {  /* add type-char to end of filename */
  885.         for (ptmp=pobuf; *ptmp; ++ptmp)
  886.         {
  887.             if (*ptmp == ' ')
  888.             {
  889.             *ptmp = *pc;
  890.             pc = "";
  891.             break;
  892.             }
  893.         }
  894.         if (*pc)  /* didn't find a space to add it, so add it to end */
  895.             strcat (pobuf, pc);
  896.         }
  897.         break;
  898.     default:
  899.         fprintf (stderr, "?unknown print option '%%%c'\n", *popt);
  900.         continue;
  901.     }
  902.  
  903.         /* update output ptr */
  904.     while (*pobuf)
  905.         ++pobuf;
  906.  
  907.     }  /* loop through print options string */
  908.  
  909.     *pobuf = '\0';
  910.     puts (obuf);
  911.  
  912. }  /* display */
  913.  
  914. /******************************************************************************/
  915.  
  916. static char *getfmtstr (ftype, width, leftjust, zerofill)
  917.     register char   ftype;    /* format type */
  918.     register int    width;    /* field width */
  919.     register int    leftjust;    /* =1 if left-justified wanted ('%-3s') */
  920.     register int    zerofill;    /* =1 if zero-fill wanted ('%03d') */
  921. {  /* build (user-specified) format string */
  922.     register char  *pbuf;
  923.     static char     buf[20];
  924.  
  925.     pbuf = buf;
  926.     *pbuf++ = '%';
  927.     if (ftype == 's' && leftjust)
  928.     *pbuf++ = '-';
  929.     else if (ftype != 's' && zerofill)
  930.     *pbuf++ = '0';
  931.  
  932.     /* encode width here instead of sprintf - too much overhead */
  933.     if (width > 99)
  934.     {    /* hundred's digit */
  935.     width %= 1000;        /* ensure a single digit for pathologic cases */
  936.     *pbuf++ = width/100 + '0';
  937.     width %= 100;
  938.     }
  939.     if (width > 9)
  940.     {    /* tens digit */
  941.     *pbuf++ = width/10 + '0';
  942.     width %= 10;
  943.     }
  944.     *pbuf++ = width + '0';    /* ones digit */
  945.     *pbuf++ = ftype;        /* the format char itself ('s', 'd', 'o') */
  946.  
  947.     *pbuf++ = '\0';
  948.     return (buf);
  949.  
  950. }  /* getfmtstr */
  951.  
  952. /******************************************************************************/
  953.  
  954. static char  *aperms (mode, poptstr, pnmod)
  955.     register int   mode;    /* mode word from stat structure */
  956.     char          *poptstr;    /* ptr to format string following %<char> */
  957.     int           *pnmod;    /* #of chars "eaten" from format string */
  958. {  /* return ascii representation of permission bits */
  959.     static char    buf[20];
  960.     register char *pbuf;
  961.     register int   i;
  962.  
  963.     *pnmod = 0;
  964.     pbuf = buf;
  965.     /* get perm bits for user, group, other */
  966.     for (i=0; i<3; ++i)
  967.     {
  968.     *pbuf++ = ((mode & (S_IREAD>>(i*3))) ? 'r' : '-');
  969.     *pbuf++ = ((mode & (S_IWRITE>>(i*3))) ? 'w' : '-');
  970.     *pbuf = ((mode & (S_IEXEC>>(i*3))) ? 'x' : '-');
  971.     if (i == 0 && (mode & S_ISUID))
  972.         *pbuf = (*pbuf == 'x' ? 's' : 'S');
  973.     else if (i == 1 && (mode & S_ISGID))
  974.         *pbuf = (*pbuf == 'x' ? 's' : 'S');
  975.     else if (i == 2 && (mode & S_ISVTX))
  976.         *pbuf = (*pbuf == 'x' ? 't' : 'T');
  977.     ++pbuf;
  978.     }
  979.     *pbuf = '\0';
  980.  
  981.     if (*poptstr && strchr ("123456789", *poptstr) != NULL)
  982.     {
  983.     buf[0] = buf[*poptstr - '0'];
  984.     buf[1] = '\0';
  985.     *pnmod = 1;
  986.     }
  987.  
  988.     return (buf);
  989.  
  990. }  /* aperms */
  991.  
  992. /******************************************************************************/
  993.  
  994. static char *showname (fname)
  995.     register char  *fname;
  996. {  /* show non-printing chars in a filename as \ddd */
  997.     static char     newname[200];
  998.     register char  *pnew;
  999.  
  1000.     for (pnew=newname; *fname; ++fname,++pnew)
  1001.     {
  1002.     if (isprint(*pnew = *fname))
  1003.         continue;
  1004.     sprintf (pnew, "\\%03o", *fname);
  1005.     pnew += strlen(pnew) - 1;
  1006.     }
  1007.     *pnew = '\0';
  1008.  
  1009.     return (newname);
  1010.  
  1011. }  /* showname */
  1012.  
  1013. /******************************************************************************/
  1014.  
  1015. static char  *Wdays[] = {
  1016.     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  1017. static char  *FWdays[] = {
  1018.     "Sunday", "Monday", "Tuesday", "Wednesday",
  1019.     "Thursday", "Friday", "Saturday" };
  1020. static char  *Months[] = {
  1021.     "January", "February", "March", "April", "May", "June",
  1022.     "July", "August", "September", "October", "November", "December" };
  1023.  
  1024. static char *fmtdate (ldate, poptstr, pnmod)
  1025.     time_t          ldate;    /* system date to be formatted */
  1026.     register char  *poptstr;    /* ptr to format string following %<char> */
  1027.     int            *pnmod;    /* #of chars "eaten" from format string */
  1028. {  /* format ldate according to string in pfmt, returning ptr to static area */
  1029.     struct tm      *localtime();
  1030.     register struct tm  *ptime;
  1031.     register char   c, *padate, *pfmt, endc;
  1032.     int             i;
  1033.     static char     adate[100];
  1034.  
  1035.     *pnmod = 0;
  1036.  
  1037.     /* get components of the system date */
  1038.     ptime = localtime (&ldate);
  1039.  
  1040.     /* check for format string */
  1041.     pfmt = poptstr;
  1042.     endc = '\0';
  1043.     if (*pfmt == '\'' || *pfmt == '\"')
  1044.     endc = *pfmt++;
  1045.     else if (Use_less)
  1046.     {
  1047.     if (ldate < Sixmosago)
  1048.         pfmt = "%h %d  19%y";    /* more than 6 mos. old (roughly) */
  1049.     else
  1050.         pfmt = "%h %d %H:%M";    /* less than 6 mos. old */
  1051.     }
  1052.     else  /* use default date format (or environment vbl) */
  1053.     pfmt = Defdatefmt;
  1054.  
  1055.     /* parse format string */
  1056.     padate = adate;
  1057.     while ((c = *pfmt++) != endc && c != '\0')
  1058.     {
  1059.     if (c != '%')
  1060.     {
  1061.         *padate++ = c;
  1062.         continue;
  1063.     }
  1064.     switch (c = *pfmt++)
  1065.     {
  1066.       case '%':    /* "%%" - print a percent sign */
  1067.         *padate++ = '%';
  1068.         break;
  1069.  
  1070.       case 'n':    /* "%n" - print a newline */
  1071.         *padate++ = '\n';
  1072.         break;
  1073.  
  1074.       case 't':    /* "%t" - print a tab */
  1075.         *padate++ = '\t';
  1076.         break;
  1077.  
  1078.       case 'm':    /* "%m" - print month of year (01 to 12) */
  1079.         sprintf (padate, "%02d", ptime->tm_mon+1);
  1080.         padate += 2;
  1081.         break;
  1082.  
  1083.       case 'd':    /* "%d" - print day of month (01 to 31) */
  1084.         sprintf (padate, "%02d", ptime->tm_mday);
  1085.         padate += 2;
  1086.         break;
  1087.  
  1088.       case 'y':    /* "%y" - print last 2 digits of year (00 to 99) */
  1089.         sprintf (padate, "%02d", ptime->tm_year);
  1090.         padate += 2;
  1091.         break;
  1092.  
  1093.       case 'D':    /* "%D" - print date as mm/dd/yy */
  1094.         sprintf (padate, "%02d/%02d/%02d",
  1095.         ptime->tm_mon+1, ptime->tm_mday, ptime->tm_year);
  1096.         padate += 8;
  1097.         break;
  1098.  
  1099.       case 'H':    /* "%H" - print hour (00 to 23) */
  1100.         sprintf (padate, "%02d", ptime->tm_hour);
  1101.         padate += 2;
  1102.         break;
  1103.  
  1104.       case 'M':    /* "%M" - print minute (00 to 59) */
  1105.         sprintf (padate, "%02d", ptime->tm_min);
  1106.         padate += 2;
  1107.         break;
  1108.  
  1109.       case 'S':    /* "%S" - print second (00 to 59) */
  1110.         sprintf (padate, "%02d", ptime->tm_sec);
  1111.         padate += 2;
  1112.         break;
  1113.  
  1114.       case 'T':    /* "%T" - print time as HH:MM:SS */
  1115.         sprintf (padate, "%02d:%02d:%02d",
  1116.         ptime->tm_hour, ptime->tm_min, ptime->tm_sec);
  1117.         padate += 8;
  1118.         break;
  1119.  
  1120.       case 'j':    /* "%j" - print julian date (001 to 366) */
  1121.         sprintf (padate, "%03d", ptime->tm_yday+1);
  1122.         padate += 3;
  1123.         break;
  1124.  
  1125.       case 'w':    /* "%w" - print day of week (0 to 6) (0=Sunday) */
  1126.         sprintf (padate, "%1d", ptime->tm_wday);
  1127.         padate += 1;
  1128.         break;
  1129.  
  1130.       case 'a':    /* "%a" - print abbreviated weekday (Sun to Sat) */
  1131.         sprintf (padate, "%3.3s", Wdays[ptime->tm_wday]);
  1132.         padate += 3;
  1133.         break;
  1134.  
  1135.       case 'W':    /* "%W" - print full weekday (Sunday to Saturday) */
  1136.         sprintf (padate, "%s", FWdays[ptime->tm_wday]);
  1137.         padate += strlen(FWdays[ptime->tm_wday]);
  1138.  
  1139.       case 'h':    /* "%h" - print abbreviated month (Jan to Dec) */
  1140.         sprintf (padate, "%3.3s", Months[ptime->tm_mon]);
  1141.         padate += 3;
  1142.         break;
  1143.  
  1144.       case 'F':    /* "%F" - print full month (January to December) */
  1145.         sprintf (padate, "%s", Months[ptime->tm_mon]);
  1146.         padate += strlen(Months[ptime->tm_mon]);
  1147.         break;
  1148.  
  1149.       case 'r':    /* "%r" - print time in AM/PM notation */
  1150.         i = ptime->tm_hour;
  1151.         if (i > 12)
  1152.         i -= 12;
  1153.         sprintf (padate, "%02d:%02d:%02d %2.2s",
  1154.         i, ptime->tm_min, ptime->tm_sec,
  1155.         ptime->tm_hour >= 12 ? "PM" : "AM");
  1156.         padate += 11;
  1157.         break;
  1158.  
  1159.       case 'E':    /* "%E" - print day of month with no padding (CFI) */
  1160.         sprintf (padate, "%d", ptime->tm_mday);
  1161.         padate += (ptime->tm_mday > 9 ? 2 : 1);
  1162.         break;
  1163.  
  1164.       case 'x':    /* "%x" - print date in system format (#seconds) */
  1165.         sprintf (padate, "%ld", (long)ldate);
  1166.         padate += strlen (padate);
  1167.         break;
  1168.  
  1169.       case 'X':    /* "%X" - print date in system format, days only */
  1170.         sprintf (padate, "%ld", (long)ldate/(60*60*24));    /* secs/day */
  1171.         padate += strlen (padate);
  1172.         break;
  1173.  
  1174.       default:
  1175.         *padate++ = '%';
  1176.         *padate++ = c;
  1177.         *padate++ = '?';
  1178.         break;
  1179.     }
  1180.     }
  1181.  
  1182.     if (c != endc)
  1183.     fprintf (stderr, "?missing close quote for date format string\n");
  1184.  
  1185.     *padate = '\0';
  1186.     if (endc != '\0')        /* need to return #chars in format string */
  1187.     *pnmod = pfmt - poptstr;
  1188.     return (&adate[0]);
  1189.  
  1190. }  /* fmtdate */
  1191.  
  1192. /******************************************************************************/
  1193.  
  1194. static char *getpwname (uid)
  1195.     register int  uid;
  1196. {  /* get user name from passwd file */
  1197.     static struct  uids {
  1198.     int   u_id;
  1199.     char *u_name;
  1200.     } uids[MAXUIDS];
  1201.     static int  nuids=0;
  1202.     register int  i;
  1203.     struct passwd  *pw, *getpwuid();
  1204.  
  1205.     for (i=0; i<nuids; ++i)
  1206.     if (uids[i].u_id == uid)
  1207.         return (uids[i].u_name);
  1208.  
  1209.     if (nuids == MAXUIDS)
  1210.     {
  1211.     fprintf (stderr, "?too many user names (max=%d)\n", MAXUIDS);
  1212.     return ((char *)NULL);
  1213.     }
  1214.  
  1215.     /* add new user name */
  1216.     uids[nuids].u_id = uid;
  1217.     if ((pw = getpwuid(uid)) == (struct passwd *)NULL)
  1218.     uids[nuids].u_name = (char *)NULL;
  1219.     else
  1220.     uids[nuids].u_name = copystr (pw->pw_name);
  1221.     return (uids[nuids++].u_name);
  1222.  
  1223. }  /* getpwname */
  1224.  
  1225. /******************************************************************************/
  1226.  
  1227. static char *getgrname (gid)
  1228.     register int  gid;
  1229. {  /* get user group name from group file */
  1230.     static struct gids {
  1231.     int   g_id;
  1232.     char *g_name;
  1233.     } gids[MAXGIDS];
  1234.     static int    ngids=0;
  1235.     register int  i;
  1236.     struct group *gr, *getgrgid();
  1237.  
  1238.     for (i=0; i<ngids; ++i)
  1239.     if (gids[i].g_id == gid)
  1240.         return (gids[i].g_name);
  1241.  
  1242.     if (ngids == MAXGIDS)
  1243.     {
  1244.     fprintf (stderr, "?too many group names (max=%d)\n", MAXGIDS);
  1245.     return ((char *)NULL);
  1246.     }
  1247.  
  1248.     /* add new group name */
  1249.     gids[ngids].g_id = gid;
  1250.     if ((gr = getgrgid(gid)) == (struct group *)NULL)
  1251.     gids[ngids].g_name = (char *)NULL;
  1252.     else
  1253.     gids[ngids].g_name = copystr (gr->gr_name);
  1254.     return (gids[ngids++].g_name);
  1255.  
  1256. }  /* getgrname */
  1257.  
  1258. /******************************************************************************/
  1259.  
  1260. static char *copystr (str)
  1261.     char  *str;
  1262. {  /* Allocate space for a string, copy it, and return ptr to new space */
  1263.     char  *p;
  1264.     int    i;
  1265.     
  1266.     i = strlen(str) + 1;
  1267.     p = (char *) malloc ((unsigned) i);
  1268.     if (p != (char *)NULL)
  1269.     strcpy (p, str);
  1270.     return (p);
  1271.  
  1272. }  /* copystr */
  1273.  
  1274. /******************************************************************************/
  1275.  
  1276. static time_t getsixmosago ()
  1277. {  /* Calculate the system date for 6 months ago */
  1278.     register int   i, ndays, month, year;
  1279.     time_t         today;
  1280.     struct tm     *now;
  1281.     
  1282.     today = time ((time_t *)NULL);
  1283.     now = localtime (&today);
  1284.     month = now->tm_mon + 1;    /* tm_mon is 0-11 */
  1285.     year = now->tm_year;
  1286.     ndays = 0;
  1287.     for (i=1; i<=6; ++i)
  1288.     {
  1289.     if (--month < 1)    /* starts with last month */
  1290.     {
  1291.         month = 12;
  1292.         year--;
  1293.     }
  1294.     switch (month)
  1295.     {
  1296.     case 1:        /* Jan */
  1297.     case 3:        /* Mar */
  1298.     case 5:        /* May */
  1299.     case 7:        /* Jul */
  1300.     case 8:        /* Aug */
  1301.     case 10:    /* Oct */
  1302.     case 12:    /* Dec */
  1303.         ndays += 31;
  1304.         break;
  1305.     case 4:        /* Apr */
  1306.     case 6:        /* Jun */
  1307.     case 9:        /* Sep */
  1308.     case 11:    /* Nov */
  1309.         ndays += 30;
  1310.         break;
  1311.     case 2:        /* Feb */
  1312.         if ((year%4) == 0 && (year%100) != 0)
  1313.         ndays += 29;
  1314.         else
  1315.         ndays += 28;
  1316.         break;
  1317.     }
  1318.     }
  1319.  
  1320.     return (today - (60*60*24*ndays));
  1321.  
  1322. }  /* getsixmosago */
  1323.