/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); 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; inext; 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 %ld\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; }