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