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.
7 /* [date unknown. Perhaps before year 2000]
8 * To achieve a small memory footprint, this version of 'ls' doesn't do any
9 * file sorting, and only has the most essential command line switches
10 * (i.e., the ones I couldn't live without :-) All features which involve
11 * linking in substantial chunks of libc can be disabled.
13 * Although I don't really want to add new features to this program to
14 * keep it small, I *am* interested to receive bug fixes and ways to make
18 * 1. hidden files can make column width too large
20 * NON-OPTIMAL BEHAVIOUR:
21 * 1. autowidth reads directories twice
22 * 2. if you do a short directory listing without filetype characters
23 * appended, there's no need to stat each one
25 * 1. requires lstat (BSD) - how do you do it without?
28 * ls sorts listing now, and supports almost all options.
34 //config: ls is used to list the contents of directories.
36 //config:config FEATURE_LS_FILETYPES
37 //config: bool "Enable filetyping options (-p and -F)"
39 //config: depends on LS
41 //config:config FEATURE_LS_FOLLOWLINKS
42 //config: bool "Enable symlinks dereferencing (-L)"
44 //config: depends on LS
46 //config:config FEATURE_LS_RECURSIVE
47 //config: bool "Enable recursion (-R)"
49 //config: depends on LS
51 //config:config FEATURE_LS_SORTFILES
52 //config: bool "Sort the file names"
54 //config: depends on LS
56 //config: Allow ls to sort file names alphabetically.
58 //config:config FEATURE_LS_TIMESTAMPS
59 //config: bool "Show file timestamps"
61 //config: depends on LS
63 //config: Allow ls to display timestamps for files.
65 //config:config FEATURE_LS_USERNAME
66 //config: bool "Show username/groupnames"
68 //config: depends on LS
70 //config: Allow ls to display username/groupname for files.
72 //config:config FEATURE_LS_COLOR
73 //config: bool "Allow use of color to identify file types"
75 //config: depends on LS && LONG_OPTS
77 //config: This enables the --color option to ls.
79 //config:config FEATURE_LS_COLOR_IS_DEFAULT
80 //config: bool "Produce colored ls output by default"
82 //config: depends on FEATURE_LS_COLOR
84 //config: Saying yes here will turn coloring on by default,
85 //config: even if no "--color" option is given to the ls command.
86 //config: This is not recommended, since the colors are not
87 //config: configurable, and the output may not be legible on
88 //config: many output screens.
90 //applet:IF_LS(APPLET_NOEXEC(ls, ls, BB_DIR_BIN, BB_SUID_DROP, ls))
92 //kbuild:lib-$(CONFIG_LS) += ls.o
94 //usage:#define ls_trivial_usage
96 //usage: IF_FEATURE_LS_FOLLOWLINKS("LH")
97 //usage: IF_FEATURE_LS_RECURSIVE("R")
98 //usage: IF_FEATURE_LS_FILETYPES("Fp") "lins"
99 //usage: IF_FEATURE_LS_TIMESTAMPS("e")
100 //usage: IF_FEATURE_HUMAN_READABLE("h")
101 //usage: IF_FEATURE_LS_SORTFILES("rSXv")
102 //usage: IF_FEATURE_LS_TIMESTAMPS("ctu")
103 //usage: IF_SELINUX("kKZ") "]"
104 //usage: IF_FEATURE_AUTOWIDTH(" [-w WIDTH]") " [FILE]..."
105 //usage:#define ls_full_usage "\n\n"
106 //usage: "List directory contents\n"
107 //usage: "\n -1 One column output"
108 //usage: "\n -a Include entries which start with ."
109 //usage: "\n -A Like -a, but exclude . and .."
110 //usage: "\n -C List by columns"
111 //usage: "\n -x List by lines"
112 //usage: "\n -d List directory entries instead of contents"
113 //usage: IF_FEATURE_LS_FOLLOWLINKS(
114 //usage: "\n -L Follow symlinks"
115 //usage: "\n -H Follow symlinks on command line"
117 //usage: IF_FEATURE_LS_RECURSIVE(
118 //usage: "\n -R Recurse"
120 //usage: IF_FEATURE_LS_FILETYPES(
121 //usage: "\n -p Append / to dir entries"
122 //usage: "\n -F Append indicator (one of */=@|) to entries"
124 //usage: "\n -l Long listing format"
125 //usage: "\n -i List inode numbers"
126 //usage: "\n -n List numeric UIDs and GIDs instead of names"
127 //usage: "\n -s List allocated blocks"
128 //usage: IF_FEATURE_LS_TIMESTAMPS(
129 //usage: "\n -e List full date and time"
131 //usage: IF_FEATURE_HUMAN_READABLE(
132 //usage: "\n -h List sizes in human readable format (1K 243M 2G)"
134 //usage: IF_FEATURE_LS_SORTFILES(
135 //usage: "\n -r Sort in reverse order"
136 //usage: "\n -S Sort by size"
137 //usage: "\n -X Sort by extension"
138 //usage: "\n -v Sort by version"
140 //usage: IF_FEATURE_LS_TIMESTAMPS(
141 //usage: "\n -c With -l: sort by ctime"
142 //usage: "\n -t With -l: sort by mtime"
143 //usage: "\n -u With -l: sort by atime"
146 //usage: "\n -k List security context"
147 //usage: "\n -K List security context in long format"
148 //usage: "\n -Z List security context and permission"
150 //usage: IF_FEATURE_AUTOWIDTH(
151 //usage: "\n -w N Assume the terminal is N columns wide"
153 //usage: IF_FEATURE_LS_COLOR(
154 //usage: "\n --color[={always,never,auto}] Control coloring"
158 #include "common_bufsiz.h"
162 /* This is a NOEXEC applet. Be very careful! */
166 /* ftpd uses ls, and without timestamps Mozilla won't understand
167 * ftpd's LIST output.
169 # undef CONFIG_FEATURE_LS_TIMESTAMPS
170 # undef ENABLE_FEATURE_LS_TIMESTAMPS
171 # undef IF_FEATURE_LS_TIMESTAMPS
172 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
173 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
174 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
175 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
176 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
181 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
187 /* Bits in G.all_fmt: */
189 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
190 /* what file information will be listed */
192 LIST_BLOCKS = 1 << 1,
193 LIST_MODEBITS = 1 << 2,
194 LIST_NLINKS = 1 << 3,
195 LIST_ID_NAME = 1 << 4,
196 LIST_ID_NUMERIC = 1 << 5,
197 LIST_CONTEXT = 1 << 6,
199 LIST_DATE_TIME = 1 << 8,
200 LIST_FULLTIME = 1 << 9,
201 LIST_SYMLINK = 1 << 10,
202 LIST_FILETYPE = 1 << 11, /* show / suffix for dirs */
203 LIST_CLASSIFY = 1 << 12, /* requires LIST_FILETYPE, also show *,|,@,= suffixes */
204 LIST_MASK = (LIST_CLASSIFY << 1) - 1,
206 /* what files will be displayed */
207 DISP_DIRNAME = 1 << 13, /* 2 or more items? label directories */
208 DISP_HIDDEN = 1 << 14, /* show filenames starting with . */
209 DISP_DOT = 1 << 15, /* show . and .. */
210 DISP_NOLIST = 1 << 16, /* show directory as itself, not contents */
211 DISP_RECURSIVE = 1 << 17, /* show directory and everything below it */
212 DISP_ROWS = 1 << 18, /* print across rows */
213 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
215 /* what is the overall style of the listing */
216 STYLE_COLUMNAR = 1 << 19, /* many records per line */
217 STYLE_LONG = 2 << 19, /* one record per line, extended info */
218 STYLE_SINGLE = 3 << 19, /* one record per line */
219 STYLE_MASK = STYLE_SINGLE,
221 /* which of the three times will be used */
222 TIME_CHANGE = (1 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
223 TIME_ACCESS = (2 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
224 TIME_MASK = (3 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
226 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
227 SORT_REVERSE = 1 << 23,
229 SORT_NAME = 0, /* sort by file name */
230 SORT_SIZE = 1 << 24, /* sort by file size */
231 SORT_ATIME = 2 << 24, /* sort by last access time */
232 SORT_CTIME = 3 << 24, /* sort by last change time */
233 SORT_MTIME = 4 << 24, /* sort by last modification time */
234 SORT_VERSION = 5 << 24, /* sort by version */
235 SORT_EXT = 6 << 24, /* sort by file name extension */
236 SORT_DIR = 7 << 24, /* sort by file or directory */
237 SORT_MASK = (7 << 24) * ENABLE_FEATURE_LS_SORTFILES,
239 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
240 LIST_DATE_TIME | LIST_SYMLINK,
243 /* -Cadil1 Std options, busybox always supports */
244 /* -gnsxA Std options, busybox always supports */
245 /* -Q GNU option, busybox always supports */
246 /* -k SELinux option, busybox always supports (ignores if !SELinux) */
247 /* Std has -k which means "show sizes in kbytes" */
248 /* -LHRctur Std options, busybox optionally supports */
249 /* -Fp Std options, busybox optionally supports */
250 /* -SXvhTw GNU options, busybox optionally supports */
251 /* -T WIDTH Ignored (we don't use tabs on output) */
252 /* -KZ SELinux mandated options, busybox optionally supports */
253 /* (coreutils 8.4 has no -K, remove it?) */
254 /* -e I think we made this one up (looks similar to GNU --full-time) */
255 /* We already used up all 32 bits, if we need to add more, candidates for removal: */
256 /* -K, -T, -e (add --full-time instead) */
257 static const char ls_options[] ALIGN1 =
258 "Cadil1gnsxQAk" /* 13 opts, total 13 */
259 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
260 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
261 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
262 IF_FEATURE_LS_RECURSIVE("R") /* 1, 24 */
263 IF_SELINUX("KZ") /* 2, 26 */
264 IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 28 */
265 IF_FEATURE_HUMAN_READABLE("h") /* 1, 29 */
266 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 31 */
267 /* with --color, we use all 32 bits */;
287 OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS,
291 OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
293 OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
294 OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
296 OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX,
298 OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
299 OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
301 OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH,
303 OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS,
304 OPT_e = (1 << OPTBIT_e) * ENABLE_FEATURE_LS_TIMESTAMPS,
305 OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS,
306 OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS,
307 OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES,
308 OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES,
309 OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES,
310 OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES,
311 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
312 OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES,
313 OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE,
314 OPT_K = (1 << OPTBIT_K) * ENABLE_SELINUX,
315 OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX,
316 OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS,
317 OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS,
318 OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE,
319 OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_AUTOWIDTH,
320 OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_AUTOWIDTH,
321 OPT_color = (1 << OPTBIT_color) * ENABLE_FEATURE_LS_COLOR,
324 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
325 static const uint32_t opt_flags[] = {
326 STYLE_COLUMNAR, /* C */
327 DISP_HIDDEN | DISP_DOT, /* a */
330 LIST_LONG | STYLE_LONG, /* l */
331 STYLE_SINGLE, /* 1 */
332 LIST_LONG | STYLE_LONG, /* g (don't show owner) - handled via OPT_g. assumes l */
333 LIST_ID_NUMERIC | LIST_LONG | STYLE_LONG, /* n (assumes l) */
335 DISP_ROWS | STYLE_COLUMNAR, /* x */
336 0, /* Q (quote filename) - handled via OPT_Q */
338 ENABLE_SELINUX * (LIST_CONTEXT|STYLE_SINGLE), /* k (ignored if !SELINUX) */
339 #if ENABLE_FEATURE_LS_TIMESTAMPS
340 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
341 LIST_FULLTIME, /* e */
342 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
343 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
345 #if ENABLE_FEATURE_LS_SORTFILES
348 SORT_REVERSE, /* r */
349 SORT_VERSION, /* v */
351 #if ENABLE_FEATURE_LS_FILETYPES
352 LIST_FILETYPE | LIST_CLASSIFY, /* F */
353 LIST_FILETYPE, /* p */
355 #if ENABLE_FEATURE_LS_RECURSIVE
356 DISP_RECURSIVE, /* R */
359 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME|STYLE_SINGLE, /* K */
360 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT|STYLE_SINGLE, /* Z */
363 /* options after Z are not processed through opt_flags */
368 * a directory entry and its stat info
371 const char *name; /* usually basename, but think "ls -l dir/file" */
372 const char *fullname; /* full name (usable for stat etc) */
373 struct dnode *dn_next; /* for linked list */
374 IF_SELINUX(security_context_t sid;)
375 smallint fname_allocated;
377 /* Used to avoid re-doing [l]stat at printout stage
378 * if we already collected needed data in scan stage:
380 mode_t dn_mode_lstat; /* obtained with lstat, or 0 */
381 mode_t dn_mode_stat; /* obtained with stat, or 0 */
383 // struct stat dstat;
384 // struct stat is huge. We don't need it in full.
385 // At least we don't need st_dev and st_blksize,
386 // but there are invisible fields as well
387 // (such as nanosecond-resolution timespamps)
388 // and padding, which we also don't want to store.
389 // We also can pre-parse dev_t dn_rdev (in glibc, it's huge).
390 // On 32-bit uclibc: dnode size went from 112 to 84 bytes.
392 /* Same names as in struct stat, but with dn_ instead of st_ pfx: */
393 mode_t dn_mode; /* obtained with lstat OR stat, depending on -L etc */
395 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
408 // blksize_t dn_blksize;
412 #if ENABLE_FEATURE_LS_COLOR
414 # define G_show_color (G.show_color)
416 # define G_show_color 0
420 #if ENABLE_FEATURE_AUTOWIDTH
421 unsigned terminal_width;
422 # define G_terminal_width (G.terminal_width)
424 # define G_terminal_width TERMINAL_WIDTH
426 #if ENABLE_FEATURE_LS_TIMESTAMPS
427 /* Do time() just once. Saves one syscall per file for "ls -l" */
428 time_t current_time_t;
431 #define G (*(struct globals*)bb_common_bufsiz1)
432 #define INIT_G() do { \
433 setup_common_bufsiz(); \
434 /* we have to zero it out because of NOEXEC */ \
435 memset(&G, 0, sizeof(G)); \
436 IF_FEATURE_AUTOWIDTH(G_terminal_width = TERMINAL_WIDTH;) \
437 IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \
441 /*** Output code ***/
444 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
445 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
446 * 3/7:multiplexed char/block device)
447 * and we use 0 for unknown and 15 for executables (see below) */
448 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
449 /* un fi chr - dir - blk - file - link - sock - - exe */
450 #define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)])
451 /* 036 black foreground 050 black background
452 037 red foreground 051 red background
453 040 green foreground 052 green background
454 041 brown foreground 053 brown background
455 042 blue foreground 054 blue background
456 043 magenta (purple) foreground 055 magenta background
457 044 cyan (light blue) foreground 056 cyan background
458 045 gray foreground 057 white background
460 #define COLOR(mode) ( \
461 /*un fi chr - dir - blk - file - link - sock - - exe */ \
462 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
464 /* Select normal (0) [actually "reset all"] or bold (1)
465 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
466 * let's use 7 for "impossible" types, just for fun)
467 * Note: coreutils 6.9 uses inverted red for setuid binaries.
469 #define ATTR(mode) ( \
470 /*un fi chr - dir - blk - file- link- sock- - exe */ \
471 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
474 #if ENABLE_FEATURE_LS_COLOR
475 /* mode of zero is interpreted as "unknown" (stat failed) */
476 static char fgcolor(mode_t mode)
478 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
479 return COLOR(0xF000); /* File is executable ... */
482 static char bold(mode_t mode)
484 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
485 return ATTR(0xF000); /* File is executable ... */
490 #if ENABLE_FEATURE_LS_FILETYPES
491 static char append_char(mode_t mode)
493 if (!(G.all_fmt & LIST_FILETYPE))
497 if (!(G.all_fmt & LIST_CLASSIFY))
499 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
501 return APPCHAR(mode);
505 static unsigned calc_name_len(const char *name)
510 // TODO: quote tab as \t, etc, if -Q
511 name = printable_string(&uni_stat, name);
513 if (!(option_mask32 & OPT_Q)) {
514 return uni_stat.unicode_width;
517 len = 2 + uni_stat.unicode_width;
519 if (*name == '"' || *name == '\\') {
527 /* Return the number of used columns.
528 * Note that only STYLE_COLUMNAR uses return value.
529 * STYLE_SINGLE and STYLE_LONG don't care.
530 * coreutils 7.2 also supports:
531 * ls -b (--escape) = octal escapes (although it doesn't look like working)
532 * ls -N (--literal) = not escape at all
534 static unsigned print_name(const char *name)
539 // TODO: quote tab as \t, etc, if -Q
540 name = printable_string(&uni_stat, name);
542 if (!(option_mask32 & OPT_Q)) {
544 return uni_stat.unicode_width;
547 len = 2 + uni_stat.unicode_width;
550 if (*name == '"' || *name == '\\') {
561 /* Return the number of used columns.
562 * Note that only STYLE_COLUMNAR uses return value,
563 * STYLE_SINGLE and STYLE_LONG don't care.
565 static NOINLINE unsigned display_single(const struct dnode *dn)
569 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
574 #if ENABLE_FEATURE_LS_FILETYPES
575 append = append_char(dn->dn_mode);
578 /* Do readlink early, so that if it fails, error message
579 * does not appear *inside* the "ls -l" line */
581 if (G.all_fmt & LIST_SYMLINK)
582 if (S_ISLNK(dn->dn_mode))
583 lpath = xmalloc_readlink_or_warn(dn->fullname);
585 if (G.all_fmt & LIST_INO)
586 column += printf("%7llu ", (long long) dn->dn_ino);
587 //TODO: -h should affect -s too:
588 if (G.all_fmt & LIST_BLOCKS)
589 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1));
590 if (G.all_fmt & LIST_MODEBITS)
591 column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode));
592 if (G.all_fmt & LIST_NLINKS)
593 column += printf("%4lu ", (long) dn->dn_nlink);
594 if (G.all_fmt & LIST_ID_NUMERIC) {
595 if (option_mask32 & OPT_g)
596 column += printf("%-8u ", (int) dn->dn_gid);
598 column += printf("%-8u %-8u ",
602 #if ENABLE_FEATURE_LS_USERNAME
603 else if (G.all_fmt & LIST_ID_NAME) {
604 if (option_mask32 & OPT_g) {
605 column += printf("%-8.8s ",
606 get_cached_groupname(dn->dn_gid));
608 column += printf("%-8.8s %-8.8s ",
609 get_cached_username(dn->dn_uid),
610 get_cached_groupname(dn->dn_gid));
614 if (G.all_fmt & LIST_SIZE) {
615 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
616 column += printf("%4u, %3u ",
620 if (option_mask32 & OPT_h) {
621 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
622 /* print size, show one fractional, use suffixes */
623 make_human_readable_str(dn->dn_size, 1, 0)
626 column += printf("%9"OFF_FMT"u ", dn->dn_size);
630 #if ENABLE_FEATURE_LS_TIMESTAMPS
631 if (G.all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
633 const time_t *ttime = &dn->dn_mtime;
634 if (G.all_fmt & TIME_ACCESS)
635 ttime = &dn->dn_atime;
636 if (G.all_fmt & TIME_CHANGE)
637 ttime = &dn->dn_ctime;
638 filetime = ctime(ttime);
639 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
640 if (G.all_fmt & LIST_FULLTIME) { /* -e */
641 /* Note: coreutils 8.4 ls --full-time prints:
642 * 2009-07-13 17:49:27.000000000 +0200
644 column += printf("%.24s ", filetime);
645 } else { /* LIST_DATE_TIME */
646 /* G.current_time_t ~== time(NULL) */
647 time_t age = G.current_time_t - *ttime;
648 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
649 /* less than 6 months old */
650 /* "mmm dd hh:mm " */
651 printf("%.12s ", filetime + 4);
654 /* "mmm dd yyyyy " after year 9999 :) */
655 strchr(filetime + 20, '\n')[0] = ' ';
656 printf("%.7s%6s", filetime + 4, filetime + 20);
663 if (G.all_fmt & LIST_CONTEXT) {
664 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
669 #if ENABLE_FEATURE_LS_COLOR
671 mode_t mode = dn->dn_mode_lstat;
673 if (lstat(dn->fullname, &statbuf) == 0)
674 mode = statbuf.st_mode;
675 printf("\033[%u;%um", bold(mode), fgcolor(mode));
678 column += print_name(dn->name);
685 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
686 if ((G.all_fmt & LIST_FILETYPE) || G_show_color) {
687 mode_t mode = dn->dn_mode_stat;
689 if (stat(dn->fullname, &statbuf) == 0)
690 mode = statbuf.st_mode;
691 # if ENABLE_FEATURE_LS_FILETYPES
692 append = append_char(mode);
694 # if ENABLE_FEATURE_LS_COLOR
696 printf("\033[%u;%um", bold(mode), fgcolor(mode));
701 column += print_name(lpath) + 4;
707 #if ENABLE_FEATURE_LS_FILETYPES
708 if (G.all_fmt & LIST_FILETYPE) {
719 static void display_files(struct dnode **dn, unsigned nfiles)
721 unsigned i, ncols, nrows, row, nc;
724 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
726 if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
729 /* find the longest file name, use that as the column width */
730 for (i = 0; dn[i]; i++) {
731 int len = calc_name_len(dn[i]->name);
732 if (column_width < len)
736 IF_SELINUX( ((G.all_fmt & LIST_CONTEXT) ? 33 : 0) + )
737 ((G.all_fmt & LIST_INO) ? 8 : 0) +
738 ((G.all_fmt & LIST_BLOCKS) ? 5 : 0);
739 ncols = (unsigned)G_terminal_width / column_width;
743 nrows = nfiles / ncols;
744 if (nrows * ncols < nfiles)
745 nrows++; /* round up fractionals */
753 for (row = 0; row < nrows; row++) {
754 for (nc = 0; nc < ncols; nc++) {
755 /* reach into the array based on the column and row */
756 if (G.all_fmt & DISP_ROWS)
757 i = (row * ncols) + nc; /* display across row */
759 i = (nc * nrows) + row; /* display by column */
763 printf("%*s", nexttab, "");
766 nexttab = column + column_width;
767 column += display_single(dn[i]);
776 /*** Dir scanning code ***/
778 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
783 cur = xzalloc(sizeof(*cur));
784 cur->fullname = fullname;
787 if ((option_mask32 & OPT_L) || force_follow) {
789 if (is_selinux_enabled()) {
790 getfilecon(fullname, &cur->sid);
793 if (stat(fullname, &statbuf)) {
794 bb_simple_perror_msg(fullname);
795 G.exit_code = EXIT_FAILURE;
799 cur->dn_mode_stat = statbuf.st_mode;
802 if (is_selinux_enabled()) {
803 lgetfilecon(fullname, &cur->sid);
806 if (lstat(fullname, &statbuf)) {
807 bb_simple_perror_msg(fullname);
808 G.exit_code = EXIT_FAILURE;
812 cur->dn_mode_lstat = statbuf.st_mode;
815 /* cur->dstat = statbuf: */
816 cur->dn_mode = statbuf.st_mode ;
817 cur->dn_size = statbuf.st_size ;
818 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
819 cur->dn_atime = statbuf.st_atime ;
820 cur->dn_mtime = statbuf.st_mtime ;
821 cur->dn_ctime = statbuf.st_ctime ;
823 cur->dn_ino = statbuf.st_ino ;
824 cur->dn_blocks = statbuf.st_blocks;
825 cur->dn_nlink = statbuf.st_nlink ;
826 cur->dn_uid = statbuf.st_uid ;
827 cur->dn_gid = statbuf.st_gid ;
828 cur->dn_rdev_maj = major(statbuf.st_rdev);
829 cur->dn_rdev_min = minor(statbuf.st_rdev);
834 static unsigned count_dirs(struct dnode **dn, int which)
846 if (!S_ISDIR((*dn)->dn_mode))
850 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
851 /* or if it's not . or .. */
853 || (name[1] && (name[1] != '.' || name[2]))
858 return which != SPLIT_FILE ? dirs : all - dirs;
861 /* get memory to hold an array of pointers */
862 static struct dnode **dnalloc(unsigned num)
867 num++; /* so that we have terminating NULL */
868 return xzalloc(num * sizeof(struct dnode *));
871 #if ENABLE_FEATURE_LS_RECURSIVE
872 static void dfree(struct dnode **dnp)
879 for (i = 0; dnp[i]; i++) {
880 struct dnode *cur = dnp[i];
881 if (cur->fname_allocated)
882 free((char*)cur->fullname);
888 #define dfree(...) ((void)0)
891 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
892 static struct dnode **splitdnarray(struct dnode **dn, int which)
900 /* count how many dirs or files there are */
901 dncnt = count_dirs(dn, which);
903 /* allocate a file array and a dir array */
904 dnp = dnalloc(dncnt);
906 /* copy the entrys into the file or dir array */
907 for (d = 0; *dn; dn++) {
908 if (S_ISDIR((*dn)->dn_mode)) {
911 if (which == SPLIT_FILE)
915 if ((which & SPLIT_DIR) /* any dir... */
916 /* ... or not . or .. */
918 || (name[1] && (name[1] != '.' || name[2]))
923 if (which == SPLIT_FILE) {
930 #if ENABLE_FEATURE_LS_SORTFILES
931 static int sortcmp(const void *a, const void *b)
933 struct dnode *d1 = *(struct dnode **)a;
934 struct dnode *d2 = *(struct dnode **)b;
935 unsigned sort_opts = G.all_fmt & SORT_MASK;
938 dif = 0; /* assume SORT_NAME */
939 // TODO: use pre-initialized function pointer
940 // instead of branch forest
941 if (sort_opts == SORT_SIZE) {
942 dif = (d2->dn_size - d1->dn_size);
944 if (sort_opts == SORT_ATIME) {
945 dif = (d2->dn_atime - d1->dn_atime);
947 if (sort_opts == SORT_CTIME) {
948 dif = (d2->dn_ctime - d1->dn_ctime);
950 if (sort_opts == SORT_MTIME) {
951 dif = (d2->dn_mtime - d1->dn_mtime);
953 if (sort_opts == SORT_DIR) {
954 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
956 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
957 if (sort_opts == SORT_VERSION) {
958 dif = strverscmp(d1->name, d2->name);
961 if (sort_opts == SORT_EXT) {
962 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
965 /* sort by name, use as tie breaker for other sorts */
966 if (ENABLE_LOCALE_SUPPORT)
967 dif = strcoll(d1->name, d2->name);
969 dif = strcmp(d1->name, d2->name);
972 /* Make dif fit into an int */
973 if (sizeof(dif) > sizeof(int)) {
974 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
975 /* shift leaving only "int" worth of bits */
977 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
981 return (G.all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
984 static void dnsort(struct dnode **dn, int size)
986 qsort(dn, size, sizeof(*dn), sortcmp);
989 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
992 display_files(dn, nfiles);
995 # define dnsort(dn, size) ((void)0)
996 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
999 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
1000 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
1002 struct dnode *dn, *cur, **dnp;
1003 struct dirent *entry;
1008 dir = warn_opendir(path);
1010 G.exit_code = EXIT_FAILURE;
1011 return NULL; /* could not open the dir */
1015 while ((entry = readdir(dir)) != NULL) {
1018 /* are we going to list the file- it may be . or .. or a hidden file */
1019 if (entry->d_name[0] == '.') {
1020 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
1021 && !(G.all_fmt & DISP_DOT)
1025 if (!(G.all_fmt & DISP_HIDDEN))
1028 fullname = concat_path_file(path, entry->d_name);
1029 cur = my_stat(fullname, bb_basename(fullname), 0);
1034 cur->fname_allocated = 1;
1044 /* now that we know how many files there are
1045 * allocate memory for an array to hold dnode pointers
1048 dnp = dnalloc(nfiles);
1049 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1050 dnp[i] = dn; /* save pointer to node in array */
1060 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
1061 * If any of the -l, -n, -s options is specified, each list
1062 * of files within the directory shall be preceded by a
1063 * status line indicating the number of file system blocks
1064 * occupied by files in the directory in 512-byte units if
1065 * the -k option is not specified, or 1024-byte units if the
1066 * -k option is specified, rounded up to the next integral
1069 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
1070 static off_t calculate_blocks(struct dnode **dn)
1075 /* st_blocks is in 512 byte blocks */
1076 blocks += (*dn)->dn_blocks;
1081 /* Even though standard says use 512 byte blocks, coreutils use 1k */
1082 /* Actually, we round up by calculating (blocks + 1) / 2,
1083 * "+ 1" was done when we initialized blocks to 1 */
1088 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1091 struct dnode **subdnp;
1094 if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
1098 printf("%s:\n", (*dn)->fullname);
1100 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1102 if ((G.all_fmt & STYLE_MASK) == STYLE_LONG || (G.all_fmt & LIST_BLOCKS))
1103 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1106 /* list all files at this level */
1107 sort_and_display_files(subdnp, nfiles);
1109 if (ENABLE_FEATURE_LS_RECURSIVE
1110 && (G.all_fmt & DISP_RECURSIVE)
1114 /* recursive - list the sub-dirs */
1115 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1116 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1118 dnsort(dnd, dndirs);
1119 scan_and_display_dirs_recur(dnd, 0);
1120 /* free the array of dnode pointers to the dirs */
1124 /* free the dnodes and the fullname mem */
1131 int ls_main(int argc UNUSED_PARAM, char **argv)
1143 #if ENABLE_FEATURE_LS_COLOR
1144 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1146 * # ls --color=BOGUS
1147 * ls: invalid argument 'BOGUS' for '--color'
1148 * Valid arguments are:
1149 * 'always', 'yes', 'force'
1150 * 'never', 'no', 'none'
1151 * 'auto', 'tty', 'if-tty'
1152 * (and substrings: "--color=alwa" work too)
1154 static const char ls_longopts[] ALIGN1 =
1155 "color\0" Optional_argument "\xff"; /* no short equivalent */
1156 static const char color_str[] ALIGN1 =
1157 "always\0""yes\0""force\0"
1158 "auto\0""tty\0""if-tty\0";
1159 /* need to initialize since --color has _an optional_ argument */
1160 const char *color_opt = color_str; /* "always" */
1167 if (ENABLE_FEATURE_LS_SORTFILES)
1168 G.all_fmt = SORT_NAME;
1170 #if ENABLE_FEATURE_AUTOWIDTH
1171 /* obtain the terminal width */
1172 G_terminal_width = get_terminal_width(STDIN_FILENO);
1173 /* go one less... */
1177 /* process options */
1178 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1181 IF_FEATURE_LS_TIMESTAMPS("el")
1182 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1183 * in some pairs of opts, only last one takes effect:
1185 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1186 // ":m-l:l-m" - we don't have -m
1187 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1188 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1189 ":C-1:1-C" /* bycols/oneline */
1190 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1191 IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */
1193 IF_FEATURE_AUTOWIDTH(":w+");
1194 opt = getopt32(argv, ls_options
1195 IF_FEATURE_AUTOWIDTH(, NULL, &G_terminal_width)
1196 IF_FEATURE_LS_COLOR(, &color_opt)
1198 for (i = 0; opt_flags[i] != (1U << 31); i++) {
1199 if (opt & (1 << i)) {
1200 uint32_t flags = opt_flags[i];
1202 if (flags & STYLE_MASK)
1203 G.all_fmt &= ~STYLE_MASK;
1204 if (flags & SORT_MASK)
1205 G.all_fmt &= ~SORT_MASK;
1206 if (flags & TIME_MASK)
1207 G.all_fmt &= ~TIME_MASK;
1213 #if ENABLE_FEATURE_LS_COLOR
1214 /* set G_show_color = 1/0 */
1215 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1216 char *p = getenv("LS_COLORS");
1217 /* LS_COLORS is unset, or (not empty && not "none") ? */
1218 if (!p || (p[0] && strcmp(p, "none") != 0))
1221 if (opt & OPT_color) {
1222 if (color_opt[0] == 'n')
1224 else switch (index_in_substrings(color_str, color_opt)) {
1228 if (isatty(STDOUT_FILENO)) {
1238 /* sort out which command line options take precedence */
1239 if (ENABLE_FEATURE_LS_RECURSIVE && (G.all_fmt & DISP_NOLIST))
1240 G.all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1241 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1242 if (G.all_fmt & TIME_CHANGE)
1243 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_CTIME;
1244 if (G.all_fmt & TIME_ACCESS)
1245 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_ATIME;
1247 if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */
1248 G.all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1250 /* choose a display format if one was not already specified by an option */
1251 if (!(G.all_fmt & STYLE_MASK))
1252 G.all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1256 *--argv = (char*)".";
1259 G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1261 /* stuff the command line file names into a dnode array */
1265 cur = my_stat(*argv, *argv,
1266 /* follow links on command line unless -l, -s or -F: */
1267 !((G.all_fmt & STYLE_MASK) == STYLE_LONG
1268 || (G.all_fmt & LIST_BLOCKS)
1269 || (option_mask32 & OPT_F)
1272 || (option_mask32 & OPT_H)
1273 /* ... or if -L, but my_stat always follows links if -L */
1278 /*cur->fname_allocated = 0; - already is */
1284 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1288 /* now that we know how many files there are
1289 * allocate memory for an array to hold dnode pointers
1291 dnp = dnalloc(nfiles);
1292 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1293 dnp[i] = dn; /* save pointer to node in array */
1299 if (G.all_fmt & DISP_NOLIST) {
1300 sort_and_display_files(dnp, nfiles);
1302 dnd = splitdnarray(dnp, SPLIT_DIR);
1303 dnf = splitdnarray(dnp, SPLIT_FILE);
1304 dndirs = count_dirs(dnp, SPLIT_DIR);
1305 dnfiles = nfiles - dndirs;
1307 sort_and_display_files(dnf, dnfiles);
1308 if (ENABLE_FEATURE_CLEAN_UP)
1312 dnsort(dnd, dndirs);
1313 scan_and_display_dirs_recur(dnd, dnfiles == 0);
1314 if (ENABLE_FEATURE_CLEAN_UP)
1319 if (ENABLE_FEATURE_CLEAN_UP)