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