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