[comp.os.minix] New, featureful ls

housel@en.ecn.purdue.edu (Peter S. Housel) (07/15/89)

	One of the larger Minix-related compilaints I have heard from
people around here is the lack of an ls(1) which does columnar
listings. This was in fact one of the first things I fixed when I got
Minix, but unfortunately due to an accident I lost the source to the
modified ls.

	This version is almost completely rewritten from scratch. It
does almost everything that the BSD and SysV versions do, without
taking up much more memory than the original Minix ls. The user time
requirements are much less, though the sys time is sometimes greater
for a long-format (-l) listing. (Fixes for this would be appreciated.)

	The source code is an experiment in "literate programming,"
in the style of Knuth's WEB programming lanugage. The tools to typeset
the code aren't perfected yet, unfortunately. However, the commenting
style doesn't get too much in the way of readability. (Not nearly as
much as WEB does, in my opinion.) The program documentation, and
(to a lesser degree) the user documentation and compilation
instructions are embedded in the code.

	Note that you need the POSIX directory routines and getopt()
to compile this program. Both have been posted by Terrence Holm and
with the 1.4a distribution.

Comments and fixes welcome.

-Peter S. Housel-	housel@ecn.purdue.edu		...!pur-ee!housel

echo 'x - ls.c'
sed 's/^X//' <<'**-ls.c-EOF-**' >ls.c
X/*@*Introduction.
X * This file is part of \fIls\fP for Minix. It was written in the spring
X * of 1989 by Peter S. Housel. This program is in the public domain and may
X * be redistributed without restriction. As such, no warranty of any kind
X * is provided.
X * .PP
X * The following changes to the program have been made:
X * .IP \(bu
X * Version 1.1 - removed references to \fB\-q\fP option, added |ONECOLUMN|
X * and other compile-time options.
X */
X#ifndef lint
Xstatic char version[] = "ls version 1.1 07/13/89";
X#endif lint
X
X/*@ \fILs\fP is a version of standard Minix directory listing program. Because
X * it use so often used, it should be as fast as possible. It should be
X * somewhat "featureful" (Rob Pike nonwithstanding), but not in a way
X * that interferes with its use as a "software tool." It should take
X * up a small to medium amount of memory.
X * .PP
X * The program should be compiled using:
X * .nf
X *	\fBcc -o ls -Di8088 ls.c
X *	chmem =4096 ls\fP
X * or
X *	\fBcc -o ls -DATARI_ST ls.c\fP
X * .fi
X * If you do not want multi-column listings to be the default when
X * standard output is a tty, define |ONECOLUMN| (by adding
X * \fB\-DONECOLUMN\fP to the compile flags). Similarly, |NFILE|,
X * |STRINGSPACE|, and |LINEWIDTH| can be changed with appropriate
X * predefines.
X */
X
X/*@ Since \fIls\fP has to know a lot about files, there are quite a few
X * headers to include.
X */
X#include <limits.h>
X#include <stdio.h>
X#include <sys/types.h>
X#include <dirent.h>
X#include <sys/stat.h>
X#include <errno.h>
Xextern int errno;
X
X/*@ |DOTDIR()| determines whether or not a given |name| is one of the
X * special directory files "." or "..".
X */
X#define DOTDIR(name)	\
X	(name[0] == '.'	\
X	 && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
X
X/*@ We use |getopt()| to parse the command-line flag arguments. This
X * annoys some people who are used to the (BSD pre-Tahoe release)
X * "infinte args \fIls\fP" that doesn't complain about anything.
X */
Xextern int getopt(/* int argc, char **argv, char *optstring */);
Xextern char *optarg;
Xextern int optind;
X
X/*@ It may be necessary to |stat()| each file that is listed.
X * The toplevel files (specified by the arguments) will need to be, to
X * determine which ones are directories. Several of the options will
X * make it necessary to have the |stat()| information (the \fB\-l\fP and
X * \fB\-R\fP options, for example), and we will turn on |stateach|
X * later on if any of these are set.
X */
Xint stateach;
X
X/*@ \fILs\fP takes quite a few options:
X * .IP \fB\-l\fP
X * Print in "long listing" format. Sets |flags_l|, unsets |flags_C|.
X * .IP \fB\-g\fP
X * Include the group ownership along with the user ownership in the long
X * format listing. Sets |flags_g|.
X * .IP \fB\-t\fP
X * Sort the listing by the modification time, with most recent times
X * printed first. Sets |flags_t|.
X * .IP \fB\-a\fP
X * Include all files. By default, files whose names begin with "." are not
X * included. Sets |flags_a|.
X * .IP \fB\-A\fP
X * Include all files, even those beginning with ".", except for "." and "..".
X * This flag is automatically turned on for the superuser. Sets |flags_A|.
X * .IP \fB\-s\fP
X * Print the size of the file (in kilobytes) with each file. Sets |flags_s|.
X * .IP \fB\-d\fP
X * List named directories explicitly instead of their contents. Sets
X * |flags_d|.
X * .IP \fB\-r\fP
X * Sort the listing in reverse order. Sets |flags_r|.
X * .IP \fB\-u\fP
X * Use the file access time instead of the modification time in the listings
X * (\fB\-l\fP option) and/or as the sort key (\fB\-t\fP option). Sets
X * |flags_u|. \fI(Currently ineffectual in Minix.)\fP
X * .IP \fB\-c\fP
X * Like \fB\-u\fP, except that the inode change time is used instead.
X * Sets |flags_c|.
X * .IP \fB\-i\fP
X * Print the file's inode number with each file. Sets |flags_i|.
X * .IP \fB\-f\fP
X * Force the named files to be interpreted as directories, whether they
X * are or not. (Actually, the POSIX-type directory routines cause problems
X * with this. They reqire that the file be a directory.) Sets |flags_f|
X * and |flags_a|, and unsets |flags_l|,
X * |flags_t|, |flags_s|, and |flags_r|.
X * .IP \fB\-F\fP
X * After the filenames of directories, print "/". After executable files,
X * print "*". Sets |flags_F|.
X * .IP \fB\-R\fP
X * Recursively print each subdirectory. Sets |flags_R|.
X * .IP \fB\-1\fP
X * Print in "one-column" format instead of columnar format. This is the
X * default if standard output is not a tty. Unsets |flags_C|.
X * .IP \fB\-C\fP
X * Print in columnar format. This is the default if standard output is
X * a tty, provided |ONECOLUMN| has not been defined. Sets |flags_C|.
X */
X#define VALID_FLAGS	"lgtaAsdrucifFR1C"
X
Xint	flags_l,	flags_g,	flags_t,	flags_a,
X	flags_A,	flags_s,	flags_d,	flags_r,
X	flags_u,	flags_c,	flags_i,	flags_f,
X	flags_F,	flags_R,	flags_C;
X
X/*@ 
X*/
Xmain(argc, argv)
Xint argc; char *argv[];
X{
X void add_args(/* int argc; char **argv; */);
X void listall(/* void */);
X
X int c;				/* option character */
X#ifdef noperprintf
X noperprintf(stdout);
X#endif
X
X#ifndef ONECOLUMN
X if(isatty(1))
X    flags_C = 1;
X#endif
X
X while((c = getopt(argc, argv, VALID_FLAGS)) != EOF)
X      {
X       switch(c)
X	     {
X	       case 'l': flags_l = 1; break;
X	       case 'g': flags_g = 1; break;
X	       case 't': flags_t = 1; break;
X	       case 'a': flags_a = 1; break;
X	       case 'A': flags_A = 1; break;
X	       case 's': flags_s = 1; break;
X	       case 'd': flags_d = 1; break;
X	       case 'r': flags_r = 1; break;
X	       case 'u': flags_u = 1; break;
X	       case 'c': flags_c = 1; break;
X	       case 'i': flags_i = 1; break;
X	       case 'f': flags_f = 1; break;
X	       case 'F': flags_F = 1; break;
X	       case 'R': flags_R = 1; break;
X	       case '1': flags_C = 0; break;
X	       case 'C': flags_C = 1; break;
X
X	       case '?':
X			 fprintf(stderr, "usage: ls -%s [file ...]\n",
X				 VALID_FLAGS);
X			 exit(1);
X	      }
X      }
X
X if(flags_f)
X   {
X    flags_l = flags_t = flags_s = flags_r = 0;
X    flags_a = 1;
X   }
X
X if(flags_l || flags_s || flags_i)
X    flags_C = 0;
X
X flags_r = flags_r ? -1 : 1;		/* multiplier for comparisons */
X
X if(geteuid() == 0 && !flags_a)
X    flags_A = 1;
X
X stateach = !flags_f;
X add_args(argc - optind + 1, argv + optind - 1);
X
X stateach = !flags_f && (flags_l || flags_t || flags_s || flags_i
X			 || flags_F || flags_R);
X listall();
X
X exit(0);
X}
X
X/*@* The files table.
X * The |struct lsfile| structure is the main data structure used by
X * \fIls\fP. The |files| array is an array of these structures, with
X * each entry representing one file to be listed by the program.
X * The |f_stat| field is the result of a |stat()| call on the file, if
X * one has been done. The |f_name| field points to the filename, either
X * directly to the command-line argument, or to a copy in string space
X * of a name read from a directory. The |f_parent| field points to the
X * parent directory of a file, so that the entire relative pathname
X * can be constructed using the |pathname()| function below.
X * .PP
X * There are at most |NFILE| entries in the staticaly-allocated table.
X * Entries are added as a directory is read in, and removed when the
X * listing of that directory is done. The |filep| variable is used to
X * point to the next free entry.
X */
X#ifndef NFILE
X#define NFILE		256
X#endif
X
Xstruct lsfile
X       {
X	struct stat	f_stat;		/* file information from inode */
X	char	       *f_name;		/* file name */
X	struct lsfile  *f_parent;	/* parent directory, if any */
X       };
X
Xstruct lsfile files[NFILE];
Xstruct lsfile *filep = files;
X
X/*@ When a directory is sorted, the time spent exchanging entries is reduced
X * by exchanging pointers to entries instead of the entries themselves.
X * When a listing is printed, the file table entries are accessed
X * indirectly through |sortindex| entries.
X */
Xstruct lsfile *sortindex[NFILE];
X
X
X/*@ The string area is used to store filenames read in from directories,
X * as well as the lines of output for columnar listings. Like the file
X * table, space in the string area is deallocated when it is no longer
X * needed.
X */
X#ifndef STRINGSPACE
X#define STRINGSPACE	2048
X#endif
X
Xchar strings[STRINGSPACE];
Xchar *stringp = strings;
X
X/*@ The |ADDSTRING()| macro is used to add characters to the string
X * table. A check for overflow is made.
X */
Xchar stringerr[] = "ls: out of string space\n";
X#define ADDSTRING(c) 				\
X	if(stringp - strings >= STRINGSPACE)	\
X	  {					\
X	   fprintf(stderr, stringerr);		\
X	   exit(1);				\
X	  }					\
X	else					\
X	   *stringp++ = c
X
X/*@ These are the forward declarations for the files table section. 
X*/
Xvoid add_file(/* char *filename, struct lsfile *parent, ino_t inum,
X		 int savename */);
Xchar *pathname(/* struct lsfile *entry */);
X
X/*@ The |add_args()| function is used to add the files specified
X * in the command line arguments to the files table. Note that
X * \fIls\fP without any file arguments is equivalent to "\fIls\ .\fP"
X */
Xvoid add_args(argc, argv)
Xint argc; char *argv[];
X{
X if(argc == 1)
X    add_file(".", (struct lsfile *)NULL, 0, 0);
X else
X    while(++argv, --argc)
X	 {
X	  add_file(*argv, (struct lsfile *)NULL, 0, 0);
X	 }
X}
X
X
X/*@ |add_dir()| opens a directory and adds the files it contains to the
X * files table. If "dot-files" are not being listed, they will be
X * omitted.
X */
Xvoid add_dir(entry)
Xstruct lsfile *entry;
X{
X DIR *dirp;
X struct dirent *dp;
X struct lsfile *parent = NULL;
X
X if(strcmp(entry->f_name, ".") != 0)	/* "./" is redundant */
X    parent = entry;
X
X if((dirp = opendir(pathname(entry))) == NULL)
X   {
X    fprintf(stderr, "ls: can't open directory %s\n", pathname(entry));
X    return;
X   }
X
X while((dp = readdir(dirp)) != NULL)
X      {
X       if(dp->d_name[0] == '.' && !(flags_A || flags_a))
X	  continue;
X       if(DOTDIR(dp->d_name) && !flags_a)
X	  continue;
X       add_file(dp->d_name, parent, (ino_t) dp->d_ino, 1);
X      }
X
X (void) closedir(dirp);
X}
X
X/*@ |add_file()| does the actual work of entering files into the table.
X * If it is necessary to save the name in the string table, this is
X * done. Also, if it is necessary to |stat()| the file, we do it here.
X */
X
Xvoid add_file(filename, parent, inum, savename)
Xchar *filename;
Xstruct lsfile *parent;
Xino_t inum;
Xint savename;
X{
X if(filep - files >= NFILE)
X   {
X    fprintf(stderr, "ls: too many files\n");
X    exit(1);
X   }
X
X if(filename[0] == '\0')	/* POSIX doesn't like null pathnames (?) */
X   {
X    fprintf(stderr, "ls: invalid null filename\n");
X    return;
X   }
X
X if(savename)
X   {
X    filep->f_name = stringp;
X    while(*filename)
X	 {
X	  ADDSTRING(*filename++);
X	 }
X    ADDSTRING('\0');
X   }
X else
X    filep->f_name = filename;
X
X filep->f_parent = parent;
X filep->f_stat.st_ino = inum;
X
X sortindex[filep - files] = filep;
X
X if(stateach)
X    if(stat(pathname(filep), &filep->f_stat) < 0)
X      {
X       int saverrno = errno;
X       fprintf(stderr, "ls: cannot stat %s", pathname(filep));
X       errno = saverrno;
X       perror("");
X       return;
X      }
X
X ++filep;
X}
X
X/*@ The |pathname()| function reconstructs the relative pathname of a 
X * file from a pointer to its table entry. As a base case, it returns
X * the stored filename of files which have no parents. Otherwise, it
X * recursively determines the pathname of the parent directory and
X * appends the stored name to it.
X * .PP
X * The name of the last parent directory searched is cached to save
X * on recursive calls.
X */
Xchar *pathname(entry)
Xstruct lsfile *entry;
X{
X static char name[PATH_MAX + 1];
X
X static char parentname[PATH_MAX + 1];
X static struct lsfile *parent;
X
X register char *p, *q;
X
X if(entry->f_parent == NULL)
X    return entry->f_name;
X
X if(entry->f_parent != parent)
X   {
X    strcpy(parentname, pathname(entry->f_parent));	/* recurse */
X    parent = entry->f_parent;
X   }
X
X for(q = parentname, p = name; *q; )
X     *p++ = *q++;
X
X if(p[-1] != '/')
X     *p++ = '/';
X
X for(q = entry->f_name; *q; )
X     *p++ = *q++;
X
X *p = '\0';
X
X return name;
X}
X
X/*@*Listing files.
X * Most of the work of listing files is done in this section, in particular
X * by the |listfiles()| function.
X */
X
X/*@ Forward declarations for this section:
X */
Xvoid listfiles(/* int baseindex; int lastindex */);
Xvoid sortfiles(/* int baseindex; int nfiles */);
Xvoid listone(/* struct lsfile *entry */);
Xvoid listcol(/* struct lsfile *entry */);
Xvoid prdate(/* time_t filetime */);
Xvoid dumpcols(/* void */);
X
Xchar *owner(/* int uid */);
Xchar *groupname(/* int gid */);
X
X/*@ 
X */
Xvoid listall()
X{
X listfiles(0, filep - files, flags_d);
X}
X
X/*@ The |listfiles()| function is, effectively, the "heart" of \fIls\fP.
X * First, the "total nnn" line (listing the total number of blocks
X * taken up by the listed files) is computed and printed if necessary.
X * Next, if we are listing files specified on the command line, list
X * all except the directories. Otherwise, list all of the files. Next,
X * go through all of the directories and recursively list them.
X */
Xvoid listfiles(baseindex, lastindex, include_dirs)
Xint baseindex;
Xint lastindex;
Xint include_dirs;
X{
X int i;
X char *name;
X char *ostringp;
X
X if(flags_l || flags_s)
X   {
X    int total = 0;
X    int counted = 0;
X    for(i = baseindex; i < lastindex; ++i)
X       {
X        if(include_dirs
X	   || (sortindex[i]->f_stat.st_mode & S_IFMT) != S_IFDIR)
X 	  {
X	   counted = 1;
X	   total += nblocks(sortindex[i]->f_stat.st_size);
X	  }
X       }
X    if(counted)
X       printf("total %d\n", total);
X   }
X
X ostringp = stringp;
X
X for(i = baseindex; i < lastindex; ++i)
X    {
X     if(!(flags_f && baseindex == 0)
X	&& (include_dirs
X	    || (sortindex[i]->f_stat.st_mode & S_IFMT) != S_IFDIR))
X	if(flags_C)
X	   listcol(sortindex[i]);
X	else
X	   listone(sortindex[i]);
X    }
X
X if(flags_C)
X    dumpcols();
X
X stringp = ostringp;
X
X fflush(stdout);
X if(flags_d || (baseindex > 0 && !flags_R))
X    return;
X
X for(i = baseindex; i < lastindex; ++i)
X    {
X     name = sortindex[i]->f_name;
X
X     if((flags_f && baseindex == 0)
X	|| ((sortindex[i]->f_stat.st_mode & S_IFMT) == S_IFDIR
X	    && (baseindex == 0 || !DOTDIR(name))))
X       {
X	struct lsfile *ofilep;
X
X	if(lastindex - baseindex > 1)
X	   printf("\n%s:\n", pathname(sortindex[i]));
X
X	ofilep = filep;
X	ostringp = stringp;
X
X	add_dir(sortindex[i]);
X	if(!flags_f)
X	   sortfiles(ofilep - files, filep - ofilep);
X	listfiles(ofilep - files, filep - files, 1);
X
X	filep = ofilep;
X	stringp = ostringp;
X       }
X    }
X}
X
X/*@ Except for columnar listings, |listone()| does the work of listing
X * individual files, on lines by themselves, to standard output.
X * Special-case formatting is done for the "-l", "-s", "-i", and "-F"
X * options.
X */
Xvoid listone(entry)
Xregister struct lsfile *entry;
X{
X unsigned short mode;
X char c, fchar(/* unsigned short mode */);
X
X if(flags_i)
X    printf("%5d ", entry->f_stat.st_ino);
X
X if(flags_s)
X    printf("%4d ", nblocks(entry->f_stat.st_size));
X
X mode = entry->f_stat.st_mode;
X
X if(flags_l)
X   {
X    static char *rwx[] = {"---", "--x", "-w-", "-wx",
X			  "r--", "r-x", "rw-", "rwx"};
X    char bits[11];
X 
X    switch(mode & S_IFMT)
X	  {
X	   case S_IFDIR:	bits[0] = 'd';
X				break;
X	   case S_IFBLK:	bits[0] = 'b';
X				break;
X	   case S_IFCHR:	bits[0] = 'c';
X				break;
X	   default:		bits[0] = '-';
X				break;
X	  }
X
X    strcpy(&bits[1], rwx[(mode >> 6) & 7]);
X    strcpy(&bits[4], rwx[(mode >> 3) & 7]);
X    strcpy(&bits[7], rwx[(mode & 7)]);
X    if(mode & S_ISUID)
X       bits[3] = 's';
X    if(mode & S_ISGID)
X       bits[6] = 's';
X    if(mode & S_ISVTX)
X       bits[9] = 't';
X    printf("%s %2d %-8.8s ", bits, entry->f_stat.st_nlink,
X			     owner(entry->f_stat.st_uid));
X    if(flags_g)
X       printf("%-8.8s ", groupname(entry->f_stat.st_gid));
X
X    if((mode & S_IFMT) == S_IFCHR || (mode & S_IFMT) == S_IFBLK)
X      {
X       printf("%3d, %3d ", major(entry->f_stat.st_rdev),
X			   minor(entry->f_stat.st_rdev));
X      }
X    else
X       printf("%8ld ", (long) entry->f_stat.st_size);
X
X    if(flags_u)
X       prdate(entry->f_stat.st_atime);
X    else if(flags_c)
X       prdate(entry->f_stat.st_ctime);
X    else
X       prdate(entry->f_stat.st_mtime);
X   }
X
X printf("%s", entry->f_name);
X
X if(flags_F && (c = fchar(mode)) != '\0')
X    putchar(c);
X putchar('\n');
X}
X
X/*@ When the "-F" option is being used, |fchar()| computes the character
X * which will follow the filename - '/' for directories, and '*' for
X * executable files. If there were sockets, FIFO's and symbolic links,
X * they would have cases here too.
X */
Xchar fchar(mode)
Xunsigned short mode;
X{
X if((mode & S_IFMT) == S_IFDIR)
X    return '/';
X else if(mode & 0111)
X    return '*';
X else
X    return '\0';
X}
X
X/*@*Listing in columns.
X * When the "-C" option is used, or an ordinary listing is done to
X * a terminal, then the listing is in columns. As many columns as will
X * fit on the screen are used. The |listcol()| function is used to
X * add an entry to the column entries table, and is called instead
X * of |listone()|. The string which will be printed for each file
X * is stored in the string table. When the listing is done, |dumpcol()|
X * will print it out.
X */
X#ifndef LINEWIDTH
X#define LINEWIDTH	79
X#endif
X
Xint maxwidth = 0;
Xchar *colentries[NFILE];
Xchar **colentp = colentries;
X
X/*@ For now, |listcol()| copies the filename to the string table
X * and leaves a pointer in the column entries table, optinally adding
X * the postfix character for the "-F" option. It also maintains
X * a "maximum width" so that |dumpcols()| can determine how many
X * columns will fit on a terminal line.
X */
Xvoid listcol(entry)
Xregister struct lsfile *entry;
X{
X char *p;
X char c;
X int width;
X
X *colentp = stringp;
X for(p = entry->f_name; *p; )
X    {
X     ADDSTRING(*p++);
X    }
X
X width = stringp - *colentp;
X if(width > maxwidth)
X    maxwidth = width;
X
X if(flags_F && (c = fchar(entry->f_stat.st_mode)) != '\0')
X    ADDSTRING(c);
X
X ADDSTRING('\0');
X ++colentp;
X}
X
X/*@ |dumpcols()| computes how many columns will fit on the output
X * line, and prints the filenames.
X */
Xvoid dumpcols()
X{
X int ncols;			/* number of columns that will fit */
X int nrows;			/* number of rows required */
X int colwidth;			/* how wide the evenly spaced columns are */
X int i;
X char **cp;			/* pointer to column entries */
X
X if(maxwidth == 0)
X    return;
X
X ncols = LINEWIDTH / (maxwidth + 2);
X if(ncols == 0)
X    ncols = 1;
X colwidth = LINEWIDTH / ncols;
X nrows = (colentp - colentries) / ncols;
X if((colentp - colentries) % ncols > 0)
X    ++nrows;
X
X for(i = 0; i < nrows; ++i)	/* loop on rows */
X    {
X     for(cp = &colentries[i]; cp < colentp; cp += nrows) /* loop on columns */
X	{
X	 printf("%-*.*s", colwidth, colwidth, *cp);
X	}
X     putchar('\n');
X    }
X
X colentp = colentries;		/* re-initialize for next listing */
X maxwidth = 0;
X}
X
X/*@*Sorting lists.
X * Sorting is done using the standard library |qsort()| function.
X * Depending on the options, either the file access time, the file
X * modify time, the inode change time, or the filename will be used
X * as the sort key. |sortfiles()| determines what comparison function to
X * use based on this and calls |qsort()|.
X */
Xvoid sortfiles(baseindex, nfiles)
Xint baseindex;
Xint nfiles;
X{
X int (*compare)();
X int comp_atime(), comp_ctime(), comp_mtime(), comp_name();
X
X if(flags_t)
X   {
X    if(flags_u)
X       compare = comp_atime;
X    else if(flags_c)
X       compare = comp_ctime;
X    else
X       compare = comp_mtime;
X   }
X else
X    compare = comp_name;
X
X qsort(&sortindex[baseindex], nfiles, sizeof sortindex[0], compare);
X}
X
Xint comp_atime(one, two)	/* compare access times */
Xstruct lsfile **one;
Xstruct lsfile **two;
X{
X register time_t diff;
X
X diff = (*one)->f_stat.st_atime - (*two)->f_stat.st_atime;
X
X if(diff == 0)
X    return 0;
X else if(diff < 0)
X    return flags_r;
X else
X    return -flags_r;
X}
X
Xint comp_ctime(one, two)	/* compare inode change times */
Xstruct lsfile **one;
Xstruct lsfile **two;
X{
X register time_t diff;
X
X diff = (*one)->f_stat.st_ctime - (*two)->f_stat.st_ctime;
X
X if(diff == 0)
X    return 0;
X else if(diff < 0)
X    return flags_r;
X else
X    return -flags_r;
X}
X
Xint comp_mtime(one, two)	/* compare file modify times */
Xstruct lsfile **one;
Xstruct lsfile **two;
X{
X register time_t diff;
X
X diff = (*one)->f_stat.st_mtime - (*two)->f_stat.st_mtime;
X
X if(diff == 0)
X    return 0;
X else if(diff < 0)
X    return flags_r;
X else
X    return -flags_r;
X}
X
Xint comp_name(one, two)		/* compare filenames */
Xstruct lsfile **one;
Xstruct lsfile **two;
X{
X return flags_r * strcmp((*one)->f_name, (*two)->f_name);
X}
X
X/*@*User and group id's.
X * |owner()| and |groupname()| are used to translate user and group
X * id's to names for the long-format listing. The last id translated
X * is cached.
X */
X#include <pwd.h>
X#include <grp.h>
X
Xchar *owner(uid)
Xint uid;
X{
X static int ouid = -1;
X static char uname[16];
X struct passwd *pw, *getpwuid(/* int uid */);
X
X if(uid == ouid)
X    return uname;
X 
X if((pw = getpwuid(uid)) == NULL)
X    sprintf(uname, "%d", uid);
X else
X    strcpy(uname, pw->pw_name);
X
X return uname;
X}
X 
Xchar *groupname(gid)
Xint gid;
X{
X static int ogid = -1;
X static char gname[16];
X struct group *gr, *getgrgid(/* int gid */);
X
X if(gid == ogid)
X    return gname;
X 
X if((gr = getgrgid(gid)) == NULL)
X    sprintf(gname, "%d", gid);
X else
X    strcpy(gname, gr->gr_name);
X
X return gname;
X}
X
X/*@*Date and time.
X * |prdate()| prints the specified time in the format used by
X * the long-format listing. The month and day are printed, along with the
X * time of day if the time is recent. If it is not, the year is printed
X * instead.
X */
X#include <time.h>
X
X#define LONGAGO	((365L * 86400L) / 2L)		/* about six months */
X
Xvoid prdate(filetime)
Xtime_t filetime;
X{
X struct tm *timep, *localtime(/* time_t *clock */);
X static time_t now;	/* approximate current time */
X 
X static char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
X			  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
X 
X if(now == 0)
X    time(&now);
X
X timep = localtime(&filetime);
X
X printf("%s %2d ", months[timep->tm_mon], timep->tm_mday);
X
X if(now - filetime >= LONGAGO || filetime > now)
X    printf(" %d ", timep->tm_year + 1900);
X else
X    printf("%02d:%02d ", timep->tm_hour, timep->tm_min);
X}
X
X/*@*Counting blocks.
X * This code is 'stolen' almost verbatim from Andrew S. Tannenbaum's
X * original Minix "ls.c" program. It is the primary non-POSIX (OS-dependant)
X * function in the program.
X */
X#include <minix/const.h>
X#include <minix/type.h>
X#include <fs/const.h>
X#include <fs/type.h>
X
Xint nblocks(size)
Xlong size;
X{
X/* Convert file length to blocks, including indirect blocks. */
X
X  int blocks, fileb;
X
X  fileb = (size + (long) BLOCK_SIZE - 1)/BLOCK_SIZE;
X  blocks = fileb;
X  if (fileb <= NR_DZONE_NUM) return(blocks);
X  blocks++;
X  fileb -= NR_DZONE_NUM;
X  if (fileb <= NR_INDIRECTS) return(blocks);
X  blocks++;
X  fileb -= NR_INDIRECTS;
X  blocks += (fileb + NR_INDIRECTS - 1)/NR_INDIRECTS;
X  return(blocks);
X}
**-ls.c-EOF-**


	

wsincc@eutrc3.urc.tue.nl (Wim van Dorst) (07/25/89)

Hello World,

Recently Mr. Housel submitted a ls which does columnar output and
other nice things like not showing .foo files. I compiled it with
no errors under 1.4a and found then the following two flaws.

When there are old files in a directory it should give as
response to ls -l the year of creation instead if the time. Using
for that localtime() it gives the year 3882 instead of 1982. My
localtime() returns the year in 19xx format, but ls.c assumes it
to be in a xx format and adds 1900. Easily fixed though.

The other flaw is that when doing a ls (with or without options)
_without_ arguments (like *.c or whatever) on a directory with quite 
some entries it just does nothing. No error and no result. When 
doing ls (with or without options, with or without arguments) on 
a directory with 1 to 40 entries it works perfect. I have no idea 
why. Who does? 

Probably not a flaw but I noticed: When doing ls -l on an empty
directory, shouldn't ls print something like   total 0?
Problably not a flaw either: when doing ls -lF should the -F
option be ignored?

Hoping for some response, met vriendelijke groeten,
Wim van Dorst (wsincc@tuerc3.urc.tue.nl)

housel@en.ecn.purdue.edu (Peter S. Housel) (07/26/89)

In article <812@eutrc3.urc.tue.nl>, wsincc@eutrc3 (Wim van Dorst) writes:
>When there are old files in a directory it should give as
>response to ls -l the year of creation instead if the time. Using
>for that localtime() it gives the year 3882 instead of 1982. My
>localtime() returns the year in 19xx format, but ls.c assumes it
>to be in a xx format and adds 1900. Easily fixed though.

	Your localtime() is broken. My Unix documentation says
that tm_year contains (year - 1900).

>The other flaw is that when doing a ls (with or without options)
>_without_ arguments (like *.c or whatever) on a directory with quite 
>some entries it just does nothing. No error and no result. When 
>doing ls (with or without options, with or without arguments) on 
>a directory with 1 to 40 entries it works perfect. I have no idea 
>why. Who does? 

	This is likely a problem with the directory library. There should
be a line in <sys/dir.h> which looks like:

	#define DIRBLKSIZ	512

If you don't, add it and recompile the library, and see if this fixes it.
If this line isn't present, some buffer in the ndir library overflows
and it gives up.

>Probably not a flaw but I noticed: When doing ls -l on an empty
>directory, shouldn't ls print something like   total 0?

	Yes, you're right.

>Problably not a flaw either: when doing ls -lF should the -F
>option be ignored?

	No. The BSD and SysV versions don't.

>Hoping for some response, met vriendelijke groeten,
>Wim van Dorst (wsincc@tuerc3.urc.tue.nl)

-Peter S. Housel-	housel@ecn.purdue.edu		...!pur-ee!housel