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