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