 0ac803292e
			
		
	
	
		0ac803292e
		
	
	
	
	
		
			
			Fix warnings about: . Unused variables . format mismatch in printf/scanf format string and arguments . Missing parenthesis around assignment as truth values . Clang warnings anout unknown GCC pragma
		
			
				
	
	
		
			1155 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1155 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*	ls 5.4 - List files.				Author: Kees J. Bot
 | |
|  *								25 Apr 1989
 | |
|  *
 | |
|  * About the amount of bytes for heap + stack under Minix:
 | |
|  * Ls needs a average amount of 42 bytes per unserviced directory entry, so
 | |
|  * scanning 10 directory levels deep in an ls -R with 100 entries per directory
 | |
|  * takes 42000 bytes of heap.  So giving ls 10000 bytes is tight, 20000 is
 | |
|  * usually enough, 40000 is pessimistic.
 | |
|  */
 | |
| 
 | |
| /* The array l_ifmt[] is used in an 'ls -l' to map the type of a file to a
 | |
|  * letter.  This is done so that ls can list any future file or device type
 | |
|  * other than symlinks, without recompilation.  (Yes it's dirty.)
 | |
|  */
 | |
| char l_ifmt[] = "0pcCd?bB-?l?s???";
 | |
| 
 | |
| #define ifmt(mode)	l_ifmt[((mode) >> 12) & 0xF]
 | |
| 
 | |
| #define nil 0
 | |
| #include <stdio.h>
 | |
| #include <string.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <stddef.h>
 | |
| #include <stdlib.h>
 | |
| #include <unistd.h>
 | |
| #include <dirent.h>
 | |
| #include <time.h>
 | |
| #include <pwd.h>
 | |
| #include <grp.h>
 | |
| #include <errno.h>
 | |
| #include <fcntl.h>
 | |
| #include <limits.h>
 | |
| #include <termios.h>
 | |
| #include <sys/ioctl.h>
 | |
| 
 | |
| #ifndef major
 | |
| #define major(dev)	((int) (((dev) >> 8) & 0xFF))
 | |
| #define minor(dev)	((int) (((dev) >> 0) & 0xFF))
 | |
| #endif
 | |
| 
 | |
| #if !__minix
 | |
| #define SUPER_ID	uid	/* Let -A flag be default for SUPER_ID == 0. */
 | |
| #else
 | |
| #define SUPER_ID	gid
 | |
| #endif
 | |
| 
 | |
| #ifdef S_IFLNK
 | |
| int (*status)(const char *file, struct stat *stp);
 | |
| #else
 | |
| #define status	stat
 | |
| #endif
 | |
| 
 | |
| /* Basic disk block size is 512 except for one niche O.S. */
 | |
| #if __minix
 | |
| #define BLOCK	1024
 | |
| #else
 | |
| #define BLOCK	 512
 | |
| #endif
 | |
| 
 | |
| /* Assume other systems have st_blocks. */
 | |
| #if !__minix
 | |
| #define ST_BLOCKS 1
 | |
| #endif
 | |
| 
 | |
| /* Some terminals ignore more than 80 characters on a line.  Dumb ones wrap
 | |
|  * when the cursor hits the side.  Nice terminals don't wrap until they have
 | |
|  * to print the 81st character.  Whether we like it or not, no column 80.
 | |
|  */
 | |
| int ncols= 79;
 | |
| 
 | |
| #define NSEP	3	/* # spaces between columns. */
 | |
| 
 | |
| #define MAXCOLS	128	/* Max # of files per line. */
 | |
| 
 | |
| char *arg0;	/* Last component of argv[0]. */
 | |
| int uid, gid;	/* callers id. */
 | |
| int ex= 0;	/* Exit status to be. */
 | |
| int istty;	/* Output is on a terminal. */
 | |
| 
 | |
| /* Safer versions of malloc and realloc: */
 | |
| 
 | |
| void heaperr(void)
 | |
| {
 | |
| 	fprintf(stderr, "%s: Out of memory\n", arg0);
 | |
| 	exit(-1);
 | |
| }
 | |
| 
 | |
| void *allocate(size_t n)
 | |
| /* Deliver or die. */
 | |
| {
 | |
| 	void *a;
 | |
| 
 | |
| 	if ((a= malloc(n)) == nil) heaperr();
 | |
| 	return a;
 | |
| }
 | |
| 
 | |
| void *reallocate(void *a, size_t n)
 | |
| {
 | |
| 	if ((a= realloc(a, n)) == nil) heaperr();
 | |
| 	return a;
 | |
| }
 | |
| 
 | |
| char allowed[] = "acdfghilnpqrstu1ACDFLMRTX";
 | |
| char flags[sizeof(allowed)];
 | |
| 
 | |
| char arg0flag[] = "cdfmrtx";	/* These in argv[0] go to upper case. */
 | |
| 
 | |
| void setflags(char *flgs)
 | |
