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