1 /* vi: set sw=4 ts=4: */
3 * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
5 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8 /* [date unknown. Perhaps before year 2000]
9 * To achieve a small memory footprint, this version of 'ls' doesn't do any
10 * file sorting, and only has the most essential command line switches
11 * (i.e., the ones I couldn't live without :-) All features which involve
12 * linking in substantial chunks of libc can be disabled.
14 * Although I don't really want to add new features to this program to
15 * keep it small, I *am* interested to receive bug fixes and ways to make
19 * 1. hidden files can make column width too large
21 * NON-OPTIMAL BEHAVIOUR:
22 * 1. autowidth reads directories twice
23 * 2. if you do a short directory listing without filetype characters
24 * appended, there's no need to stat each one
26 * 1. requires lstat (BSD) - how do you do it without?
29 * ls sorts listing now, and supports almost all options.
32 //usage:#define ls_trivial_usage
34 //usage: IF_FEATURE_LS_FOLLOWLINKS("LH")
35 //usage: IF_FEATURE_LS_RECURSIVE("R")
36 //usage: IF_FEATURE_LS_FILETYPES("Fp") "lins"
37 //usage: IF_FEATURE_LS_TIMESTAMPS("e")
38 //usage: IF_FEATURE_HUMAN_READABLE("h")
39 //usage: IF_FEATURE_LS_SORTFILES("rSXv")
40 //usage: IF_FEATURE_LS_TIMESTAMPS("ctu")
41 //usage: IF_SELINUX("kKZ") "]"
42 //usage: IF_FEATURE_AUTOWIDTH(" [-w WIDTH]") " [FILE]..."
43 //usage:#define ls_full_usage "\n\n"
44 //usage: "List directory contents\n"
45 //usage: "\n -1 One column output"
46 //usage: "\n -a Include entries which start with ."
47 //usage: "\n -A Like -a, but exclude . and .."
48 //usage: "\n -C List by columns"
49 //usage: "\n -x List by lines"
50 //usage: "\n -d List directory entries instead of contents"
51 //usage: IF_FEATURE_LS_FOLLOWLINKS(
52 //usage: "\n -L Follow symlinks"
53 //usage: "\n -H Follow symlinks on command line"
55 //usage: IF_FEATURE_LS_RECURSIVE(
56 //usage: "\n -R Recurse"
58 //usage: IF_FEATURE_LS_FILETYPES(
59 //usage: "\n -p Append / to dir entries"
60 //usage: "\n -F Append indicator (one of */=@|) to entries"
62 //usage: "\n -l Long listing format"
63 //usage: "\n -i List inode numbers"
64 //usage: "\n -n List numeric UIDs and GIDs instead of names"
65 //usage: "\n -s List allocated blocks"
66 //usage: IF_FEATURE_LS_TIMESTAMPS(
67 //usage: "\n -e List full date and time"
69 //usage: IF_FEATURE_HUMAN_READABLE(
70 //usage: "\n -h List sizes in human readable format (1K 243M 2G)"
72 //usage: IF_FEATURE_LS_SORTFILES(
73 //usage: "\n -r Sort in reverse order"
74 //usage: "\n -S Sort by size"
75 //usage: "\n -X Sort by extension"
76 //usage: "\n -v Sort by version"
78 //usage: IF_FEATURE_LS_TIMESTAMPS(
79 //usage: "\n -c With -l: sort by ctime"
80 //usage: "\n -t With -l: sort by mtime"
81 //usage: "\n -u With -l: sort by atime"
84 //usage: "\n -k List security context"
85 //usage: "\n -K List security context in long format"
86 //usage: "\n -Z List security context and permission"
88 //usage: IF_FEATURE_AUTOWIDTH(
89 //usage: "\n -w N Assume the terminal is N columns wide"
91 //usage: IF_FEATURE_LS_COLOR(
92 //usage: "\n --color[={always,never,auto}] Control coloring"
99 /* This is a NOEXEC applet. Be very careful! */
103 /* ftpd uses ls, and without timestamps Mozilla won't understand
104 * ftpd's LIST output.
106 # undef CONFIG_FEATURE_LS_TIMESTAMPS
107 # undef ENABLE_FEATURE_LS_TIMESTAMPS
108 # undef IF_FEATURE_LS_TIMESTAMPS
109 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
110 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
111 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
112 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
113 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
118 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
124 /* Bits in G.all_fmt: */
126 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
127 /* what file information will be listed */
129 LIST_BLOCKS = 1 << 1,
130 LIST_MODEBITS = 1 << 2,
131 LIST_NLINKS = 1 << 3,
132 LIST_ID_NAME = 1 << 4,
133 LIST_ID_NUMERIC = 1 << 5,
134 LIST_CONTEXT = 1 << 6,
136 LIST_DATE_TIME = 1 << 8,
137 LIST_FULLTIME = 1 << 9,
138 LIST_SYMLINK = 1 << 10,
139 LIST_FILETYPE = 1 << 11, /* show / suffix for dirs */
140 LIST_CLASSIFY = 1 << 12, /* requires LIST_FILETYPE, also show *,|,@,= suffixes */
141 LIST_MASK = (LIST_CLASSIFY << 1) - 1,
143 /* what files will be displayed */
144 DISP_DIRNAME = 1 << 13, /* 2 or more items? label directories */
145 DISP_HIDDEN = 1 << 14, /* show filenames starting with . */
146 DISP_DOT = 1 << 15, /* show . and .. */
147 DISP_NOLIST = 1 << 16, /* show directory as itself, not contents */
148 DISP_RECURSIVE = 1 << 17, /* show directory and everything below it */
149 DISP_ROWS = 1 << 18, /* print across rows */
150 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
152 /* what is the overall style of the listing */
153 STYLE_COLUMNAR = 1 << 19, /* many records per line */
154 STYLE_LONG = 2 << 19, /* one record per line, extended info */
155 STYLE_SINGLE = 3 << 19, /* one record per line */
156 STYLE_MASK = STYLE_SINGLE,
158 /* which of the three times will be used */
159 TIME_CHANGE = (1 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
160 TIME_ACCESS = (2 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
161 TIME_MASK = (3 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
163 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
164 SORT_REVERSE = 1 << 23,
166 SORT_NAME = 0, /* sort by file name */
167 SORT_SIZE = 1 << 24, /* sort by file size */
168 SORT_ATIME = 2 << 24, /* sort by last access time */
169 SORT_CTIME = 3 << 24, /* sort by last change time */
170 SORT_MTIME = 4 << 24, /* sort by last modification time */
171 SORT_VERSION = 5 << 24, /* sort by version */
172 SORT_EXT = 6 << 24, /* sort by file name extension */
173 SORT_DIR = 7 << 24, /* sort by file or directory */
174 SORT_MASK = (7 << 24) * ENABLE_FEATURE_LS_SORTFILES,
176 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
177 LIST_DATE_TIME | LIST_SYMLINK,
180 /* -Cadil1 Std options, busybox always supports */
181 /* -gnsxA Std options, busybox always supports */
182 /* -Q GNU option, busybox always supports */
183 /* -k SELinux option, busybox always supports (ignores if !SELinux) */
184 /* Std has -k which means "show sizes in kbytes" */
185 /* -FLHRctur Std options, busybox optionally supports */
186 /* -p Std option, busybox optionally supports */
187 /* Not fully compatible - we show not only '/' but other chars too */
188 /* -SXvhTw GNU options, busybox optionally supports */
189 /* -T TABWIDTH is ignored (we don't use tabs on output) */
190 /* -KZ SELinux mandated options, busybox optionally supports */
191 /* (coreutils 8.4 has no -K, remove it?) */
192 /* -e I think we made this one up (looks similar to GNU --full-time) */
193 /* We already used up all 32 bits, if we need to add more, candidates for removal: */
194 /* -K, -T, -e (add --full-time instead) */
195 static const char ls_options[] ALIGN1 =
196 "Cadil1gnsxQAk" /* 13 opts, total 13 */
197 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
198 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
199 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
200 IF_FEATURE_LS_RECURSIVE("R") /* 1, 24 */
201 IF_SELINUX("KZ") /* 2, 26 */
202 IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 28 */
203 IF_FEATURE_HUMAN_READABLE("h") /* 1, 29 */
204 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 31 */
205 /* with --color, we use all 32 bits */;
225 OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS,
229 OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
231 OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
232 OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
234 OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX,
236 OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
237 OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
239 OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH,
241 OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS,
242 OPT_e = (1 << OPTBIT_e) * ENABLE_FEATURE_LS_TIMESTAMPS,
243 OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS,
244 OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS,
245 OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES,
246 OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES,
247 OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES,
248 OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES,
249 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
250 OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES,
251 OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE,
252 OPT_K = (1 << OPTBIT_K) * ENABLE_SELINUX,
253 OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX,
254 OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS,
255 OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS,
256 OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE,
257 OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_AUTOWIDTH,
258 OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_AUTOWIDTH,
259 OPT_color = (1 << OPTBIT_color) * ENABLE_FEATURE_LS_COLOR,
262 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
263 static const uint32_t opt_flags[] = {
264 STYLE_COLUMNAR, /* C */
265 DISP_HIDDEN | DISP_DOT, /* a */
268 LIST_LONG | STYLE_LONG, /* l */
269 STYLE_SINGLE, /* 1 */
270 LIST_LONG | STYLE_LONG, /* g (don't show owner) - handled via OPT_g. assumes l */
271 LIST_ID_NUMERIC | LIST_LONG | STYLE_LONG, /* n (assumes l) */
273 DISP_ROWS | STYLE_COLUMNAR, /* x */
274 0, /* Q (quote filename) - handled via OPT_Q */
276 ENABLE_SELINUX * (LIST_CONTEXT|STYLE_SINGLE), /* k (ignored if !SELINUX) */
277 #if ENABLE_FEATURE_LS_TIMESTAMPS
278 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
279 LIST_FULLTIME, /* e */
280 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
281 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
283 #if ENABLE_FEATURE_LS_SORTFILES
286 SORT_REVERSE, /* r */
287 SORT_VERSION, /* v */
289 #if ENABLE_FEATURE_LS_FILETYPES
290 LIST_FILETYPE | LIST_CLASSIFY, /* F */
291 LIST_FILETYPE, /* p */
293 #if ENABLE_FEATURE_LS_RECURSIVE
294 DISP_RECURSIVE, /* R */
297 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME|STYLE_SINGLE, /* K */
298 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT|STYLE_SINGLE, /* Z */
301 /* options after Z are not processed through opt_flags */
306 * a directory entry and its stat info
309 const char *name; /* usually basename, but think "ls -l dir/file" */
310 const char *fullname; /* full name (usable for stat etc) */
311 struct dnode *dn_next; /* for linked list */
312 IF_SELINUX(security_context_t sid;)
313 smallint fname_allocated;
315 /* Used to avoid re-doing [l]stat at printout stage
316 * if we already collected needed data in scan stage:
318 mode_t dn_mode_lstat; /* obtained with lstat, or 0 */
319 mode_t dn_mode_stat; /* obtained with stat, or 0 */
321 // struct stat dstat;
322 // struct stat is huge. We don't need it in full.
323 // At least we don't need st_dev and st_blksize,
324 // but there are invisible fields as well
325 // (such as nanosecond-resolution timespamps)
326 // and padding, which we also don't want to store.
327 // We also can pre-parse dev_t dn_rdev (in glibc, it's huge).
328 // On 32-bit uclibc: dnode size went from 112 to 84 bytes.
330 /* Same names as in struct stat, but with dn_ instead of st_ pfx: */
331 mode_t dn_mode; /* obtained with lstat OR stat, depending on -L etc */
333 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
346 // blksize_t dn_blksize;
350 #if ENABLE_FEATURE_LS_COLOR
352 # define G_show_color (G.show_color)
354 # define G_show_color 0
358 #if ENABLE_FEATURE_AUTOWIDTH
359 unsigned terminal_width;
360 # define G_terminal_width (G.terminal_width)
362 # define G_terminal_width TERMINAL_WIDTH
364 #if ENABLE_FEATURE_LS_TIMESTAMPS
365 /* Do time() just once. Saves one syscall per file for "ls -l" */
366 time_t current_time_t;
369 #define G (*(struct globals*)&bb_common_bufsiz1)
370 #define INIT_G() do { \
371 /* we have to zero it out because of NOEXEC */ \
372 memset(&G, 0, sizeof(G)); \
373 IF_FEATURE_AUTOWIDTH(G_terminal_width = TERMINAL_WIDTH;) \
374 IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \
378 /*** Output code ***/
381 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
382 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
383 * 3/7:multiplexed char/block device)
384 * and we use 0 for unknown and 15 for executables (see below) */
385 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
386 /* un fi chr - dir - blk - file - link - sock - - exe */
387 #define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)])
388 /* 036 black foreground 050 black background
389 037 red foreground 051 red background
390 040 green foreground 052 green background
391 041 brown foreground 053 brown background
392 042 blue foreground 054 blue background
393 043 magenta (purple) foreground 055 magenta background
394 044 cyan (light blue) foreground 056 cyan background
395 045 gray foreground 057 white background
397 #define COLOR(mode) ( \
398 /*un fi chr - dir - blk - file - link - sock - - exe */ \
399 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
401 /* Select normal (0) [actually "reset all"] or bold (1)
402 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
403 * let's use 7 for "impossible" types, just for fun)
404 * Note: coreutils 6.9 uses inverted red for setuid binaries.
406 #define ATTR(mode) ( \
407 /*un fi chr - dir - blk - file- link- sock- - exe */ \
408 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
411 #if ENABLE_FEATURE_LS_COLOR
412 /* mode of zero is interpreted as "unknown" (stat failed) */
413 static char fgcolor(mode_t mode)
415 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
416 return COLOR(0xF000); /* File is executable ... */
419 static char bold(mode_t mode)
421 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
422 return ATTR(0xF000); /* File is executable ... */
427 #if ENABLE_FEATURE_LS_FILETYPES
428 static char append_char(mode_t mode)
430 if (!(G.all_fmt & LIST_FILETYPE))
434 if (!(G.all_fmt & LIST_CLASSIFY))
436 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
438 return APPCHAR(mode);
442 static unsigned calc_name_len(const char *name)
447 // TODO: quote tab as \t, etc, if -Q
448 name = printable_string(&uni_stat, name);
450 if (!(option_mask32 & OPT_Q)) {
451 return uni_stat.unicode_width;
454 len = 2 + uni_stat.unicode_width;
456 if (*name == '"' || *name == '\\') {
464 /* Return the number of used columns.
465 * Note that only STYLE_COLUMNAR uses return value.
466 * STYLE_SINGLE and STYLE_LONG don't care.
467 * coreutils 7.2 also supports:
468 * ls -b (--escape) = octal escapes (although it doesn't look like working)
469 * ls -N (--literal) = not escape at all
471 static unsigned print_name(const char *name)
476 // TODO: quote tab as \t, etc, if -Q
477 name = printable_string(&uni_stat, name);
479 if (!(option_mask32 & OPT_Q)) {
481 return uni_stat.unicode_width;
484 len = 2 + uni_stat.unicode_width;
487 if (*name == '"' || *name == '\\') {
498 /* Return the number of used columns.
499 * Note that only STYLE_COLUMNAR uses return value,
500 * STYLE_SINGLE and STYLE_LONG don't care.
502 static NOINLINE unsigned display_single(const struct dnode *dn)
506 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
511 #if ENABLE_FEATURE_LS_FILETYPES
512 append = append_char(dn->dn_mode);
515 /* Do readlink early, so that if it fails, error message
516 * does not appear *inside* the "ls -l" line */
518 if (G.all_fmt & LIST_SYMLINK)
519 if (S_ISLNK(dn->dn_mode))
520 lpath = xmalloc_readlink_or_warn(dn->fullname);
522 if (G.all_fmt & LIST_INO)
523 column += printf("%7llu ", (long long) dn->dn_ino);
524 //TODO: -h should affect -s too:
525 if (G.all_fmt & LIST_BLOCKS)
526 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1));
527 if (G.all_fmt & LIST_MODEBITS)
528 column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode));
529 if (G.all_fmt & LIST_NLINKS)
530 column += printf("%4lu ", (long) dn->dn_nlink);
531 if (G.all_fmt & LIST_ID_NUMERIC) {
532 if (option_mask32 & OPT_g)
533 column += printf("%-8u ", (int) dn->dn_gid);
535 column += printf("%-8u %-8u ",
539 #if ENABLE_FEATURE_LS_USERNAME
540 else if (G.all_fmt & LIST_ID_NAME) {
541 if (option_mask32 & OPT_g) {
542 column += printf("%-8.8s ",
543 get_cached_groupname(dn->dn_gid));
545 column += printf("%-8.8s %-8.8s ",
546 get_cached_username(dn->dn_uid),
547 get_cached_groupname(dn->dn_gid));
551 if (G.all_fmt & LIST_SIZE) {
552 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
553 column += printf("%4u, %3u ",
557 if (option_mask32 & OPT_h) {
558 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
559 /* print size, show one fractional, use suffixes */
560 make_human_readable_str(dn->dn_size, 1, 0)
563 column += printf("%9"OFF_FMT"u ", dn->dn_size);
567 #if ENABLE_FEATURE_LS_TIMESTAMPS
568 if (G.all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
570 time_t ttime = dn->dn_mtime;
571 if (G.all_fmt & TIME_ACCESS)
572 ttime = dn->dn_atime;
573 if (G.all_fmt & TIME_CHANGE)
574 ttime = dn->dn_ctime;
575 filetime = ctime(&ttime);
576 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
577 if (G.all_fmt & LIST_FULLTIME) { /* -e */
578 /* Note: coreutils 8.4 ls --full-time prints:
579 * 2009-07-13 17:49:27.000000000 +0200
581 column += printf("%.24s ", filetime);
582 } else { /* LIST_DATE_TIME */
583 /* G.current_time_t ~== time(NULL) */
584 time_t age = G.current_time_t - ttime;
585 printf("%.6s ", filetime + 4); /* "Jun 30" */
586 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
587 /* hh:mm if less than 6 months old */
588 printf("%.5s ", filetime + 11);
589 } else { /* year. buggy if year > 9999 ;) */
590 printf(" %.4s ", filetime + 20);
597 if (G.all_fmt & LIST_CONTEXT) {
598 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
603 #if ENABLE_FEATURE_LS_COLOR
605 mode_t mode = dn->dn_mode_lstat;
607 if (lstat(dn->fullname, &statbuf) == 0)
608 mode = statbuf.st_mode;
609 printf("\033[%u;%um", bold(mode), fgcolor(mode));
612 column += print_name(dn->name);
619 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
620 if ((G.all_fmt & LIST_FILETYPE) || G_show_color) {
621 mode_t mode = dn->dn_mode_stat;
623 if (stat(dn->fullname, &statbuf) == 0)
624 mode = statbuf.st_mode;
625 # if ENABLE_FEATURE_LS_FILETYPES
626 append = append_char(mode);
628 # if ENABLE_FEATURE_LS_COLOR
630 printf("\033[%u;%um", bold(mode), fgcolor(mode));
635 column += print_name(lpath) + 4;
641 #if ENABLE_FEATURE_LS_FILETYPES
642 if (G.all_fmt & LIST_FILETYPE) {
653 static void display_files(struct dnode **dn, unsigned nfiles)
655 unsigned i, ncols, nrows, row, nc;
658 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
660 if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
663 /* find the longest file name, use that as the column width */
664 for (i = 0; dn[i]; i++) {
665 int len = calc_name_len(dn[i]->name);
666 if (column_width < len)
670 IF_SELINUX( ((G.all_fmt & LIST_CONTEXT) ? 33 : 0) + )
671 ((G.all_fmt & LIST_INO) ? 8 : 0) +
672 ((G.all_fmt & LIST_BLOCKS) ? 5 : 0);
673 ncols = (unsigned)G_terminal_width / column_width;
677 nrows = nfiles / ncols;
678 if (nrows * ncols < nfiles)
679 nrows++; /* round up fractionals */
687 for (row = 0; row < nrows; row++) {
688 for (nc = 0; nc < ncols; nc++) {
689 /* reach into the array based on the column and row */
690 if (G.all_fmt & DISP_ROWS)
691 i = (row * ncols) + nc; /* display across row */
693 i = (nc * nrows) + row; /* display by column */
697 printf("%*s ", nexttab, "");
698 column += nexttab + 1;
700 nexttab = column + column_width;
701 column += display_single(dn[i]);
710 /*** Dir scanning code ***/
712 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
717 cur = xzalloc(sizeof(*cur));
718 cur->fullname = fullname;
721 if ((option_mask32 & OPT_L) || force_follow) {
723 if (is_selinux_enabled()) {
724 getfilecon(fullname, &cur->sid);
727 if (stat(fullname, &statbuf)) {
728 bb_simple_perror_msg(fullname);
729 G.exit_code = EXIT_FAILURE;
733 cur->dn_mode_stat = statbuf.st_mode;
736 if (is_selinux_enabled()) {
737 lgetfilecon(fullname, &cur->sid);
740 if (lstat(fullname, &statbuf)) {
741 bb_simple_perror_msg(fullname);
742 G.exit_code = EXIT_FAILURE;
746 cur->dn_mode_lstat = statbuf.st_mode;
749 /* cur->dstat = statbuf: */
750 cur->dn_mode = statbuf.st_mode ;
751 cur->dn_size = statbuf.st_size ;
752 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
753 cur->dn_atime = statbuf.st_atime ;
754 cur->dn_mtime = statbuf.st_mtime ;
755 cur->dn_ctime = statbuf.st_ctime ;
757 cur->dn_ino = statbuf.st_ino ;
758 cur->dn_blocks = statbuf.st_blocks;
759 cur->dn_nlink = statbuf.st_nlink ;
760 cur->dn_uid = statbuf.st_uid ;
761 cur->dn_gid = statbuf.st_gid ;
762 cur->dn_rdev_maj = major(statbuf.st_rdev);
763 cur->dn_rdev_min = minor(statbuf.st_rdev);
768 static unsigned count_dirs(struct dnode **dn, int which)
780 if (!S_ISDIR((*dn)->dn_mode))
784 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
785 /* or if it's not . or .. */
787 || (name[1] && (name[1] != '.' || name[2]))
792 return which != SPLIT_FILE ? dirs : all - dirs;
795 /* get memory to hold an array of pointers */
796 static struct dnode **dnalloc(unsigned num)
801 num++; /* so that we have terminating NULL */
802 return xzalloc(num * sizeof(struct dnode *));
805 #if ENABLE_FEATURE_LS_RECURSIVE
806 static void dfree(struct dnode **dnp)
813 for (i = 0; dnp[i]; i++) {
814 struct dnode *cur = dnp[i];
815 if (cur->fname_allocated)
816 free((char*)cur->fullname);
822 #define dfree(...) ((void)0)
825 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
826 static struct dnode **splitdnarray(struct dnode **dn, int which)
834 /* count how many dirs or files there are */
835 dncnt = count_dirs(dn, which);
837 /* allocate a file array and a dir array */
838 dnp = dnalloc(dncnt);
840 /* copy the entrys into the file or dir array */
841 for (d = 0; *dn; dn++) {
842 if (S_ISDIR((*dn)->dn_mode)) {
845 if (which == SPLIT_FILE)
849 if ((which & SPLIT_DIR) /* any dir... */
850 /* ... or not . or .. */
852 || (name[1] && (name[1] != '.' || name[2]))
857 if (which == SPLIT_FILE) {
864 #if ENABLE_FEATURE_LS_SORTFILES
865 static int sortcmp(const void *a, const void *b)
867 struct dnode *d1 = *(struct dnode **)a;
868 struct dnode *d2 = *(struct dnode **)b;
869 unsigned sort_opts = G.all_fmt & SORT_MASK;
872 dif = 0; /* assume SORT_NAME */
873 // TODO: use pre-initialized function pointer
874 // instead of branch forest
875 if (sort_opts == SORT_SIZE) {
876 dif = (d2->dn_size - d1->dn_size);
878 if (sort_opts == SORT_ATIME) {
879 dif = (d2->dn_atime - d1->dn_atime);
881 if (sort_opts == SORT_CTIME) {
882 dif = (d2->dn_ctime - d1->dn_ctime);
884 if (sort_opts == SORT_MTIME) {
885 dif = (d2->dn_mtime - d1->dn_mtime);
887 if (sort_opts == SORT_DIR) {
888 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
890 if (sort_opts == SORT_VERSION) {
891 dif = strverscmp(d1->name, d2->name);
893 if (sort_opts == SORT_EXT) {
894 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
897 /* sort by name, use as tie breaker for other sorts */
898 if (ENABLE_LOCALE_SUPPORT)
899 dif = strcoll(d1->name, d2->name);
901 dif = strcmp(d1->name, d2->name);
904 /* Make dif fit into an int */
905 if (sizeof(dif) > sizeof(int)) {
906 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
907 /* shift leaving only "int" worth of bits */
909 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
913 return (G.all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
916 static void dnsort(struct dnode **dn, int size)
918 qsort(dn, size, sizeof(*dn), sortcmp);
921 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
924 display_files(dn, nfiles);
927 # define dnsort(dn, size) ((void)0)
928 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
931 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
932 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
934 struct dnode *dn, *cur, **dnp;
935 struct dirent *entry;
940 dir = warn_opendir(path);
942 G.exit_code = EXIT_FAILURE;
943 return NULL; /* could not open the dir */
947 while ((entry = readdir(dir)) != NULL) {
950 /* are we going to list the file- it may be . or .. or a hidden file */
951 if (entry->d_name[0] == '.') {
952 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
953 && !(G.all_fmt & DISP_DOT)
957 if (!(G.all_fmt & DISP_HIDDEN))
960 fullname = concat_path_file(path, entry->d_name);
961 cur = my_stat(fullname, bb_basename(fullname), 0);
966 cur->fname_allocated = 1;
976 /* now that we know how many files there are
977 * allocate memory for an array to hold dnode pointers
980 dnp = dnalloc(nfiles);
981 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
982 dnp[i] = dn; /* save pointer to node in array */
992 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
993 * If any of the -l, -n, -s options is specified, each list
994 * of files within the directory shall be preceded by a
995 * status line indicating the number of file system blocks
996 * occupied by files in the directory in 512-byte units if
997 * the -k option is not specified, or 1024-byte units if the
998 * -k option is specified, rounded up to the next integral
1001 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
1002 static off_t calculate_blocks(struct dnode **dn)
1007 /* st_blocks is in 512 byte blocks */
1008 blocks += (*dn)->dn_blocks;
1013 /* Even though standard says use 512 byte blocks, coreutils use 1k */
1014 /* Actually, we round up by calculating (blocks + 1) / 2,
1015 * "+ 1" was done when we initialized blocks to 1 */
1020 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1023 struct dnode **subdnp;
1026 if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
1030 printf("%s:\n", (*dn)->fullname);
1032 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1034 if ((G.all_fmt & STYLE_MASK) == STYLE_LONG)
1035 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1038 /* list all files at this level */
1039 sort_and_display_files(subdnp, nfiles);
1041 if (ENABLE_FEATURE_LS_RECURSIVE
1042 && (G.all_fmt & DISP_RECURSIVE)
1046 /* recursive - list the sub-dirs */
1047 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1048 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1050 dnsort(dnd, dndirs);
1051 scan_and_display_dirs_recur(dnd, 0);
1052 /* free the array of dnode pointers to the dirs */
1056 /* free the dnodes and the fullname mem */
1063 int ls_main(int argc UNUSED_PARAM, char **argv)
1075 #if ENABLE_FEATURE_LS_COLOR
1076 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1078 * # ls --color=BOGUS
1079 * ls: invalid argument 'BOGUS' for '--color'
1080 * Valid arguments are:
1081 * 'always', 'yes', 'force'
1082 * 'never', 'no', 'none'
1083 * 'auto', 'tty', 'if-tty'
1084 * (and substrings: "--color=alwa" work too)
1086 static const char ls_longopts[] ALIGN1 =
1087 "color\0" Optional_argument "\xff"; /* no short equivalent */
1088 static const char color_str[] ALIGN1 =
1089 "always\0""yes\0""force\0"
1090 "auto\0""tty\0""if-tty\0";
1091 /* need to initialize since --color has _an optional_ argument */
1092 const char *color_opt = color_str; /* "always" */
1099 if (ENABLE_FEATURE_LS_SORTFILES)
1100 G.all_fmt = SORT_NAME;
1102 #if ENABLE_FEATURE_AUTOWIDTH
1103 /* obtain the terminal width */
1104 get_terminal_width_height(STDIN_FILENO, &G_terminal_width, NULL);
1105 /* go one less... */
1109 /* process options */
1110 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1114 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1115 * in some pairs of opts, only last one takes effect:
1117 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1118 // ":m-l:l-m" - we don't have -m
1119 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1120 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1121 ":C-1:1-C" /* bycols/oneline */
1122 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1123 ":c-u:u-c" /* mtime/atime */
1125 IF_FEATURE_AUTOWIDTH(":w+");
1126 opt = getopt32(argv, ls_options
1127 IF_FEATURE_AUTOWIDTH(, NULL, &G_terminal_width)
1128 IF_FEATURE_LS_COLOR(, &color_opt)
1130 for (i = 0; opt_flags[i] != (1U << 31); i++) {
1131 if (opt & (1 << i)) {
1132 uint32_t flags = opt_flags[i];
1134 if (flags & STYLE_MASK)
1135 G.all_fmt &= ~STYLE_MASK;
1136 if (flags & SORT_MASK)
1137 G.all_fmt &= ~SORT_MASK;
1138 if (flags & TIME_MASK)
1139 G.all_fmt &= ~TIME_MASK;
1145 #if ENABLE_FEATURE_LS_COLOR
1146 /* set G_show_color = 1/0 */
1147 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1148 char *p = getenv("LS_COLORS");
1149 /* LS_COLORS is unset, or (not empty && not "none") ? */
1150 if (!p || (p[0] && strcmp(p, "none") != 0))
1153 if (opt & OPT_color) {
1154 if (color_opt[0] == 'n')
1156 else switch (index_in_substrings(color_str, color_opt)) {
1160 if (isatty(STDOUT_FILENO)) {
1170 /* sort out which command line options take precedence */
1171 if (ENABLE_FEATURE_LS_RECURSIVE && (G.all_fmt & DISP_NOLIST))
1172 G.all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1173 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1174 if (G.all_fmt & TIME_CHANGE)
1175 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_CTIME;
1176 if (G.all_fmt & TIME_ACCESS)
1177 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_ATIME;
1179 if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */
1180 G.all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1182 /* choose a display format if one was not already specified by an option */
1183 if (!(G.all_fmt & STYLE_MASK))
1184 G.all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1188 *--argv = (char*)".";
1191 G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1193 /* stuff the command line file names into a dnode array */
1197 cur = my_stat(*argv, *argv,
1198 /* follow links on command line unless -l, -s or -F: */
1199 !((G.all_fmt & STYLE_MASK) == STYLE_LONG
1200 || (G.all_fmt & LIST_BLOCKS)
1201 || (option_mask32 & OPT_F)
1204 || (option_mask32 & OPT_H)
1205 /* ... or if -L, but my_stat always follows links if -L */
1210 /*cur->fname_allocated = 0; - already is */
1216 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1220 /* now that we know how many files there are
1221 * allocate memory for an array to hold dnode pointers
1223 dnp = dnalloc(nfiles);
1224 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1225 dnp[i] = dn; /* save pointer to node in array */
1231 if (G.all_fmt & DISP_NOLIST) {
1232 sort_and_display_files(dnp, nfiles);
1234 dnd = splitdnarray(dnp, SPLIT_DIR);
1235 dnf = splitdnarray(dnp, SPLIT_FILE);
1236 dndirs = count_dirs(dnp, SPLIT_DIR);
1237 dnfiles = nfiles - dndirs;
1239 sort_and_display_files(dnf, dnfiles);
1240 if (ENABLE_FEATURE_CLEAN_UP)
1244 dnsort(dnd, dndirs);
1245 scan_and_display_dirs_recur(dnd, dnfiles == 0);
1246 if (ENABLE_FEATURE_CLEAN_UP)
1251 if (ENABLE_FEATURE_CLEAN_UP)