1 /* ls.c - list files
2  *
3  * Copyright 2012 Andre Renaud <andre@bluewatersys.com>
4  * Copyright 2012 Rob Landley <rob@landley.net>
5  *
6  * See http://opengroup.org/onlinepubs/9699919799/utilities/ls.html
7  *
8  * Deviations from posix:
9  *   add -b (and default to it instead of -q for an unambiguous representation
10  *   that doesn't cause collisions)
11  *   add -Z -ll --color
12  *   Posix says the -l date format should vary based on how recent it is
13  *   and we do --time-style=long-iso instead
14 
15 USE_LS(NEWTOY(ls, "(color):;(full-time)(show-control-chars)ZgoACFHLRSabcdfhikl@mnpqrstuw#=80<0x1[-Cxm1][-Cxml][-Cxmo][-Cxmg][-cu][-ftS][-HL][!qb]", TOYFLAG_BIN|TOYFLAG_LOCALE))
16 
17 config LS
18   bool "ls"
19   default y
20   help
21     usage: ls [-ACFHLRSZacdfhiklmnpqrstuwx1] [--color[=auto]] [FILE...]
22 
23     List files.
24 
25     what to show:
26     -a  all files including .hidden    -b  escape nongraphic chars
27     -c  use ctime for timestamps       -d  directory, not contents
28     -i  inode number                   -p  put a '/' after dir names
29     -q  unprintable chars as '?'       -s  storage used (1024 byte units)
30     -u  use access time for timestamps -A  list all files but . and ..
31     -H  follow command line symlinks   -L  follow symlinks
32     -R  recursively list in subdirs    -F  append /dir *exe @sym |FIFO
33     -Z  security context
34 
35     output formats:
36     -1  list one file per line         -C  columns (sorted vertically)
37     -g  like -l but no owner           -h  human readable sizes
38     -l  long (show full details)       -m  comma separated
39     -n  like -l but numeric uid/gid    -o  like -l but no group
40     -w  set column width               -x  columns (horizontal sort)
41     -ll long with nanoseconds (--full-time)
42     --color  device=yellow  symlink=turquoise/red  dir=blue  socket=purple
43              files: exe=green  suid=red  suidfile=redback  stickydir=greenback
44              =auto means detect if output is a tty.
45 
46     sorting (default is alphabetical):
47     -f  unsorted    -r  reverse    -t  timestamp    -S  size
48 */
49 
50 #define FOR_ls
51 #include "toys.h"
52 
53 // test sst output (suid/sticky in ls flaglist)
54 
55 // ls -lR starts .: then ./subdir:
56 
57 GLOBALS(
58   long w;
59   long l;
60   char *color;
61 
62   struct dirtree *files, *singledir;
63   unsigned screen_width;
64   int nl_title;
65   char *escmore;
66 )
67 
68 // Callback from crunch_str to represent unprintable chars
crunch_qb(FILE * out,int cols,int wc)69 static int crunch_qb(FILE *out, int cols, int wc)
70 {
71   int len = 1;
72   char buf[32];
73 
74   if (FLAG(q)) *buf = '?';
75   else {
76     if (wc<256) *buf = wc;
77     // scrute the inscrutable, eff the ineffable, print the unprintable
78     else if ((len = wcrtomb(buf, wc, 0) ) == -1) len = 1;
79     if (FLAG(b)) {
80       char *to = buf, *from = buf+24;
81       int i, j;
82 
83       memcpy(from, to, 8);
84       for (i = 0; i<len; i++) {
85         *to++ = '\\';
86         if (strchr(TT.escmore, from[i])) *to++ = from[i];
87         else if (-1 != (j = stridx("\\\a\b\033\f\n\r\t\v", from[i])))
88           *to++ = "\\abefnrtv"[j];
89         else to += sprintf(to, "%03o", from[i]);
90       }
91       len = to-buf;
92     }
93   }
94 
95   if (cols<len) len = cols;
96   if (out) fwrite(buf, len, 1, out);
97 
98   return len;
99 }
100 
101 // Returns wcwidth(utf8) version of strlen with -qb escapes
strwidth(char * s)102 static int strwidth(char *s)
103 {
104   return crunch_str(&s, INT_MAX, 0, TT.escmore, crunch_qb);
105 }
106 
endtype(struct stat * st)107 static char endtype(struct stat *st)
108 {
109   mode_t mode = st->st_mode;
110   if ((FLAG(F)||FLAG(p)) && S_ISDIR(mode)) return '/';
111   if (FLAG(F)) {
112     if (S_ISLNK(mode)) return '@';
113     if (S_ISREG(mode) && (mode&0111)) return '*';
114     if (S_ISFIFO(mode)) return '|';
115     if (S_ISSOCK(mode)) return '=';
116   }
117   return 0;
118 }
119 
numlen(long long ll)120 static int numlen(long long ll)
121 {
122   return snprintf(0, 0, "%llu", ll);
123 }
124 
print_with_h(char * s,long long value,int units)125 static int print_with_h(char *s, long long value, int units)
126 {
127   if (FLAG(h)) return human_readable(s, value*units, 0);
128   else return sprintf(s, "%lld", value);
129 }
130 
131 // Figure out size of printable entry fields for display indent/wrap
132 
entrylen(struct dirtree * dt,unsigned * len)133 static void entrylen(struct dirtree *dt, unsigned *len)
134 {
135   struct stat *st = &(dt->st);
136   char tmp[64];
137 
138   *len = strwidth(dt->name);
139   if (endtype(st)) ++*len;
140   if (FLAG(m)) ++*len;
141 
142   len[1] = FLAG(i) ? numlen(st->st_ino) : 0;
143   if (FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)) {
144     len[2] = numlen(st->st_nlink);
145     len[3] = FLAG(n) ? numlen(st->st_uid) : strwidth(getusername(st->st_uid));
146     len[4] = FLAG(n) ? numlen(st->st_gid) : strwidth(getgroupname(st->st_gid));
147     if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) {
148       // cheating slightly here: assuming minor is always 3 digits to avoid
149       // tracking another column
150       len[5] = numlen(dev_major(st->st_rdev))+5;
151     } else len[5] = print_with_h(tmp, st->st_size, 1);
152   }
153 
154   len[6] = FLAG(s) ? print_with_h(tmp, st->st_blocks, 1024) : 0;
155   len[7] = FLAG(Z) ? strwidth((char *)dt->extra) : 0;
156 }
157 
compare(void * a,void * b)158 static int compare(void *a, void *b)
159 {
160   struct dirtree *dta = *(struct dirtree **)a;
161   struct dirtree *dtb = *(struct dirtree **)b;
162   int ret = 0, reverse = FLAG(r) ? -1 : 1;
163 
164   if (FLAG(S)) {
165     if (dta->st.st_size > dtb->st.st_size) ret = -1;
166     else if (dta->st.st_size < dtb->st.st_size) ret = 1;
167   }
168   if (FLAG(t)) {
169     if (dta->st.st_mtime > dtb->st.st_mtime) ret = -1;
170     else if (dta->st.st_mtime < dtb->st.st_mtime) ret = 1;
171     else if (dta->st.st_mtim.tv_nsec > dtb->st.st_mtim.tv_nsec) ret = -1;
172     else if (dta->st.st_mtim.tv_nsec < dtb->st.st_mtim.tv_nsec) ret = 1;
173   }
174   if (!ret) ret = strcmp(dta->name, dtb->name);
175   return ret * reverse;
176 }
177 
178 // callback from dirtree_recurse() determining how to handle this entry.
179 
filter(struct dirtree * new)180 static int filter(struct dirtree *new)
181 {
182   // Special case to handle enormous dirs without running out of memory.
183   if (toys.optflags == (FLAG_1|FLAG_f)) {
184     xprintf("%s\n", new->name);
185     return 0;
186   }
187 
188   if (FLAG(Z)) {
189     if (!CFG_TOYBOX_LSM_NONE) {
190       // Linux doesn't support fgetxattr(2) on O_PATH file descriptors (though
191       // bionic works around that), and there are no *xattrat(2) calls, so we
192       // just use lgetxattr(2).
193       char *path = dirtree_path(new, 0);
194 
195       (FLAG(L) ? lsm_get_context : lsm_lget_context)(path,(char **)&new->extra);
196       free(path);
197     }
198     if (CFG_TOYBOX_LSM_NONE || !new->extra) new->extra = (long)xstrdup("?");
199   }
200 
201   if (FLAG(u)) new->st.st_mtime = new->st.st_atime;
202   if (FLAG(c)) new->st.st_mtime = new->st.st_ctime;
203   new->st.st_blocks >>= 1; // Use 1KiB blocks rather than 512B blocks.
204 
205   if (FLAG(a)||FLAG(f)) return DIRTREE_SAVE;
206   if (!FLAG(A) && new->name[0]=='.') return 0;
207 
208   return dirtree_notdotdot(new) & DIRTREE_SAVE;
209 }
210 
211 // For column view, calculate horizontal position (for padding) and return
212 // index of next entry to display.
213 
next_column(unsigned long ul,unsigned long dtlen,unsigned columns,unsigned * xpos)214 static unsigned long next_column(unsigned long ul, unsigned long dtlen,
215   unsigned columns, unsigned *xpos)
216 {
217   unsigned height, extra;
218 
219   // Horizontal sort is easy
220   if (!FLAG(C)) {
221     *xpos = ul % columns;
222     return ul;
223   }
224 
225   // vertical sort (-x), uneven rounding goes along right edge
226   height = (dtlen+columns-1)/columns; // round up
227   extra = dtlen%height; // how many rows are wider?
228   if (extra && ul >= extra*columns) ul -= extra*columns--;
229   else extra = 0;
230 
231   return (*xpos = ul % columns)*height + extra + ul/columns;
232 }
233 
color_from_mode(mode_t mode)234 static int color_from_mode(mode_t mode)
235 {
236   int color = 0;
237 
238   if (S_ISDIR(mode)) color = 256+34;
239   else if (S_ISLNK(mode)) color = 256+36;
240   else if (S_ISBLK(mode) || S_ISCHR(mode)) color = 256+33;
241   else if (S_ISREG(mode) && (mode&0111)) color = 256+32;
242   else if (S_ISFIFO(mode)) color = 33;
243   else if (S_ISSOCK(mode)) color = 256+35;
244 
245   return color;
246 }
247 
zprint(int zap,char * pat,int len,unsigned long arg)248 static void zprint(int zap, char *pat, int len, unsigned long arg)
249 {
250   char tmp[32];
251 
252   sprintf(tmp, "%%*%s", zap ? "s" : pat);
253   if (zap && pat[strlen(pat)-1]==' ') strcat(tmp, " ");
254   printf(tmp, len, zap ? (unsigned long)"?" : arg);
255 }
256 
257 // Display a list of dirtree entries, according to current format
258 // Output types -1, -l, -C, or stream
259 
listfiles(int dirfd,struct dirtree * indir)260 static void listfiles(int dirfd, struct dirtree *indir)
261 {
262   struct dirtree *dt, **sort;
263   unsigned long dtlen, ul = 0;
264   unsigned width, totals[8], len[8], totpad = 0,
265     *colsizes = (unsigned *)toybuf, columns = sizeof(toybuf)/4;
266   char tmp[64];
267 
268   if (-1 == dirfd) {
269     perror_msg_raw(indir->name);
270 
271     return;
272   }
273 
274   memset(totals, 0, sizeof(totals));
275   memset(len, 0, sizeof(len));
276 
277   // Top level directory was already populated by main()
278   if (!indir->parent) {
279     // Silently descend into single directory listed by itself on command line.
280     // In this case only show dirname/total header when given -R.
281     dt = indir->child;
282     if (dt && S_ISDIR(dt->st.st_mode) && !dt->next && !(FLAG(d)||FLAG(R))) {
283       listfiles(open(dt->name, 0), TT.singledir = dt);
284 
285       return;
286     }
287 
288     // Do preprocessing (Dirtree didn't populate, so callback wasn't called.)
289     for (;dt; dt = dt->next) filter(dt);
290     if (toys.optflags == (FLAG_1|FLAG_f)) return;
291   // Read directory contents. We dup() the fd because this will close it.
292   // This reads/saves contents to display later, except for in "ls -1f" mode.
293   } else dirtree_recurse(indir, filter, dup(dirfd),
294       DIRTREE_STATLESS|DIRTREE_SYMFOLLOW*!!FLAG(L));
295 
296   // Copy linked list to array and sort it. Directories go in array because
297   // we visit them in sorted order too. (The nested loops let us measure and
298   // fill with the same inner loop.)
299   for (sort = 0;;sort = xmalloc(dtlen*sizeof(void *))) {
300     for (dtlen = 0, dt = indir->child; dt; dt = dt->next, dtlen++)
301       if (sort) sort[dtlen] = dt;
302     if (sort || !dtlen) break;
303   }
304 
305   // Label directory if not top of tree, or if -R
306   if (indir->parent && (TT.singledir!=indir || FLAG(R))) {
307     char *path = dirtree_path(indir, 0);
308 
309     if (TT.nl_title++) xputc('\n');
310     xprintf("%s:\n", path);
311     free(path);
312   }
313 
314   // Measure each entry to work out whitespace padding and total blocks
315   if (!FLAG(f)) {
316     unsigned long long blocks = 0;
317 
318     qsort(sort, dtlen, sizeof(void *), (void *)compare);
319     for (ul = 0; ul<dtlen; ul++) {
320       entrylen(sort[ul], len);
321       for (width = 0; width<8; width++)
322         if (len[width]>totals[width]) totals[width] = len[width];
323       blocks += sort[ul]->st.st_blocks;
324     }
325     totpad = totals[1]+!!totals[1]+totals[6]+!!totals[6]+totals[7]+!!totals[7];
326     if ((FLAG(h)||FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)||FLAG(s)) && indir->parent)
327     {
328       print_with_h(tmp, blocks, 1024);
329       xprintf("total %s\n", tmp);
330     }
331   }
332 
333   // Find largest entry in each field for display alignment
334   if (FLAG(C)||FLAG(x)) {
335 
336     // columns can't be more than toybuf can hold, or more than files,
337     // or > 1/2 screen width (one char filename, one space).
338     if (columns > TT.screen_width/2) columns = TT.screen_width/2;
339     if (columns > dtlen) columns = dtlen;
340 
341     // Try to fit as many columns as we can, dropping down by one each time
342     for (;columns > 1; columns--) {
343       unsigned c, cc, totlen = columns;
344 
345       memset(colsizes, 0, columns*sizeof(unsigned));
346       for (ul = 0; ul<dtlen; ul++) {
347         cc = next_column(ul, dtlen, columns, &c);
348         if (cc>=dtlen) break; // tilt: remainder bigger than height
349         entrylen(sort[cc], len);
350         if (c<columns-1) *len += totpad+2;  // 2 spaces between filenames
351         // Expand this column if necessary, break if that puts us over budget
352         if (*len > colsizes[c]) {
353           totlen += (*len)-colsizes[c];
354           colsizes[c] = *len;
355           if (totlen > TT.screen_width) break;
356         }
357       }
358       // If everything fit, stop here
359       if (ul == dtlen) break;
360     }
361   }
362 
363   // Loop through again to produce output.
364   width = 0;
365   for (ul = 0; ul<dtlen; ul++) {
366     int ii, zap;
367     unsigned curcol, lastlen = *len, color = 0;
368     struct stat *st = &((dt = sort[next_column(ul,dtlen,columns,&curcol)])->st);
369     mode_t mode = st->st_mode;
370     char et = endtype(st), *ss;
371 
372     // If we couldn't stat, output ? for most fields
373     zap = !st->st_blksize && !st->st_dev && !st->st_ino;
374 
375     // Skip directories at the top of the tree when -d isn't set
376     if (S_ISDIR(mode) && !indir->parent && !FLAG(d)) continue;
377     TT.nl_title=1;
378 
379     // Handle padding and wrapping for display purposes
380     entrylen(dt, len);
381     if (ul) {
382       int mm = !!FLAG(m);
383 
384       if (mm) xputc(',');
385       if (FLAG(C)||FLAG(x)) {
386         if (!curcol) xputc('\n');
387         else {
388           if (ul) next_column(ul-1, dtlen, columns, &curcol);
389           printf("%*c", colsizes[ul ? curcol : 0]-lastlen-totpad, ' ');
390         }
391       } else if (FLAG(1) || width+1+*len > TT.screen_width) {
392         xputc('\n');
393         width = 0;
394       } else {
395         printf("  "+mm, 0); // shut up the stupid compiler
396         width += 2-mm;
397       }
398     }
399     width += *len;
400 
401     if (FLAG(i)) zprint(zap, "lu ", totals[1], st->st_ino);
402 
403     if (FLAG(s)) {
404       print_with_h(tmp, st->st_blocks, 1024);
405       zprint(zap, "s ", totals[6], (unsigned long)tmp);
406     }
407 
408     if (FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)) {
409       mode_to_string(mode, tmp);
410       if (zap) memset(tmp+1, '?', 9);
411       printf("%s", tmp);
412       zprint(zap, "ld", totals[2]+1, st->st_nlink);
413 
414       // print user
415       if (!FLAG(g)) {
416         putchar(' ');
417         ii = -totals[3];
418         if (zap || FLAG(n)) zprint(zap, "lu", ii, st->st_uid);
419         else draw_trim_esc(getusername(st->st_uid), ii, abs(ii), TT.escmore,
420                            crunch_qb);
421       }
422 
423       // print group
424       if (!FLAG(o)) {
425         putchar(' ');
426         ii = -totals[4];
427         if (zap || FLAG(n)) zprint(zap, "lu", ii, st->st_gid);
428         else draw_trim_esc(getgroupname(st->st_gid), ii, abs(ii), TT.escmore,
429                            crunch_qb);
430       }
431     }
432     if (FLAG(Z)) printf(" %-*s "+!FLAG(l), -(int)totals[7], (char *)dt->extra);
433 
434     if (FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)) {
435       struct tm *tm;
436 
437       // print major/minor, or size
438       if (!zap && (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)))
439         printf("% *d,% 4d", totals[5]-4, dev_major(st->st_rdev),
440           dev_minor(st->st_rdev));
441       else {
442         print_with_h(tmp, st->st_size, 1);
443         zprint(zap, "s", totals[5]+1, (unsigned long)tmp);
444       }
445 
446       // print time, always in --time-style=long-iso
447       tm = localtime(&(st->st_mtime));
448       strftime(tmp, sizeof(tmp), " %F %H:%M", tm);
449       if (TT.l>1) {
450         char *s = tmp+strlen(tmp);
451 
452         s += sprintf(s, ":%02d.%09d ", tm->tm_sec, (int)st->st_mtim.tv_nsec);
453         strftime(s, sizeof(tmp)-(s-tmp), "%z", tm);
454       }
455       zprint(zap, "s ", 17+(TT.l>1)*13, (unsigned long)tmp);
456     }
457 
458     if (FLAG(color)) {
459       color = color_from_mode(st->st_mode);
460       if (color) printf("\033[%d;%dm", color>>8, color&255);
461     }
462 
463     ss = dt->name;
464     crunch_str(&ss, INT_MAX, stdout, TT.escmore, crunch_qb);
465     if (color) printf("\033[0m");
466 
467     if ((FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)) && S_ISLNK(mode)) {
468       printf(" -> ");
469       if (!zap && FLAG(color)) {
470         struct stat st2;
471 
472         if (fstatat(dirfd, dt->symlink, &st2, 0)) color = 256+31;
473         else color = color_from_mode(st2.st_mode);
474 
475         if (color) printf("\033[%d;%dm", color>>8, color&255);
476       }
477 
478       zprint(zap, "s", 0, (unsigned long)dt->symlink);
479       if (!zap && color) printf("\033[0m");
480     }
481 
482     if (et) xputc(et);
483   }
484 
485   if (width) xputc('\n');
486 
487   // Free directory entries, recursing first if necessary.
488 
489   for (ul = 0; ul<dtlen; free(sort[ul++])) {
490     if (FLAG(d) || !S_ISDIR(sort[ul]->st.st_mode)) continue;
491 
492     // Recurse into dirs if at top of the tree or given -R
493     if (!indir->parent || (FLAG(R) && dirtree_notdotdot(sort[ul])))
494       listfiles(openat(dirfd, sort[ul]->name, 0), sort[ul]);
495     free((void *)sort[ul]->extra);
496   }
497   free(sort);
498   if (dirfd != AT_FDCWD) close(dirfd);
499 }
500 
ls_main(void)501 void ls_main(void)
502 {
503   char **s, *noargs[] = {".", 0};
504   struct dirtree *dt;
505 
506   if (FLAG(full_time)) {
507     toys.optflags |= FLAG_l;
508     TT.l = 2;
509   }
510 
511   // Do we have an implied -1
512   if (isatty(1)) {
513     if (!FLAG(show_control_chars)) toys.optflags |= FLAG_b;
514     if (FLAG(l)||FLAG(o)||FLAG(n)||FLAG(g)) toys.optflags |= FLAG_1;
515     else if (!(FLAG(1)||FLAG(x)||FLAG(m))) toys.optflags |= FLAG_C;
516   } else {
517     if (!FLAG(m)) toys.optflags |= FLAG_1;
518     if (TT.color) toys.optflags ^= FLAG_color;
519   }
520 
521   TT.screen_width = 80;
522   if (FLAG(w)) TT.screen_width = TT.w+2;
523   else terminal_size(&TT.screen_width, NULL);
524   if (TT.screen_width<2) TT.screen_width = 2;
525   if (FLAG(b)) TT.escmore = " \\";
526 
527   // The optflags parsing infrastructure should really do this for us,
528   // but currently it has "switch off when this is set", so "-dR" and "-Rd"
529   // behave differently
530   if (FLAG(d)) toys.optflags &= ~FLAG_R;
531 
532   // Iterate through command line arguments, collecting directories and files.
533   // Non-absolute paths are relative to current directory. Top of tree is
534   // a dummy node to collect command line arguments into pseudo-directory.
535   TT.files = dirtree_add_node(0, 0, 0);
536   TT.files->dirfd = AT_FDCWD;
537   for (s = *toys.optargs ? toys.optargs : noargs; *s; s++) {
538     int sym = !(FLAG(l)||FLAG(d)||FLAG(F)) || FLAG(L) || FLAG(H);
539 
540     dt = dirtree_add_node(0, *s, DIRTREE_STATLESS|DIRTREE_SYMFOLLOW*sym);
541 
542     // note: double_list->prev temporarily goes in dirtree->parent
543     if (dt) {
544       if (dt->again&2) {
545         perror_msg_raw(*s);
546         free(dt);
547       } else dlist_add_nomalloc((void *)&TT.files->child, (void *)dt);
548     } else toys.exitval = 1;
549   }
550 
551   // Convert double_list into dirtree.
552   dlist_terminate(TT.files->child);
553   for (dt = TT.files->child; dt; dt = dt->next) dt->parent = TT.files;
554 
555   // Display the files we collected
556   listfiles(AT_FDCWD, TT.files);
557 
558   if (CFG_TOYBOX_FREE) free(TT.files);
559 }
560