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