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: Enable the ls options (-p and -F).
43 //config:config FEATURE_LS_FOLLOWLINKS
44 //config: bool "Enable symlinks dereferencing (-L)"
46 //config: depends on LS
48 //config: Enable the ls option (-L).
50 //config:config FEATURE_LS_RECURSIVE
51 //config: bool "Enable recursion (-R)"
53 //config: depends on LS
55 //config: Enable the ls option (-R).
57 //config:config FEATURE_LS_SORTFILES
58 //config: bool "Sort the file names"
60 //config: depends on LS
62 //config: Allow ls to sort file names alphabetically.
64 //config:config FEATURE_LS_TIMESTAMPS
65 //config: bool "Show file timestamps"
67 //config: depends on LS
69 //config: Allow ls to display timestamps for files.
71 //config:config FEATURE_LS_USERNAME
72 //config: bool "Show username/groupnames"
74 //config: depends on LS
76 //config: Allow ls to display username/groupname for files.
78 //config:config FEATURE_LS_COLOR
79 //config: bool "Allow use of color to identify file types"
81 //config: depends on LS && LONG_OPTS
83 //config: This enables the --color option to ls.
85 //config:config FEATURE_LS_COLOR_IS_DEFAULT
86 //config: bool "Produce colored ls output by default"
88 //config: depends on FEATURE_LS_COLOR
90 //config: Saying yes here will turn coloring on by default,
91 //config: even if no "--color" option is given to the ls command.
92 //config: This is not recommended, since the colors are not
93 //config: configurable, and the output may not be legible on
94 //config: many output screens.
96 //applet:IF_LS(APPLET_NOEXEC(ls, ls, BB_DIR_BIN, BB_SUID_DROP, ls))
98 //kbuild:lib-$(CONFIG_LS) += ls.o
100 //usage:#define ls_trivial_usage
102 //usage: IF_FEATURE_LS_FOLLOWLINKS("LH")
103 //usage: IF_FEATURE_LS_RECURSIVE("R")
104 //usage: IF_FEATURE_LS_FILETYPES("Fp") "lins"
105 //usage: IF_FEATURE_LS_TIMESTAMPS("e")
106 //usage: IF_FEATURE_HUMAN_READABLE("h")
107 //usage: IF_FEATURE_LS_SORTFILES("rSXv")
108 //usage: IF_FEATURE_LS_TIMESTAMPS("ctu")
109 //usage: IF_SELINUX("kKZ") "]"
110 //usage: IF_FEATURE_AUTOWIDTH(" [-w WIDTH]") " [FILE]..."
111 //usage:#define ls_full_usage "\n\n"
112 //usage: "List directory contents\n"
113 //usage: "\n -1 One column output"
114 //usage: "\n -a Include entries which start with ."
115 //usage: "\n -A Like -a, but exclude . and .."
116 //usage: "\n -C List by columns"
117 //usage: "\n -x List by lines"
118 //usage: "\n -d List directory entries instead of contents"
119 //usage: IF_FEATURE_LS_FOLLOWLINKS(
120 //usage: "\n -L Follow symlinks"
121 //usage: "\n -H Follow symlinks on command line"
123 //usage: IF_FEATURE_LS_RECURSIVE(
124 //usage: "\n -R Recurse"
126 //usage: IF_FEATURE_LS_FILETYPES(
127 //usage: "\n -p Append / to dir entries"
128 //usage: "\n -F Append indicator (one of */=@|) to entries"
130 //usage: "\n -l Long listing format"
131 //usage: "\n -i List inode numbers"
132 //usage: "\n -n List numeric UIDs and GIDs instead of names"
133 //usage: "\n -s List allocated blocks"
134 //usage: IF_FEATURE_LS_TIMESTAMPS(
135 //usage: "\n -e List full date and time"
137 //usage: IF_FEATURE_HUMAN_READABLE(
138 //usage: "\n -h List sizes in human readable format (1K 243M 2G)"
140 //usage: IF_FEATURE_LS_SORTFILES(
141 //usage: "\n -r Sort in reverse order"
142 //usage: "\n -S Sort by size"
143 //usage: "\n -X Sort by extension"
144 //usage: "\n -v Sort by version"
146 //usage: IF_FEATURE_LS_TIMESTAMPS(
147 //usage: "\n -c With -l: sort by ctime"
148 //usage: "\n -t With -l: sort by mtime"
149 //usage: "\n -u With -l: sort by atime"
152 //usage: "\n -k List security context"
153 //usage: "\n -K List security context in long format"
154 //usage: "\n -Z List security context and permission"
156 //usage: IF_FEATURE_AUTOWIDTH(
157 //usage: "\n -w N Assume the terminal is N columns wide"
159 //usage: IF_FEATURE_LS_COLOR(
160 //usage: "\n --color[={always,never,auto}] Control coloring"
164 #include "common_bufsiz.h"
168 /* This is a NOEXEC applet. Be very careful! */
172 /* ftpd uses ls, and without timestamps Mozilla won't understand
173 * ftpd's LIST output.
175 # undef CONFIG_FEATURE_LS_TIMESTAMPS
176 # undef ENABLE_FEATURE_LS_TIMESTAMPS
177 # undef IF_FEATURE_LS_TIMESTAMPS
178 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
179 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
180 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
181 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
182 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
187 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
193 /* Bits in G.all_fmt: */
195 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
196 /* what file information will be listed */
198 LIST_BLOCKS = 1 << 1,
199 LIST_MODEBITS = 1 << 2,
200 LIST_NLINKS = 1 << 3,
201 LIST_ID_NAME = 1 << 4,
202 LIST_ID_NUMERIC = 1 << 5,
203 LIST_CONTEXT = 1 << 6,
205 LIST_DATE_TIME = 1 << 8,
206 LIST_FULLTIME = 1 << 9,
207 LIST_SYMLINK = 1 << 10,
208 LIST_FILETYPE = 1 << 11, /* show / suffix for dirs */
209 LIST_CLASSIFY = 1 << 12, /* requires LIST_FILETYPE, also show *,|,@,= suffixes */
210 LIST_MASK = (LIST_CLASSIFY << 1) - 1,
212 /* what files will be displayed */
213 DISP_DIRNAME = 1 << 13, /* 2 or more items? label directories */
214 DISP_HIDDEN = 1 << 14, /* show filenames starting with . */
215 DISP_DOT = 1 << 15, /* show . and .. */
216 DISP_NOLIST = 1 << 16, /* show directory as itself, not contents */
217 DISP_RECURSIVE = 1 << 17, /* show directory and everything below it */
218 DISP_ROWS = 1 << 18, /* print across rows */
219 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
221 /* what is the overall style of the listing */
222 STYLE_COLUMNAR = 1 << 19, /* many records per line */
223 STYLE_LONG = 2 << 19, /* one record per line, extended info */
224 STYLE_SINGLE = 3 << 19, /* one record per line */
225 STYLE_MASK = STYLE_SINGLE,
227 /* which of the three times will be used */
228 TIME_CHANGE = (1 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
229 TIME_ACCESS = (2 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
230 TIME_MASK = (3 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
232 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
233 SORT_REVERSE = 1 << 23,
235 SORT_NAME = 0, /* sort by file name */
236 SORT_SIZE = 1 << 24, /* sort by file size */
237 SORT_ATIME = 2 << 24, /* sort by last access time */
238 SORT_CTIME = 3 << 24, /* sort by last change time */
239 SORT_MTIME = 4 << 24, /* sort by last modification time */
240 SORT_VERSION = 5 << 24, /* sort by version */
241 SORT_EXT = 6 << 24, /* sort by file name extension */
242 SORT_DIR = 7 << 24, /* sort by file or directory */
243 SORT_MASK = (7 << 24) * ENABLE_FEATURE_LS_SORTFILES,
245 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
246 LIST_DATE_TIME | LIST_SYMLINK,
249 /* -Cadil1 Std options, busybox always supports */
250 /* -gnsxA Std options, busybox always supports */
251 /* -Q GNU option, busybox always supports */
252 /* -k SELinux option, busybox always supports (ignores if !SELinux) */
253 /* Std has -k which means "show sizes in kbytes" */
254 /* -LHRctur Std options, busybox optionally supports */
255 /* -Fp Std options, busybox optionally supports */
256 /* -SXvhTw GNU options, busybox optionally supports */
257 /* -T WIDTH Ignored (we don't use tabs on output) */
258 /* -KZ SELinux mandated options, busybox optionally supports */
259 /* (coreutils 8.4 has no -K, remove it?) */
260 /* -e I think we made this one up (looks similar to GNU --full-time) */
261 /* We already used up all 32 bits, if we need to add more, candidates for removal: */
262 /* -K, -T, -e (add --full-time instead) */
263 static const char ls_options[] ALIGN1 =
264 "Cadil1gnsxQAk" /* 13 opts, total 13 */
265 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
266 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
267 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
268 IF_FEATURE_LS_RECURSIVE("R") /* 1, 24 */
269 IF_SELINUX("KZ") /* 2, 26 */
270 IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 28 */
271 IF_FEATURE_HUMAN_READABLE("h") /* 1, 29 */
272 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 31 */
273 /* with --color, we use all 32 bits */;
293 OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS,
297 OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
299 OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
300 OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
302 OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX,
304 OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
305 OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
307 OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH,
309 OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS,
310 OPT_e = (1 << OPTBIT_e) * ENABLE_FEATURE_LS_TIMESTAMPS,
311 OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS,
312 OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS,
313 OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES,
314 OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES,
315 OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES,
316 OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES,
317 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
318 OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES,
319 OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE,
320 OPT_K = (1 << OPTBIT_K) * ENABLE_SELINUX,
321 OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX,
322 OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS,
323 OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS,
324 OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE,
325 OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_AUTOWIDTH,
326 OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_AUTOWIDTH,
327 OPT_color = (1 << OPTBIT_color) * ENABLE_FEATURE_LS_COLOR,
330 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
331 static const uint32_t opt_flags[] = {
332 STYLE_COLUMNAR, /* C */
333 DISP_HIDDEN | DISP_DOT, /* a */
336 LIST_LONG | STYLE_LONG, /* l */
337 STYLE_SINGLE, /* 1 */
338 LIST_LONG | STYLE_LONG, /* g (don't show owner) - handled via OPT_g. assumes l */
339 LIST_ID_NUMERIC | LIST_LONG | STYLE_LONG, /* n (assumes l) */
341 DISP_ROWS | STYLE_COLUMNAR, /* x */
342 0, /* Q (quote filename) - handled via OPT_Q */
344 ENABLE_SELINUX * (LIST_CONTEXT|STYLE_SINGLE), /* k (ignored if !SELINUX) */
345 #if ENABLE_FEATURE_LS_TIMESTAMPS
346 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
347 LIST_FULLTIME, /* e */
348 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
349 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
351 #if ENABLE_FEATURE_LS_SORTFILES
354 SORT_REVERSE, /* r */
355 SORT_VERSION, /* v */
357 #if ENABLE_FEATURE_LS_FILETYPES
358 LIST_FILETYPE | LIST_CLASSIFY, /* F */
359 LIST_FILETYPE, /* p */
361 #if ENABLE_FEATURE_LS_RECURSIVE
362 DISP_RECURSIVE, /* R */
365 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME|STYLE_SINGLE, /* K */
366 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT|STYLE_SINGLE, /* Z */
369 /* options after Z are not processed through opt_flags */
374 * a directory entry and its stat info
377 const char *name; /* usually basename, but think "ls -l dir/file" */
378 const char *fullname; /* full name (usable for stat etc) */
379 struct dnode *dn_next; /* for linked list */
380 IF_SELINUX(security_context_t sid;)
381 smallint fname_allocated;
383 /* Used to avoid re-doing [l]stat at printout stage
384 * if we already collected needed data in scan stage:
386 mode_t dn_mode_lstat; /* obtained with lstat, or 0 */
387 mode_t dn_mode_stat; /* obtained with stat, or 0 */
389 // struct stat dstat;
390 // struct stat is huge. We don't need it in full.
391 // At least we don't need st_dev and st_blksize,
392 // but there are invisible fields as well
393 // (such as nanosecond-resolution timespamps)
394 // and padding, which we also don't want to store.
395 // We also can pre-parse dev_t dn_rdev (in glibc, it's huge).
396 // On 32-bit uclibc: dnode size went from 112 to 84 bytes.
398 /* Same names as in struct stat, but with dn_ instead of st_ pfx: */
399 mode_t dn_mode; /* obtained with lstat OR stat, depending on -L etc */
401 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
414 // blksize_t dn_blksize;
418 #if ENABLE_FEATURE_LS_COLOR
420 # define G_show_color (G.show_color)
422 # define G_show_color 0
426 #if ENABLE_FEATURE_AUTOWIDTH
427 unsigned terminal_width;
428 # define G_terminal_width (G.terminal_width)
430 # define G_terminal_width TERMINAL_WIDTH
432 #if ENABLE_FEATURE_LS_TIMESTAMPS
433 /* Do time() just once. Saves one syscall per file for "ls -l" */
434 time_t current_time_t;
437 #define G (*(struct globals*)bb_common_bufsiz1)
438 #define INIT_G() do { \
439 setup_common_bufsiz(); \
440 /* we have to zero it out because of NOEXEC */ \
441 memset(&G, 0, sizeof(G)); \
442 IF_FEATURE_AUTOWIDTH(G_terminal_width = TERMINAL_WIDTH;) \
443 IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \
447 /*** Output code ***/
450 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
451 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
452 * 3/7:multiplexed char/block device)
453 * and we use 0 for unknown and 15 for executables (see below) */
454 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
455 /* un fi chr - dir - blk - file - link - sock - - exe */
456 #define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)])
457 /* 036 black foreground 050 black background
458 037 red foreground 051 red background
459 040 green foreground 052 green background
460 041 brown foreground 053 brown background
461 042 blue foreground 054 blue background
462 043 magenta (purple) foreground 055 magenta background
463 044 cyan (light blue) foreground 056 cyan background
464 045 gray foreground 057 white background
466 #define COLOR(mode) ( \
467 /*un fi chr - dir - blk - file - link - sock - - exe */ \
468 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
470 /* Select normal (0) [actually "reset all"] or bold (1)
471 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
472 * let's use 7 for "impossible" types, just for fun)
473 * Note: coreutils 6.9 uses inverted red for setuid binaries.
475 #define ATTR(mode) ( \
476 /*un fi chr - dir - blk - file- link- sock- - exe */ \
477 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
480 #if ENABLE_FEATURE_LS_COLOR
481 /* mode of zero is interpreted as "unknown" (stat failed) */
482 static char fgcolor(mode_t mode)
484 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
485 return COLOR(0xF000); /* File is executable ... */
488 static char bold(mode_t mode)
490 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
491 return ATTR(0xF000); /* File is executable ... */
496 #if ENABLE_FEATURE_LS_FILETYPES
497 static char append_char(mode_t mode)
499 if (!(G.all_fmt & LIST_FILETYPE))
503 if (!(G.all_fmt & LIST_CLASSIFY))
505 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
507 return APPCHAR(mode);
511 static unsigned calc_name_len(const char *name)
516 // TODO: quote tab as \t, etc, if -Q
517 name = printable_string(&uni_stat, name);
519 if (!(option_mask32 & OPT_Q)) {
520 return uni_stat.unicode_width;
523 len = 2 + uni_stat.unicode_width;
525 if (*name == '"' || *name == '\\') {
533 /* Return the number of used columns.
534 * Note that only STYLE_COLUMNAR uses return value.
535 * STYLE_SINGLE and STYLE_LONG don't care.
536 * coreutils 7.2 also supports:
537 * ls -b (--escape) = octal escapes (although it doesn't look like working)
538 * ls -N (--literal) = not escape at all
540 static unsigned print_name(const char *name)
545 // TODO: quote tab as \t, etc, if -Q
546 name = printable_string(&uni_stat, name);
548 if (!(option_mask32 & OPT_Q)) {
550 return uni_stat.unicode_width;
553 len = 2 + uni_stat.unicode_width;
556 if (*name == '"' || *name == '\\') {
567 /* Return the number of used columns.
568 * Note that only STYLE_COLUMNAR uses return value,
569 * STYLE_SINGLE and STYLE_LONG don't care.
571 static NOINLINE unsigned display_single(const struct dnode *dn)
575 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
580 #if ENABLE_FEATURE_LS_FILETYPES
581 append = append_char(dn->dn_mode);
584 /* Do readlink early, so that if it fails, error message
585 * does not appear *inside* the "ls -l" line */
587 if (G.all_fmt & LIST_SYMLINK)
588 if (S_ISLNK(dn->dn_mode))
589 lpath = xmalloc_readlink_or_warn(dn->fullname);
591 if (G.all_fmt & LIST_INO)
592 column += printf("%7llu ", (long long) dn->dn_ino);
593 //TODO: -h should affect -s too:
594 if (G.all_fmt & LIST_BLOCKS)
595 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1));
596 if (G.all_fmt & LIST_MODEBITS)
597 column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode));
598 if (G.all_fmt & LIST_NLINKS)
599 column += printf("%4lu ", (long) dn->dn_nlink);
600 if (G.all_fmt & LIST_ID_NUMERIC) {
601 if (option_mask32 & OPT_g)
602 column += printf("%-8u ", (int) dn->dn_gid);
604 column += printf("%-8u %-8u ",
608 #if ENABLE_FEATURE_LS_USERNAME
609 else if (G.all_fmt & LIST_ID_NAME) {
610 if (option_mask32 & OPT_g) {
611 column += printf("%-8.8s ",
612 get_cached_groupname(dn->dn_gid));
614 column += printf("%-8.8s %-8.8s ",
615 get_cached_username(dn->dn_uid),
616 get_cached_groupname(dn->dn_gid));
620 if (G.all_fmt & LIST_SIZE) {
621 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
622 column += printf("%4u, %3u ",
626 if (option_mask32 & OPT_h) {
627 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
628 /* print size, show one fractional, use suffixes */
629 make_human_readable_str(dn->dn_size, 1, 0)
632 column += printf("%9"OFF_FMT"u ", dn->dn_size);
636 #if ENABLE_FEATURE_LS_TIMESTAMPS
637 if (G.all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
639 const time_t *ttime = &dn->dn_mtime;
640 if (G.all_fmt & TIME_ACCESS)
641 ttime = &dn->dn_atime;
642 if (G.all_fmt & TIME_CHANGE)
643 ttime = &dn->dn_ctime;
644 filetime = ctime(ttime);
645 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
646 if (G.all_fmt & LIST_FULLTIME) { /* -e */
647 /* Note: coreutils 8.4 ls --full-time prints:
648 * 2009-07-13 17:49:27.000000000 +0200
650 column += printf("%.24s ", filetime);
651 } else { /* LIST_DATE_TIME */
652 /* G.current_time_t ~== time(NULL) */
653 time_t age = G.current_time_t - *ttime;
654 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
655 /* less than 6 months old */
656 /* "mmm dd hh:mm " */
657 printf("%.12s ", filetime + 4);
660 /* "mmm dd yyyyy " after year 9999 :) */
661 strchr(filetime + 20, '\n')[0] = ' ';
662 printf("%.7s%6s", filetime + 4, filetime + 20);
669 if (G.all_fmt & LIST_CONTEXT) {
670 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
675 #if ENABLE_FEATURE_LS_COLOR
677 mode_t mode = dn->dn_mode_lstat;
679 if (lstat(dn->fullname, &statbuf) == 0)
680 mode = statbuf.st_mode;
681 printf("\033[%u;%um", bold(mode), fgcolor(mode));
684 column += print_name(dn->name);
691 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
692 if ((G.all_fmt & LIST_FILETYPE) || G_show_color) {
693 mode_t mode = dn->dn_mode_stat;
695 if (stat(dn->fullname, &statbuf) == 0)
696 mode = statbuf.st_mode;
697 # if ENABLE_FEATURE_LS_FILETYPES
698 append = append_char(mode);
700 # if ENABLE_FEATURE_LS_COLOR
702 printf("\033[%u;%um", bold(mode), fgcolor(mode));
707 column += print_name(lpath) + 4;
713 #if ENABLE_FEATURE_LS_FILETYPES
714 if (G.all_fmt & LIST_FILETYPE) {
725 static void display_files(struct dnode **dn, unsigned nfiles)
727 unsigned i, ncols, nrows, row, nc;
730 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
732 if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
735 /* find the longest file name, use that as the column width */
736 for (i = 0; dn[i]; i++) {
737 int len = calc_name_len(dn[i]->name);
738 if (column_width < len)
742 IF_SELINUX( ((G.all_fmt & LIST_CONTEXT) ? 33 : 0) + )
743 ((G.all_fmt & LIST_INO) ? 8 : 0) +
744 ((G.all_fmt & LIST_BLOCKS) ? 5 : 0);
745 ncols = (unsigned)G_terminal_width / column_width;
749 nrows = nfiles / ncols;
750 if (nrows * ncols < nfiles)
751 nrows++; /* round up fractionals */
759 for (row = 0; row < nrows; row++) {
760 for (nc = 0; nc < ncols; nc++) {
761 /* reach into the array based on the column and row */
762 if (G.all_fmt & DISP_ROWS)
763 i = (row * ncols) + nc; /* display across row */
765 i = (nc * nrows) + row; /* display by column */
769 printf("%*s", nexttab, "");
772 nexttab = column + column_width;
773 column += display_single(dn[i]);
782 /*** Dir scanning code ***/
784 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
789 cur = xzalloc(sizeof(*cur));
790 cur->fullname = fullname;
793 if ((option_mask32 & OPT_L) || force_follow) {
795 if (is_selinux_enabled()) {
796 getfilecon(fullname, &cur->sid);
799 if (stat(fullname, &statbuf)) {
800 bb_simple_perror_msg(fullname);
801 G.exit_code = EXIT_FAILURE;
805 cur->dn_mode_stat = statbuf.st_mode;
808 if (is_selinux_enabled()) {
809 lgetfilecon(fullname, &cur->sid);
812 if (lstat(fullname, &statbuf)) {
813 bb_simple_perror_msg(fullname);
814 G.exit_code = EXIT_FAILURE;
818 cur->dn_mode_lstat = statbuf.st_mode;
821 /* cur->dstat = statbuf: */
822 cur->dn_mode = statbuf.st_mode ;
823 cur->dn_size = statbuf.st_size ;
824 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
825 cur->dn_atime = statbuf.st_atime ;
826 cur->dn_mtime = statbuf.st_mtime ;
827 cur->dn_ctime = statbuf.st_ctime ;
829 cur->dn_ino = statbuf.st_ino ;
830 cur->dn_blocks = statbuf.st_blocks;
831 cur->dn_nlink = statbuf.st_nlink ;
832 cur->dn_uid = statbuf.st_uid ;
833 cur->dn_gid = statbuf.st_gid ;
834 cur->dn_rdev_maj = major(statbuf.st_rdev);
835 cur->dn_rdev_min = minor(statbuf.st_rdev);
840 static unsigned count_dirs(struct dnode **dn, int which)
852 if (!S_ISDIR((*dn)->dn_mode))
856 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
857 /* or if it's not . or .. */
859 || (name[1] && (name[1] != '.' || name[2]))
864 return which != SPLIT_FILE ? dirs : all - dirs;
867 /* get memory to hold an array of pointers */
868 static struct dnode **dnalloc(unsigned num)
873 num++; /* so that we have terminating NULL */
874 return xzalloc(num * sizeof(struct dnode *));
877 #if ENABLE_FEATURE_LS_RECURSIVE
878 static void dfree(struct dnode **dnp)
885 for (i = 0; dnp[i]; i++) {
886 struct dnode *cur = dnp[i];
887 if (cur->fname_allocated)
888 free((char*)cur->fullname);
894 #define dfree(...) ((void)0)
897 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
898 static struct dnode **splitdnarray(struct dnode **dn, int which)
906 /* count how many dirs or files there are */
907 dncnt = count_dirs(dn, which);
909 /* allocate a file array and a dir array */
910 dnp = dnalloc(dncnt);
912 /* copy the entrys into the file or dir array */
913 for (d = 0; *dn; dn++) {
914 if (S_ISDIR((*dn)->dn_mode)) {
917 if (which == SPLIT_FILE)
921 if ((which & SPLIT_DIR) /* any dir... */
922 /* ... or not . or .. */
924 || (name[1] && (name[1] != '.' || name[2]))
929 if (which == SPLIT_FILE) {
936 #if ENABLE_FEATURE_LS_SORTFILES
937 static int sortcmp(const void *a, const void *b)
939 struct dnode *d1 = *(struct dnode **)a;
940 struct dnode *d2 = *(struct dnode **)b;
941 unsigned sort_opts = G.all_fmt & SORT_MASK;
944 dif = 0; /* assume SORT_NAME */
945 // TODO: use pre-initialized function pointer
946 // instead of branch forest
947 if (sort_opts == SORT_SIZE) {
948 dif = (d2->dn_size - d1->dn_size);
950 if (sort_opts == SORT_ATIME) {
951 dif = (d2->dn_atime - d1->dn_atime);
953 if (sort_opts == SORT_CTIME) {
954 dif = (d2->dn_ctime - d1->dn_ctime);
956 if (sort_opts == SORT_MTIME) {
957 dif = (d2->dn_mtime - d1->dn_mtime);
959 if (sort_opts == SORT_DIR) {
960 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
962 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
963 if (sort_opts == SORT_VERSION) {
964 dif = strverscmp(d1->name, d2->name);
967 if (sort_opts == SORT_EXT) {
968 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
971 /* sort by name, use as tie breaker for other sorts */
972 if (ENABLE_LOCALE_SUPPORT)
973 dif = strcoll(d1->name, d2->name);
975 dif = strcmp(d1->name, d2->name);
978 /* Make dif fit into an int */
979 if (sizeof(dif) > sizeof(int)) {
980 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
981 /* shift leaving only "int" worth of bits */
983 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
987 return (G.all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
990 static void dnsort(struct dnode **dn, int size)
992 qsort(dn, size, sizeof(*dn), sortcmp);
995 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
998 display_files(dn, nfiles);
1001 # define dnsort(dn, size) ((void)0)
1002 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
1005 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
1006 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
1008 struct dnode *dn, *cur, **dnp;
1009 struct dirent *entry;
1014 dir = warn_opendir(path);
1016 G.exit_code = EXIT_FAILURE;
1017 return NULL; /* could not open the dir */
1021 while ((entry = readdir(dir)) != NULL) {
1024 /* are we going to list the file- it may be . or .. or a hidden file */
1025 if (entry->d_name[0] == '.') {
1026 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
1027 && !(G.all_fmt & DISP_DOT)
1031 if (!(G.all_fmt & DISP_HIDDEN))
1034 fullname = concat_path_file(path, entry->d_name);
1035 cur = my_stat(fullname, bb_basename(fullname), 0);
1040 cur->fname_allocated = 1;
1050 /* now that we know how many files there are
1051 * allocate memory for an array to hold dnode pointers
1054 dnp = dnalloc(nfiles);
1055 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1056 dnp[i] = dn; /* save pointer to node in array */
1066 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
1067 * If any of the -l, -n, -s options is specified, each list
1068 * of files within the directory shall be preceded by a
1069 * status line indicating the number of file system blocks
1070 * occupied by files in the directory in 512-byte units if
1071 * the -k option is not specified, or 1024-byte units if the
1072 * -k option is specified, rounded up to the next integral
1075 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
1076 static off_t calculate_blocks(struct dnode **dn)
1081 /* st_blocks is in 512 byte blocks */
1082 blocks += (*dn)->dn_blocks;
1087 /* Even though standard says use 512 byte blocks, coreutils use 1k */
1088 /* Actually, we round up by calculating (blocks + 1) / 2,
1089 * "+ 1" was done when we initialized blocks to 1 */
1094 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1097 struct dnode **subdnp;
1100 if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
1104 printf("%s:\n", (*dn)->fullname);
1106 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1108 if ((G.all_fmt & STYLE_MASK) == STYLE_LONG || (G.all_fmt & LIST_BLOCKS))
1109 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1112 /* list all files at this level */
1113 sort_and_display_files(subdnp, nfiles);
1115 if (ENABLE_FEATURE_LS_RECURSIVE
1116 && (G.all_fmt & DISP_RECURSIVE)
1120 /* recursive - list the sub-dirs */
1121 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1122 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1124 dnsort(dnd, dndirs);
1125 scan_and_display_dirs_recur(dnd, 0);
1126 /* free the array of dnode pointers to the dirs */
1130 /* free the dnodes and the fullname mem */
1137 int ls_main(int argc UNUSED_PARAM, char **argv)
1149 #if ENABLE_FEATURE_LS_COLOR
1150 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1152 * # ls --color=BOGUS
1153 * ls: invalid argument 'BOGUS' for '--color'
1154 * Valid arguments are:
1155 * 'always', 'yes', 'force'
1156 * 'never', 'no', 'none'
1157 * 'auto', 'tty', 'if-tty'
1158 * (and substrings: "--color=alwa" work too)
1160 static const char ls_longopts[] ALIGN1 =
1161 "color\0" Optional_argument "\xff"; /* no short equivalent */
1162 static const char color_str[] ALIGN1 =
1163 "always\0""yes\0""force\0"
1164 "auto\0""tty\0""if-tty\0";
1165 /* need to initialize since --color has _an optional_ argument */
1166 const char *color_opt = color_str; /* "always" */
1173 if (ENABLE_FEATURE_LS_SORTFILES)
1174 G.all_fmt = SORT_NAME;
1176 #if ENABLE_FEATURE_AUTOWIDTH
1177 /* obtain the terminal width */
1178 G_terminal_width = get_terminal_width(STDIN_FILENO);
1179 /* go one less... */
1183 /* process options */
1184 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1187 IF_FEATURE_LS_TIMESTAMPS("el")
1188 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1189 * in some pairs of opts, only last one takes effect:
1191 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1192 // ":m-l:l-m" - we don't have -m
1193 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1194 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1195 ":C-1:1-C" /* bycols/oneline */
1196 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1197 IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */
1199 IF_FEATURE_AUTOWIDTH(":w+");
1200 opt = getopt32(argv, ls_options
1201 IF_FEATURE_AUTOWIDTH(, NULL, &G_terminal_width)
1202 IF_FEATURE_LS_COLOR(, &color_opt)
1204 for (i = 0; opt_flags[i] != (1U << 31); i++) {
1205 if (opt & (1 << i)) {
1206 uint32_t flags = opt_flags[i];
1208 if (flags & STYLE_MASK)
1209 G.all_fmt &= ~STYLE_MASK;
1210 if (flags & SORT_MASK)
1211 G.all_fmt &= ~SORT_MASK;
1212 if (flags & TIME_MASK)
1213 G.all_fmt &= ~TIME_MASK;
1219 #if ENABLE_FEATURE_LS_COLOR
1220 /* set G_show_color = 1/0 */
1221 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1222 char *p = getenv("LS_COLORS");
1223 /* LS_COLORS is unset, or (not empty && not "none") ? */
1224 if (!p || (p[0] && strcmp(p, "none") != 0))
1227 if (opt & OPT_color) {
1228 if (color_opt[0] == 'n')
1230 else switch (index_in_substrings(color_str, color_opt)) {
1234 if (isatty(STDOUT_FILENO)) {
1244 /* sort out which command line options take precedence */
1245 if (ENABLE_FEATURE_LS_RECURSIVE && (G.all_fmt & DISP_NOLIST))
1246 G.all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1247 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1248 if (G.all_fmt & TIME_CHANGE)
1249 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_CTIME;
1250 if (G.all_fmt & TIME_ACCESS)
1251 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_ATIME;
1253 if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */
1254 G.all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1256 /* choose a display format if one was not already specified by an option */
1257 if (!(G.all_fmt & STYLE_MASK))
1258 G.all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1262 *--argv = (char*)".";
1265 G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1267 /* stuff the command line file names into a dnode array */
1271 cur = my_stat(*argv, *argv,
1272 /* follow links on command line unless -l, -s or -F: */
1273 !((G.all_fmt & STYLE_MASK) == STYLE_LONG
1274 || (G.all_fmt & LIST_BLOCKS)
1275 || (option_mask32 & OPT_F)
1278 || (option_mask32 & OPT_H)
1279 /* ... or if -L, but my_stat always follows links if -L */
1284 /*cur->fname_allocated = 0; - already is */
1290 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1294 /* now that we know how many files there are
1295 * allocate memory for an array to hold dnode pointers
1297 dnp = dnalloc(nfiles);
1298 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1299 dnp[i] = dn; /* save pointer to node in array */
1305 if (G.all_fmt & DISP_NOLIST) {
1306 sort_and_display_files(dnp, nfiles);
1308 dnd = splitdnarray(dnp, SPLIT_DIR);
1309 dnf = splitdnarray(dnp, SPLIT_FILE);
1310 dndirs = count_dirs(dnp, SPLIT_DIR);
1311 dnfiles = nfiles - dndirs;
1313 sort_and_display_files(dnf, dnfiles);
1314 if (ENABLE_FEATURE_CLEAN_UP)
1318 dnsort(dnd, dndirs);
1319 scan_and_display_dirs_recur(dnd, dnfiles == 0);
1320 if (ENABLE_FEATURE_CLEAN_UP)
1325 if (ENABLE_FEATURE_CLEAN_UP)