| {
 | |
| 	int c;
 | |
| 
 | |
| 	while ((c= *flgs++) != 0) {
 | |
| 		if (strchr(allowed, c) == nil) {
 | |
| 			fprintf(stderr, "Usage: %s [-%s] [file ...]\n",
 | |
| 				arg0, allowed);
 | |
| 			exit(1);
 | |
| 		} else
 | |
| 		if (strchr(flags, c) == nil) {
 | |
| 			flags[strlen(flags)] = c;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int present(int f)
 | |
| {
 | |
| 	return f == 0 || strchr(flags, f) != nil;
 | |
| }
 | |
| 
 | |
| void report(char *f)
 | |
| /* Like perror(3), but in the style: "ls: junk: No such file or directory. */
 | |
| {
 | |
| 	fprintf(stderr, "%s: %s: %s\n", arg0, f, strerror(errno));
 | |
| 	ex= 1;
 | |
| }
 | |
| 
 | |
| /* Two functions, uidname and gidname, translate id's to readable names.
 | |
|  * All names are remembered to avoid searching the password file.
 | |
|  */
 | |
| #define NNAMES	(1 << (sizeof(int) + sizeof(char *)))
 | |
| enum whatmap { PASSWD, GROUP };
 | |
| 
 | |
| struct idname {		/* Hash list of names. */
 | |
| 	struct idname	*next;
 | |
| 	char		*name;
 | |
| 	uid_t		id;
 | |
| } *uids[NNAMES], *gids[NNAMES];
 | |
| 
 | |
| char *idname(unsigned id, enum whatmap map)
 | |
| /* Return name for a given user/group id. */
 | |
| {
 | |
| 	struct idname *i;
 | |
| 	struct idname **ids= &(map == PASSWD ? uids : gids)[id % NNAMES];
 | |
| 
 | |
| 	while ((i= *ids) != nil && id < i->id) ids= &i->next;
 | |
| 
 | |
| 	if (i == nil || id != i->id) {
 | |
| 		/* Not found, go look in the password or group map. */
 | |
| 		char *name= nil;
 | |
| 		char noname[3 * sizeof(uid_t)];
 | |
| 
 | |
| 		if (!present('n')) {
 | |
| 			if (map == PASSWD) {
 | |
| 				struct passwd *pw= getpwuid(id);
 | |
| 
 | |
| 				if (pw != nil) name= pw->pw_name;
 | |
| 			} else {
 | |
| 				struct group *gr= getgrgid(id);
 | |
| 
 | |
| 				if (gr != nil) name= gr->gr_name;
 | |
| 			}
 | |
| 		}
 | |
| 		if (name == nil) {
 | |
| 			/* Can't find it, weird.  Use numerical "name." */
 | |
| 			sprintf(noname, "%u", id);
 | |
| 			name= noname;
 | |
| 		}
 | |
| 
 | |
| 		/* Add a new id-to-name cell. */
 | |
| 		i= allocate(sizeof(*i));
 | |
| 		i->id= id;
 | |
| 		i->name= allocate(strlen(name) + 1);
 | |
| 		strcpy(i->name, name);
 | |
| 		i->next= *ids;
 | |
| 		*ids= i;
 | |
| 	}
 | |
| 	return i->name;
 | |
| }
 | |
| 
 | |
| #define uidname(uid)	idname((uid), PASSWD)
 | |
| #define gidname(gid)	idname((gid), GROUP)
 | |
| 
 | |
| /* Path name construction, addpath adds a component, delpath removes it.
 | |
|  * The string path is used throughout the program as the file under examination.
 | |
|  */
 | |
| 
 | |
| char *path;	/* Path name constructed in path[]. */
 | |
| int plen= 0, pidx= 0;	/* Lenght/index for path[]. */
 | |
| 
 | |
| void addpath(int *didx, char *name)
 | |
| /* Add a component to path. (name may also be a full path at the first call)
 | |
|  * The index where the current path ends is stored in *pdi.
 | |
|  */
 | |
| {
 | |
| 	if (plen == 0) path= (char *) allocate((plen= 32) * sizeof(path[0]));
 | |
| 
 | |
| 	if (pidx == 1 && path[0] == '.') pidx= 0;	/* Remove "." */
 | |
| 
 | |
| 	*didx= pidx;	/* Record point to go back to for delpath. */
 | |
| 
 | |
| 	if (pidx > 0 && path[pidx-1] != '/') path[pidx++]= '/';
 | |
| 
 | |
| 	do {
 | |
| 		if (*name != '/' || pidx == 0 || path[pidx-1] != '/') {
 | |
| 			if (pidx == plen) {
 | |
| 				path= (char *) reallocate((void *) path,
 | |
| 						(plen*= 2) * sizeof(path[0]));
 | |
| 			}
 | |
| 			path[pidx++]= *name;
 | |
| 		}
 | |
| 	} while (*name++ != 0);
 | |
| 
 | |
| 	--pidx;		/* Put pidx back at the null.  The path[pidx++]= '/'
 | |
| 			 * statement will overwrite it at the next call.
 | |
| 			 */
 | |
| }
 | |
| 
 | |
| #define delpath(didx)	(path[pidx= didx]= 0)	/* Remove component. */
 | |
| 
 | |
| int field = 0;	/* (used to be) Fields that must be printed. */
 | |
| 		/* (now) Effects triggered by certain flags. */
 | |
| 
 | |
| #define L_INODE		0x0001	/* -i */
 | |
| #define L_BLOCKS	0x0002	/* -s */
 | |
| #define L_EXTRA		0x0004	/* -X */
 | |
| #define L_MODE		0x0008	/* -lMX */
 | |
| #define L_LONG		0x0010	/* -l */
 | |
| #define L_GROUP		0x0020	/* -g */
 | |
| #define L_BYTIME	0x0040	/* -tuc */
 | |
| #define L_ATIME		0x0080	/* -u */
 | |
| #define L_CTIME		0x0100	/* -c */
 | |
| #define L_MARK		0x0200	/* -F */
 | |
| #define L_MARKDIR	0x0400	/* -p */
 | |
| #define L_TYPE		0x0800	/* -D */
 | |
| #define L_LONGTIME	0x1000	/* -T */
 | |
| #define L_DIR		0x2000	/* -d */
 | |
| #define L_KMG		0x4000	/* -h */
 | |
| 
 | |
| struct file {		/* A file plus stat(2) information. */
 | |
| 	struct file	*next;	/* Lists are made of them. */
 | |
| 	char		*name;	/* Null terminated name. */
 | |
| 	ino_t		ino;
 | |
| 	mode_t		mode;
 | |
| 	uid_t		uid;
 | |
| 	gid_t		gid;
 | |
| 	nlink_t		nlink;
 | |
| 	dev_t		rdev;
 | |
| 	off_t		size;
 | |
| 	time_t		mtime;
 | |
| 	time_t		atime;
 | |
| 	time_t		ctime;
 | |
| #if ST_BLOCKS
 | |
| 	long		blocks;
 | |
| #endif
 | |
| };
 | |
| 
 | |
| void setstat(struct file *f, struct stat *stp)
 | |
| {
 | |
| 	f->ino=		stp->st_ino;
 | |
| 	f->mode=	stp->st_mode;
 | |
| 	f->nlink=	stp->st_nlink;
 | |
| 	f->uid=		stp->st_uid;
 | |
| 	f->gid=		stp->st_gid;
 | |
| 	f->rdev=	stp->st_rdev;
 | |
| 	f->size=	stp->st_size;
 | |
| 	f->mtime=	stp->st_mtime;
 | |
| 	f->atime=	stp->st_atime;
 | |
| 	f->ctime=	stp->st_ctime;
 | |
| #if ST_BLOCKS
 | |
| 	f->blocks=	stp->st_blocks;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| #define	PAST	(26*7*24*3600L)	/* Half a year ago. */
 | |
| /* Between PAST and FUTURE from now a time is printed, otherwise a year. */
 | |
| #define FUTURE	( 1*7*24*3600L)	/* One week. */
 | |
| 
 | |
| static char *timestamp(struct file *f)
 | |
| /* Transform the right time field into something readable. */
 | |
| {
 | |
| 	struct tm *tm;
 | |
| 	time_t t;
 | |
| 	static time_t now;
 | |
| 	static int drift= 0;
 | |
| 	static char date[] = "Jan 19 03:14:07 2038";
 | |
| 	static char month[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
 | |
| 
 | |
| 	t= f->mtime;
 | |
| 	if (field & L_ATIME) t= f->atime;
 | |
| 	if (field & L_CTIME) t= f->ctime;
 | |
| 
 | |
| 	tm= localtime(&t);
 | |
| 	if (--drift < 0) { time(&now); drift= 50; }	/* limit time() calls */
 | |
| 
 | |
| 	if (field & L_LONGTIME) {
 | |
| 		sprintf(date, "%.3s %2d %02d:%02d:%02d %d",
 | |
| 			month + 3*tm->tm_mon,
 | |
| 			tm->tm_mday,
 | |
| 			tm->tm_hour, tm->tm_min, tm->tm_sec,
 | |
| 			1900 + tm->tm_year);
 | |
| 	} else
 | |
| 	if (t < now - PAST || t > now + FUTURE) {
 | |
| 		sprintf(date, "%.3s %2d  %d",
 | |
| 			month + 3*tm->tm_mon,
 | |
| 			tm->tm_mday,
 | |
| 			1900 + tm->tm_year);
 | |
| 	} else {
 | |
| 		sprintf(date, "%.3s %2d %02d:%02d",
 | |
| 			month + 3*tm->tm_mon,
 | |
| 			tm->tm_mday,
 | |
| 			tm->tm_hour, tm->tm_min);
 | |
| 	}
 | |
| 	return date;
 | |
| }
 | |
| 
 | |
| char *permissions(struct file *f)
 | |
| /* Compute long or short rwx bits. */
 | |
| {
 | |
| 	static char rwx[] = "drwxr-x--x";
 | |
| 
 | |
| 	rwx[0] = ifmt(f->mode);
 | |
| 	/* Note that rwx[0] is a guess for the more alien file types.  It is
 | |
| 	 * correct for BSD4.3 and derived systems.  I just don't know how
 | |
| 	 * "standardized" these numbers are.
 | |
| 	 */
 | |
| 
 | |
| 	if (field & L_EXTRA) {		/* Short style */
 | |
| 		int mode = f->mode, ucase= 0;
 | |
| 
 | |
| 		if (uid == f->uid) {	/* What group of bits to use. */
 | |
| 			/* mode<<= 0, */
 | |
| 			ucase= (mode<<3) | (mode<<6);
 | |
| 			/* Remember if group or others have permissions. */
 | |
| 		} else
 | |
| 		if (gid == f->gid) {
 | |
| 			mode<<= 3;
 | |
| 		} else {
 | |
| 			mode<<= 6;
 | |
| 		}
 | |
| 		rwx[1]= mode&S_IRUSR ? (ucase&S_IRUSR ? 'R' : 'r') : '-';
 | |
| 		rwx[2]= mode&S_IWUSR ? (ucase&S_IWUSR ? 'W' : 'w') : '-';
 | |
| 
 | |
| 		if (mode&S_IXUSR) {
 | |
| 			static char sbit[]= { 'x', 'g', 'u', 's' };
 | |
| 
 | |
| 			rwx[3]= sbit[(f->mode&(S_ISUID|S_ISGID))>>10];
 | |
| 			if (ucase&S_IXUSR) rwx[3] += 'A'-'a';
 | |
| 		} else {
 | |
| 			rwx[3]= f->mode&(S_ISUID|S_ISGID) ? '=' : '-';
 | |
| 		}
 | |
| 		rwx[4]= 0;
 | |
| 	} else {		/* Long form. */
 | |
| 		char *p= rwx+1;
 | |
| 		int mode= f->mode;
 | |
| 
 | |
| 		do {
 | |
| 			p[0] = (mode & S_IRUSR) ? 'r' : '-';
 | |
| 			p[1] = (mode & S_IWUSR) ? 'w' : '-';
 | |
| 			p[2] = (mode & S_IXUSR) ? 'x' : '-';
 | |
| 			mode<<= 3;
 | |
| 		} while ((p+=3) <= rwx+7);
 | |
| 
 | |
| 		if (f->mode&S_ISUID) rwx[3]= f->mode&(S_IXUSR>>0) ? 's' : '=';
 | |
| 		if (f->mode&S_ISGID) rwx[6]= f->mode&(S_IXUSR>>3) ? 's' : '=';
 | |
| 		if (f->mode&S_ISVTX) rwx[9]= f->mode&(S_IXUSR>>6) ? 't' : '=';
 | |
| 	}
 | |
| 	return rwx;
 | |
| }
 | |
| 
 | |
| void numeral(int i, char **pp)
 | |
| {
 | |
| 	char itoa[3*sizeof(int)], *a=itoa;
 | |
| 
 | |
| 	do *a++ = i%10 + '0'; while ((i/=10) > 0);
 | |
| 
 | |
| 	do *(*pp)++ = *--a; while (a>itoa);
 | |
| }
 | |
| 
 | |
| #define K	1024L		/* A kilobyte counts in multiples of K */
 | |
| #define T	1000L		/* A megabyte in T*K, a gigabyte in T*T*K */
 | |
| 
 | |
| char *cxsize(struct file *f)
 | |
| /* Try and fail to turn a 32 bit size into 4 readable characters. */
 | |
| {
 | |
| 	static char siz[] = "1.2m";
 | |
| 	char *p= siz;
 | |
| 	off_t z;
 | |
| 
 | |
| 	siz[1]= siz[2]= siz[3]= 0;
 | |
| 
 | |
| 	if (f->size <= 5*K) {	/* <= 5K prints as is. */
 | |
| 		numeral((int) f->size, &p);
 | |
| 		return siz;
 | |
| 	}
 | |
| 	z= (f->size + K-1) / K;
 | |
| 
 | |
| 	if (z <= 999) {		/* Print as 123k. */
 | |
| 		numeral((int) z, &p);
 | |
| 		*p = 'k';	/* Can't use 'K', looks bad */
 | |
| 	} else
 | |
| 	if (z*10 <= 99*T) {	/* 1.2m (Try ls -X /dev/at0) */
 | |
| 		z= (z*10 + T-1) / T;	/* Force roundup */
 | |
| 		numeral((int) z / 10, &p);
 | |
| 		*p++ = '.';
 | |
| 		numeral((int) z % 10, &p);
 | |
| 		*p = 'm';
 | |
| 	} else
 | |
| 	if (z <= 999*T) {	/* 123m */
 | |
| 		numeral((int) ((z + T-1) / T), &p);
 | |
| 		*p = 'm';
 | |
| 	} else {		/* 1.2g */
 | |
| 		z= (z*10 + T*T-1) / (T*T);
 | |
| 		numeral((int) z / 10, &p);
 | |
| 		*p++ = '.';
 | |
| 		numeral((int) z % 10, &p);
 | |
| 		*p = 'g';
 | |
| 	}
 | |
| 	return siz;
 | |
| }
 | |
| 
 | |
| /* Transform size of file to number of blocks.  This was once a function that
 | |
|  * guessed the number of indirect blocks, but that nonsense has been removed.
 | |
|  */
 | |
| #if ST_BLOCKS
 | |
| #define nblocks(f)	((f)->blocks)
 | |
| #else
 | |
| #define nblocks(f)	(((f)->size + BLOCK-1) / BLOCK)
 | |
| #endif
 | |
| 
 | |
| /* From number of blocks to kilobytes. */
 | |
| #if BLOCK < 1024
 | |
| #define nblk2k(nb)	(((nb) + (1024 / BLOCK - 1)) / (1024 / BLOCK))
 | |
| #else
 | |
| #define nblk2k(nb)	((nb) * (BLOCK / 1024))
 | |
| #endif
 | |
| 
 | |
| static int (*CMP)(struct file *f1, struct file *f2);
 | |
| static int (*rCMP)(struct file *f1, struct file *f2);
 | |
| 
 | |
| #ifdef __NBSD_LIBC
 | |
| #define mergesort _ls_mergesort
 | |
| #endif
 | |
| 
 | |
| static void mergesort(struct file **al)
 | |
| /* This is either a stable mergesort, or thermal noise, I'm no longer sure.
 | |
|  * It must be called like this: if (L != nil && L->next != nil) mergesort(&L);
 | |
|  */
 | |
| {
 | |
| 	/* static */ struct file *l1, **mid;  /* Need not be local */
 | |
| 	struct file *l2;
 | |
| 
 | |
| 	l1= *(mid= &(*al)->next);
 | |
| 	do {
 | |
| 		if ((l1= l1->next) == nil) break;
 | |
| 		mid= &(*mid)->next;
 | |
| 	} while ((l1= l1->next) != nil);
 | |
| 
 | |
| 	l2= *mid;
 | |
| 	*mid= nil;
 | |
| 
 | |
| 	if ((*al)->next != nil) mergesort(al);
 | |
| 	if (l2->next != nil) mergesort(&l2);
 | |
| 
 | |
| 	l1= *al;
 | |
| 	for (;;) {
 | |
| 		if ((*CMP)(l1, l2) <= 0) {
 | |
| 			if ((l1= *(al= &l1->next)) == nil) {
 | |
| 				*al= l2;
 | |
| 				break;
 | |
| 			}
 | |
| 		} else {
 | |
| 			*al= l2;
 | |
| 			l2= *(al= &l2->next);
 | |
| 			*al= l1;
 | |
| 			if (l2 == nil) break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int namecmp(struct file *f1, struct file *f2)
 | |
| {
 | |
| 	return strcmp(f1->name, f2->name);
 | |
| }
 | |
| 
 | |
| int mtimecmp(struct file *f1, struct file *f2)
 | |
| {
 | |
| 	return f1->mtime == f2->mtime ? 0 : f1->mtime > f2->mtime ? -1 : 1;
 | |
| }
 | |
| 
 | |
| int atimecmp(struct file *f1, struct file *f2)
 | |
| {
 | |
| 	return f1->atime == f2->atime ? 0 : f1->atime > f2->atime ? -1 : 1;
 | |
| }
 | |
| 
 | |
| int ctimecmp(struct file *f1, struct file *f2)
 | |
| {
 | |
| 	return f1->ctime == f2->ctime ? 0 : f1->ctime > f2->ctime ? -1 : 1;
 | |
| }
 | |
| 
 | |
| int typecmp(struct file *f1, struct file *f2)
 | |
| {
 | |
| 	return ifmt(f1->mode) - ifmt(f2->mode);
 | |
| }
 | |
| 
 | |
| int revcmp(struct file *f1, struct file *f2) { return (*rCMP)(f2, f1); }
 | |
| 
 | |
| static void sort(struct file **al)
 | |
| /* Sort the files according to the flags. */
 | |
| {
 | |
| 	if (!present('f') && *al != nil && (*al)->next != nil) {
 | |
| 		CMP= namecmp;
 | |
| 
 | |
| 		if (!(field & L_BYTIME)) {
 | |
| 			/* Sort on name */
 | |
| 
 | |
| 			if (present('r')) { rCMP= CMP; CMP= revcmp; }
 | |
| 			mergesort(al);
 | |
| 		} else {
 | |
| 			/* Sort on name first, then sort on time. */
 | |
| 
 | |
| 			mergesort(al);
 | |
| 			if (field & L_CTIME) {
 | |
| 				CMP= ctimecmp;
 | |
| 			} else
 | |
| 			if (field & L_ATIME) {
 | |
| 				CMP= atimecmp;
 | |
| 			} else {
 | |
| 				CMP= mtimecmp;
 | |
| 			}
 | |
| 
 | |
| 			if (present('r')) { rCMP= CMP; CMP= revcmp; }
 | |
| 			mergesort(al);
 | |
| 		}
 | |
| 		/* Separate by file type if so desired. */
 | |
| 
 | |
| 		if (field & L_TYPE) {
 | |
| 			CMP= typecmp;
 | |
| 			mergesort(al);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| struct file *newfile(char *name)
 | |
| /* Create file structure for given name. */
 | |
| {
 | |
| 	struct file *new;
 | |
| 
 | |
| 	new= (struct file *) allocate(sizeof(*new));
 | |
| 	new->name= strcpy((char *) allocate(strlen(name)+1), name);
 | |
| 	return new;
 | |
| }
 | |
| 
 | |
| void pushfile(struct file **flist, struct file *new)
 | |
| /* Add file to the head of a list. */
 | |
| {
 | |
| 	new->next= *flist;
 | |
| 	*flist= new;
 | |
| }
 | |
| 
 | |
| void delfile(struct file *old)
 | |
| /* Release old file structure. */
 | |
| {
 | |
| 	free((void *) old->name);
 | |
| 	free((void *) old);
 | |
| }
 | |
| 
 | |
| struct file *popfile(struct file **flist)
 | |
| /* Pop file off top of file list. */
 | |
| {
 | |
| 	struct file *f;
 | |
| 
 | |
| 	f= *flist;
 | |
| 	*flist= f->next;
 | |
| 	return f;
 | |
| }
 | |
| 
 | |
| int dotflag(char *name)
 | |
| /* Return flag that would make ls list this name: -a or -A. */
 | |
| {
 | |
| 	if (*name++ != '.') return 0;
 | |
| 
 | |
| 	switch (*name++) {
 | |
| 	case 0:		return 'a';			/* "." */
 | |
| 	case '.':	if (*name == 0) return 'a';	/* ".." */
 | |
| 	default:	return 'A';			/* ".*" */
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int adddir(struct file **aflist, char *name)
 | |
| /* Add directory entries of directory name to a file list. */
 | |
| {
 | |
| 	DIR *d;
 | |
| 	struct dirent *e;
 | |
| 
 | |
| 	if (access(name, 0) < 0) {
 | |
| 		report(name);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if ((d= opendir(name)) == nil) {
 | |
| 		report(name);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	while ((e= readdir(d)) != nil) {
 | |
| 		if (e->d_ino != 0 && present(dotflag(e->d_name))) {
 | |
| 			pushfile(aflist, newfile(e->d_name));
 | |
| 			aflist= &(*aflist)->next;
 | |
| 		}
 | |
| 	}
 | |
| 	closedir(d);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| off_t countblocks(struct file *flist)
 | |
| /* Compute total block count for a list of files. */
 | |
| {
 | |
| 	off_t cb = 0;
 | |
| 
 | |
| 	while (flist != nil) {
 | |
| 		switch (flist->mode & S_IFMT) {
 | |
| 		case S_IFDIR:
 | |
| 		case S_IFREG:
 | |
| #ifdef S_IFLNK
 | |
| 		case S_IFLNK:
 | |
| #endif
 | |
| 			cb += nblocks(flist);
 | |
| 		}
 | |
| 		flist= flist->next;
 | |
| 	}
 | |
| 	return cb;
 | |
| }
 | |
| 
 | |
| void printname(char *name)
 | |
| /* Print a name with control characters as '?' (unless -q).  The terminal is
 | |
|  * assumed to be eight bit clean.
 | |
|  */
 | |
| {
 | |
| 	int c, q= present('q');
 | |
| 
 | |
| 	while ((c= (unsigned char) *name++) != 0) {
 | |
| 		if (q && (c < ' ' || c == 0177)) c= '?';
 | |
| 		putchar(c);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int mark(struct file *f, int doit)
 | |
| {
 | |
| 	int c;
 | |
| 
 | |
| 	c= 0;
 | |
| 
 | |
| 	if (field & L_MARK) {
 | |
| 		switch (f->mode & S_IFMT) {
 | |
| 		case S_IFDIR:	c= '/'; break;
 | |
| #ifdef S_IFIFO
 | |
| 		case S_IFIFO:	c= '|'; break;
 | |
| #endif
 | |
| #ifdef S_IFLNK
 | |
| 		case S_IFLNK:	c= '@'; break;
 | |
| #endif
 | |
| #ifdef S_IFSOCK
 | |
| 		case S_IFSOCK:	c= '='; break;
 | |
| #endif
 | |
| 		case S_IFREG:
 | |
| 			if (f->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) c= '*';
 | |
| 			break;
 | |
| 		}
 | |
| 	} else
 | |
| 	if (field & L_MARKDIR) {
 | |
| 		if (S_ISDIR(f->mode)) c= '/';
 | |
| 	}
 | |
| 
 | |
| 	if (doit && c != 0) putchar(c);
 | |
| 	return c;
 | |
| }
 | |
| 
 | |
| /* Width of entire column, and of several fields. */
 | |
| enum { W_COL, W_INO, W_BLK, W_NLINK, W_UID, W_GID, W_SIZE, W_NAME, MAXFLDS };
 | |
| 
 | |
| unsigned char fieldwidth[MAXCOLS][MAXFLDS];
 | |
| 
 | |
| void maxise(unsigned char *aw, int w)
 | |
| /* Set *aw to the larger of it and w. */
 | |
| {
 | |
| 	if (w > *aw) {
 | |
| 		if (w > UCHAR_MAX) w= UCHAR_MAX;
 | |
| 		*aw= w;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int numwidth(unsigned long n)
 | |
| /* Compute width of 'n' when printed. */
 | |
| {
 | |
| 	int width= 0;
 | |
| 
 | |
| 	do { width++; } while ((n /= 10) > 0);
 | |
| 	return width;
 | |
| }
 | |
| 
 | |
| #if !__minix
 | |
| int numxwidth(unsigned long n)
 | |
| /* Compute width of 'n' when printed in hex. */
 | |
| {
 | |
| 	int width= 0;
 | |
| 
 | |
| 	do { width++; } while ((n /= 16) > 0);
 | |
| 	return width;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static int nsp= 0;	/* This many spaces have not been printed yet. */
 | |
| #define spaces(n)	(nsp= (n))
 | |
| #define terpri()	(nsp= 0, putchar('\n'))		/* No trailing spaces */
 | |
| 
 | |
| void print1(struct file *f, int col, int doit)
 | |
| /* Either compute the number of spaces needed to print file f (doit == 0) or
 | |
|  * really print it (doit == 1).
 | |
|  */
 | |
| {
 | |
| 	int width= 0, n;
 | |
| 	char *p;
 | |
| 	unsigned char *f1width = fieldwidth[col];
 | |
| 
 | |
| 	while (nsp>0) { putchar(' '); nsp--; }/* Fill gap between two columns */
 | |
| 
 | |
| 	if (field & L_INODE) {
 | |
| 		if (doit) {
 | |
| 			printf("%*d ", f1width[W_INO], f->ino);
 | |
| 		} else {
 | |
| 			maxise(&f1width[W_INO], numwidth(f->ino));
 | |
| 			width++;
 | |
| 		}
 | |
| 	}
 | |
| 	if (field & L_BLOCKS) {
 | |
| 		unsigned long nb= nblk2k(nblocks(f));
 | |
| 		if (doit) {
 | |
| 			printf("%*lu ", f1width[W_BLK], nb);
 | |
| 		} else {
 | |
| 			maxise(&f1width[W_BLK], numwidth(nb));
 | |
| 			width++;
 | |
| 		}
 | |
| 	}
 | |
| 	if (field & L_MODE) {
 | |
| 		if (doit) {
 | |
| 			printf("%s ", permissions(f));
 | |
| 		} else {
 | |
| 			width+= (field & L_EXTRA) ? 5 : 11;
 | |
| 		}
 | |
| 	}
 | |
| 	if (field & L_EXTRA) {
 | |
| 		p= cxsize(f);
 | |
| 		n= strlen(p)+1;
 | |
| 
 | |
| 		if (doit) {
 | |
| 			n= f1width[W_SIZE] - n;
 | |
| 			while (n > 0) { putchar(' '); --n; }
 | |
| 			printf("%s ", p);
 | |
| 		} else {
 | |
| 			maxise(&f1width[W_SIZE], n);
 | |
| 		}
 | |
| 	}
 | |
| 	if (field & L_LONG) {
 | |
| 		if (doit) {
 | |
| 			printf("%*u ", f1width[W_NLINK], (unsigned) f->nlink);
 | |
| 		} else {
 | |
| 			maxise(&f1width[W_NLINK], numwidth(f->nlink));
 | |
| 			width++;
 | |
| 		}
 | |
| 		if (!(field & L_GROUP)) {
 | |
| 			if (doit) {
 | |
| 				printf("%-*s  ", f1width[W_UID],
 | |
| 							uidname(f->uid));
 | |
| 			} else {
 | |
| 				maxise(&f1width[W_UID],
 | |
| 						strlen(uidname(f->uid)));
 | |
| 				width+= 2;
 | |
| 			}
 | |
| 		}
 | |
| 		if (doit) {
 | |
| 			printf("%-*s  ", f1width[W_GID], gidname(f->gid));
 | |
| 		} else {
 | |
| 			maxise(&f1width[W_GID], strlen(gidname(f->gid)));
 | |
| 			width+= 2;
 | |
| 		}
 | |
| 
 | |
| 		switch (f->mode & S_IFMT) {
 | |
| 		case S_IFBLK:
 | |
| 		case S_IFCHR:
 | |
| #ifdef S_IFMPB
 | |
| 		case S_IFMPB:
 | |
| #endif
 | |
| #ifdef S_IFMPC
 | |
| 		case S_IFMPC:
 | |
| #endif
 | |
| #if __minix
 | |
| 			if (doit) {
 | |
| 				printf("%*d, %3d ", f1width[W_SIZE] - 5,
 | |
| 					major(f->rdev), minor(f->rdev));
 | |
| 			} else {
 | |
| 				maxise(&f1width[W_SIZE],
 | |
| 						numwidth(major(f->rdev)) + 5);
 | |
| 				width++;
 | |
| 			}
 | |
| #else /* !__minix */
 | |
| 			if (doit) {
 | |
| 				printf("%*lX ", f1width[W_SIZE],
 | |
| 					(unsigned long) f->rdev);
 | |
| 			} else {
 | |
| 				maxise(&f1width[W_SIZE], numwidth(f->rdev));
 | |
| 				width++;
 | |
| 			}
 | |
| #endif /* !__minix */
 | |
| 			break;
 | |
| 		default:
 | |
| 			if (field & L_KMG) {
 | |
| 				p= cxsize(f);
 | |
| 				n= strlen(p)+1;
 | |
| 
 | |
| 				if (doit) {
 | |
| 					n= f1width[W_SIZE] - n;
 | |
| 					while (n > 0) { putchar(' '); --n; }
 | |
| 					printf("%s ", p);
 | |
| 				} else {
 | |
| 					maxise(&f1width[W_SIZE], n);
 | |
| 				}
 | |
| 			} else {
 | |
| 				if (doit) {
 | |
| 					printf("%*lu ", f1width[W_SIZE],
 | |
| 						(unsigned long) f->size);
 | |
| 				} else {
 | |
| 					maxise(&f1width[W_SIZE],
 | |
| 						numwidth(f->size));
 | |
| 					width++;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (doit) {
 | |
| 			printf("%s ", timestamp(f));
 | |
| 		} else {
 | |
| 			width+= (field & L_LONGTIME) ? 21 : 13;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	n= strlen(f->name);
 | |
| 	if (doit) {
 | |
| 		printname(f->name);
 | |
| 		if (mark(f, 1) != 0) n++;
 | |
| #ifdef S_IFLNK
 | |
| 		if ((field & L_LONG) && (f->mode & S_IFMT) == S_IFLNK) {
 | |
| 			char *buf;
 | |
| 			int r, didx;
 | |
| 
 | |
| 			buf= (char *) allocate(((size_t) f->size + 1)
 | |
| 							* sizeof(buf[0]));
 | |
| 			addpath(&didx, f->name);
 | |
| 			r= readlink(path, buf, (int) f->size);
 | |
| 			delpath(didx);
 | |
| 			if (r > 0) buf[r] = 0; else r=1, strcpy(buf, "?");
 | |
| 			printf(" -> ");
 | |
| 			printname(buf);
 | |
| 			free((void *) buf);
 | |
| 			n+= 4 + r;
 | |
| 		}
 | |
| #endif
 | |
| 		spaces(f1width[W_NAME] - n);
 | |
| 	} else {
 | |
| 		if (mark(f, 0) != 0) n++;
 | |
| #ifdef S_IFLNK
 | |
| 		if ((field & L_LONG) && (f->mode & S_IFMT) == S_IFLNK) {
 | |
| 			n+= 4 + (int) f->size;
 | |
| 		}
 | |
| #endif
 | |
| 		maxise(&f1width[W_NAME], n + NSEP);
 | |
| 
 | |
| 		for (n= 1; n < MAXFLDS; n++) width+= f1width[n];
 | |
| 		maxise(&f1width[W_COL], width);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int countfiles(struct file *flist)
 | |
| /* Return number of files in the list. */
 | |
| {
 | |
| 	int n= 0;
 | |
| 
 | |
| 	while (flist != nil) { n++; flist= flist->next; }
 | |
| 
 | |
| 	return n;
 | |
| }
 | |
| 
 | |
| struct file *filecol[MAXCOLS];	/* filecol[i] is list of files for column i. */
 | |
| int nfiles, nlines;	/* # files to print, # of lines needed. */
 | |
| 
 | |
| void columnise(struct file *flist, int nplin)
 | |
| /* Chop list of files up in columns.  Note that 3 columns are used for 5 files
 | |
|  * even though nplin may be 4, filecol[3] will simply be nil.
 | |
|  */
 | |
| {
 | |
| 	int i, j;
 | |
| 
 | |
| 	nlines= (nfiles + nplin - 1) / nplin;	/* nlines needed for nfiles */
 | |
| 
 | |
| 	filecol[0]= flist;
 | |
| 
 | |
| 	for (i=1; i<nplin; i++) {	/* Give nlines files to each column. */
 | |
| 		for (j=0; j<nlines && flist != nil; j++) flist= flist->next;
 | |
| 
 | |
| 		filecol[i]= flist;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int print(struct file *flist, int nplin, int doit)
 | |
| /* Try (doit == 0), or really print the list of files over nplin columns.
 | |
|  * Return true if it can be done in nplin columns or if nplin == 1.
 | |
|  */
 | |
| {
 | |
| 	register struct file *f;
 | |
| 	register int col, fld, totlen;
 | |
| 
 | |
| 	columnise(flist, nplin);
 | |
| 
 | |
| 	if (!doit) {
 | |
| 		for (col= 0; col < nplin; col++) {
 | |
| 			for (fld= 0; fld < MAXFLDS; fld++) {
 | |
| 				fieldwidth[col][fld]= 0;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	while (--nlines >= 0) {
 | |
| 		totlen= 0;
 | |
| 
 | |
| 		for (col= 0; col < nplin; col++) {
 | |
| 			if ((f= filecol[col]) != nil) {
 | |
| 				filecol[col]= f->next;
 | |
| 				print1(f, col, doit);
 | |
| 			}
 | |
| 			if (!doit && nplin > 1) {
 | |
| 				/* See if this line is not too long. */
 | |
| 				if (fieldwidth[col][W_COL] == UCHAR_MAX) {
 | |
| 					return 0;
 | |
| 				}
 | |
| 				totlen+= fieldwidth[col][W_COL];
 | |
| 				if (totlen > ncols+NSEP) return 0;
 | |
| 			}
 | |
| 		}
 | |
| 		if (doit) terpri();
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| enum depth { SURFACE, SURFACE1, SUBMERGED };
 | |
| enum state { BOTTOM, SINKING, FLOATING };
 | |
| 
 | |
| void listfiles(struct file *flist, enum depth depth, enum state state)
 | |
| /* Main workhorse of ls, it sorts and prints the list of files.  Flags:
 | |
|  * depth: working with the command line / just one file / listing dir.
 | |
|  * state: How "recursive" do we have to be.
 | |
|  */
 | |
| {
 | |
| 	struct file *dlist= nil, **afl= &flist, **adl= &dlist;
 | |
| 	int nplin;
 | |
| 	static int white = 1;	/* Nothing printed yet. */
 | |
| 
 | |
| 	/* Flush everything previously printed, so new error output will
 | |
| 	 * not intermix with files listed earlier.
 | |
| 	 */
 | |
| 	fflush(stdout);
 | |
| 
 | |
| 	if (field != 0 || state != BOTTOM) {	/* Need stat(2) info. */
 | |
| 		while (*afl != nil) {
 | |
| 			static struct stat st;
 | |
| 			int r, didx;
 | |
| 
 | |
| 			addpath(&didx, (*afl)->name);
 | |
| 
 | |
| 			if ((r= status(path, &st)) < 0
 | |
| #ifdef S_IFLNK
 | |
| 				&& (status == lstat || lstat(path, &st) < 0)
 | |
| #endif
 | |
| 			) {
 | |
| 				if (depth != SUBMERGED || errno != ENOENT)
 | |
| 					report((*afl)->name);
 | |
| 				delfile(popfile(afl));
 | |
| 			} else {
 | |
| 				setstat(*afl, &st);
 | |
| 				afl= &(*afl)->next;
 | |
| 			}
 | |
| 			delpath(didx);
 | |
| 		}
 | |
| 	}
 | |
| 	sort(&flist);
 | |
| 
 | |
| 	if (depth == SUBMERGED && (field & (L_BLOCKS | L_LONG))) {
 | |
| 		printf("total %d\n", nblk2k(countblocks(flist)));
 | |
| 	}
 | |
| 
 | |
| 	if (state == SINKING || depth == SURFACE1) {
 | |
| 	/* Don't list directories themselves, list their contents later. */
 | |
| 		afl= &flist;
 | |
| 		while (*afl != nil) {
 | |
| 			if (((*afl)->mode & S_IFMT) == S_IFDIR) {
 | |
| 				pushfile(adl, popfile(afl));
 | |
| 				adl= &(*adl)->next;
 | |
| 			} else {
 | |
| 				afl= &(*afl)->next;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if ((nfiles= countfiles(flist)) > 0) {
 | |
| 		/* Print files in how many columns? */
 | |
| 		nplin= !present('C') ? 1 : nfiles < MAXCOLS ? nfiles : MAXCOLS;
 | |
| 
 | |
| 		while (!print(flist, nplin, 0)) nplin--;	/* Try first */
 | |
| 
 | |
| 		print(flist, nplin, 1);		/* Then do it! */
 | |
| 		white = 0;
 | |
| 	}
 | |
| 
 | |
| 	while (flist != nil) {	/* Destroy file list */
 | |
| 		if (state == FLOATING && (flist->mode & S_IFMT) == S_IFDIR) {
 | |
| 			/* But keep these directories for ls -R. */
 | |
| 			pushfile(adl, popfile(&flist));
 | |
| 			adl= &(*adl)->next;
 | |
| 		} else {
 | |
| 			delfile(popfile(&flist));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	while (dlist != nil) {	/* List directories */
 | |
| 		if (dotflag(dlist->name) != 'a' || depth != SUBMERGED) {
 | |
| 			int didx;
 | |
| 
 | |
| 			addpath(&didx, dlist->name);
 | |
| 
 | |
| 			flist= nil;
 | |
| 			if (adddir(&flist, path)) {
 | |
| 				if (depth != SURFACE1) {
 | |
| 					if (!white) putchar('\n');
 | |
| 					printf("%s:\n", path);
 | |
| 					white = 0;
 | |
| 				}
 | |
| 				listfiles(flist, SUBMERGED,
 | |
| 					state == FLOATING ? FLOATING : BOTTOM);
 | |
| 			}
 | |
| 			delpath(didx);
 | |
| 		}
 | |
| 		delfile(popfile(&dlist));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int main(int argc, char **argv)
 | |
| {
 | |
| 	struct file *flist= nil, **aflist= &flist;
 | |
| 	enum depth depth;
 | |
| 	char *lsflags;
 | |
| 	struct winsize ws;
 | |
| 
 | |
| 	uid= geteuid();
 | |
| 	gid= getegid();
 | |
| 
 | |
| 	if ((arg0= strrchr(argv[0], '/')) == nil) arg0= argv[0]; else arg0++;
 | |
| 	argv++;
 | |
| 
 | |
| 	if (strcmp(arg0, "ls") != 0) {
 | |
| 		char *p= arg0+1;
 | |
| 
 | |
| 		while (*p != 0) {
 | |
| 			if (strchr(arg0flag, *p) != nil) *p += 'A' - 'a';
 | |
| 			p++;
 | |
| 		}
 | |
| 		setflags(arg0+1);
 | |
| 	}
 | |
| 	while (*argv != nil && (*argv)[0] == '-') {
 | |
| 		if ((*argv)[1] == '-' && (*argv)[2] == 0) {
 | |
| 			argv++;
 | |
| 			break;
 | |
| 		}
 | |
| 		setflags(*argv++ + 1);
 | |
| 	}
 | |
| 
 | |
| 	istty= isatty(1);
 | |
| 
 | |
| 	if (istty && (lsflags= getenv("LSOPTS")) != nil) {
 | |
| 		if (*lsflags == '-') lsflags++;
 | |
| 		setflags(lsflags);
 | |
| 	}
 | |
| 
 | |
| 	if (!present('1') && !present('C') && !present('l')
 | |
| 		&& (istty || present('M') || present('X') || present('F'))
 | |
| 	) setflags("C");
 | |
| 
 | |
| 	if (istty) setflags("q");
 | |
| 
 | |
| 	if (SUPER_ID == 0 || present('a')) setflags("A");
 | |
| 
 | |
| 	if (present('i')) field|= L_INODE;
 | |
| 	if (present('s')) field|= L_BLOCKS;
 | |
| 	if (present('M')) field|= L_MODE;
 | |
| 	if (present('X')) field|= L_EXTRA | L_MODE;
 | |
| 	if (present('t')) field|= L_BYTIME;
 | |
| 	if (present('u')) field|= L_ATIME;
 | |
| 	if (present('c')) field|= L_CTIME;
 | |
| 	if (present('l')) field|= L_MODE | L_LONG;
 | |
| 	if (present('g')) field|= L_MODE | L_LONG | L_GROUP;
 | |
| 	if (present('F')) field|= L_MARK;
 | |
| 	if (present('p')) field|= L_MARKDIR;
 | |
| 	if (present('D')) field|= L_TYPE;
 | |
| 	if (present('T')) field|= L_MODE | L_LONG | L_LONGTIME;
 | |
| 	if (present('d')) field|= L_DIR;
 | |
| 	if (present('h')) field|= L_KMG;
 | |
| 	if (field & L_LONG) field&= ~L_EXTRA;
 | |
| 
 | |
| #ifdef S_IFLNK
 | |
| 	status= present('L') ? stat : lstat;
 | |
| #endif
 | |
| 
 | |
| 	if (present('C')) {
 | |
| 		int t= istty ? 1 : open("/dev/tty", O_WRONLY);
 | |
| 
 | |
| 		if (t >= 0 && ioctl(t, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0)
 | |
| 			ncols= ws.ws_col - 1;
 | |
| 
 | |
| 		if (t != 1 && t != -1) close(t);
 | |
| 	}
 | |
| 
 | |
| 	depth= SURFACE;
 | |
| 
 | |
| 	if (*argv == nil) {
 | |
| 		if (!(field & L_DIR)) depth= SURFACE1;
 | |
| 		pushfile(aflist, newfile("."));
 | |
| 	} else {
 | |
| 		if (argv[1] == nil && !(field & L_DIR)) depth= SURFACE1;
 | |
| 
 | |
| 		do {
 | |
| 			pushfile(aflist, newfile(*argv++));
 | |
| 			aflist= &(*aflist)->next;
 | |
| 		} while (*argv!=nil);
 | |
| 	}
 | |
| 	listfiles(flist, depth,
 | |
| 		(field & L_DIR) ? BOTTOM : present('R') ? FLOATING : SINKING);
 | |
| 	return ex;
 | |
| }
 |