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