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