0f35c70d51df9f9cdedb5b0755716024e93c5d4a
[oweals/busybox.git] / coreutils / ls.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
4  *
5  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
6  */
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.
12  *
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
15  * it more portable.
16  *
17  * KNOWN BUGS:
18  * 1. hidden files can make column width too large
19  *
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
24  * PORTABILITY:
25  * 1. requires lstat (BSD) - how do you do it without?
26  *
27  * [2009-03]
28  * ls sorts listing now, and supports almost all options.
29  */
30 //config:config LS
31 //config:       bool "ls"
32 //config:       default y
33 //config:       help
34 //config:         ls is used to list the contents of directories.
35 //config:
36 //config:config FEATURE_LS_FILETYPES
37 //config:       bool "Enable filetyping options (-p and -F)"
38 //config:       default y
39 //config:       depends on LS
40 //config:       help
41 //config:         Enable the ls options (-p and -F).
42 //config:
43 //config:config FEATURE_LS_FOLLOWLINKS
44 //config:       bool "Enable symlinks dereferencing (-L)"
45 //config:       default y
46 //config:       depends on LS
47 //config:       help
48 //config:         Enable the ls option (-L).
49 //config:
50 //config:config FEATURE_LS_RECURSIVE
51 //config:       bool "Enable recursion (-R)"
52 //config:       default y
53 //config:       depends on LS
54 //config:       help
55 //config:         Enable the ls option (-R).
56 //config:
57 //config:config FEATURE_LS_SORTFILES
58 //config:       bool "Sort the file names"
59 //config:       default y
60 //config:       depends on LS
61 //config:       help
62 //config:         Allow ls to sort file names alphabetically.
63 //config:
64 //config:config FEATURE_LS_TIMESTAMPS
65 //config:       bool "Show file timestamps"
66 //config:       default y
67 //config:       depends on LS
68 //config:       help
69 //config:         Allow ls to display timestamps for files.
70 //config:
71 //config:config FEATURE_LS_USERNAME
72 //config:       bool "Show username/groupnames"
73 //config:       default y
74 //config:       depends on LS
75 //config:       help
76 //config:         Allow ls to display username/groupname for files.
77 //config:
78 //config:config FEATURE_LS_COLOR
79 //config:       bool "Allow use of color to identify file types"
80 //config:       default y
81 //config:       depends on LS && LONG_OPTS
82 //config:       help
83 //config:         This enables the --color option to ls.
84 //config:
85 //config:config FEATURE_LS_COLOR_IS_DEFAULT
86 //config:       bool "Produce colored ls output by default"
87 //config:       default y
88 //config:       depends on FEATURE_LS_COLOR
89 //config:       help
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.
95
96 //applet:IF_LS(APPLET_NOEXEC(ls, ls, BB_DIR_BIN, BB_SUID_DROP, ls))
97
98 //kbuild:lib-$(CONFIG_LS) += ls.o
99
100 //usage:#define ls_trivial_usage
101 //usage:        "[-1AaCxd"
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"
122 //usage:        )
123 //usage:        IF_FEATURE_LS_RECURSIVE(
124 //usage:     "\n        -R      Recurse"
125 //usage:        )
126 //usage:        IF_FEATURE_LS_FILETYPES(
127 //usage:     "\n        -p      Append / to dir entries"
128 //usage:     "\n        -F      Append indicator (one of */=@|) to entries"
129 //usage:        )
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"
136 //usage:        )
137 //usage:        IF_FEATURE_HUMAN_READABLE(
138 //usage:     "\n        -h      List sizes in human readable format (1K 243M 2G)"
139 //usage:        )
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"
145 //usage:        )
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"
150 //usage:        )
151 //usage:        IF_SELINUX(
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"
155 //usage:        )
156 //usage:        IF_FEATURE_AUTOWIDTH(
157 //usage:     "\n        -w N    Assume the terminal is N columns wide"
158 //usage:        )
159 //usage:        IF_FEATURE_LS_COLOR(
160 //usage:     "\n        --color[={always,never,auto}]   Control coloring"
161 //usage:        )
162
163 #include "libbb.h"
164 #include "common_bufsiz.h"
165 #include "unicode.h"
166
167
168 /* This is a NOEXEC applet. Be very careful! */
169
170
171 #if ENABLE_FTPD
172 /* ftpd uses ls, and without timestamps Mozilla won't understand
173  * ftpd's LIST output.
174  */
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(...)
183 #endif
184
185
186 enum {
187 TERMINAL_WIDTH  = 80,           /* use 79 if terminal has linefold bug */
188
189 SPLIT_FILE      = 0,
190 SPLIT_DIR       = 1,
191 SPLIT_SUBDIR    = 2,
192
193 /* Bits in G.all_fmt: */
194
195 /* 51306 lrwxrwxrwx  1 root     root         2 May 11 01:43 /bin/view -> vi* */
196 /* what file information will be listed */
197 LIST_INO        = 1 << 0,
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,
204 LIST_SIZE       = 1 << 7,
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,
211
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),
220
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,
226
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,
231
232 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
233 SORT_REVERSE    = 1 << 23,
234
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,
244
245 LIST_LONG       = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
246                   LIST_DATE_TIME | LIST_SYMLINK,
247 };
248
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 */;
274 enum {
275         //OPT_C = (1 << 0),
276         //OPT_a = (1 << 1),
277         //OPT_d = (1 << 2),
278         //OPT_i = (1 << 3),
279         //OPT_l = (1 << 4),
280         //OPT_1 = (1 << 5),
281         OPT_g = (1 << 6),
282         //OPT_n = (1 << 7),
283         //OPT_s = (1 << 8),
284         //OPT_x = (1 << 9),
285         OPT_Q = (1 << 10),
286         //OPT_A = (1 << 11),
287         //OPT_k = (1 << 12),
288
289         OPTBIT_c = 13,
290         OPTBIT_e,
291         OPTBIT_t,
292         OPTBIT_u,
293         OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS,
294         OPTBIT_X, /* 18 */
295         OPTBIT_r,
296         OPTBIT_v,
297         OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
298         OPTBIT_p, /* 22 */
299         OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
300         OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
301         OPTBIT_Z, /* 25 */
302         OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX,
303         OPTBIT_H, /* 27 */
304         OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
305         OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
306         OPTBIT_w, /* 30 */
307         OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH,
308
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,
328 };
329
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 */
334         DISP_NOLIST,                 /* d */
335         LIST_INO,                    /* i */
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) */
340         LIST_BLOCKS,                 /* s */
341         DISP_ROWS | STYLE_COLUMNAR,  /* x */
342         0,                           /* Q (quote filename) - handled via OPT_Q */
343         DISP_HIDDEN,                 /* A */
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 */
350 #endif
351 #if ENABLE_FEATURE_LS_SORTFILES
352         SORT_SIZE,                   /* S */
353         SORT_EXT,                    /* X */
354         SORT_REVERSE,                /* r */
355         SORT_VERSION,                /* v */
356 #endif
357 #if ENABLE_FEATURE_LS_FILETYPES
358         LIST_FILETYPE | LIST_CLASSIFY, /* F */
359         LIST_FILETYPE,               /* p */
360 #endif
361 #if ENABLE_FEATURE_LS_RECURSIVE
362         DISP_RECURSIVE,              /* R */
363 #endif
364 #if ENABLE_SELINUX
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 */
367 #endif
368         (1U << 31)
369         /* options after Z are not processed through opt_flags */
370 };
371
372
373 /*
374  * a directory entry and its stat info
375  */
376 struct dnode {
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;
382
383         /* Used to avoid re-doing [l]stat at printout stage
384          * if we already collected needed data in scan stage:
385          */
386         mode_t    dn_mode_lstat;   /* obtained with lstat, or 0 */
387         mode_t    dn_mode_stat;    /* obtained with stat, or 0 */
388
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.
397 //
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 */
400         off_t     dn_size;
401 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
402         time_t    dn_atime;
403         time_t    dn_mtime;
404         time_t    dn_ctime;
405 #endif
406         ino_t     dn_ino;
407         blkcnt_t  dn_blocks;
408         nlink_t   dn_nlink;
409         uid_t     dn_uid;
410         gid_t     dn_gid;
411         int       dn_rdev_maj;
412         int       dn_rdev_min;
413 //      dev_t     dn_dev;
414 //      blksize_t dn_blksize;
415 };
416
417 struct globals {
418 #if ENABLE_FEATURE_LS_COLOR
419         smallint show_color;
420 # define G_show_color (G.show_color)
421 #else
422 # define G_show_color 0
423 #endif
424         smallint exit_code;
425         unsigned all_fmt;
426 #if ENABLE_FEATURE_AUTOWIDTH
427         unsigned terminal_width;
428 # define G_terminal_width (G.terminal_width)
429 #else
430 # define G_terminal_width TERMINAL_WIDTH
431 #endif
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;
435 #endif
436 } FIX_ALIASING;
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);) \
444 } while (0)
445
446
447 /*** Output code ***/
448
449
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
465 */
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" \
469         [TYPEINDEX(mode)])
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.
474  */
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" \
478         [TYPEINDEX(mode)])
479
480 #if ENABLE_FEATURE_LS_COLOR
481 /* mode of zero is interpreted as "unknown" (stat failed) */
482 static char fgcolor(mode_t mode)
483 {
484         if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
485                 return COLOR(0xF000);   /* File is executable ... */
486         return COLOR(mode);
487 }
488 static char bold(mode_t mode)
489 {
490         if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
491                 return ATTR(0xF000);    /* File is executable ... */
492         return ATTR(mode);
493 }
494 #endif
495
496 #if ENABLE_FEATURE_LS_FILETYPES
497 static char append_char(mode_t mode)
498 {
499         if (!(G.all_fmt & LIST_FILETYPE))
500                 return '\0';
501         if (S_ISDIR(mode))
502                 return '/';
503         if (!(G.all_fmt & LIST_CLASSIFY))
504                 return '\0';
505         if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
506                 return '*';
507         return APPCHAR(mode);
508 }
509 #endif
510
511 static unsigned calc_name_len(const char *name)
512 {
513         unsigned len;
514         uni_stat_t uni_stat;
515
516         // TODO: quote tab as \t, etc, if -Q
517         name = printable_string(&uni_stat, name);
518
519         if (!(option_mask32 & OPT_Q)) {
520                 return uni_stat.unicode_width;
521         }
522
523         len = 2 + uni_stat.unicode_width;
524         while (*name) {
525                 if (*name == '"' || *name == '\\') {
526                         len++;
527                 }
528                 name++;
529         }
530         return len;
531 }
532
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
539  */
540 static unsigned print_name(const char *name)
541 {
542         unsigned len;
543         uni_stat_t uni_stat;
544
545         // TODO: quote tab as \t, etc, if -Q
546         name = printable_string(&uni_stat, name);
547
548         if (!(option_mask32 & OPT_Q)) {
549                 fputs(name, stdout);
550                 return uni_stat.unicode_width;
551         }
552
553         len = 2 + uni_stat.unicode_width;
554         putchar('"');
555         while (*name) {
556                 if (*name == '"' || *name == '\\') {
557                         putchar('\\');
558                         len++;
559                 }
560                 putchar(*name);
561                 name++;
562         }
563         putchar('"');
564         return len;
565 }
566
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.
570  */
571 static NOINLINE unsigned display_single(const struct dnode *dn)
572 {
573         unsigned column = 0;
574         char *lpath;
575 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
576         struct stat statbuf;
577         char append;
578 #endif
579
580 #if ENABLE_FEATURE_LS_FILETYPES
581         append = append_char(dn->dn_mode);
582 #endif
583
584         /* Do readlink early, so that if it fails, error message
585          * does not appear *inside* the "ls -l" line */
586         lpath = NULL;
587         if (G.all_fmt & LIST_SYMLINK)
588                 if (S_ISLNK(dn->dn_mode))
589                         lpath = xmalloc_readlink_or_warn(dn->fullname);
590
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);
603                 else
604                         column += printf("%-8u %-8u ",
605                                         (int) dn->dn_uid,
606                                         (int) dn->dn_gid);
607         }
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));
613                 } else {
614                         column += printf("%-8.8s %-8.8s ",
615                                 get_cached_username(dn->dn_uid),
616                                 get_cached_groupname(dn->dn_gid));
617                 }
618         }
619 #endif
620         if (G.all_fmt & LIST_SIZE) {
621                 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
622                         column += printf("%4u, %3u ",
623                                         dn->dn_rdev_maj,
624                                         dn->dn_rdev_min);
625                 } else {
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)
630                                 );
631                         } else {
632                                 column += printf("%9"OFF_FMT"u ", dn->dn_size);
633                         }
634                 }
635         }
636 #if ENABLE_FEATURE_LS_TIMESTAMPS
637         if (G.all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
638                 char *filetime;
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
649                          */
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);
658                         } else {
659                                 /* "mmm dd  yyyy " */
660                                 /* "mmm dd yyyyy " after year 9999 :) */
661                                 strchr(filetime + 20, '\n')[0] = ' ';
662                                 printf("%.7s%6s", filetime + 4, filetime + 20);
663                         }
664                         column += 13;
665                 }
666         }
667 #endif
668 #if ENABLE_SELINUX
669         if (G.all_fmt & LIST_CONTEXT) {
670                 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
671                 freecon(dn->sid);
672         }
673 #endif
674
675 #if ENABLE_FEATURE_LS_COLOR
676         if (G_show_color) {
677                 mode_t mode = dn->dn_mode_lstat;
678                 if (!mode)
679                         if (lstat(dn->fullname, &statbuf) == 0)
680                                 mode = statbuf.st_mode;
681                 printf("\033[%u;%um", bold(mode), fgcolor(mode));
682         }
683 #endif
684         column += print_name(dn->name);
685         if (G_show_color) {
686                 printf("\033[0m");
687         }
688
689         if (lpath) {
690                 printf(" -> ");
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;
694                         if (!mode)
695                                 if (stat(dn->fullname, &statbuf) == 0)
696                                         mode = statbuf.st_mode;
697 # if ENABLE_FEATURE_LS_FILETYPES
698                         append = append_char(mode);
699 # endif
700 # if ENABLE_FEATURE_LS_COLOR
701                         if (G_show_color) {
702                                 printf("\033[%u;%um", bold(mode), fgcolor(mode));
703                         }
704 # endif
705                 }
706 #endif
707                 column += print_name(lpath) + 4;
708                 free(lpath);
709                 if (G_show_color) {
710                         printf("\033[0m");
711                 }
712         }
713 #if ENABLE_FEATURE_LS_FILETYPES
714         if (G.all_fmt & LIST_FILETYPE) {
715                 if (append) {
716                         putchar(append);
717                         column++;
718                 }
719         }
720 #endif
721
722         return column;
723 }
724
725 static void display_files(struct dnode **dn, unsigned nfiles)
726 {
727         unsigned i, ncols, nrows, row, nc;
728         unsigned column;
729         unsigned nexttab;
730         unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
731
732         if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
733                 ncols = 1;
734         } else {
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)
739                                 column_width = len;
740                 }
741                 column_width += 2 +
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;
746         }
747
748         if (ncols > 1) {
749                 nrows = nfiles / ncols;
750                 if (nrows * ncols < nfiles)
751                         nrows++;                /* round up fractionals */
752         } else {
753                 nrows = nfiles;
754                 ncols = 1;
755         }
756
757         column = 0;
758         nexttab = 0;
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 */
764                         else
765                                 i = (nc * nrows) + row; /* display by column */
766                         if (i < nfiles) {
767                                 if (column > 0) {
768                                         nexttab -= column;
769                                         printf("%*s", nexttab, "");
770                                         column += nexttab;
771                                 }
772                                 nexttab = column + column_width;
773                                 column += display_single(dn[i]);
774                         }
775                 }
776                 putchar('\n');
777                 column = 0;
778         }
779 }
780
781
782 /*** Dir scanning code ***/
783
784 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
785 {
786         struct stat statbuf;
787         struct dnode *cur;
788
789         cur = xzalloc(sizeof(*cur));
790         cur->fullname = fullname;
791         cur->name = name;
792
793         if ((option_mask32 & OPT_L) || force_follow) {
794 #if ENABLE_SELINUX
795                 if (is_selinux_enabled())  {
796                         getfilecon(fullname, &cur->sid);
797                 }
798 #endif
799                 if (stat(fullname, &statbuf)) {
800                         bb_simple_perror_msg(fullname);
801                         G.exit_code = EXIT_FAILURE;
802                         free(cur);
803                         return NULL;
804                 }
805                 cur->dn_mode_stat = statbuf.st_mode;
806         } else {
807 #if ENABLE_SELINUX
808                 if (is_selinux_enabled()) {
809                         lgetfilecon(fullname, &cur->sid);
810                 }
811 #endif
812                 if (lstat(fullname, &statbuf)) {
813                         bb_simple_perror_msg(fullname);
814                         G.exit_code = EXIT_FAILURE;
815                         free(cur);
816                         return NULL;
817                 }
818                 cur->dn_mode_lstat = statbuf.st_mode;
819         }
820
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 ;
828 #endif
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);
836
837         return cur;
838 }
839
840 static unsigned count_dirs(struct dnode **dn, int which)
841 {
842         unsigned dirs, all;
843
844         if (!dn)
845                 return 0;
846
847         dirs = all = 0;
848         for (; *dn; dn++) {
849                 const char *name;
850
851                 all++;
852                 if (!S_ISDIR((*dn)->dn_mode))
853                         continue;
854
855                 name = (*dn)->name;
856                 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
857                  /* or if it's not . or .. */
858                  || name[0] != '.'
859                  || (name[1] && (name[1] != '.' || name[2]))
860                 ) {
861                         dirs++;
862                 }
863         }
864         return which != SPLIT_FILE ? dirs : all - dirs;
865 }
866
867 /* get memory to hold an array of pointers */
868 static struct dnode **dnalloc(unsigned num)
869 {
870         if (num < 1)
871                 return NULL;
872
873         num++; /* so that we have terminating NULL */
874         return xzalloc(num * sizeof(struct dnode *));
875 }
876
877 #if ENABLE_FEATURE_LS_RECURSIVE
878 static void dfree(struct dnode **dnp)
879 {
880         unsigned i;
881
882         if (dnp == NULL)
883                 return;
884
885         for (i = 0; dnp[i]; i++) {
886                 struct dnode *cur = dnp[i];
887                 if (cur->fname_allocated)
888                         free((char*)cur->fullname);
889                 free(cur);
890         }
891         free(dnp);
892 }
893 #else
894 #define dfree(...) ((void)0)
895 #endif
896
897 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
898 static struct dnode **splitdnarray(struct dnode **dn, int which)
899 {
900         unsigned dncnt, d;
901         struct dnode **dnp;
902
903         if (dn == NULL)
904                 return NULL;
905
906         /* count how many dirs or files there are */
907         dncnt = count_dirs(dn, which);
908
909         /* allocate a file array and a dir array */
910         dnp = dnalloc(dncnt);
911
912         /* copy the entrys into the file or dir array */
913         for (d = 0; *dn; dn++) {
914                 if (S_ISDIR((*dn)->dn_mode)) {
915                         const char *name;
916
917                         if (which == SPLIT_FILE)
918                                 continue;
919
920                         name = (*dn)->name;
921                         if ((which & SPLIT_DIR) /* any dir... */
922                         /* ... or not . or .. */
923                          || name[0] != '.'
924                          || (name[1] && (name[1] != '.' || name[2]))
925                         ) {
926                                 dnp[d++] = *dn;
927                         }
928                 } else
929                 if (which == SPLIT_FILE) {
930                         dnp[d++] = *dn;
931                 }
932         }
933         return dnp;
934 }
935
936 #if ENABLE_FEATURE_LS_SORTFILES
937 static int sortcmp(const void *a, const void *b)
938 {
939         struct dnode *d1 = *(struct dnode **)a;
940         struct dnode *d2 = *(struct dnode **)b;
941         unsigned sort_opts = G.all_fmt & SORT_MASK;
942         off_t dif;
943
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);
949         } else
950         if (sort_opts == SORT_ATIME) {
951                 dif = (d2->dn_atime - d1->dn_atime);
952         } else
953         if (sort_opts == SORT_CTIME) {
954                 dif = (d2->dn_ctime - d1->dn_ctime);
955         } else
956         if (sort_opts == SORT_MTIME) {
957                 dif = (d2->dn_mtime - d1->dn_mtime);
958         } else
959         if (sort_opts == SORT_DIR) {
960                 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
961         } else
962 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
963         if (sort_opts == SORT_VERSION) {
964                 dif = strverscmp(d1->name, d2->name);
965         } else
966 #endif
967         if (sort_opts == SORT_EXT) {
968                 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
969         }
970         if (dif == 0) {
971                 /* sort by name, use as tie breaker for other sorts */
972                 if (ENABLE_LOCALE_SUPPORT)
973                         dif = strcoll(d1->name, d2->name);
974                 else
975                         dif = strcmp(d1->name, d2->name);
976         }
977
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 */
982                 if (dif != 0) {
983                         dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
984                 }
985         }
986
987         return (G.all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
988 }
989
990 static void dnsort(struct dnode **dn, int size)
991 {
992         qsort(dn, size, sizeof(*dn), sortcmp);
993 }
994
995 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
996 {
997         dnsort(dn, nfiles);
998         display_files(dn, nfiles);
999 }
1000 #else
1001 # define dnsort(dn, size) ((void)0)
1002 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
1003 #endif
1004
1005 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
1006 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
1007 {
1008         struct dnode *dn, *cur, **dnp;
1009         struct dirent *entry;
1010         DIR *dir;
1011         unsigned i, nfiles;
1012
1013         *nfiles_p = 0;
1014         dir = warn_opendir(path);
1015         if (dir == NULL) {
1016                 G.exit_code = EXIT_FAILURE;
1017                 return NULL;    /* could not open the dir */
1018         }
1019         dn = NULL;
1020         nfiles = 0;
1021         while ((entry = readdir(dir)) != NULL) {
1022                 char *fullname;
1023
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)
1028                         ) {
1029                                 continue;
1030                         }
1031                         if (!(G.all_fmt & DISP_HIDDEN))
1032                                 continue;
1033                 }
1034                 fullname = concat_path_file(path, entry->d_name);
1035                 cur = my_stat(fullname, bb_basename(fullname), 0);
1036                 if (!cur) {
1037                         free(fullname);
1038                         continue;
1039                 }
1040                 cur->fname_allocated = 1;
1041                 cur->dn_next = dn;
1042                 dn = cur;
1043                 nfiles++;
1044         }
1045         closedir(dir);
1046
1047         if (dn == NULL)
1048                 return NULL;
1049
1050         /* now that we know how many files there are
1051          * allocate memory for an array to hold dnode pointers
1052          */
1053         *nfiles_p = nfiles;
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 */
1057                 dn = dn->dn_next;
1058                 if (!dn)
1059                         break;
1060         }
1061
1062         return dnp;
1063 }
1064
1065 #if ENABLE_DESKTOP
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
1073  * number of units.
1074  */
1075 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
1076 static off_t calculate_blocks(struct dnode **dn)
1077 {
1078         uoff_t blocks = 1;
1079         if (dn) {
1080                 while (*dn) {
1081                         /* st_blocks is in 512 byte blocks */
1082                         blocks += (*dn)->dn_blocks;
1083                         dn++;
1084                 }
1085         }
1086
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 */
1090         return blocks >> 1;
1091 }
1092 #endif
1093
1094 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1095 {
1096         unsigned nfiles;
1097         struct dnode **subdnp;
1098
1099         for (; *dn; dn++) {
1100                 if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
1101                         if (!first)
1102                                 bb_putchar('\n');
1103                         first = 0;
1104                         printf("%s:\n", (*dn)->fullname);
1105                 }
1106                 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1107 #if ENABLE_DESKTOP
1108                 if ((G.all_fmt & STYLE_MASK) == STYLE_LONG || (G.all_fmt & LIST_BLOCKS))
1109                         printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1110 #endif
1111                 if (nfiles > 0) {
1112                         /* list all files at this level */
1113                         sort_and_display_files(subdnp, nfiles);
1114
1115                         if (ENABLE_FEATURE_LS_RECURSIVE
1116                          && (G.all_fmt & DISP_RECURSIVE)
1117                         ) {
1118                                 struct dnode **dnd;
1119                                 unsigned dndirs;
1120                                 /* recursive - list the sub-dirs */
1121                                 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1122                                 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1123                                 if (dndirs > 0) {
1124                                         dnsort(dnd, dndirs);
1125                                         scan_and_display_dirs_recur(dnd, 0);
1126                                         /* free the array of dnode pointers to the dirs */
1127                                         free(dnd);
1128                                 }
1129                         }
1130                         /* free the dnodes and the fullname mem */
1131                         dfree(subdnp);
1132                 }
1133         }
1134 }
1135
1136
1137 int ls_main(int argc UNUSED_PARAM, char **argv)
1138 {
1139         struct dnode **dnd;
1140         struct dnode **dnf;
1141         struct dnode **dnp;
1142         struct dnode *dn;
1143         struct dnode *cur;
1144         unsigned opt;
1145         unsigned nfiles;
1146         unsigned dnfiles;
1147         unsigned dndirs;
1148         unsigned i;
1149 #if ENABLE_FEATURE_LS_COLOR
1150         /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1151         /* coreutils 6.10:
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)
1159          */
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" */
1167 #endif
1168
1169         INIT_G();
1170
1171         init_unicode();
1172
1173         if (ENABLE_FEATURE_LS_SORTFILES)
1174                 G.all_fmt = SORT_NAME;
1175
1176 #if ENABLE_FEATURE_AUTOWIDTH
1177         /* obtain the terminal width */
1178         G_terminal_width = get_terminal_width(STDIN_FILENO);
1179         /* go one less... */
1180         G_terminal_width--;
1181 #endif
1182
1183         /* process options */
1184         IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1185         opt_complementary =
1186                 /* -e implies -l */
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:
1190                  */
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 */
1198                 /* -w NUM: */
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)
1203         );
1204         for (i = 0; opt_flags[i] != (1U << 31); i++) {
1205                 if (opt & (1 << i)) {
1206                         uint32_t flags = opt_flags[i];
1207
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;
1214
1215                         G.all_fmt |= flags;
1216                 }
1217         }
1218
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))
1225                         G_show_color = 1;
1226         }
1227         if (opt & OPT_color) {
1228                 if (color_opt[0] == 'n')
1229                         G_show_color = 0;
1230                 else switch (index_in_substrings(color_str, color_opt)) {
1231                 case 3:
1232                 case 4:
1233                 case 5:
1234                         if (isatty(STDOUT_FILENO)) {
1235                 case 0:
1236                 case 1:
1237                 case 2:
1238                                 G_show_color = 1;
1239                         }
1240                 }
1241         }
1242 #endif
1243
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;
1252         }
1253         if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */
1254                 G.all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1255
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);
1259
1260         argv += optind;
1261         if (!argv[0])
1262                 *--argv = (char*)".";
1263
1264         if (argv[1])
1265                 G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1266
1267         /* stuff the command line file names into a dnode array */
1268         dn = NULL;
1269         nfiles = 0;
1270         do {
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)
1276                         )
1277                         /* ... or if -H: */
1278                         || (option_mask32 & OPT_H)
1279                         /* ... or if -L, but my_stat always follows links if -L */
1280                 );
1281                 argv++;
1282                 if (!cur)
1283                         continue;
1284                 /*cur->fname_allocated = 0; - already is */
1285                 cur->dn_next = dn;
1286                 dn = cur;
1287                 nfiles++;
1288         } while (*argv);
1289
1290         /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1291         if (nfiles == 0)
1292                 return G.exit_code;
1293
1294         /* now that we know how many files there are
1295          * allocate memory for an array to hold dnode pointers
1296          */
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 */
1300                 dn = dn->dn_next;
1301                 if (!dn)
1302                         break;
1303         }
1304
1305         if (G.all_fmt & DISP_NOLIST) {
1306                 sort_and_display_files(dnp, nfiles);
1307         } else {
1308                 dnd = splitdnarray(dnp, SPLIT_DIR);
1309                 dnf = splitdnarray(dnp, SPLIT_FILE);
1310                 dndirs = count_dirs(dnp, SPLIT_DIR);
1311                 dnfiles = nfiles - dndirs;
1312                 if (dnfiles > 0) {
1313                         sort_and_display_files(dnf, dnfiles);
1314                         if (ENABLE_FEATURE_CLEAN_UP)
1315                                 free(dnf);
1316                 }
1317                 if (dndirs > 0) {
1318                         dnsort(dnd, dndirs);
1319                         scan_and_display_dirs_recur(dnd, dnfiles == 0);
1320                         if (ENABLE_FEATURE_CLEAN_UP)
1321                                 free(dnd);
1322                 }
1323         }
1324
1325         if (ENABLE_FEATURE_CLEAN_UP)
1326                 dfree(dnp);
1327         return G.exit_code;
1328 }