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"
46 //usage: "\n -1 One column output"
47 //usage: "\n -a Include entries which start with ."
48 //usage: "\n -A Like -a, but exclude . and .."
49 //usage: "\n -C List by columns"
50 //usage: "\n -x List by lines"
51 //usage: "\n -d List directory entries instead of contents"
52 //usage: IF_FEATURE_LS_FOLLOWLINKS(
53 //usage: "\n -L Follow symlinks"
54 //usage: "\n -H Follow symlinks on command line"
56 //usage: IF_FEATURE_LS_RECURSIVE(
57 //usage: "\n -R Recurse"
59 //usage: IF_FEATURE_LS_FILETYPES(
60 //usage: "\n -p Append / to dir entries"
61 //usage: "\n -F Append indicator (one of */=@|) to entries"
63 //usage: "\n -l Long listing format"
64 //usage: "\n -i List inode numbers"
65 //usage: "\n -n List numeric UIDs and GIDs instead of names"
66 //usage: "\n -s List allocated blocks"
67 //usage: IF_FEATURE_LS_TIMESTAMPS(
68 //usage: "\n -e List full date and time"
70 //usage: IF_FEATURE_HUMAN_READABLE(
71 //usage: "\n -h List sizes in human readable format (1K 243M 2G)"
73 //usage: IF_FEATURE_LS_SORTFILES(
74 //usage: "\n -r Sort in reverse order"
75 //usage: "\n -S Sort by size"
76 //usage: "\n -X Sort by extension"
77 //usage: "\n -v Sort by version"
79 //usage: IF_FEATURE_LS_TIMESTAMPS(
80 //usage: "\n -c With -l: sort by ctime"
81 //usage: "\n -t With -l: sort by mtime"
82 //usage: "\n -u With -l: sort by atime"
85 //usage: "\n -k List security context"
86 //usage: "\n -K List security context in long format"
87 //usage: "\n -Z List security context and permission"
89 //usage: IF_FEATURE_AUTOWIDTH(
90 //usage: "\n -w N Assume the terminal is N columns wide"
92 //usage: IF_FEATURE_LS_COLOR(
93 //usage: "\n --color[={always,never,auto}] Control coloring"
100 /* This is a NOEXEC applet. Be very careful! */
104 /* ftpd uses ls, and without timestamps Mozilla won't understand
105 * ftpd's LIST output.
107 # undef CONFIG_FEATURE_LS_TIMESTAMPS
108 # undef ENABLE_FEATURE_LS_TIMESTAMPS
109 # undef IF_FEATURE_LS_TIMESTAMPS
110 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
111 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
112 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
113 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
114 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
119 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
125 /* Bits in G.all_fmt: */
127 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
128 /* what file information will be listed */
130 LIST_BLOCKS = 1 << 1,
131 LIST_MODEBITS = 1 << 2,
132 LIST_NLINKS = 1 << 3,
133 LIST_ID_NAME = 1 << 4,
134 LIST_ID_NUMERIC = 1 << 5,
135 LIST_CONTEXT = 1 << 6,
137 LIST_DATE_TIME = 1 << 8,
138 LIST_FULLTIME = 1 << 9,
139 LIST_SYMLINK = 1 << 10,
140 LIST_FILETYPE = 1 << 11, /* show / suffix for dirs */
141 LIST_CLASSIFY = 1 << 12, /* requires LIST_FILETYPE, also show *,|,@,= suffixes */
142 LIST_MASK = (LIST_CLASSIFY << 1) - 1,
144 /* what files will be displayed */
145 DISP_DIRNAME = 1 << 13, /* 2 or more items? label directories */
146 DISP_HIDDEN = 1 << 14, /* show filenames starting with . */
147 DISP_DOT = 1 << 15, /* show . and .. */
148 DISP_NOLIST = 1 << 16, /* show directory as itself, not contents */
149 DISP_RECURSIVE = 1 << 17, /* show directory and everything below it */
150 DISP_ROWS = 1 << 18, /* print across rows */
151 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
153 /* what is the overall style of the listing */
154 STYLE_COLUMNAR = 1 << 19, /* many records per line */
155 STYLE_LONG = 2 << 19, /* one record per line, extended info */
156 STYLE_SINGLE = 3 << 19, /* one record per line */
157 STYLE_MASK = STYLE_SINGLE,
159 /* which of the three times will be used */
160 TIME_CHANGE = (1 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
161 TIME_ACCESS = (2 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
162 TIME_MASK = (3 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
164 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
165 SORT_REVERSE = 1 << 23,
167 SORT_NAME = 0, /* sort by file name */
168 SORT_SIZE = 1 << 24, /* sort by file size */
169 SORT_ATIME = 2 << 24, /* sort by last access time */
170 SORT_CTIME = 3 << 24, /* sort by last change time */
171 SORT_MTIME = 4 << 24, /* sort by last modification time */
172 SORT_VERSION = 5 << 24, /* sort by version */
173 SORT_EXT = 6 << 24, /* sort by file name extension */
174 SORT_DIR = 7 << 24, /* sort by file or directory */
175 SORT_MASK = (7 << 24) * ENABLE_FEATURE_LS_SORTFILES,
177 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
178 LIST_DATE_TIME | LIST_SYMLINK,
181 /* -Cadil1 Std options, busybox always supports */
182 /* -gnsxA Std options, busybox always supports */
183 /* -Q GNU option, busybox always supports */
184 /* -k SELinux option, busybox always supports (ignores if !SELinux) */
185 /* Std has -k which means "show sizes in kbytes" */
186 /* -FLHRctur Std options, busybox optionally supports */
187 /* -p Std option, busybox optionally supports */
188 /* Not fully compatible - we show not only '/' but other chars too */
189 /* -SXvhTw GNU options, busybox optionally supports */
190 /* -T TABWIDTH is ignored (we don't use tabs on output) */
191 /* -KZ SELinux mandated options, busybox optionally supports */
192 /* (coreutils 8.4 has no -K, remove it?) */
193 /* -e I think we made this one up (looks similar to GNU --full-time) */
194 /* We already used up all 32 bits, if we need to add more, candidates for removal: */
195 /* -K, -T, -e (add --full-time instead) */
196 static const char ls_options[] ALIGN1 =
197 "Cadil1gnsxQAk" /* 13 opts, total 13 */
198 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
199 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
200 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
201 IF_FEATURE_LS_RECURSIVE("R") /* 1, 24 */
202 IF_SELINUX("KZ") /* 2, 26 */
203 IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 28 */
204 IF_FEATURE_HUMAN_READABLE("h") /* 1, 29 */
205 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 31 */
206 /* with --color, we use all 32 bits */;
226 OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS,
230 OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
232 OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
233 OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
235 OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX,
237 OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
238 OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
240 OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH,
242 OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS,
243 OPT_e = (1 << OPTBIT_e) * ENABLE_FEATURE_LS_TIMESTAMPS,
244 OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS,
245 OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS,
246 OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES,
247 OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES,
248 OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES,
249 OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES,
250 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
251 OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES,
252 OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE,
253 OPT_K = (1 << OPTBIT_K) * ENABLE_SELINUX,
254 OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX,
255 OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS,
256 OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS,
257 OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE,
258 OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_AUTOWIDTH,
259 OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_AUTOWIDTH,
260 OPT_color = (1 << OPTBIT_color) * ENABLE_FEATURE_LS_COLOR,
263 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
264 static const uint32_t opt_flags[] = {
265 STYLE_COLUMNAR, /* C */
266 DISP_HIDDEN | DISP_DOT, /* a */
269 LIST_LONG | STYLE_LONG, /* l */
270 STYLE_SINGLE, /* 1 */
271 LIST_LONG | STYLE_LONG, /* g (don't show owner) - handled via OPT_g. assumes l */
272 LIST_ID_NUMERIC | LIST_LONG | STYLE_LONG, /* n (assumes l) */
274 DISP_ROWS | STYLE_COLUMNAR, /* x */
275 0, /* Q (quote filename) - handled via OPT_Q */
277 ENABLE_SELINUX * (LIST_CONTEXT|STYLE_SINGLE), /* k (ignored if !SELINUX) */
278 #if ENABLE_FEATURE_LS_TIMESTAMPS
279 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
280 LIST_FULLTIME, /* e */
281 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
282 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
284 #if ENABLE_FEATURE_LS_SORTFILES
287 SORT_REVERSE, /* r */
288 SORT_VERSION, /* v */
290 #if ENABLE_FEATURE_LS_FILETYPES
291 LIST_FILETYPE | LIST_CLASSIFY, /* F */
292 LIST_FILETYPE, /* p */
294 #if ENABLE_FEATURE_LS_RECURSIVE
295 DISP_RECURSIVE, /* R */
298 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME|STYLE_SINGLE, /* K */
299 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT|STYLE_SINGLE, /* Z */
302 /* options after Z are not processed through opt_flags */
307 * a directory entry and its stat info
310 const char *name; /* usually basename, but think "ls -l dir/file" */
311 const char *fullname; /* full name (usable for stat etc) */
312 struct dnode *dn_next; /* for linked list */
313 IF_SELINUX(security_context_t sid;)
314 smallint fname_allocated;
316 /* Used to avoid re-doing [l]stat at printout stage
317 * if we already collected needed data in scan stage:
319 mode_t dn_mode_lstat; /* obtained with lstat, or 0 */
320 mode_t dn_mode_stat; /* obtained with stat, or 0 */
322 // struct stat dstat;
323 // struct stat is huge. We don't need it in full.
324 // At least we don't need st_dev and st_blksize,
325 // but there are invisible fields as well
326 // (such as nanosecond-resolution timespamps)
327 // and padding, which we also don't want to store.
328 // We also can pre-parse dev_t dn_rdev (in glibc, it's huge).
329 // On 32-bit uclibc: dnode size went from 112 to 84 bytes.
331 /* Same names as in struct stat, but with dn_ instead of st_ pfx: */
332 mode_t dn_mode; /* obtained with lstat OR stat, depending on -L etc */
334 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
347 // blksize_t dn_blksize;
351 #if ENABLE_FEATURE_LS_COLOR
353 # define G_show_color (G.show_color)
355 # define G_show_color 0
359 #if ENABLE_FEATURE_AUTOWIDTH
360 unsigned terminal_width;
361 # define G_terminal_width (G.terminal_width)
363 # define G_terminal_width TERMINAL_WIDTH
365 #if ENABLE_FEATURE_LS_TIMESTAMPS
366 /* Do time() just once. Saves one syscall per file for "ls -l" */
367 time_t current_time_t;
370 #define G (*(struct globals*)&bb_common_bufsiz1)
371 #define INIT_G() do { \
372 /* we have to zero it out because of NOEXEC */ \
373 memset(&G, 0, sizeof(G)); \
374 IF_FEATURE_AUTOWIDTH(G_terminal_width = TERMINAL_WIDTH;) \
375 IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \
379 /*** Output code ***/
382 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
383 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
384 * 3/7:multiplexed char/block device)
385 * and we use 0 for unknown and 15 for executables (see below) */
386 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
387 /* un fi chr - dir - blk - file - link - sock - - exe */
388 #define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)])
389 /* 036 black foreground 050 black background
390 037 red foreground 051 red background
391 040 green foreground 052 green background
392 041 brown foreground 053 brown background
393 042 blue foreground 054 blue background
394 043 magenta (purple) foreground 055 magenta background
395 044 cyan (light blue) foreground 056 cyan background
396 045 gray foreground 057 white background
398 #define COLOR(mode) ( \
399 /*un fi chr - dir - blk - file - link - sock - - exe */ \
400 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
402 /* Select normal (0) [actually "reset all"] or bold (1)
403 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
404 * let's use 7 for "impossible" types, just for fun)
405 * Note: coreutils 6.9 uses inverted red for setuid binaries.
407 #define ATTR(mode) ( \
408 /*un fi chr - dir - blk - file- link- sock- - exe */ \
409 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
412 #if ENABLE_FEATURE_LS_COLOR
413 /* mode of zero is interpreted as "unknown" (stat failed) */
414 static char fgcolor(mode_t mode)
416 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
417 return COLOR(0xF000); /* File is executable ... */
420 static char bold(mode_t mode)
422 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
423 return ATTR(0xF000); /* File is executable ... */
428 #if ENABLE_FEATURE_LS_FILETYPES
429 static char append_char(mode_t mode)
431 if (!(G.all_fmt & LIST_FILETYPE))
435 if (!(G.all_fmt & LIST_CLASSIFY))
437 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
439 return APPCHAR(mode);
443 static unsigned calc_name_len(const char *name)
448 // TODO: quote tab as \t, etc, if -Q
449 name = printable_string(&uni_stat, name);
451 if (!(option_mask32 & OPT_Q)) {
452 return uni_stat.unicode_width;
455 len = 2 + uni_stat.unicode_width;
457 if (*name == '"' || *name == '\\') {
465 /* Return the number of used columns.
466 * Note that only STYLE_COLUMNAR uses return value.
467 * STYLE_SINGLE and STYLE_LONG don't care.
468 * coreutils 7.2 also supports:
469 * ls -b (--escape) = octal escapes (although it doesn't look like working)
470 * ls -N (--literal) = not escape at all
472 static unsigned print_name(const char *name)
477 // TODO: quote tab as \t, etc, if -Q
478 name = printable_string(&uni_stat, name);
480 if (!(option_mask32 & OPT_Q)) {
482 return uni_stat.unicode_width;
485 len = 2 + uni_stat.unicode_width;
488 if (*name == '"' || *name == '\\') {
499 /* Return the number of used columns.
500 * Note that only STYLE_COLUMNAR uses return value,
501 * STYLE_SINGLE and STYLE_LONG don't care.
503 static NOINLINE unsigned display_single(const struct dnode *dn)
507 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
512 #if ENABLE_FEATURE_LS_FILETYPES
513 append = append_char(dn->dn_mode);
516 /* Do readlink early, so that if it fails, error message
517 * does not appear *inside* the "ls -l" line */
519 if (G.all_fmt & LIST_SYMLINK)
520 if (S_ISLNK(dn->dn_mode))
521 lpath = xmalloc_readlink_or_warn(dn->fullname);
523 if (G.all_fmt & LIST_INO)
524 column += printf("%7llu ", (long long) dn->dn_ino);
525 //TODO: -h should affect -s too:
526 if (G.all_fmt & LIST_BLOCKS)
527 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1));
528 if (G.all_fmt & LIST_MODEBITS)
529 column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode));
530 if (G.all_fmt & LIST_NLINKS)
531 column += printf("%4lu ", (long) dn->dn_nlink);
532 if (G.all_fmt & LIST_ID_NUMERIC) {
533 if (option_mask32 & OPT_g)
534 column += printf("%-8u ", (int) dn->dn_gid);
536 column += printf("%-8u %-8u ",
540 #if ENABLE_FEATURE_LS_USERNAME
541 else if (G.all_fmt & LIST_ID_NAME) {
542 if (option_mask32 & OPT_g) {
543 column += printf("%-8.8s ",
544 get_cached_groupname(dn->dn_gid));
546 column += printf("%-8.8s %-8.8s ",
547 get_cached_username(dn->dn_uid),
548 get_cached_groupname(dn->dn_gid));
552 if (G.all_fmt & LIST_SIZE) {
553 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
554 column += printf("%4u, %3u ",
558 if (option_mask32 & OPT_h) {
559 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
560 /* print size, show one fractional, use suffixes */
561 make_human_readable_str(dn->dn_size, 1, 0)
564 column += printf("%9"OFF_FMT"u ", dn->dn_size);
568 #if ENABLE_FEATURE_LS_TIMESTAMPS
569 if (G.all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
571 time_t ttime = dn->dn_mtime;
572 if (G.all_fmt & TIME_ACCESS)
573 ttime = dn->dn_atime;
574 if (G.all_fmt & TIME_CHANGE)
575 ttime = dn->dn_ctime;
576 filetime = ctime(&ttime);
577 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
578 if (G.all_fmt & LIST_FULLTIME) { /* -e */
579 /* Note: coreutils 8.4 ls --full-time prints:
580 * 2009-07-13 17:49:27.000000000 +0200
582 column += printf("%.24s ", filetime);
583 } else { /* LIST_DATE_TIME */
584 /* G.current_time_t ~== time(NULL) */
585 time_t age = G.current_time_t - ttime;
586 printf("%.6s ", filetime + 4); /* "Jun 30" */
587 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
588 /* hh:mm if less than 6 months old */
589 printf("%.5s ", filetime + 11);
590 } else { /* year. buggy if year > 9999 ;) */
591 printf(" %.4s ", filetime + 20);
598 if (G.all_fmt & LIST_CONTEXT) {
599 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
604 #if ENABLE_FEATURE_LS_COLOR
606 mode_t mode = dn->dn_mode_lstat;
608 if (lstat(dn->fullname, &statbuf) == 0)
609 mode = statbuf.st_mode;
610 printf("\033[%u;%um", bold(mode), fgcolor(mode));
613 column += print_name(dn->name);
620 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
621 if ((G.all_fmt & LIST_FILETYPE) || G_show_color) {
622 mode_t mode = dn->dn_mode_stat;
624 if (stat(dn->fullname, &statbuf) == 0)
625 mode = statbuf.st_mode;
626 # if ENABLE_FEATURE_LS_FILETYPES
627 append = append_char(mode);
629 # if ENABLE_FEATURE_LS_COLOR
631 printf("\033[%u;%um", bold(mode), fgcolor(mode));
636 column += print_name(lpath) + 4;
642 #if ENABLE_FEATURE_LS_FILETYPES
643 if (G.all_fmt & LIST_FILETYPE) {
654 static void display_files(struct dnode **dn, unsigned nfiles)
656 unsigned i, ncols, nrows, row, nc;
659 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
661 if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
664 /* find the longest file name, use that as the column width */
665 for (i = 0; dn[i]; i++) {
666 int len = calc_name_len(dn[i]->name);
667 if (column_width < len)
671 IF_SELINUX( ((G.all_fmt & LIST_CONTEXT) ? 33 : 0) + )
672 ((G.all_fmt & LIST_INO) ? 8 : 0) +
673 ((G.all_fmt & LIST_BLOCKS) ? 5 : 0);
674 ncols = (unsigned)G_terminal_width / column_width;
678 nrows = nfiles / ncols;
679 if (nrows * ncols < nfiles)
680 nrows++; /* round up fractionals */
688 for (row = 0; row < nrows; row++) {
689 for (nc = 0; nc < ncols; nc++) {
690 /* reach into the array based on the column and row */
691 if (G.all_fmt & DISP_ROWS)
692 i = (row * ncols) + nc; /* display across row */
694 i = (nc * nrows) + row; /* display by column */
698 printf("%*s ", nexttab, "");
699 column += nexttab + 1;
701 nexttab = column + column_width;
702 column += display_single(dn[i]);
711 /*** Dir scanning code ***/
713 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
718 cur = xzalloc(sizeof(*cur));
719 cur->fullname = fullname;
722 if ((option_mask32 & OPT_L) || force_follow) {
724 if (is_selinux_enabled()) {
725 getfilecon(fullname, &cur->sid);
728 if (stat(fullname, &statbuf)) {
729 bb_simple_perror_msg(fullname);
730 G.exit_code = EXIT_FAILURE;
734 cur->dn_mode_stat = statbuf.st_mode;
737 if (is_selinux_enabled()) {
738 lgetfilecon(fullname, &cur->sid);
741 if (lstat(fullname, &statbuf)) {
742 bb_simple_perror_msg(fullname);
743 G.exit_code = EXIT_FAILURE;
747 cur->dn_mode_lstat = statbuf.st_mode;
750 /* cur->dstat = statbuf: */
751 cur->dn_mode = statbuf.st_mode ;
752 cur->dn_size = statbuf.st_size ;
753 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
754 cur->dn_atime = statbuf.st_atime ;
755 cur->dn_mtime = statbuf.st_mtime ;
756 cur->dn_ctime = statbuf.st_ctime ;
758 cur->dn_ino = statbuf.st_ino ;
759 cur->dn_blocks = statbuf.st_blocks;
760 cur->dn_nlink = statbuf.st_nlink ;
761 cur->dn_uid = statbuf.st_uid ;
762 cur->dn_gid = statbuf.st_gid ;
763 cur->dn_rdev_maj = major(statbuf.st_rdev);
764 cur->dn_rdev_min = minor(statbuf.st_rdev);
769 static unsigned count_dirs(struct dnode **dn, int which)
781 if (!S_ISDIR((*dn)->dn_mode))
785 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
786 /* or if it's not . or .. */
788 || (name[1] && (name[1] != '.' || name[2]))
793 return which != SPLIT_FILE ? dirs : all - dirs;
796 /* get memory to hold an array of pointers */
797 static struct dnode **dnalloc(unsigned num)
802 num++; /* so that we have terminating NULL */
803 return xzalloc(num * sizeof(struct dnode *));
806 #if ENABLE_FEATURE_LS_RECURSIVE
807 static void dfree(struct dnode **dnp)
814 for (i = 0; dnp[i]; i++) {
815 struct dnode *cur = dnp[i];
816 if (cur->fname_allocated)
817 free((char*)cur->fullname);
823 #define dfree(...) ((void)0)
826 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
827 static struct dnode **splitdnarray(struct dnode **dn, int which)
835 /* count how many dirs or files there are */
836 dncnt = count_dirs(dn, which);
838 /* allocate a file array and a dir array */
839 dnp = dnalloc(dncnt);
841 /* copy the entrys into the file or dir array */
842 for (d = 0; *dn; dn++) {
843 if (S_ISDIR((*dn)->dn_mode)) {
846 if (which == SPLIT_FILE)
850 if ((which & SPLIT_DIR) /* any dir... */
851 /* ... or not . or .. */
853 || (name[1] && (name[1] != '.' || name[2]))
858 if (which == SPLIT_FILE) {
865 #if ENABLE_FEATURE_LS_SORTFILES
866 static int sortcmp(const void *a, const void *b)
868 struct dnode *d1 = *(struct dnode **)a;
869 struct dnode *d2 = *(struct dnode **)b;
870 unsigned sort_opts = G.all_fmt & SORT_MASK;
873 dif = 0; /* assume SORT_NAME */
874 // TODO: use pre-initialized function pointer
875 // instead of branch forest
876 if (sort_opts == SORT_SIZE) {
877 dif = (d2->dn_size - d1->dn_size);
878 } else if (sort_opts == SORT_ATIME) {
879 dif = (d2->dn_atime - d1->dn_atime);
880 } else if (sort_opts == SORT_CTIME) {
881 dif = (d2->dn_ctime - d1->dn_ctime);
882 } else if (sort_opts == SORT_MTIME) {
883 dif = (d2->dn_mtime - d1->dn_mtime);
884 } else if (sort_opts == SORT_DIR) {
885 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
886 /* } else if (sort_opts == SORT_VERSION) { */
887 /* } else if (sort_opts == SORT_EXT) { */
890 /* sort by name, or tie_breaker for other sorts */
891 if (ENABLE_LOCALE_SUPPORT)
892 dif = strcoll(d1->name, d2->name);
894 dif = strcmp(d1->name, d2->name);
897 /* Make dif fit into an int */
898 if (sizeof(dif) > sizeof(int)) {
899 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
900 /* shift leaving only "int" worth of bits */
902 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
906 return (G.all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
909 static void dnsort(struct dnode **dn, int size)
911 qsort(dn, size, sizeof(*dn), sortcmp);
914 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
917 display_files(dn, nfiles);
920 # define dnsort(dn, size) ((void)0)
921 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
924 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
925 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
927 struct dnode *dn, *cur, **dnp;
928 struct dirent *entry;
933 dir = warn_opendir(path);
935 G.exit_code = EXIT_FAILURE;
936 return NULL; /* could not open the dir */
940 while ((entry = readdir(dir)) != NULL) {
943 /* are we going to list the file- it may be . or .. or a hidden file */
944 if (entry->d_name[0] == '.') {
945 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
946 && !(G.all_fmt & DISP_DOT)
950 if (!(G.all_fmt & DISP_HIDDEN))
953 fullname = concat_path_file(path, entry->d_name);
954 cur = my_stat(fullname, bb_basename(fullname), 0);
959 cur->fname_allocated = 1;
969 /* now that we know how many files there are
970 * allocate memory for an array to hold dnode pointers
973 dnp = dnalloc(nfiles);
974 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
975 dnp[i] = dn; /* save pointer to node in array */
985 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
986 * If any of the -l, -n, -s options is specified, each list
987 * of files within the directory shall be preceded by a
988 * status line indicating the number of file system blocks
989 * occupied by files in the directory in 512-byte units if
990 * the -k option is not specified, or 1024-byte units if the
991 * -k option is specified, rounded up to the next integral
994 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
995 static off_t calculate_blocks(struct dnode **dn)
1000 /* st_blocks is in 512 byte blocks */
1001 blocks += (*dn)->dn_blocks;
1006 /* Even though standard says use 512 byte blocks, coreutils use 1k */
1007 /* Actually, we round up by calculating (blocks + 1) / 2,
1008 * "+ 1" was done when we initialized blocks to 1 */
1013 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1016 struct dnode **subdnp;
1019 if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
1023 printf("%s:\n", (*dn)->fullname);
1025 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1027 if ((G.all_fmt & STYLE_MASK) == STYLE_LONG)
1028 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1031 /* list all files at this level */
1032 sort_and_display_files(subdnp, nfiles);
1034 if (ENABLE_FEATURE_LS_RECURSIVE
1035 && (G.all_fmt & DISP_RECURSIVE)
1039 /* recursive - list the sub-dirs */
1040 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1041 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1043 dnsort(dnd, dndirs);
1044 scan_and_display_dirs_recur(dnd, 0);
1045 /* free the array of dnode pointers to the dirs */
1049 /* free the dnodes and the fullname mem */
1056 int ls_main(int argc UNUSED_PARAM, char **argv)
1068 #if ENABLE_FEATURE_LS_COLOR
1069 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1071 * # ls --color=BOGUS
1072 * ls: invalid argument 'BOGUS' for '--color'
1073 * Valid arguments are:
1074 * 'always', 'yes', 'force'
1075 * 'never', 'no', 'none'
1076 * 'auto', 'tty', 'if-tty'
1077 * (and substrings: "--color=alwa" work too)
1079 static const char ls_longopts[] ALIGN1 =
1080 "color\0" Optional_argument "\xff"; /* no short equivalent */
1081 static const char color_str[] ALIGN1 =
1082 "always\0""yes\0""force\0"
1083 "auto\0""tty\0""if-tty\0";
1084 /* need to initialize since --color has _an optional_ argument */
1085 const char *color_opt = color_str; /* "always" */
1092 if (ENABLE_FEATURE_LS_SORTFILES)
1093 G.all_fmt = SORT_NAME;
1095 #if ENABLE_FEATURE_AUTOWIDTH
1096 /* obtain the terminal width */
1097 get_terminal_width_height(STDIN_FILENO, &G_terminal_width, NULL);
1098 /* go one less... */
1102 /* process options */
1103 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1107 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1108 * in some pairs of opts, only last one takes effect:
1110 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1111 // ":m-l:l-m" - we don't have -m
1112 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1113 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1114 ":C-1:1-C" /* bycols/oneline */
1115 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1116 ":c-u:u-c" /* mtime/atime */
1118 IF_FEATURE_AUTOWIDTH(":w+");
1119 opt = getopt32(argv, ls_options
1120 IF_FEATURE_AUTOWIDTH(, NULL, &G_terminal_width)
1121 IF_FEATURE_LS_COLOR(, &color_opt)
1123 for (i = 0; opt_flags[i] != (1U << 31); i++) {
1124 if (opt & (1 << i)) {
1125 uint32_t flags = opt_flags[i];
1127 if (flags & STYLE_MASK)
1128 G.all_fmt &= ~STYLE_MASK;
1129 if (flags & SORT_MASK)
1130 G.all_fmt &= ~SORT_MASK;
1131 if (flags & TIME_MASK)
1132 G.all_fmt &= ~TIME_MASK;
1138 #if ENABLE_FEATURE_LS_COLOR
1139 /* set G_show_color = 1/0 */
1140 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1141 char *p = getenv("LS_COLORS");
1142 /* LS_COLORS is unset, or (not empty && not "none") ? */
1143 if (!p || (p[0] && strcmp(p, "none") != 0))
1146 if (opt & OPT_color) {
1147 if (color_opt[0] == 'n')
1149 else switch (index_in_substrings(color_str, color_opt)) {
1153 if (isatty(STDOUT_FILENO)) {
1163 /* sort out which command line options take precedence */
1164 if (ENABLE_FEATURE_LS_RECURSIVE && (G.all_fmt & DISP_NOLIST))
1165 G.all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1166 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1167 if (G.all_fmt & TIME_CHANGE)
1168 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_CTIME;
1169 if (G.all_fmt & TIME_ACCESS)
1170 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_ATIME;
1172 if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */
1173 G.all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1175 /* choose a display format if one was not already specified by an option */
1176 if (!(G.all_fmt & STYLE_MASK))
1177 G.all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1181 *--argv = (char*)".";
1184 G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1186 /* stuff the command line file names into a dnode array */
1190 cur = my_stat(*argv, *argv,
1191 /* follow links on command line unless -l, -s or -F: */
1192 !((G.all_fmt & STYLE_MASK) == STYLE_LONG
1193 || (G.all_fmt & LIST_BLOCKS)
1194 || (option_mask32 & OPT_F)
1197 || (option_mask32 & OPT_H)
1198 /* ... or if -L, but my_stat always follows links if -L */
1203 /*cur->fname_allocated = 0; - already is */
1209 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1213 /* now that we know how many files there are
1214 * allocate memory for an array to hold dnode pointers
1216 dnp = dnalloc(nfiles);
1217 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1218 dnp[i] = dn; /* save pointer to node in array */
1224 if (G.all_fmt & DISP_NOLIST) {
1225 sort_and_display_files(dnp, nfiles);
1227 dnd = splitdnarray(dnp, SPLIT_DIR);
1228 dnf = splitdnarray(dnp, SPLIT_FILE);
1229 dndirs = count_dirs(dnp, SPLIT_DIR);
1230 dnfiles = nfiles - dndirs;
1232 sort_and_display_files(dnf, dnfiles);
1233 if (ENABLE_FEATURE_CLEAN_UP)
1237 dnsort(dnd, dndirs);
1238 scan_and_display_dirs_recur(dnd, dnfiles == 0);
1239 if (ENABLE_FEATURE_CLEAN_UP)
1244 if (ENABLE_FEATURE_CLEAN_UP)