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 USE_LS(NEWTOY(ls, USE_LS_COLOR("(color):;")"ZgoACFHLRSacdfhiklmnpqrstux1[-Cxm1][-Cxml][-Cxmo][-Cxmg][-cu][-ftS][-HL]", TOYFLAG_BIN|TOYFLAG_LOCALE))
9 
10 config LS
11   bool "ls"
12   default y
13   help
14     usage: ls [-ACFHLRSZacdfhiklmnpqrstux1] [directory...]
15 
16     list files
17 
18     what to show:
19     -a	all files including .hidden		-c  use ctime for timestamps
20     -d	directory, not contents			-i  inode number
21     -k	block sizes in kilobytes		-p  put a '/' after dir names
22     -q	unprintable chars as '?'		-s  size (in blocks)
23     -u	use access time for timestamps		-A  list all files but . and ..
24     -H	follow command line symlinks		-L  follow symlinks
25     -R	recursively list files in subdirs	-F  append /dir *exe @sym |FIFO
26     -Z	security context
27 
28     output formats:
29     -1	list one file per line			-C  columns (sorted vertically)
30     -g	like -l but no owner			-h  human readable sizes
31     -l	long (show full details)		-m  comma separated
32     -n	like -l but numeric uid/gid		-o  like -l but no group
33     -x	columns (horizontal sort)
34 
35     sorting (default is alphabetical):
36     -f	unsorted	-r  reverse	-t  timestamp	-S  size
37 
38 config LS_COLOR
39   bool "ls --color"
40   default y
41   depends on LS
42   help
43     usage: ls --color[=auto]
44 
45     --color  device=yellow  symlink=turquoise/red  dir=blue  socket=purple
46              files: exe=green  suid=red  suidfile=redback  stickydir=greenback
47              =auto means detect if output is a tty.
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   char *color;
59 
60   struct dirtree *files, *singledir;
61 
62   unsigned screen_width;
63   int nl_title;
64   char uid_buf[12], gid_buf[12];
65 )
66 
67 // Does two things: 1) Returns wcwidth(utf8) version of strlen,
68 // 2) replaces unprintable characters input string with '?' wildcard char.
strwidth(char * s)69 int strwidth(char *s)
70 {
71   int total = 0, width, len;
72   wchar_t c;
73 
74   if (!CFG_TOYBOX_I18N) {
75     total = strlen(s);
76     if (toys.optflags & FLAG_q) for (; *s; s++) if (!isprint(*s)) *s = '?';
77   } else while (*s) {
78     len = mbrtowc(&c, s, MB_CUR_MAX, 0);
79     if (len < 1 || (width = wcwidth(c)) < 0) {
80       total++;
81       if (toys.optflags & FLAG_q) *s = '?';
82       s++;
83     } else {
84       s += len;
85       total += width;
86     }
87   }
88 
89   return total;
90 }
91 
endtype(struct stat * st)92 static char endtype(struct stat *st)
93 {
94   mode_t mode = st->st_mode;
95   if ((toys.optflags&(FLAG_F|FLAG_p)) && S_ISDIR(mode)) return '/';
96   if (toys.optflags & FLAG_F) {
97     if (S_ISLNK(mode)) return '@';
98     if (S_ISREG(mode) && (mode&0111)) return '*';
99     if (S_ISFIFO(mode)) return '|';
100     if (S_ISSOCK(mode)) return '=';
101   }
102   return 0;
103 }
104 
getusername(uid_t uid)105 static char *getusername(uid_t uid)
106 {
107   struct passwd *pw = getpwuid(uid);
108 
109   sprintf(TT.uid_buf, "%u", (unsigned)uid);
110   return pw ? pw->pw_name : TT.uid_buf;
111 }
112 
getgroupname(gid_t gid)113 static char *getgroupname(gid_t gid)
114 {
115   struct group *gr = getgrgid(gid);
116 
117   sprintf(TT.gid_buf, "%u", (unsigned)gid);
118   return gr ? gr->gr_name : TT.gid_buf;
119 }
120 
numlen(long long ll)121 static int numlen(long long ll)
122 {
123   return snprintf(0, 0, "%llu", ll);
124 }
125 
126 // Figure out size of printable entry fields for display indent/wrap
127 
entrylen(struct dirtree * dt,unsigned * len)128 static void entrylen(struct dirtree *dt, unsigned *len)
129 {
130   struct stat *st = &(dt->st);
131   unsigned flags = toys.optflags;
132   char tmp[64];
133 
134   *len = strwidth(dt->name);
135   if (endtype(st)) ++*len;
136   if (flags & FLAG_m) ++*len;
137 
138   len[1] = (flags & FLAG_i) ? numlen(st->st_ino) : 0;
139   if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) {
140     unsigned fn = flags & FLAG_n;
141     len[2] = numlen(st->st_nlink);
142     len[3] = fn ? numlen(st->st_uid) : strwidth(getusername(st->st_uid));
143     len[4] = fn ? numlen(st->st_gid) : strwidth(getgroupname(st->st_gid));
144     if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) {
145       // cheating slightly here: assuming minor is always 3 digits to avoid
146       // tracking another column
147       len[5] = numlen(major(st->st_rdev))+5;
148     } else if (flags & FLAG_h) {
149         human_readable(tmp, st->st_size, 0);
150         len[5] = strwidth(tmp);
151     } else len[5] = numlen(st->st_size);
152   }
153 
154   len[6] = (flags & FLAG_s) ? numlen(st->st_blocks) : 0;
155   len[7] = (flags & 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 = (toys.optflags & FLAG_r) ? -1 : 1;
163 
164   if (toys.optflags & 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 (toys.optflags & 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   }
172   if (!ret) ret = strcmp(dta->name, dtb->name);
173   return ret * reverse;
174 }
175 
176 // callback from dirtree_recurse() determining how to handle this entry.
177 
filter(struct dirtree * new)178 static int filter(struct dirtree *new)
179 {
180   int flags = toys.optflags;
181 
182   // Special case to handle enormous dirs without running out of memory.
183   if (flags == (FLAG_1|FLAG_f)) {
184     xprintf("%s\n", new->name);
185     return 0;
186   }
187 
188   if (flags & FLAG_Z) {
189     if (!CFG_TOYBOX_LSM_NONE) {
190 
191       // (Wouldn't it be nice if the lsm functions worked like openat(),
192       // fchmodat(), mknodat(), readlinkat() so we could do this without
193       // even O_PATH? But no, this is 1990's tech.)
194       int fd = openat(dirtree_parentfd(new), new->name,
195         O_PATH|(O_NOFOLLOW*!(toys.optflags&FLAG_L)));
196 
197       if (fd != -1) {
198         if (-1 == lsm_fget_context(fd, (char **)&new->extra) && errno == EBADF)
199         {
200           char hack[32];
201 
202           // Work around kernel bug that won't let us read this "metadata" from
203           // the filehandle unless we have permission to read the data. (We can
204           // query the same data in by path, but can't do it through an O_PATH
205           // filehandle, because reasons. But for some reason, THIS is ok? If
206           // they ever fix the kernel, this should stop triggering.)
207 
208           sprintf(hack, "/proc/self/fd/%d", fd);
209           lsm_lget_context(hack, (char **)&new->extra);
210         }
211         close(fd);
212       }
213     }
214     if (CFG_TOYBOX_LSM_NONE || !new->extra) new->extra = (long)xstrdup("?");
215   }
216 
217   if (flags & FLAG_u) new->st.st_mtime = new->st.st_atime;
218   if (flags & FLAG_c) new->st.st_mtime = new->st.st_ctime;
219   if (flags & FLAG_k) new->st.st_blocks = (new->st.st_blocks + 1) / 2;
220 
221   if (flags & (FLAG_a|FLAG_f)) return DIRTREE_SAVE;
222   if (!(flags & FLAG_A) && new->name[0]=='.') return 0;
223 
224   return dirtree_notdotdot(new) & DIRTREE_SAVE;
225 }
226 
227 // For column view, calculate horizontal position (for padding) and return
228 // index of next entry to display.
229 
next_column(unsigned long ul,unsigned long dtlen,unsigned columns,unsigned * xpos)230 static unsigned long next_column(unsigned long ul, unsigned long dtlen,
231   unsigned columns, unsigned *xpos)
232 {
233   unsigned long transition;
234   unsigned height, widecols;
235 
236   // Horizontal sort is easy
237   if (!(toys.optflags & FLAG_C)) {
238     *xpos = ul % columns;
239     return ul;
240   }
241 
242   // vertical sort
243 
244   // For -x, calculate height of display, rounded up
245   height = (dtlen+columns-1)/columns;
246 
247   // Sanity check: does wrapping render this column count impossible
248   // due to the right edge wrapping eating a whole row?
249   if (height*columns - dtlen >= height) {
250     *xpos = columns;
251     return 0;
252   }
253 
254   // Uneven rounding goes along right edge
255   widecols = dtlen % height;
256   if (!widecols) widecols = height;
257   transition = widecols * columns;
258   if (ul < transition) {
259     *xpos =  ul % columns;
260     return (*xpos*height) + (ul/columns);
261   }
262 
263   ul -= transition;
264   *xpos = ul % (columns-1);
265 
266   return (*xpos*height) + widecols + (ul/(columns-1));
267 }
268 
color_from_mode(mode_t mode)269 int color_from_mode(mode_t mode)
270 {
271   int color = 0;
272 
273   if (S_ISDIR(mode)) color = 256+34;
274   else if (S_ISLNK(mode)) color = 256+36;
275   else if (S_ISBLK(mode) || S_ISCHR(mode)) color = 256+33;
276   else if (S_ISREG(mode) && (mode&0111)) color = 256+32;
277   else if (S_ISFIFO(mode)) color = 33;
278   else if (S_ISSOCK(mode)) color = 256+35;
279 
280   return color;
281 }
282 
283 // Display a list of dirtree entries, according to current format
284 // Output types -1, -l, -C, or stream
285 
listfiles(int dirfd,struct dirtree * indir)286 static void listfiles(int dirfd, struct dirtree *indir)
287 {
288   struct dirtree *dt, **sort;
289   unsigned long dtlen, ul = 0;
290   unsigned width, flags = toys.optflags, totals[8], len[8], totpad = 0,
291     *colsizes = (unsigned *)(toybuf+260), columns = (sizeof(toybuf)-260)/4;
292   char tmp[64];
293 
294   if (-1 == dirfd) {
295     strwidth(indir->name);
296     perror_msg_raw(indir->name);
297 
298     return;
299   }
300 
301   memset(totals, 0, sizeof(totals));
302   if (CFG_TOYBOX_ON_ANDROID || CFG_TOYBOX_DEBUG) memset(len, 0, sizeof(len));
303 
304   // Top level directory was already populated by main()
305   if (!indir->parent) {
306     // Silently descend into single directory listed by itself on command line.
307     // In this case only show dirname/total header when given -R.
308     dt = indir->child;
309     if (dt && S_ISDIR(dt->st.st_mode) && !dt->next && !(flags&(FLAG_d|FLAG_R)))
310     {
311       listfiles(open(dt->name, 0), TT.singledir = dt);
312 
313       return;
314     }
315 
316     // Do preprocessing (Dirtree didn't populate, so callback wasn't called.)
317     for (;dt; dt = dt->next) filter(dt);
318     if (flags == (FLAG_1|FLAG_f)) return;
319   } else {
320     // Read directory contents. We dup() the fd because this will close it.
321     // This reads/saves contents to display later, except for in "ls -1f" mode.
322     indir->dirfd = dup(dirfd);
323     dirtree_recurse(indir, filter, DIRTREE_SYMFOLLOW*!!(flags&FLAG_L));
324   }
325 
326   // Copy linked list to array and sort it. Directories go in array because
327   // we visit them in sorted order too. (The nested loops let us measure and
328   // fill with the same inner loop.)
329   for (sort = 0;;sort = xmalloc(dtlen*sizeof(void *))) {
330     for (dtlen = 0, dt = indir->child; dt; dt = dt->next, dtlen++)
331       if (sort) sort[dtlen] = dt;
332     if (sort || !dtlen) break;
333   }
334 
335   // Label directory if not top of tree, or if -R
336   if (indir->parent && (TT.singledir!=indir || (flags&FLAG_R)))
337   {
338     char *path = dirtree_path(indir, 0);
339 
340     if (TT.nl_title++) xputc('\n');
341     xprintf("%s:\n", path);
342     free(path);
343   }
344 
345   // Measure each entry to work out whitespace padding and total blocks
346   if (!(flags & FLAG_f)) {
347     unsigned long long blocks = 0;
348 
349     qsort(sort, dtlen, sizeof(void *), (void *)compare);
350     for (ul = 0; ul<dtlen; ul++) {
351       entrylen(sort[ul], len);
352       for (width = 0; width<8; width++)
353         if (len[width]>totals[width]) totals[width] = len[width];
354       blocks += sort[ul]->st.st_blocks;
355     }
356     totpad = totals[1]+!!totals[1]+totals[6]+!!totals[6]+totals[7]+!!totals[7];
357     if ((flags&(FLAG_h|FLAG_l|FLAG_o|FLAG_n|FLAG_g|FLAG_s)) && indir->parent) {
358       if (flags&FLAG_h) {
359         human_readable(tmp, blocks*512, 0);
360         xprintf("total %s\n", tmp);
361       } else xprintf("total %llu\n", blocks);
362     }
363   }
364 
365   // Find largest entry in each field for display alignment
366   if (flags & (FLAG_C|FLAG_x)) {
367 
368     // columns can't be more than toybuf can hold, or more than files,
369     // or > 1/2 screen width (one char filename, one space).
370     if (columns > TT.screen_width/2) columns = TT.screen_width/2;
371     if (columns > dtlen) columns = dtlen;
372 
373     // Try to fit as many columns as we can, dropping down by one each time
374     for (;columns > 1; columns--) {
375       unsigned c, totlen = columns;
376 
377       memset(colsizes, 0, columns*sizeof(unsigned));
378       for (ul=0; ul<dtlen; ul++) {
379         entrylen(sort[next_column(ul, dtlen, columns, &c)], len);
380         *len += totpad;
381         if (c == columns) break;
382         // Expand this column if necessary, break if that puts us over budget
383         if (*len > colsizes[c]) {
384           totlen += (*len)-colsizes[c];
385           colsizes[c] = *len;
386           if (totlen > TT.screen_width) break;
387         }
388       }
389       // If everything fit, stop here
390       if (ul == dtlen) break;
391     }
392   }
393 
394   // Loop through again to produce output.
395   memset(toybuf, ' ', 256);
396   width = 0;
397   for (ul = 0; ul<dtlen; ul++) {
398     unsigned curcol, color = 0;
399     unsigned long next = next_column(ul, dtlen, columns, &curcol);
400     struct stat *st = &(sort[next]->st);
401     mode_t mode = st->st_mode;
402     char et = endtype(st);
403 
404     // Skip directories at the top of the tree when -d isn't set
405     if (S_ISDIR(mode) && !indir->parent && !(flags & FLAG_d)) continue;
406     TT.nl_title=1;
407 
408     // Handle padding and wrapping for display purposes
409     entrylen(sort[next], len);
410     if (ul) {
411       if (flags & FLAG_m) xputc(',');
412       if (flags & (FLAG_C|FLAG_x)) {
413         if (!curcol) xputc('\n');
414       } else if ((flags & FLAG_1) || width+1+*len > TT.screen_width) {
415         xputc('\n');
416         width = 0;
417       } else {
418         xputc(' ');
419         width++;
420       }
421     }
422     width += *len;
423 
424     if (flags & FLAG_i)
425       xprintf("%*lu ", totals[1], (unsigned long)st->st_ino);
426     if (flags & FLAG_s)
427       xprintf("%*lu ", totals[6], (unsigned long)st->st_blocks);
428 
429     if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) {
430       struct tm *tm;
431       char *ss;
432 
433       // (long) is to coerce the st types into something we know we can print.
434       mode_to_string(mode, tmp);
435       printf("%s% *ld", tmp, totals[2]+1, (long)st->st_nlink);
436 
437       // print user
438       if (!(flags&FLAG_g)) {
439         if (flags&FLAG_n) sprintf(ss = tmp, "%u", (unsigned)st->st_uid);
440         else strwidth(ss = getusername(st->st_uid));
441         printf(" %-*s", (int)totals[3], ss);
442       }
443 
444       // print group
445       if (!(flags&FLAG_o)) {
446         if (flags&FLAG_n) sprintf(ss = tmp, "%u", (unsigned)st->st_gid);
447         else strwidth(ss = getgroupname(st->st_gid));
448         printf(" %-*s", (int)totals[4], ss);
449       }
450 
451       if (flags & FLAG_Z)
452         printf(" %-*s", -(int)totals[7], (char *)sort[next]->extra);
453 
454       // print major/minor, or size
455       if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
456         printf("% *d,% 4d", totals[5]-4, major(st->st_rdev),minor(st->st_rdev));
457       else if (flags&FLAG_h) {
458         human_readable(tmp, st->st_size, 0);
459         xprintf("%*s", totals[5]+1, tmp);
460       } else printf("% *lld", totals[5]+1, (long long)st->st_size);
461 
462       // print time, always in --time-style=long-iso
463       tm = localtime(&(st->st_mtime));
464       strftime(tmp, sizeof(tmp), "%F %H:%M", tm);
465       xprintf(" %s ", tmp);
466     } else if (flags & FLAG_Z)
467       printf("%-*s ", (int)totals[7], (char *)sort[next]->extra);
468 
469     if (flags & FLAG_color) {
470       color = color_from_mode(st->st_mode);
471       if (color) printf("\033[%d;%dm", color>>8, color&255);
472     }
473 
474     if (flags & FLAG_q) {
475       char *p;
476       for (p=sort[next]->name; *p; p++) fputc(isprint(*p) ? *p : '?', stdout);
477     } else xprintf("%s", sort[next]->name);
478     if (color) xprintf("\033[0m");
479 
480     if ((flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) && S_ISLNK(mode)) {
481       printf(" -> ");
482       if (flags & FLAG_color) {
483         struct stat st2;
484 
485         if (fstatat(dirfd, sort[next]->symlink, &st2, 0)) color = 256+31;
486         else color = color_from_mode(st2.st_mode);
487 
488         if (color) printf("\033[%d;%dm", color>>8, color&255);
489       }
490 
491       printf("%s", sort[next]->symlink);
492       if (color) printf("\033[0m");
493     }
494 
495     if (et) xputc(et);
496 
497     // Pad columns
498     if (flags & (FLAG_C|FLAG_x)) {
499       curcol = colsizes[curcol]-(*len)-totpad;
500       if (curcol < 255) xprintf("%s", toybuf+255-curcol);
501     }
502   }
503 
504   if (width) xputc('\n');
505 
506   // Free directory entries, recursing first if necessary.
507 
508   for (ul = 0; ul<dtlen; free(sort[ul++])) {
509     if ((flags & FLAG_d) || !S_ISDIR(sort[ul]->st.st_mode)) continue;
510 
511     // Recurse into dirs if at top of the tree or given -R
512     if (!indir->parent || ((flags&FLAG_R) && dirtree_notdotdot(sort[ul])))
513       listfiles(openat(dirfd, sort[ul]->name, 0), sort[ul]);
514     free((void *)sort[ul]->extra);
515   }
516   free(sort);
517   if (dirfd != AT_FDCWD) close(dirfd);
518 }
519 
ls_main(void)520 void ls_main(void)
521 {
522   char **s, *noargs[] = {".", 0};
523   struct dirtree *dt;
524 
525   TT.screen_width = 80;
526   terminal_size(&TT.screen_width, NULL);
527   if (TT.screen_width<2) TT.screen_width = 2;
528 
529   // Do we have an implied -1
530   if (!isatty(1)) {
531     if (!(toys.optflags & FLAG_m)) toys.optflags |= FLAG_1;
532     if (TT.color) toys.optflags ^= FLAG_color;
533   } else if (toys.optflags&(FLAG_l|FLAG_o|FLAG_n|FLAG_g))
534     toys.optflags |= FLAG_1;
535   else if (!(toys.optflags&(FLAG_1|FLAG_x|FLAG_m))) toys.optflags |= FLAG_C;
536   // The optflags parsing infrastructure should really do this for us,
537   // but currently it has "switch off when this is set", so "-dR" and "-Rd"
538   // behave differently
539   if (toys.optflags & FLAG_d) toys.optflags &= ~FLAG_R;
540 
541   // Iterate through command line arguments, collecting directories and files.
542   // Non-absolute paths are relative to current directory.
543   TT.files = dirtree_start(0, 0);
544   TT.files->dirfd = AT_FDCWD;
545   for (s = *toys.optargs ? toys.optargs : noargs; *s; s++) {
546     dt = dirtree_start(*s, !(toys.optflags&(FLAG_l|FLAG_d|FLAG_F)) ||
547                             (toys.optflags&(FLAG_L|FLAG_H)));
548 
549     // note: double_list->prev temporarirly goes in dirtree->parent
550     if (dt) dlist_add_nomalloc((void *)&TT.files->child, (void *)dt);
551     else toys.exitval = 1;
552   }
553 
554   // Convert double_list into dirtree.
555   dlist_terminate(TT.files->child);
556   for (dt = TT.files->child; dt; dt = dt->next) dt->parent = TT.files;
557 
558   // Display the files we collected
559   listfiles(AT_FDCWD, TT.files);
560 
561   if (CFG_TOYBOX_FREE) free(TT.files);
562 }
563