Big cleanup in config help and description
[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:
41 //config:config FEATURE_LS_FOLLOWLINKS
42 //config:       bool "Enable symlinks dereferencing (-L)"
43 //config:       default y
44 //config:       depends on LS
45 //config:
46 //config:config FEATURE_LS_RECURSIVE
47 //config:       bool "Enable recursion (-R)"
48 //config:       default y
49 //config:       depends on LS
50 //config:
51 //config:config FEATURE_LS_SORTFILES
52 //config:       bool "Sort the file names"
53 //config:       default y
54 //config:       depends on LS
55 //config:       help
56 //config:         Allow ls to sort file names alphabetically.
57 //config:
58 //config:config FEATURE_LS_TIMESTAMPS
59 //config:       bool "Show file timestamps"
60 //config:       default y
61 //config:       depends on LS
62 //config:       help
63 //config:         Allow ls to display timestamps for files.
64 //config:
65 //config:config FEATURE_LS_USERNAME
66 //config:       bool "Show username/groupnames"
67 //config:       default y
68 //config:       depends on LS
69 //config:       help
70 //config:         Allow ls to display username/groupname for files.
71 //config:
72 //config:config FEATURE_LS_COLOR
73 //config:       bool "Allow use of color to identify file types"
74 //config:       default y
75 //config:       depends on LS && LONG_OPTS
76 //config:       help
77 //config:         This enables the --color option to ls.
78 //config:
79 //config:config FEATURE_LS_COLOR_IS_DEFAULT
80 //config:       bool "Produce colored ls output by default"
81 //config:       default y
82 //config:       depends on FEATURE_LS_COLOR
83 //config:       help
84 //config:         Saying yes here will turn coloring on by default,
85 //config:         even if no "--color" option is given to the ls command.
86 //config:         This is not recommended, since the colors are not
87 //config:         configurable, and the output may not be legible on
88 //config:         many output screens.
89
90 //applet:IF_LS(APPLET_NOEXEC(ls, ls, BB_DIR_BIN, BB_SUID_DROP, ls))
91
92 //kbuild:lib-$(CONFIG_LS) += ls.o
93
94 //usage:#define ls_trivial_usage
95 //usage:        "[-1AaCxd"
96 //usage:        IF_FEATURE_LS_FOLLOWLINKS("LH")
97 //usage:        IF_FEATURE_LS_RECURSIVE("R")
98 //usage:        IF_FEATURE_LS_FILETYPES("Fp") "lins"
99 //usage:        IF_FEATURE_LS_TIMESTAMPS("e")
100 //usage:        IF_FEATURE_HUMAN_READABLE("h")
101 //usage:        IF_FEATURE_LS_SORTFILES("rSXv")
102 //usage:        IF_FEATURE_LS_TIMESTAMPS("ctu")
103 //usage:        IF_SELINUX("kKZ") "]"
104 //usage:        IF_FEATURE_AUTOWIDTH(" [-w WIDTH]") " [FILE]..."
105 //usage:#define ls_full_usage "\n\n"
106 //usage:       "List directory contents\n"
107 //usage:     "\n        -1      One column output"
108 //usage:     "\n        -a      Include entries which start with ."
109 //usage:     "\n        -A      Like -a, but exclude . and .."
110 //usage:     "\n        -C      List by columns"
111 //usage:     "\n        -x      List by lines"
112 //usage:     "\n        -d      List directory entries instead of contents"
113 //usage:        IF_FEATURE_LS_FOLLOWLINKS(
114 //usage:     "\n        -L      Follow symlinks"
115 //usage:     "\n        -H      Follow symlinks on command line"
116 //usage:        )
117 //usage:        IF_FEATURE_LS_RECURSIVE(
118 //usage:     "\n        -R      Recurse"
119 //usage:        )
120 //usage:        IF_FEATURE_LS_FILETYPES(
121 //usage:     "\n        -p      Append / to dir entries"
122 //usage:     "\n        -F      Append indicator (one of */=@|) to entries"
123 //usage:        )
124 //usage:     "\n        -l      Long listing format"
125 //usage:     "\n        -i      List inode numbers"
126 //usage:     "\n        -n      List numeric UIDs and GIDs instead of names"
127 //usage:     "\n        -s      List allocated blocks"
128 //usage:        IF_FEATURE_LS_TIMESTAMPS(
129 //usage:     "\n        -e      List full date and time"
130 //usage:        )
131 //usage:        IF_FEATURE_HUMAN_READABLE(
132 //usage:     "\n        -h      List sizes in human readable format (1K 243M 2G)"
133 //usage:        )
134 //usage:        IF_FEATURE_LS_SORTFILES(
135 //usage:     "\n        -r      Sort in reverse order"
136 //usage:     "\n        -S      Sort by size"
137 //usage:     "\n        -X      Sort by extension"
138 //usage:     "\n        -v      Sort by version"
139 //usage:        )
140 //usage:        IF_FEATURE_LS_TIMESTAMPS(
141 //usage:     "\n        -c      With -l: sort by ctime"
142 //usage:     "\n        -t      With -l: sort by mtime"
143 //usage:     "\n        -u      With -l: sort by atime"
144 //usage:        )
145 //usage:        IF_SELINUX(
146 //usage:     "\n        -k      List security context"
147 //usage:     "\n        -K      List security context in long format"
148 //usage:     "\n        -Z      List security context and permission"
149 //usage:        )
150 //usage:        IF_FEATURE_AUTOWIDTH(
151 //usage:     "\n        -w N    Assume the terminal is N columns wide"
152 //usage:        )
153 //usage:        IF_FEATURE_LS_COLOR(
154 //usage:     "\n        --color[={always,never,auto}]   Control coloring"
155 //usage:        )
156
157 #include "libbb.h"
158 #include "common_bufsiz.h"
159 #include "unicode.h"
160
161
162 /* This is a NOEXEC applet. Be very careful! */
163
164
165 #if ENABLE_FTPD
166 /* ftpd uses ls, and without timestamps Mozilla won't understand
167  * ftpd's LIST output.
168  */
169 # undef CONFIG_FEATURE_LS_TIMESTAMPS
170 # undef ENABLE_FEATURE_LS_TIMESTAMPS
171 # undef IF_FEATURE_LS_TIMESTAMPS
172 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
173 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
174 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
175 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
176 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
177 #endif
178
179
180 enum {
181 TERMINAL_WIDTH  = 80,           /* use 79 if terminal has linefold bug */
182
183 SPLIT_FILE      = 0,
184 SPLIT_DIR       = 1,
185 SPLIT_SUBDIR    = 2,
186
187 /* Bits in G.all_fmt: */
188
189 /* 51306 lrwxrwxrwx  1 root     root         2 May 11 01:43 /bin/view -> vi* */
190 /* what file information will be listed */
191 LIST_INO        = 1 << 0,
192 LIST_BLOCKS     = 1 << 1,
193 LIST_MODEBITS   = 1 << 2,
194 LIST_NLINKS     = 1 << 3,
195 LIST_ID_NAME    = 1 << 4,
196 LIST_ID_NUMERIC = 1 << 5,
197 LIST_CONTEXT    = 1 << 6,
198 LIST_SIZE       = 1 << 7,
199 LIST_DATE_TIME  = 1 << 8,
200 LIST_FULLTIME   = 1 << 9,
201 LIST_SYMLINK    = 1 << 10,
202 LIST_FILETYPE   = 1 << 11, /* show / suffix for dirs */
203 LIST_CLASSIFY   = 1 << 12, /* requires LIST_FILETYPE, also show *,|,@,= suffixes */
204 LIST_MASK       = (LIST_CLASSIFY << 1) - 1,
205
206 /* what files will be displayed */
207 DISP_DIRNAME    = 1 << 13,      /* 2 or more items? label directories */
208 DISP_HIDDEN     = 1 << 14,      /* show filenames starting with . */
209 DISP_DOT        = 1 << 15,      /* show . and .. */
210 DISP_NOLIST     = 1 << 16,      /* show directory as itself, not contents */
211 DISP_RECURSIVE  = 1 << 17,      /* show directory and everything below it */
212 DISP_ROWS       = 1 << 18,      /* print across rows */
213 DISP_MASK       = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
214
215 /* what is the overall style of the listing */
216 STYLE_COLUMNAR  = 1 << 19,      /* many records per line */
217 STYLE_LONG      = 2 << 19,      /* one record per line, extended info */
218 STYLE_SINGLE    = 3 << 19,      /* one record per line */
219 STYLE_MASK      = STYLE_SINGLE,
220
221 /* which of the three times will be used */
222 TIME_CHANGE     = (1 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
223 TIME_ACCESS     = (2 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
224 TIME_MASK       = (3 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
225
226 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
227 SORT_REVERSE    = 1 << 23,
228
229 SORT_NAME       = 0,            /* sort by file name */
230 SORT_SIZE       = 1 << 24,      /* sort by file size */
231 SORT_ATIME      = 2 << 24,      /* sort by last access time */
232 SORT_CTIME      = 3 << 24,      /* sort by last change time */
233 SORT_MTIME      = 4 << 24,      /* sort by last modification time */
234 SORT_VERSION    = 5 << 24,      /* sort by version */
235 SORT_EXT        = 6 << 24,      /* sort by file name extension */
236 SORT_DIR        = 7 << 24,      /* sort by file or directory */
237 SORT_MASK       = (7 << 24) * ENABLE_FEATURE_LS_SORTFILES,
238
239 LIST_LONG       = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
240                   LIST_DATE_TIME | LIST_SYMLINK,
241 };
242
243 /* -Cadil1  Std options, busybox always supports */
244 /* -gnsxA   Std options, busybox always supports */
245 /* -Q       GNU option, busybox always supports */
246 /* -k       SELinux option, busybox always supports (ignores if !SELinux) */
247 /*          Std has -k which means "show sizes in kbytes" */
248 /* -LHRctur Std options, busybox optionally supports */
249 /* -Fp      Std options, busybox optionally supports */
250 /* -SXvhTw  GNU options, busybox optionally supports */
251 /* -T WIDTH Ignored (we don't use tabs on output) */
252 /* -KZ      SELinux mandated options, busybox optionally supports */
253 /*          (coreutils 8.4 has no -K, remove it?) */
254 /* -e       I think we made this one up (looks similar to GNU --full-time) */
255 /* We already used up all 32 bits, if we need to add more, candidates for removal: */
256 /* -K, -T, -e (add --full-time instead) */
257 static const char ls_options[] ALIGN1 =
258         "Cadil1gnsxQAk"      /* 13 opts, total 13 */
259         IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
260         IF_FEATURE_LS_SORTFILES("SXrv")  /* 4, 21 */
261         IF_FEATURE_LS_FILETYPES("Fp")    /* 2, 23 */
262         IF_FEATURE_LS_RECURSIVE("R")     /* 1, 24 */
263         IF_SELINUX("KZ")                 /* 2, 26 */
264         IF_FEATURE_LS_FOLLOWLINKS("LH")  /* 2, 28 */
265         IF_FEATURE_HUMAN_READABLE("h")   /* 1, 29 */
266         IF_FEATURE_AUTOWIDTH("T:w:")     /* 2, 31 */
267         /* with --color, we use all 32 bits */;
268 enum {
269         //OPT_C = (1 << 0),
270         //OPT_a = (1 << 1),
271         //OPT_d = (1 << 2),
272         //OPT_i = (1 << 3),
273         //OPT_l = (1 << 4),
274         //OPT_1 = (1 << 5),
275         OPT_g = (1 << 6),
276         //OPT_n = (1 << 7),
277         //OPT_s = (1 << 8),
278         //OPT_x = (1 << 9),
279         OPT_Q = (1 << 10),
280         //OPT_A = (1 << 11),
281         //OPT_k = (1 << 12),
282
283         OPTBIT_c = 13,
284         OPTBIT_e,
285         OPTBIT_t,
286         OPTBIT_u,
287         OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS,
288         OPTBIT_X, /* 18 */
289         OPTBIT_r,
290         OPTBIT_v,
291         OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
292         OPTBIT_p, /* 22 */
293         OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
294         OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
295         OPTBIT_Z, /* 25 */
296         OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX,
297         OPTBIT_H, /* 27 */
298         OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
299         OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
300         OPTBIT_w, /* 30 */
301         OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH,
302
303         OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS,
304         OPT_e = (1 << OPTBIT_e) * ENABLE_FEATURE_LS_TIMESTAMPS,
305         OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS,
306         OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS,
307         OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES,
308         OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES,
309         OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES,
310         OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES,
311         OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
312         OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES,
313         OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE,
314         OPT_K = (1 << OPTBIT_K) * ENABLE_SELINUX,
315         OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX,
316         OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS,
317         OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS,
318         OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE,
319         OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_AUTOWIDTH,
320         OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_AUTOWIDTH,
321         OPT_color = (1 << OPTBIT_color) * ENABLE_FEATURE_LS_COLOR,
322 };
323
324 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
325 static const uint32_t opt_flags[] = {
326         STYLE_COLUMNAR,              /* C */
327         DISP_HIDDEN | DISP_DOT,      /* a */
328         DISP_NOLIST,                 /* d */
329         LIST_INO,                    /* i */
330         LIST_LONG | STYLE_LONG,      /* l */
331         STYLE_SINGLE,                /* 1 */
332         LIST_LONG | STYLE_LONG,      /* g (don't show owner) - handled via OPT_g. assumes l */
333         LIST_ID_NUMERIC | LIST_LONG | STYLE_LONG, /* n (assumes l) */
334         LIST_BLOCKS,                 /* s */
335         DISP_ROWS | STYLE_COLUMNAR,  /* x */
336         0,                           /* Q (quote filename) - handled via OPT_Q */
337         DISP_HIDDEN,                 /* A */
338         ENABLE_SELINUX * (LIST_CONTEXT|STYLE_SINGLE), /* k (ignored if !SELINUX) */
339 #if ENABLE_FEATURE_LS_TIMESTAMPS
340         TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
341         LIST_FULLTIME,               /* e */
342         ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
343         TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
344 #endif
345 #if ENABLE_FEATURE_LS_SORTFILES
346         SORT_SIZE,                   /* S */
347         SORT_EXT,                    /* X */
348         SORT_REVERSE,                /* r */
349         SORT_VERSION,                /* v */
350 #endif
351 #if ENABLE_FEATURE_LS_FILETYPES
352         LIST_FILETYPE | LIST_CLASSIFY, /* F */
353         LIST_FILETYPE,               /* p */
354 #endif
355 #if ENABLE_FEATURE_LS_RECURSIVE
356         DISP_RECURSIVE,              /* R */
357 #endif
358 #if ENABLE_SELINUX
359         LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME|STYLE_SINGLE, /* K */
360         LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT|STYLE_SINGLE, /* Z */
361 #endif
362         (1U << 31)
363         /* options after Z are not processed through opt_flags */
364 };
365
366
367 /*
368  * a directory entry and its stat info
369  */
370 struct dnode {
371         const char *name;       /* usually basename, but think "ls -l dir/file" */
372         const char *fullname;   /* full name (usable for stat etc) */
373         struct dnode *dn_next;  /* for linked list */
374         IF_SELINUX(security_context_t sid;)
375         smallint fname_allocated;
376
377         /* Used to avoid re-doing [l]stat at printout stage
378          * if we already collected needed data in scan stage:
379          */
380         mode_t    dn_mode_lstat;   /* obtained with lstat, or 0 */
381         mode_t    dn_mode_stat;    /* obtained with stat, or 0 */
382
383 //      struct stat dstat;
384 // struct stat is huge. We don't need it in full.
385 // At least we don't need st_dev and st_blksize,
386 // but there are invisible fields as well
387 // (such as nanosecond-resolution timespamps)
388 // and padding, which we also don't want to store.
389 // We also can pre-parse dev_t dn_rdev (in glibc, it's huge).
390 // On 32-bit uclibc: dnode size went from 112 to 84 bytes.
391 //
392         /* Same names as in struct stat, but with dn_ instead of st_ pfx: */
393         mode_t    dn_mode; /* obtained with lstat OR stat, depending on -L etc */
394         off_t     dn_size;
395 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
396         time_t    dn_atime;
397         time_t    dn_mtime;
398         time_t    dn_ctime;
399 #endif
400         ino_t     dn_ino;
401         blkcnt_t  dn_blocks;
402         nlink_t   dn_nlink;
403         uid_t     dn_uid;
404         gid_t     dn_gid;
405         int       dn_rdev_maj;
406         int       dn_rdev_min;
407 //      dev_t     dn_dev;
408 //      blksize_t dn_blksize;
409 };
410
411 struct globals {
412 #if ENABLE_FEATURE_LS_COLOR
413         smallint show_color;
414 # define G_show_color (G.show_color)
415 #else
416 # define G_show_color 0
417 #endif
418         smallint exit_code;
419         unsigned all_fmt;
420 #if ENABLE_FEATURE_AUTOWIDTH
421         unsigned terminal_width;
422 # define G_terminal_width (G.terminal_width)
423 #else
424 # define G_terminal_width TERMINAL_WIDTH
425 #endif
426 #if ENABLE_FEATURE_LS_TIMESTAMPS
427         /* Do time() just once. Saves one syscall per file for "ls -l" */
428         time_t current_time_t;
429 #endif
430 } FIX_ALIASING;
431 #define G (*(struct globals*)bb_common_bufsiz1)
432 #define INIT_G() do { \
433         setup_common_bufsiz(); \
434         /* we have to zero it out because of NOEXEC */ \
435         memset(&G, 0, sizeof(G)); \
436         IF_FEATURE_AUTOWIDTH(G_terminal_width = TERMINAL_WIDTH;) \
437         IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \
438 } while (0)
439
440
441 /*** Output code ***/
442
443
444 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
445  * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
446  *  3/7:multiplexed char/block device)
447  * and we use 0 for unknown and 15 for executables (see below) */
448 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
449 /*                       un  fi chr -   dir -  blk  -  file -  link - sock -   - exe */
450 #define APPCHAR(mode)   ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)])
451 /* 036 black foreground              050 black background
452    037 red foreground                051 red background
453    040 green foreground              052 green background
454    041 brown foreground              053 brown background
455    042 blue foreground               054 blue background
456    043 magenta (purple) foreground   055 magenta background
457    044 cyan (light blue) foreground  056 cyan background
458    045 gray foreground               057 white background
459 */
460 #define COLOR(mode) ( \
461         /*un  fi  chr  -  dir  -  blk  -  file -  link -  sock -   -  exe */ \
462         "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
463         [TYPEINDEX(mode)])
464 /* Select normal (0) [actually "reset all"] or bold (1)
465  * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
466  *  let's use 7 for "impossible" types, just for fun)
467  * Note: coreutils 6.9 uses inverted red for setuid binaries.
468  */
469 #define ATTR(mode) ( \
470         /*un fi chr - dir - blk - file- link- sock- -  exe */ \
471         "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
472         [TYPEINDEX(mode)])
473
474 #if ENABLE_FEATURE_LS_COLOR
475 /* mode of zero is interpreted as "unknown" (stat failed) */
476 static char fgcolor(mode_t mode)
477 {
478         if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
479                 return COLOR(0xF000);   /* File is executable ... */
480         return COLOR(mode);
481 }
482 static char bold(mode_t mode)
483 {
484         if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
485                 return ATTR(0xF000);    /* File is executable ... */
486         return ATTR(mode);
487 }
488 #endif
489
490 #if ENABLE_FEATURE_LS_FILETYPES
491 static char append_char(mode_t mode)
492 {
493         if (!(G.all_fmt & LIST_FILETYPE))
494                 return '\0';
495         if (S_ISDIR(mode))
496                 return '/';
497         if (!(G.all_fmt & LIST_CLASSIFY))
498                 return '\0';
499         if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
500                 return '*';
501         return APPCHAR(mode);
502 }
503 #endif
504
505 static unsigned calc_name_len(const char *name)
506 {
507         unsigned len;
508         uni_stat_t uni_stat;
509
510         // TODO: quote tab as \t, etc, if -Q
511         name = printable_string(&uni_stat, name);
512
513         if (!(option_mask32 & OPT_Q)) {
514                 return uni_stat.unicode_width;
515         }
516
517         len = 2 + uni_stat.unicode_width;
518         while (*name) {
519                 if (*name == '"' || *name == '\\') {
520                         len++;
521                 }
522                 name++;
523         }
524         return len;
525 }
526
527 /* Return the number of used columns.
528  * Note that only STYLE_COLUMNAR uses return value.
529  * STYLE_SINGLE and STYLE_LONG don't care.
530  * coreutils 7.2 also supports:
531  * ls -b (--escape) = octal escapes (although it doesn't look like working)
532  * ls -N (--literal) = not escape at all
533  */
534 static unsigned print_name(const char *name)
535 {
536         unsigned len;
537         uni_stat_t uni_stat;
538
539         // TODO: quote tab as \t, etc, if -Q
540         name = printable_string(&uni_stat, name);
541
542         if (!(option_mask32 & OPT_Q)) {
543                 fputs(name, stdout);
544                 return uni_stat.unicode_width;
545         }
546
547         len = 2 + uni_stat.unicode_width;
548         putchar('"');
549         while (*name) {
550                 if (*name == '"' || *name == '\\') {
551                         putchar('\\');
552                         len++;
553                 }
554                 putchar(*name);
555                 name++;
556         }
557         putchar('"');
558         return len;
559 }
560
561 /* Return the number of used columns.
562  * Note that only STYLE_COLUMNAR uses return value,
563  * STYLE_SINGLE and STYLE_LONG don't care.
564  */
565 static NOINLINE unsigned display_single(const struct dnode *dn)
566 {
567         unsigned column = 0;
568         char *lpath;
569 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
570         struct stat statbuf;
571         char append;
572 #endif
573
574 #if ENABLE_FEATURE_LS_FILETYPES
575         append = append_char(dn->dn_mode);
576 #endif
577
578         /* Do readlink early, so that if it fails, error message
579          * does not appear *inside* the "ls -l" line */
580         lpath = NULL;
581         if (G.all_fmt & LIST_SYMLINK)
582                 if (S_ISLNK(dn->dn_mode))
583                         lpath = xmalloc_readlink_or_warn(dn->fullname);
584
585         if (G.all_fmt & LIST_INO)
586                 column += printf("%7llu ", (long long) dn->dn_ino);
587 //TODO: -h should affect -s too:
588         if (G.all_fmt & LIST_BLOCKS)
589                 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1));
590         if (G.all_fmt & LIST_MODEBITS)
591                 column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode));
592         if (G.all_fmt & LIST_NLINKS)
593                 column += printf("%4lu ", (long) dn->dn_nlink);
594         if (G.all_fmt & LIST_ID_NUMERIC) {
595                 if (option_mask32 & OPT_g)
596                         column += printf("%-8u ", (int) dn->dn_gid);
597                 else
598                         column += printf("%-8u %-8u ",
599                                         (int) dn->dn_uid,
600                                         (int) dn->dn_gid);
601         }
602 #if ENABLE_FEATURE_LS_USERNAME
603         else if (G.all_fmt & LIST_ID_NAME) {
604                 if (option_mask32 & OPT_g) {
605                         column += printf("%-8.8s ",
606                                 get_cached_groupname(dn->dn_gid));
607                 } else {
608                         column += printf("%-8.8s %-8.8s ",
609                                 get_cached_username(dn->dn_uid),
610                                 get_cached_groupname(dn->dn_gid));
611                 }
612         }
613 #endif
614         if (G.all_fmt & LIST_SIZE) {
615                 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
616                         column += printf("%4u, %3u ",
617                                         dn->dn_rdev_maj,
618                                         dn->dn_rdev_min);
619                 } else {
620                         if (option_mask32 & OPT_h) {
621                                 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
622                                         /* print size, show one fractional, use suffixes */
623                                         make_human_readable_str(dn->dn_size, 1, 0)
624                                 );
625                         } else {
626                                 column += printf("%9"OFF_FMT"u ", dn->dn_size);
627                         }
628                 }
629         }
630 #if ENABLE_FEATURE_LS_TIMESTAMPS
631         if (G.all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
632                 char *filetime;
633                 const time_t *ttime = &dn->dn_mtime;
634                 if (G.all_fmt & TIME_ACCESS)
635                         ttime = &dn->dn_atime;
636                 if (G.all_fmt & TIME_CHANGE)
637                         ttime = &dn->dn_ctime;
638                 filetime = ctime(ttime);
639                 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
640                 if (G.all_fmt & LIST_FULLTIME) { /* -e */
641                         /* Note: coreutils 8.4 ls --full-time prints:
642                          * 2009-07-13 17:49:27.000000000 +0200
643                          */
644                         column += printf("%.24s ", filetime);
645                 } else { /* LIST_DATE_TIME */
646                         /* G.current_time_t ~== time(NULL) */
647                         time_t age = G.current_time_t - *ttime;
648                         if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
649                                 /* less than 6 months old */
650                                 /* "mmm dd hh:mm " */
651                                 printf("%.12s ", filetime + 4);
652                         } else {
653                                 /* "mmm dd  yyyy " */
654                                 /* "mmm dd yyyyy " after year 9999 :) */
655                                 strchr(filetime + 20, '\n')[0] = ' ';
656                                 printf("%.7s%6s", filetime + 4, filetime + 20);
657                         }
658                         column += 13;
659                 }
660         }
661 #endif
662 #if ENABLE_SELINUX
663         if (G.all_fmt & LIST_CONTEXT) {
664                 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
665                 freecon(dn->sid);
666         }
667 #endif
668
669 #if ENABLE_FEATURE_LS_COLOR
670         if (G_show_color) {
671                 mode_t mode = dn->dn_mode_lstat;
672                 if (!mode)
673                         if (lstat(dn->fullname, &statbuf) == 0)
674                                 mode = statbuf.st_mode;
675                 printf("\033[%u;%um", bold(mode), fgcolor(mode));
676         }
677 #endif
678         column += print_name(dn->name);
679         if (G_show_color) {
680                 printf("\033[0m");
681         }
682
683         if (lpath) {
684                 printf(" -> ");
685 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
686                 if ((G.all_fmt & LIST_FILETYPE) || G_show_color) {
687                         mode_t mode = dn->dn_mode_stat;
688                         if (!mode)
689                                 if (stat(dn->fullname, &statbuf) == 0)
690                                         mode = statbuf.st_mode;
691 # if ENABLE_FEATURE_LS_FILETYPES
692                         append = append_char(mode);
693 # endif
694 # if ENABLE_FEATURE_LS_COLOR
695                         if (G_show_color) {
696                                 printf("\033[%u;%um", bold(mode), fgcolor(mode));
697                         }
698 # endif
699                 }
700 #endif
701                 column += print_name(lpath) + 4;
702                 free(lpath);
703                 if (G_show_color) {
704                         printf("\033[0m");
705                 }
706         }
707 #if ENABLE_FEATURE_LS_FILETYPES
708         if (G.all_fmt & LIST_FILETYPE) {
709                 if (append) {
710                         putchar(append);
711                         column++;
712                 }
713         }
714 #endif
715
716         return column;
717 }
718
719 static void display_files(struct dnode **dn, unsigned nfiles)
720 {
721         unsigned i, ncols, nrows, row, nc;
722         unsigned column;
723         unsigned nexttab;
724         unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
725
726         if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
727                 ncols = 1;
728         } else {
729                 /* find the longest file name, use that as the column width */
730                 for (i = 0; dn[i]; i++) {
731                         int len = calc_name_len(dn[i]->name);
732                         if (column_width < len)
733                                 column_width = len;
734                 }
735                 column_width += 2 +
736                         IF_SELINUX( ((G.all_fmt & LIST_CONTEXT) ? 33 : 0) + )
737                                 ((G.all_fmt & LIST_INO) ? 8 : 0) +
738                                 ((G.all_fmt & LIST_BLOCKS) ? 5 : 0);
739                 ncols = (unsigned)G_terminal_width / column_width;
740         }
741
742         if (ncols > 1) {
743                 nrows = nfiles / ncols;
744                 if (nrows * ncols < nfiles)
745                         nrows++;                /* round up fractionals */
746         } else {
747                 nrows = nfiles;
748                 ncols = 1;
749         }
750
751         column = 0;
752         nexttab = 0;
753         for (row = 0; row < nrows; row++) {
754                 for (nc = 0; nc < ncols; nc++) {
755                         /* reach into the array based on the column and row */
756                         if (G.all_fmt & DISP_ROWS)
757                                 i = (row * ncols) + nc; /* display across row */
758                         else
759                                 i = (nc * nrows) + row; /* display by column */
760                         if (i < nfiles) {
761                                 if (column > 0) {
762                                         nexttab -= column;
763                                         printf("%*s", nexttab, "");
764                                         column += nexttab;
765                                 }
766                                 nexttab = column + column_width;
767                                 column += display_single(dn[i]);
768                         }
769                 }
770                 putchar('\n');
771                 column = 0;
772         }
773 }
774
775
776 /*** Dir scanning code ***/
777
778 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
779 {
780         struct stat statbuf;
781         struct dnode *cur;
782
783         cur = xzalloc(sizeof(*cur));
784         cur->fullname = fullname;
785         cur->name = name;
786
787         if ((option_mask32 & OPT_L) || force_follow) {
788 #if ENABLE_SELINUX
789                 if (is_selinux_enabled())  {
790                         getfilecon(fullname, &cur->sid);
791                 }
792 #endif
793                 if (stat(fullname, &statbuf)) {
794                         bb_simple_perror_msg(fullname);
795                         G.exit_code = EXIT_FAILURE;
796                         free(cur);
797                         return NULL;
798                 }
799                 cur->dn_mode_stat = statbuf.st_mode;
800         } else {
801 #if ENABLE_SELINUX
802                 if (is_selinux_enabled()) {
803                         lgetfilecon(fullname, &cur->sid);
804                 }
805 #endif
806                 if (lstat(fullname, &statbuf)) {
807                         bb_simple_perror_msg(fullname);
808                         G.exit_code = EXIT_FAILURE;
809                         free(cur);
810                         return NULL;
811                 }
812                 cur->dn_mode_lstat = statbuf.st_mode;
813         }
814
815         /* cur->dstat = statbuf: */
816         cur->dn_mode   = statbuf.st_mode  ;
817         cur->dn_size   = statbuf.st_size  ;
818 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
819         cur->dn_atime  = statbuf.st_atime ;
820         cur->dn_mtime  = statbuf.st_mtime ;
821         cur->dn_ctime  = statbuf.st_ctime ;
822 #endif
823         cur->dn_ino    = statbuf.st_ino   ;
824         cur->dn_blocks = statbuf.st_blocks;
825         cur->dn_nlink  = statbuf.st_nlink ;
826         cur->dn_uid    = statbuf.st_uid   ;
827         cur->dn_gid    = statbuf.st_gid   ;
828         cur->dn_rdev_maj = major(statbuf.st_rdev);
829         cur->dn_rdev_min = minor(statbuf.st_rdev);
830
831         return cur;
832 }
833
834 static unsigned count_dirs(struct dnode **dn, int which)
835 {
836         unsigned dirs, all;
837
838         if (!dn)
839                 return 0;
840
841         dirs = all = 0;
842         for (; *dn; dn++) {
843                 const char *name;
844
845                 all++;
846                 if (!S_ISDIR((*dn)->dn_mode))
847                         continue;
848
849                 name = (*dn)->name;
850                 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
851                  /* or if it's not . or .. */
852                  || name[0] != '.'
853                  || (name[1] && (name[1] != '.' || name[2]))
854                 ) {
855                         dirs++;
856                 }
857         }
858         return which != SPLIT_FILE ? dirs : all - dirs;
859 }
860
861 /* get memory to hold an array of pointers */
862 static struct dnode **dnalloc(unsigned num)
863 {
864         if (num < 1)
865                 return NULL;
866
867         num++; /* so that we have terminating NULL */
868         return xzalloc(num * sizeof(struct dnode *));
869 }
870
871 #if ENABLE_FEATURE_LS_RECURSIVE
872 static void dfree(struct dnode **dnp)
873 {
874         unsigned i;
875
876         if (dnp == NULL)
877                 return;
878
879         for (i = 0; dnp[i]; i++) {
880                 struct dnode *cur = dnp[i];
881                 if (cur->fname_allocated)
882                         free((char*)cur->fullname);
883                 free(cur);
884         }
885         free(dnp);
886 }
887 #else
888 #define dfree(...) ((void)0)
889 #endif
890
891 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
892 static struct dnode **splitdnarray(struct dnode **dn, int which)
893 {
894         unsigned dncnt, d;
895         struct dnode **dnp;
896
897         if (dn == NULL)
898                 return NULL;
899
900         /* count how many dirs or files there are */
901         dncnt = count_dirs(dn, which);
902
903         /* allocate a file array and a dir array */
904         dnp = dnalloc(dncnt);
905
906         /* copy the entrys into the file or dir array */
907         for (d = 0; *dn; dn++) {
908                 if (S_ISDIR((*dn)->dn_mode)) {
909                         const char *name;
910
911                         if (which == SPLIT_FILE)
912                                 continue;
913
914                         name = (*dn)->name;
915                         if ((which & SPLIT_DIR) /* any dir... */
916                         /* ... or not . or .. */
917                          || name[0] != '.'
918                          || (name[1] && (name[1] != '.' || name[2]))
919                         ) {
920                                 dnp[d++] = *dn;
921                         }
922                 } else
923                 if (which == SPLIT_FILE) {
924                         dnp[d++] = *dn;
925                 }
926         }
927         return dnp;
928 }
929
930 #if ENABLE_FEATURE_LS_SORTFILES
931 static int sortcmp(const void *a, const void *b)
932 {
933         struct dnode *d1 = *(struct dnode **)a;
934         struct dnode *d2 = *(struct dnode **)b;
935         unsigned sort_opts = G.all_fmt & SORT_MASK;
936         off_t dif;
937
938         dif = 0; /* assume SORT_NAME */
939         // TODO: use pre-initialized function pointer
940         // instead of branch forest
941         if (sort_opts == SORT_SIZE) {
942                 dif = (d2->dn_size - d1->dn_size);
943         } else
944         if (sort_opts == SORT_ATIME) {
945                 dif = (d2->dn_atime - d1->dn_atime);
946         } else
947         if (sort_opts == SORT_CTIME) {
948                 dif = (d2->dn_ctime - d1->dn_ctime);
949         } else
950         if (sort_opts == SORT_MTIME) {
951                 dif = (d2->dn_mtime - d1->dn_mtime);
952         } else
953         if (sort_opts == SORT_DIR) {
954                 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
955         } else
956 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
957         if (sort_opts == SORT_VERSION) {
958                 dif = strverscmp(d1->name, d2->name);
959         } else
960 #endif
961         if (sort_opts == SORT_EXT) {
962                 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
963         }
964         if (dif == 0) {
965                 /* sort by name, use as tie breaker for other sorts */
966                 if (ENABLE_LOCALE_SUPPORT)
967                         dif = strcoll(d1->name, d2->name);
968                 else
969                         dif = strcmp(d1->name, d2->name);
970         }
971
972         /* Make dif fit into an int */
973         if (sizeof(dif) > sizeof(int)) {
974                 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
975                 /* shift leaving only "int" worth of bits */
976                 if (dif != 0) {
977                         dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
978                 }
979         }
980
981         return (G.all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
982 }
983
984 static void dnsort(struct dnode **dn, int size)
985 {
986         qsort(dn, size, sizeof(*dn), sortcmp);
987 }
988
989 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
990 {
991         dnsort(dn, nfiles);
992         display_files(dn, nfiles);
993 }
994 #else
995 # define dnsort(dn, size) ((void)0)
996 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
997 #endif
998
999 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
1000 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
1001 {
1002         struct dnode *dn, *cur, **dnp;
1003         struct dirent *entry;
1004         DIR *dir;
1005         unsigned i, nfiles;
1006
1007         *nfiles_p = 0;
1008         dir = warn_opendir(path);
1009         if (dir == NULL) {
1010                 G.exit_code = EXIT_FAILURE;
1011                 return NULL;    /* could not open the dir */
1012         }
1013         dn = NULL;
1014         nfiles = 0;
1015         while ((entry = readdir(dir)) != NULL) {
1016                 char *fullname;
1017
1018                 /* are we going to list the file- it may be . or .. or a hidden file */
1019                 if (entry->d_name[0] == '.') {
1020                         if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
1021                          && !(G.all_fmt & DISP_DOT)
1022                         ) {
1023                                 continue;
1024                         }
1025                         if (!(G.all_fmt & DISP_HIDDEN))
1026                                 continue;
1027                 }
1028                 fullname = concat_path_file(path, entry->d_name);
1029                 cur = my_stat(fullname, bb_basename(fullname), 0);
1030                 if (!cur) {
1031                         free(fullname);
1032                         continue;
1033                 }
1034                 cur->fname_allocated = 1;
1035                 cur->dn_next = dn;
1036                 dn = cur;
1037                 nfiles++;
1038         }
1039         closedir(dir);
1040
1041         if (dn == NULL)
1042                 return NULL;
1043
1044         /* now that we know how many files there are
1045          * allocate memory for an array to hold dnode pointers
1046          */
1047         *nfiles_p = nfiles;
1048         dnp = dnalloc(nfiles);
1049         for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1050                 dnp[i] = dn;    /* save pointer to node in array */
1051                 dn = dn->dn_next;
1052                 if (!dn)
1053                         break;
1054         }
1055
1056         return dnp;
1057 }
1058
1059 #if ENABLE_DESKTOP
1060 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
1061  * If any of the -l, -n, -s options is specified, each list
1062  * of files within the directory shall be preceded by a
1063  * status line indicating the number of file system blocks
1064  * occupied by files in the directory in 512-byte units if
1065  * the -k option is not specified, or 1024-byte units if the
1066  * -k option is specified, rounded up to the next integral
1067  * number of units.
1068  */
1069 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
1070 static off_t calculate_blocks(struct dnode **dn)
1071 {
1072         uoff_t blocks = 1;
1073         if (dn) {
1074                 while (*dn) {
1075                         /* st_blocks is in 512 byte blocks */
1076                         blocks += (*dn)->dn_blocks;
1077                         dn++;
1078                 }
1079         }
1080
1081         /* Even though standard says use 512 byte blocks, coreutils use 1k */
1082         /* Actually, we round up by calculating (blocks + 1) / 2,
1083          * "+ 1" was done when we initialized blocks to 1 */
1084         return blocks >> 1;
1085 }
1086 #endif
1087
1088 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1089 {
1090         unsigned nfiles;
1091         struct dnode **subdnp;
1092
1093         for (; *dn; dn++) {
1094                 if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
1095                         if (!first)
1096                                 bb_putchar('\n');
1097                         first = 0;
1098                         printf("%s:\n", (*dn)->fullname);
1099                 }
1100                 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1101 #if ENABLE_DESKTOP
1102                 if ((G.all_fmt & STYLE_MASK) == STYLE_LONG || (G.all_fmt & LIST_BLOCKS))
1103                         printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1104 #endif
1105                 if (nfiles > 0) {
1106                         /* list all files at this level */
1107                         sort_and_display_files(subdnp, nfiles);
1108
1109                         if (ENABLE_FEATURE_LS_RECURSIVE
1110                          && (G.all_fmt & DISP_RECURSIVE)
1111                         ) {
1112                                 struct dnode **dnd;
1113                                 unsigned dndirs;
1114                                 /* recursive - list the sub-dirs */
1115                                 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1116                                 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1117                                 if (dndirs > 0) {
1118                                         dnsort(dnd, dndirs);
1119                                         scan_and_display_dirs_recur(dnd, 0);
1120                                         /* free the array of dnode pointers to the dirs */
1121                                         free(dnd);
1122                                 }
1123                         }
1124                         /* free the dnodes and the fullname mem */
1125                         dfree(subdnp);
1126                 }
1127         }
1128 }
1129
1130
1131 int ls_main(int argc UNUSED_PARAM, char **argv)
1132 {
1133         struct dnode **dnd;
1134         struct dnode **dnf;
1135         struct dnode **dnp;
1136         struct dnode *dn;
1137         struct dnode *cur;
1138         unsigned opt;
1139         unsigned nfiles;
1140         unsigned dnfiles;
1141         unsigned dndirs;
1142         unsigned i;
1143 #if ENABLE_FEATURE_LS_COLOR
1144         /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1145         /* coreutils 6.10:
1146          * # ls --color=BOGUS
1147          * ls: invalid argument 'BOGUS' for '--color'
1148          * Valid arguments are:
1149          * 'always', 'yes', 'force'
1150          * 'never', 'no', 'none'
1151          * 'auto', 'tty', 'if-tty'
1152          * (and substrings: "--color=alwa" work too)
1153          */
1154         static const char ls_longopts[] ALIGN1 =
1155                 "color\0" Optional_argument "\xff"; /* no short equivalent */
1156         static const char color_str[] ALIGN1 =
1157                 "always\0""yes\0""force\0"
1158                 "auto\0""tty\0""if-tty\0";
1159         /* need to initialize since --color has _an optional_ argument */
1160         const char *color_opt = color_str; /* "always" */
1161 #endif
1162
1163         INIT_G();
1164
1165         init_unicode();
1166
1167         if (ENABLE_FEATURE_LS_SORTFILES)
1168                 G.all_fmt = SORT_NAME;
1169
1170 #if ENABLE_FEATURE_AUTOWIDTH
1171         /* obtain the terminal width */
1172         G_terminal_width = get_terminal_width(STDIN_FILENO);
1173         /* go one less... */
1174         G_terminal_width--;
1175 #endif
1176
1177         /* process options */
1178         IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1179         opt_complementary =
1180                 /* -e implies -l */
1181                 IF_FEATURE_LS_TIMESTAMPS("el")
1182                 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1183                  * in some pairs of opts, only last one takes effect:
1184                  */
1185                 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1186                 // ":m-l:l-m" - we don't have -m
1187                 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1188                 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1189                 ":C-1:1-C" /* bycols/oneline */
1190                 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1191                 IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */
1192                 /* -w NUM: */
1193                 IF_FEATURE_AUTOWIDTH(":w+");
1194         opt = getopt32(argv, ls_options
1195                 IF_FEATURE_AUTOWIDTH(, NULL, &G_terminal_width)
1196                 IF_FEATURE_LS_COLOR(, &color_opt)
1197         );
1198         for (i = 0; opt_flags[i] != (1U << 31); i++) {
1199                 if (opt & (1 << i)) {
1200                         uint32_t flags = opt_flags[i];
1201
1202                         if (flags & STYLE_MASK)
1203                                 G.all_fmt &= ~STYLE_MASK;
1204                         if (flags & SORT_MASK)
1205                                 G.all_fmt &= ~SORT_MASK;
1206                         if (flags & TIME_MASK)
1207                                 G.all_fmt &= ~TIME_MASK;
1208
1209                         G.all_fmt |= flags;
1210                 }
1211         }
1212
1213 #if ENABLE_FEATURE_LS_COLOR
1214         /* set G_show_color = 1/0 */
1215         if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1216                 char *p = getenv("LS_COLORS");
1217                 /* LS_COLORS is unset, or (not empty && not "none") ? */
1218                 if (!p || (p[0] && strcmp(p, "none") != 0))
1219                         G_show_color = 1;
1220         }
1221         if (opt & OPT_color) {
1222                 if (color_opt[0] == 'n')
1223                         G_show_color = 0;
1224                 else switch (index_in_substrings(color_str, color_opt)) {
1225                 case 3:
1226                 case 4:
1227                 case 5:
1228                         if (isatty(STDOUT_FILENO)) {
1229                 case 0:
1230                 case 1:
1231                 case 2:
1232                                 G_show_color = 1;
1233                         }
1234                 }
1235         }
1236 #endif
1237
1238         /* sort out which command line options take precedence */
1239         if (ENABLE_FEATURE_LS_RECURSIVE && (G.all_fmt & DISP_NOLIST))
1240                 G.all_fmt &= ~DISP_RECURSIVE;   /* no recurse if listing only dir */
1241         if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1242                 if (G.all_fmt & TIME_CHANGE)
1243                         G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_CTIME;
1244                 if (G.all_fmt & TIME_ACCESS)
1245                         G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_ATIME;
1246         }
1247         if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */
1248                 G.all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1249
1250         /* choose a display format if one was not already specified by an option */
1251         if (!(G.all_fmt & STYLE_MASK))
1252                 G.all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1253
1254         argv += optind;
1255         if (!argv[0])
1256                 *--argv = (char*)".";
1257
1258         if (argv[1])
1259                 G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1260
1261         /* stuff the command line file names into a dnode array */
1262         dn = NULL;
1263         nfiles = 0;
1264         do {
1265                 cur = my_stat(*argv, *argv,
1266                         /* follow links on command line unless -l, -s or -F: */
1267                         !((G.all_fmt & STYLE_MASK) == STYLE_LONG
1268                           || (G.all_fmt & LIST_BLOCKS)
1269                           || (option_mask32 & OPT_F)
1270                         )
1271                         /* ... or if -H: */
1272                         || (option_mask32 & OPT_H)
1273                         /* ... or if -L, but my_stat always follows links if -L */
1274                 );
1275                 argv++;
1276                 if (!cur)
1277                         continue;
1278                 /*cur->fname_allocated = 0; - already is */
1279                 cur->dn_next = dn;
1280                 dn = cur;
1281                 nfiles++;
1282         } while (*argv);
1283
1284         /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1285         if (nfiles == 0)
1286                 return G.exit_code;
1287
1288         /* now that we know how many files there are
1289          * allocate memory for an array to hold dnode pointers
1290          */
1291         dnp = dnalloc(nfiles);
1292         for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1293                 dnp[i] = dn;    /* save pointer to node in array */
1294                 dn = dn->dn_next;
1295                 if (!dn)
1296                         break;
1297         }
1298
1299         if (G.all_fmt & DISP_NOLIST) {
1300                 sort_and_display_files(dnp, nfiles);
1301         } else {
1302                 dnd = splitdnarray(dnp, SPLIT_DIR);
1303                 dnf = splitdnarray(dnp, SPLIT_FILE);
1304                 dndirs = count_dirs(dnp, SPLIT_DIR);
1305                 dnfiles = nfiles - dndirs;
1306                 if (dnfiles > 0) {
1307                         sort_and_display_files(dnf, dnfiles);
1308                         if (ENABLE_FEATURE_CLEAN_UP)
1309                                 free(dnf);
1310                 }
1311                 if (dndirs > 0) {
1312                         dnsort(dnd, dndirs);
1313                         scan_and_display_dirs_recur(dnd, dnfiles == 0);
1314                         if (ENABLE_FEATURE_CLEAN_UP)
1315                                 free(dnd);
1316                 }
1317         }
1318
1319         if (ENABLE_FEATURE_CLEAN_UP)
1320                 dfree(dnp);
1321         return G.exit_code;
1322 }