1 /* vi: set sw=4 ts=4: */
3 * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
5 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8 /* [date unknown. Perhaps before year 2000]
9 * To achieve a small memory footprint, this version of 'ls' doesn't do any
10 * file sorting, and only has the most essential command line switches
11 * (i.e., the ones I couldn't live without :-) All features which involve
12 * linking in substantial chunks of libc can be disabled.
14 * Although I don't really want to add new features to this program to
15 * keep it small, I *am* interested to receive bug fixes and ways to make
19 * 1. hidden files can make column width too large
21 * NON-OPTIMAL BEHAVIOUR:
22 * 1. autowidth reads directories twice
23 * 2. if you do a short directory listing without filetype characters
24 * appended, there's no need to stat each one
26 * 1. requires lstat (BSD) - how do you do it without?
29 * ls sorts listing now, and supports almost all options.
32 //usage:#define ls_trivial_usage
34 //usage: IF_FEATURE_LS_FOLLOWLINKS("LH")
35 //usage: IF_FEATURE_LS_RECURSIVE("R")
36 //usage: IF_FEATURE_LS_FILETYPES("Fp") "lins"
37 //usage: IF_FEATURE_LS_TIMESTAMPS("e")
38 //usage: IF_FEATURE_HUMAN_READABLE("h")
39 //usage: IF_FEATURE_LS_SORTFILES("rSXv")
40 //usage: IF_FEATURE_LS_TIMESTAMPS("ctu")
41 //usage: IF_SELINUX("kKZ") "]"
42 //usage: IF_FEATURE_AUTOWIDTH(" [-w WIDTH]") " [FILE]..."
43 //usage:#define ls_full_usage "\n\n"
44 //usage: "List directory contents\n"
45 //usage: "\n -1 One column output"
46 //usage: "\n -a Include entries which start with ."
47 //usage: "\n -A Like -a, but exclude . and .."
48 //usage: "\n -C List by columns"
49 //usage: "\n -x List by lines"
50 //usage: "\n -d List directory entries instead of contents"
51 //usage: IF_FEATURE_LS_FOLLOWLINKS(
52 //usage: "\n -L Follow symlinks"
53 //usage: "\n -H Follow symlinks on command line"
55 //usage: IF_FEATURE_LS_RECURSIVE(
56 //usage: "\n -R Recurse"
58 //usage: IF_FEATURE_LS_FILETYPES(
59 //usage: "\n -p Append / to dir entries"
60 //usage: "\n -F Append indicator (one of */=@|) to entries"
62 //usage: "\n -l Long listing format"
63 //usage: "\n -i List inode numbers"
64 //usage: "\n -n List numeric UIDs and GIDs instead of names"
65 //usage: "\n -s List allocated blocks"
66 //usage: IF_FEATURE_LS_TIMESTAMPS(
67 //usage: "\n -e List full date and time"
69 //usage: IF_FEATURE_HUMAN_READABLE(
70 //usage: "\n -h List sizes in human readable format (1K 243M 2G)"
72 //usage: IF_FEATURE_LS_SORTFILES(
73 //usage: "\n -r Sort in reverse order"
74 //usage: "\n -S Sort by size"
75 //usage: "\n -X Sort by extension"
76 //usage: "\n -v Sort by version"
78 //usage: IF_FEATURE_LS_TIMESTAMPS(
79 //usage: "\n -c With -l: sort by ctime"
80 //usage: "\n -t With -l: sort by mtime"
81 //usage: "\n -u With -l: sort by atime"
84 //usage: "\n -k List security context"
85 //usage: "\n -K List security context in long format"
86 //usage: "\n -Z List security context and permission"
88 //usage: IF_FEATURE_AUTOWIDTH(
89 //usage: "\n -w N Assume the terminal is N columns wide"
91 //usage: IF_FEATURE_LS_COLOR(
92 //usage: "\n --color[={always,never,auto}] Control coloring"
99 /* This is a NOEXEC applet. Be very careful! */
103 /* ftpd uses ls, and without timestamps Mozilla won't understand
104 * ftpd's LIST output.
106 # undef CONFIG_FEATURE_LS_TIMESTAMPS
107 # undef ENABLE_FEATURE_LS_TIMESTAMPS
108 # undef IF_FEATURE_LS_TIMESTAMPS
109 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
110 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
111 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
112 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
113 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
118 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
124 /* Bits in G.all_fmt: */
126 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
127 /* what file information will be listed */
129 LIST_BLOCKS = 1 << 1,
130 LIST_MODEBITS = 1 << 2,
131 LIST_NLINKS = 1 << 3,
132 LIST_ID_NAME = 1 << 4,
133 LIST_ID_NUMERIC = 1 << 5,
134 LIST_CONTEXT = 1 << 6,
136 LIST_DATE_TIME = 1 << 8,
137 LIST_FULLTIME = 1 << 9,
138 LIST_SYMLINK = 1 << 10,
139 LIST_FILETYPE = 1 << 11, /* show / suffix for dirs */
140 LIST_CLASSIFY = 1 << 12, /* requires LIST_FILETYPE, also show *,|,@,= suffixes */
141 LIST_MASK = (LIST_CLASSIFY << 1) - 1,
143 /* what files will be displayed */
144 DISP_DIRNAME = 1 << 13, /* 2 or more items? label directories */
145 DISP_HIDDEN = 1 << 14, /* show filenames starting with . */
146 DISP_DOT = 1 << 15, /* show . and .. */
147 DISP_NOLIST = 1 << 16, /* show directory as itself, not contents */
148 DISP_RECURSIVE = 1 << 17, /* show directory and everything below it */
149 DISP_ROWS = 1 << 18, /* print across rows */
150 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
152 /* what is the overall style of the listing */
153 STYLE_COLUMNAR = 1 << 19, /* many records per line */
154 STYLE_LONG = 2 << 19, /* one record per line, extended info */
155 STYLE_SINGLE = 3 << 19, /* one record per line */
156 STYLE_MASK = STYLE_SINGLE,
158 /* which of the three times will be used */
159 TIME_CHANGE = (1 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
160 TIME_ACCESS = (2 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
161 TIME_MASK = (3 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
163 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
164 SORT_REVERSE = 1 << 23,
166 SORT_NAME = 0, /* sort by file name */
167 SORT_SIZE = 1 << 24, /* sort by file size */
168 SORT_ATIME = 2 << 24, /* sort by last access time */
169 SORT_CTIME = 3 << 24, /* sort by last change time */
170 SORT_MTIME = 4 << 24, /* sort by last modification time */
171 SORT_VERSION = 5 << 24, /* sort by version */
172 SORT_EXT = 6 << 24, /* sort by file name extension */
173 SORT_DIR = 7 << 24, /* sort by file or directory */
174 SORT_MASK = (7 << 24) * ENABLE_FEATURE_LS_SORTFILES,
176 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
177 LIST_DATE_TIME | LIST_SYMLINK,
180 /* -Cadil1 Std options, busybox always supports */
181 /* -gnsxA Std options, busybox always supports */
182 /* -Q GNU option, busybox always supports */
183 /* -k SELinux option, busybox always supports (ignores if !SELinux) */
184 /* Std has -k which means "show sizes in kbytes" */
185 /* -LHRctur Std options, busybox optionally supports */
186 /* -Fp Std options, busybox optionally supports */
187 /* -SXvhTw GNU options, busybox optionally supports */
188 /* -T WIDTH Ignored (we don't use tabs on output) */
189 /* -KZ SELinux mandated options, busybox optionally supports */
190 /* (coreutils 8.4 has no -K, remove it?) */
191 /* -e I think we made this one up (looks similar to GNU --full-time) */
192 /* We already used up all 32 bits, if we need to add more, candidates for removal: */
193 /* -K, -T, -e (add --full-time instead) */
194 static const char ls_options[] ALIGN1 =
195 "Cadil1gnsxQAk" /* 13 opts, total 13 */
196 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
197 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
198 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
199 IF_FEATURE_LS_RECURSIVE("R") /* 1, 24 */
200 IF_SELINUX("KZ") /* 2, 26 */
201 IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 28 */
202 IF_FEATURE_HUMAN_READABLE("h") /* 1, 29 */
203 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 31 */
204 /* with --color, we use all 32 bits */;
224 OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS,
228 OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
230 OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
231 OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
233 OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX,
235 OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
236 OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
238 OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH,
240 OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS,
241 OPT_e = (1 << OPTBIT_e) * ENABLE_FEATURE_LS_TIMESTAMPS,
242 OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS,
243 OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS,
244 OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES,
245 OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES,
246 OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES,
247 OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES,
248 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
249 OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES,
250 OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE,
251 OPT_K = (1 << OPTBIT_K) * ENABLE_SELINUX,
252 OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX,
253 OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS,
254 OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS,
255 OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE,
256 OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_AUTOWIDTH,
257 OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_AUTOWIDTH,
258 OPT_color = (1 << OPTBIT_color) * ENABLE_FEATURE_LS_COLOR,
261 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
262 static const uint32_t opt_flags[] = {
263 STYLE_COLUMNAR, /* C */
264 DISP_HIDDEN | DISP_DOT, /* a */
267 LIST_LONG | STYLE_LONG, /* l */
268 STYLE_SINGLE, /* 1 */
269 LIST_LONG | STYLE_LONG, /* g (don't show owner) - handled via OPT_g. assumes l */
270 LIST_ID_NUMERIC | LIST_LONG | STYLE_LONG, /* n (assumes l) */
272 DISP_ROWS | STYLE_COLUMNAR, /* x */
273 0, /* Q (quote filename) - handled via OPT_Q */
275 ENABLE_SELINUX * (LIST_CONTEXT|STYLE_SINGLE), /* k (ignored if !SELINUX) */
276 #if ENABLE_FEATURE_LS_TIMESTAMPS
277 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
278 LIST_FULLTIME, /* e */
279 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
280 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
282 #if ENABLE_FEATURE_LS_SORTFILES
285 SORT_REVERSE, /* r */
286 SORT_VERSION, /* v */
288 #if ENABLE_FEATURE_LS_FILETYPES
289 LIST_FILETYPE | LIST_CLASSIFY, /* F */
290 LIST_FILETYPE, /* p */
292 #if ENABLE_FEATURE_LS_RECURSIVE
293 DISP_RECURSIVE, /* R */
296 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME|STYLE_SINGLE, /* K */
297 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT|STYLE_SINGLE, /* Z */
300 /* options after Z are not processed through opt_flags */
305 * a directory entry and its stat info
308 const char *name; /* usually basename, but think "ls -l dir/file" */
309 const char *fullname; /* full name (usable for stat etc) */
310 struct dnode *dn_next; /* for linked list */
311 IF_SELINUX(security_context_t sid;)
312 smallint fname_allocated;
314 /* Used to avoid re-doing [l]stat at printout stage
315 * if we already collected needed data in scan stage:
317 mode_t dn_mode_lstat; /* obtained with lstat, or 0 */
318 mode_t dn_mode_stat; /* obtained with stat, or 0 */
320 // struct stat dstat;
321 // struct stat is huge. We don't need it in full.
322 // At least we don't need st_dev and st_blksize,
323 // but there are invisible fields as well
324 // (such as nanosecond-resolution timespamps)
325 // and padding, which we also don't want to store.
326 // We also can pre-parse dev_t dn_rdev (in glibc, it's huge).
327 // On 32-bit uclibc: dnode size went from 112 to 84 bytes.
329 /* Same names as in struct stat, but with dn_ instead of st_ pfx: */
330 mode_t dn_mode; /* obtained with lstat OR stat, depending on -L etc */
332 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
345 // blksize_t dn_blksize;
349 #if ENABLE_FEATURE_LS_COLOR
351 # define G_show_color (G.show_color)
353 # define G_show_color 0
357 #if ENABLE_FEATURE_AUTOWIDTH
358 unsigned terminal_width;
359 # define G_terminal_width (G.terminal_width)
361 # define G_terminal_width TERMINAL_WIDTH
363 #if ENABLE_FEATURE_LS_TIMESTAMPS
364 /* Do time() just once. Saves one syscall per file for "ls -l" */
365 time_t current_time_t;
368 #define G (*(struct globals*)&bb_common_bufsiz1)
369 #define INIT_G() do { \
370 /* we have to zero it out because of NOEXEC */ \
371 memset(&G, 0, sizeof(G)); \
372 IF_FEATURE_AUTOWIDTH(G_terminal_width = TERMINAL_WIDTH;) \
373 IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \
377 /*** Output code ***/
380 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
381 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
382 * 3/7:multiplexed char/block device)
383 * and we use 0 for unknown and 15 for executables (see below) */
384 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
385 /* un fi chr - dir - blk - file - link - sock - - exe */
386 #define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)])
387 /* 036 black foreground 050 black background
388 037 red foreground 051 red background
389 040 green foreground 052 green background
390 041 brown foreground 053 brown background
391 042 blue foreground 054 blue background
392 043 magenta (purple) foreground 055 magenta background
393 044 cyan (light blue) foreground 056 cyan background
394 045 gray foreground 057 white background
396 #define COLOR(mode) ( \
397 /*un fi chr - dir - blk - file - link - sock - - exe */ \
398 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
400 /* Select normal (0) [actually "reset all"] or bold (1)
401 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
402 * let's use 7 for "impossible" types, just for fun)
403 * Note: coreutils 6.9 uses inverted red for setuid binaries.
405 #define ATTR(mode) ( \
406 /*un fi chr - dir - blk - file- link- sock- - exe */ \
407 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
410 #if ENABLE_FEATURE_LS_COLOR
411 /* mode of zero is interpreted as "unknown" (stat failed) */
412 static char fgcolor(mode_t mode)
414 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
415 return COLOR(0xF000); /* File is executable ... */
418 static char bold(mode_t mode)
420 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
421 return ATTR(0xF000); /* File is executable ... */
426 #if ENABLE_FEATURE_LS_FILETYPES
427 static char append_char(mode_t mode)
429 if (!(G.all_fmt & LIST_FILETYPE))
433 if (!(G.all_fmt & LIST_CLASSIFY))
435 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
437 return APPCHAR(mode);
441 static unsigned calc_name_len(const char *name)
446 // TODO: quote tab as \t, etc, if -Q
447 name = printable_string(&uni_stat, name);
449 if (!(option_mask32 & OPT_Q)) {
450 return uni_stat.unicode_width;
453 len = 2 + uni_stat.unicode_width;
455 if (*name == '"' || *name == '\\') {
463 /* Return the number of used columns.
464 * Note that only STYLE_COLUMNAR uses return value.
465 * STYLE_SINGLE and STYLE_LONG don't care.
466 * coreutils 7.2 also supports:
467 * ls -b (--escape) = octal escapes (although it doesn't look like working)
468 * ls -N (--literal) = not escape at all
470 static unsigned print_name(const char *name)
475 // TODO: quote tab as \t, etc, if -Q
476 name = printable_string(&uni_stat, name);
478 if (!(option_mask32 & OPT_Q)) {
480 return uni_stat.unicode_width;
483 len = 2 + uni_stat.unicode_width;
486 if (*name == '"' || *name == '\\') {
497 /* Return the number of used columns.
498 * Note that only STYLE_COLUMNAR uses return value,
499 * STYLE_SINGLE and STYLE_LONG don't care.
501 static NOINLINE unsigned display_single(const struct dnode *dn)
505 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
510 #if ENABLE_FEATURE_LS_FILETYPES
511 append = append_char(dn->dn_mode);
514 /* Do readlink early, so that if it fails, error message
515 * does not appear *inside* the "ls -l" line */
517 if (G.all_fmt & LIST_SYMLINK)
518 if (S_ISLNK(dn->dn_mode))
519 lpath = xmalloc_readlink_or_warn(dn->fullname);
521 if (G.all_fmt & LIST_INO)
522 column += printf("%7llu ", (long long) dn->dn_ino);
523 //TODO: -h should affect -s too:
524 if (G.all_fmt & LIST_BLOCKS)
525 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1));
526 if (G.all_fmt & LIST_MODEBITS)
527 column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode));
528 if (G.all_fmt & LIST_NLINKS)
529 column += printf("%4lu ", (long) dn->dn_nlink);
530 if (G.all_fmt & LIST_ID_NUMERIC) {
531 if (option_mask32 & OPT_g)
532 column += printf("%-8u ", (int) dn->dn_gid);
534 column += printf("%-8u %-8u ",
538 #if ENABLE_FEATURE_LS_USERNAME
539 else if (G.all_fmt & LIST_ID_NAME) {
540 if (option_mask32 & OPT_g) {
541 column += printf("%-8.8s ",
542 get_cached_groupname(dn->dn_gid));
544 column += printf("%-8.8s %-8.8s ",
545 get_cached_username(dn->dn_uid),
546 get_cached_groupname(dn->dn_gid));
550 if (G.all_fmt & LIST_SIZE) {
551 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
552 column += printf("%4u, %3u ",
556 if (option_mask32 & OPT_h) {
557 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
558 /* print size, show one fractional, use suffixes */
559 make_human_readable_str(dn->dn_size, 1, 0)
562 column += printf("%9"OFF_FMT"u ", dn->dn_size);
566 #if ENABLE_FEATURE_LS_TIMESTAMPS
567 if (G.all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
569 const time_t *ttime = &dn->dn_mtime;
570 if (G.all_fmt & TIME_ACCESS)
571 ttime = &dn->dn_atime;
572 if (G.all_fmt & TIME_CHANGE)
573 ttime = &dn->dn_ctime;
574 filetime = ctime(ttime);
575 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
576 if (G.all_fmt & LIST_FULLTIME) { /* -e */
577 /* Note: coreutils 8.4 ls --full-time prints:
578 * 2009-07-13 17:49:27.000000000 +0200
580 column += printf("%.24s ", filetime);
581 } else { /* LIST_DATE_TIME */
582 /* G.current_time_t ~== time(NULL) */
583 time_t age = G.current_time_t - *ttime;
584 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
585 /* less than 6 months old */
586 /* "mmm dd hh:mm " */
587 printf("%.12s ", filetime + 4);
590 /* "mmm dd yyyyy " after year 9999 :) */
591 strchr(filetime + 20, '\n')[0] = ' ';
592 printf("%.7s%6s", filetime + 4, filetime + 20);
599 if (G.all_fmt & LIST_CONTEXT) {
600 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
605 #if ENABLE_FEATURE_LS_COLOR
607 mode_t mode = dn->dn_mode_lstat;
609 if (lstat(dn->fullname, &statbuf) == 0)
610 mode = statbuf.st_mode;
611 printf("\033[%u;%um", bold(mode), fgcolor(mode));
614 column += print_name(dn->name);
621 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
622 if ((G.all_fmt & LIST_FILETYPE) || G_show_color) {
623 mode_t mode = dn->dn_mode_stat;
625 if (stat(dn->fullname, &statbuf) == 0)
626 mode = statbuf.st_mode;
627 # if ENABLE_FEATURE_LS_FILETYPES
628 append = append_char(mode);
630 # if ENABLE_FEATURE_LS_COLOR
632 printf("\033[%u;%um", bold(mode), fgcolor(mode));
637 column += print_name(lpath) + 4;
643 #if ENABLE_FEATURE_LS_FILETYPES
644 if (G.all_fmt & LIST_FILETYPE) {
655 static void display_files(struct dnode **dn, unsigned nfiles)
657 unsigned i, ncols, nrows, row, nc;
660 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
662 if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
665 /* find the longest file name, use that as the column width */
666 for (i = 0; dn[i]; i++) {
667 int len = calc_name_len(dn[i]->name);
668 if (column_width < len)
672 IF_SELINUX( ((G.all_fmt & LIST_CONTEXT) ? 33 : 0) + )
673 ((G.all_fmt & LIST_INO) ? 8 : 0) +
674 ((G.all_fmt & LIST_BLOCKS) ? 5 : 0);
675 ncols = (unsigned)G_terminal_width / column_width;
679 nrows = nfiles / ncols;
680 if (nrows * ncols < nfiles)
681 nrows++; /* round up fractionals */
689 for (row = 0; row < nrows; row++) {
690 for (nc = 0; nc < ncols; nc++) {
691 /* reach into the array based on the column and row */
692 if (G.all_fmt & DISP_ROWS)
693 i = (row * ncols) + nc; /* display across row */
695 i = (nc * nrows) + row; /* display by column */
699 printf("%*s ", nexttab, "");
700 column += nexttab + 1;
702 nexttab = column + column_width;
703 column += display_single(dn[i]);
712 /*** Dir scanning code ***/
714 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
719 cur = xzalloc(sizeof(*cur));
720 cur->fullname = fullname;
723 if ((option_mask32 & OPT_L) || force_follow) {
725 if (is_selinux_enabled()) {
726 getfilecon(fullname, &cur->sid);
729 if (stat(fullname, &statbuf)) {
730 bb_simple_perror_msg(fullname);
731 G.exit_code = EXIT_FAILURE;
735 cur->dn_mode_stat = statbuf.st_mode;
738 if (is_selinux_enabled()) {
739 lgetfilecon(fullname, &cur->sid);
742 if (lstat(fullname, &statbuf)) {
743 bb_simple_perror_msg(fullname);
744 G.exit_code = EXIT_FAILURE;
748 cur->dn_mode_lstat = statbuf.st_mode;
751 /* cur->dstat = statbuf: */
752 cur->dn_mode = statbuf.st_mode ;
753 cur->dn_size = statbuf.st_size ;
754 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
755 cur->dn_atime = statbuf.st_atime ;
756 cur->dn_mtime = statbuf.st_mtime ;
757 cur->dn_ctime = statbuf.st_ctime ;
759 cur->dn_ino = statbuf.st_ino ;
760 cur->dn_blocks = statbuf.st_blocks;
761 cur->dn_nlink = statbuf.st_nlink ;
762 cur->dn_uid = statbuf.st_uid ;
763 cur->dn_gid = statbuf.st_gid ;
764 cur->dn_rdev_maj = major(statbuf.st_rdev);
765 cur->dn_rdev_min = minor(statbuf.st_rdev);
770 static unsigned count_dirs(struct dnode **dn, int which)
782 if (!S_ISDIR((*dn)->dn_mode))
786 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
787 /* or if it's not . or .. */
789 || (name[1] && (name[1] != '.' || name[2]))
794 return which != SPLIT_FILE ? dirs : all - dirs;
797 /* get memory to hold an array of pointers */
798 static struct dnode **dnalloc(unsigned num)
803 num++; /* so that we have terminating NULL */
804 return xzalloc(num * sizeof(struct dnode *));
807 #if ENABLE_FEATURE_LS_RECURSIVE
808 static void dfree(struct dnode **dnp)
815 for (i = 0; dnp[i]; i++) {
816 struct dnode *cur = dnp[i];
817 if (cur->fname_allocated)
818 free((char*)cur->fullname);
824 #define dfree(...) ((void)0)
827 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
828 static struct dnode **splitdnarray(struct dnode **dn, int which)
836 /* count how many dirs or files there are */
837 dncnt = count_dirs(dn, which);
839 /* allocate a file array and a dir array */
840 dnp = dnalloc(dncnt);
842 /* copy the entrys into the file or dir array */
843 for (d = 0; *dn; dn++) {
844 if (S_ISDIR((*dn)->dn_mode)) {
847 if (which == SPLIT_FILE)
851 if ((which & SPLIT_DIR) /* any dir... */
852 /* ... or not . or .. */
854 || (name[1] && (name[1] != '.' || name[2]))
859 if (which == SPLIT_FILE) {
866 #if ENABLE_FEATURE_LS_SORTFILES
867 static int sortcmp(const void *a, const void *b)
869 struct dnode *d1 = *(struct dnode **)a;
870 struct dnode *d2 = *(struct dnode **)b;
871 unsigned sort_opts = G.all_fmt & SORT_MASK;
874 dif = 0; /* assume SORT_NAME */
875 // TODO: use pre-initialized function pointer
876 // instead of branch forest
877 if (sort_opts == SORT_SIZE) {
878 dif = (d2->dn_size - d1->dn_size);
880 if (sort_opts == SORT_ATIME) {
881 dif = (d2->dn_atime - d1->dn_atime);
883 if (sort_opts == SORT_CTIME) {
884 dif = (d2->dn_ctime - d1->dn_ctime);
886 if (sort_opts == SORT_MTIME) {
887 dif = (d2->dn_mtime - d1->dn_mtime);
889 if (sort_opts == SORT_DIR) {
890 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
892 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
893 if (sort_opts == SORT_VERSION) {
894 dif = strverscmp(d1->name, d2->name);
897 if (sort_opts == SORT_EXT) {
898 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
901 /* sort by name, use as tie breaker for other sorts */
902 if (ENABLE_LOCALE_SUPPORT)
903 dif = strcoll(d1->name, d2->name);
905 dif = strcmp(d1->name, d2->name);
908 /* Make dif fit into an int */
909 if (sizeof(dif) > sizeof(int)) {
910 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
911 /* shift leaving only "int" worth of bits */
913 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
917 return (G.all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
920 static void dnsort(struct dnode **dn, int size)
922 qsort(dn, size, sizeof(*dn), sortcmp);
925 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
928 display_files(dn, nfiles);
931 # define dnsort(dn, size) ((void)0)
932 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
935 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
936 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
938 struct dnode *dn, *cur, **dnp;
939 struct dirent *entry;
944 dir = warn_opendir(path);
946 G.exit_code = EXIT_FAILURE;
947 return NULL; /* could not open the dir */
951 while ((entry = readdir(dir)) != NULL) {
954 /* are we going to list the file- it may be . or .. or a hidden file */
955 if (entry->d_name[0] == '.') {
956 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
957 && !(G.all_fmt & DISP_DOT)
961 if (!(G.all_fmt & DISP_HIDDEN))
964 fullname = concat_path_file(path, entry->d_name);
965 cur = my_stat(fullname, bb_basename(fullname), 0);
970 cur->fname_allocated = 1;
980 /* now that we know how many files there are
981 * allocate memory for an array to hold dnode pointers
984 dnp = dnalloc(nfiles);
985 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
986 dnp[i] = dn; /* save pointer to node in array */
996 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
997 * If any of the -l, -n, -s options is specified, each list
998 * of files within the directory shall be preceded by a
999 * status line indicating the number of file system blocks
1000 * occupied by files in the directory in 512-byte units if
1001 * the -k option is not specified, or 1024-byte units if the
1002 * -k option is specified, rounded up to the next integral
1005 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
1006 static off_t calculate_blocks(struct dnode **dn)
1011 /* st_blocks is in 512 byte blocks */
1012 blocks += (*dn)->dn_blocks;
1017 /* Even though standard says use 512 byte blocks, coreutils use 1k */
1018 /* Actually, we round up by calculating (blocks + 1) / 2,
1019 * "+ 1" was done when we initialized blocks to 1 */
1024 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1027 struct dnode **subdnp;
1030 if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
1034 printf("%s:\n", (*dn)->fullname);
1036 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1038 if ((G.all_fmt & STYLE_MASK) == STYLE_LONG || (G.all_fmt & LIST_BLOCKS))
1039 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1042 /* list all files at this level */
1043 sort_and_display_files(subdnp, nfiles);
1045 if (ENABLE_FEATURE_LS_RECURSIVE
1046 && (G.all_fmt & DISP_RECURSIVE)
1050 /* recursive - list the sub-dirs */
1051 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1052 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1054 dnsort(dnd, dndirs);
1055 scan_and_display_dirs_recur(dnd, 0);
1056 /* free the array of dnode pointers to the dirs */
1060 /* free the dnodes and the fullname mem */
1067 int ls_main(int argc UNUSED_PARAM, char **argv)
1079 #if ENABLE_FEATURE_LS_COLOR
1080 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1082 * # ls --color=BOGUS
1083 * ls: invalid argument 'BOGUS' for '--color'
1084 * Valid arguments are:
1085 * 'always', 'yes', 'force'
1086 * 'never', 'no', 'none'
1087 * 'auto', 'tty', 'if-tty'
1088 * (and substrings: "--color=alwa" work too)
1090 static const char ls_longopts[] ALIGN1 =
1091 "color\0" Optional_argument "\xff"; /* no short equivalent */
1092 static const char color_str[] ALIGN1 =
1093 "always\0""yes\0""force\0"
1094 "auto\0""tty\0""if-tty\0";
1095 /* need to initialize since --color has _an optional_ argument */
1096 const char *color_opt = color_str; /* "always" */
1103 if (ENABLE_FEATURE_LS_SORTFILES)
1104 G.all_fmt = SORT_NAME;
1106 #if ENABLE_FEATURE_AUTOWIDTH
1107 /* obtain the terminal width */
1108 get_terminal_width_height(STDIN_FILENO, &G_terminal_width, NULL);
1109 /* go one less... */
1113 /* process options */
1114 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1117 IF_FEATURE_LS_TIMESTAMPS("el")
1118 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1119 * in some pairs of opts, only last one takes effect:
1121 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1122 // ":m-l:l-m" - we don't have -m
1123 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1124 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1125 ":C-1:1-C" /* bycols/oneline */
1126 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1127 IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */
1129 IF_FEATURE_AUTOWIDTH(":w+");
1130 opt = getopt32(argv, ls_options
1131 IF_FEATURE_AUTOWIDTH(, NULL, &G_terminal_width)
1132 IF_FEATURE_LS_COLOR(, &color_opt)
1134 for (i = 0; opt_flags[i] != (1U << 31); i++) {
1135 if (opt & (1 << i)) {
1136 uint32_t flags = opt_flags[i];
1138 if (flags & STYLE_MASK)
1139 G.all_fmt &= ~STYLE_MASK;
1140 if (flags & SORT_MASK)
1141 G.all_fmt &= ~SORT_MASK;
1142 if (flags & TIME_MASK)
1143 G.all_fmt &= ~TIME_MASK;
1149 #if ENABLE_FEATURE_LS_COLOR
1150 /* set G_show_color = 1/0 */
1151 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1152 char *p = getenv("LS_COLORS");
1153 /* LS_COLORS is unset, or (not empty && not "none") ? */
1154 if (!p || (p[0] && strcmp(p, "none") != 0))
1157 if (opt & OPT_color) {
1158 if (color_opt[0] == 'n')
1160 else switch (index_in_substrings(color_str, color_opt)) {
1164 if (isatty(STDOUT_FILENO)) {
1174 /* sort out which command line options take precedence */
1175 if (ENABLE_FEATURE_LS_RECURSIVE && (G.all_fmt & DISP_NOLIST))
1176 G.all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1177 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1178 if (G.all_fmt & TIME_CHANGE)
1179 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_CTIME;
1180 if (G.all_fmt & TIME_ACCESS)
1181 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_ATIME;
1183 if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */
1184 G.all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1186 /* choose a display format if one was not already specified by an option */
1187 if (!(G.all_fmt & STYLE_MASK))
1188 G.all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1192 *--argv = (char*)".";
1195 G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1197 /* stuff the command line file names into a dnode array */
1201 cur = my_stat(*argv, *argv,
1202 /* follow links on command line unless -l, -s or -F: */
1203 !((G.all_fmt & STYLE_MASK) == STYLE_LONG
1204 || (G.all_fmt & LIST_BLOCKS)
1205 || (option_mask32 & OPT_F)
1208 || (option_mask32 & OPT_H)
1209 /* ... or if -L, but my_stat always follows links if -L */
1214 /*cur->fname_allocated = 0; - already is */
1220 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1224 /* now that we know how many files there are
1225 * allocate memory for an array to hold dnode pointers
1227 dnp = dnalloc(nfiles);
1228 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1229 dnp[i] = dn; /* save pointer to node in array */
1235 if (G.all_fmt & DISP_NOLIST) {
1236 sort_and_display_files(dnp, nfiles);
1238 dnd = splitdnarray(dnp, SPLIT_DIR);
1239 dnf = splitdnarray(dnp, SPLIT_FILE);
1240 dndirs = count_dirs(dnp, SPLIT_DIR);
1241 dnfiles = nfiles - dndirs;
1243 sort_and_display_files(dnf, dnfiles);
1244 if (ENABLE_FEATURE_CLEAN_UP)
1248 dnsort(dnd, dndirs);
1249 scan_and_display_dirs_recur(dnd, dnfiles == 0);
1250 if (ENABLE_FEATURE_CLEAN_UP)
1255 if (ENABLE_FEATURE_CLEAN_UP)