38cabcab606491fafe3ccce7443282839b89f3b7
[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 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         int 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 = (int) (d2->dstat.st_size - d1->dstat.st_size);
491         } else if (sort_opts == SORT_ATIME) {
492                 dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime);
493         } else if (sort_opts == SORT_CTIME) {
494                 dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime);
495         } else if (sort_opts == SORT_MTIME) {
496                 dif = (int) (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
503         if (dif == 0) {
504                 /* sort by name - may be a tie_breaker for time or size cmp */
505                 if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name);
506                 else dif = strcmp(d1->name, d2->name);
507         }
508
509         if (all_fmt & SORT_REVERSE) {
510                 dif = -dif;
511         }
512         return dif;
513 }
514
515 static void dnsort(struct dnode **dn, int size)
516 {
517         qsort(dn, size, sizeof(*dn), sortcmp);
518 }
519 #else
520 #define dnsort(dn, size) ((void)0)
521 #endif
522
523
524 static void showfiles(struct dnode **dn, unsigned nfiles)
525 {
526         unsigned i, ncols, nrows, row, nc;
527         unsigned column = 0;
528         unsigned nexttab = 0;
529         unsigned column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */
530
531         /* Never happens:
532         if (dn == NULL || nfiles < 1)
533                 return;
534         */
535
536         if (all_fmt & STYLE_LONG) {
537                 ncols = 1;
538         } else {
539                 /* find the longest file name, use that as the column width */
540                 for (i = 0; dn[i]; i++) {
541                         int len = bb_mbstrlen(dn[i]->name);
542                         if (column_width < len)
543                                 column_width = len;
544                 }
545                 column_width += tabstops +
546                         IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
547                                      ((all_fmt & LIST_INO) ? 8 : 0) +
548                                      ((all_fmt & LIST_BLOCKS) ? 5 : 0);
549                 ncols = (int) (terminal_width / column_width);
550         }
551
552         if (ncols > 1) {
553                 nrows = nfiles / ncols;
554                 if (nrows * ncols < nfiles)
555                         nrows++;                /* round up fractionals */
556         } else {
557                 nrows = nfiles;
558                 ncols = 1;
559         }
560
561         for (row = 0; row < nrows; row++) {
562                 for (nc = 0; nc < ncols; nc++) {
563                         /* reach into the array based on the column and row */
564                         if (all_fmt & DISP_ROWS)
565                                 i = (row * ncols) + nc; /* display across row */
566                         else
567                                 i = (nc * nrows) + row; /* display by column */
568                         if (i < nfiles) {
569                                 if (column > 0) {
570                                         nexttab -= column;
571                                         printf("%*s", nexttab, "");
572                                         column += nexttab;
573                                 }
574                                 nexttab = column + column_width;
575                                 column += list_single(dn[i]);
576                         }
577                 }
578                 putchar('\n');
579                 column = 0;
580         }
581 }
582
583
584 #if ENABLE_DESKTOP
585 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
586  * If any of the -l, -n, -s options is specified, each list
587  * of files within the directory shall be preceded by a
588  * status line indicating the number of file system blocks
589  * occupied by files in the directory in 512-byte units if
590  * the -k option is not specified, or 1024-byte units if the
591  * -k option is specified, rounded up to the next integral
592  * number of units.
593  */
594 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
595 static off_t calculate_blocks(struct dnode **dn)
596 {
597         uoff_t blocks = 1;
598         if (dn) {
599                 while (*dn) {
600                         /* st_blocks is in 512 byte blocks */
601                         blocks += (*dn)->dstat.st_blocks;
602                         dn++;
603                 }
604         }
605
606         /* Even though standard says use 512 byte blocks, coreutils use 1k */
607         /* Actually, we round up by calculating (blocks + 1) / 2,
608          * "+ 1" was done when we initialized blocks to 1 */
609         return blocks >> 1;
610 }
611 #endif
612
613
614 static void showdirs(struct dnode **dn, int first)
615 {
616         unsigned nfiles;
617         unsigned dndirs;
618         struct dnode **subdnp;
619         struct dnode **dnd;
620
621         /* Never happens:
622         if (dn == NULL || ndirs < 1) {
623                 return;
624         }
625         */
626
627         for (; *dn; dn++) {
628                 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
629                         if (!first)
630                                 bb_putchar('\n');
631                         first = 0;
632                         printf("%s:\n", (*dn)->fullname);
633                 }
634                 subdnp = list_dir((*dn)->fullname, &nfiles);
635 #if ENABLE_DESKTOP
636                 if (all_fmt & STYLE_LONG)
637                         printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
638 #endif
639                 if (nfiles > 0) {
640                         /* list all files at this level */
641                         dnsort(subdnp, nfiles);
642                         showfiles(subdnp, nfiles);
643                         if (ENABLE_FEATURE_LS_RECURSIVE
644                          && (all_fmt & DISP_RECURSIVE)
645                         ) {
646                                 /* recursive - list the sub-dirs */
647                                 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
648                                 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
649                                 if (dndirs > 0) {
650                                         dnsort(dnd, dndirs);
651                                         showdirs(dnd, 0);
652                                         /* free the array of dnode pointers to the dirs */
653                                         free(dnd);
654                                 }
655                         }
656                         /* free the dnodes and the fullname mem */
657                         dfree(subdnp);
658                 }
659         }
660 }
661
662
663 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
664 static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
665 {
666         struct dnode *dn, *cur, **dnp;
667         struct dirent *entry;
668         DIR *dir;
669         unsigned i, nfiles;
670
671         /* Never happens:
672         if (path == NULL)
673                 return NULL;
674         */
675
676         *nfiles_p = 0;
677         dir = warn_opendir(path);
678         if (dir == NULL) {
679                 exit_code = EXIT_FAILURE;
680                 return NULL;    /* could not open the dir */
681         }
682         dn = NULL;
683         nfiles = 0;
684         while ((entry = readdir(dir)) != NULL) {
685                 char *fullname;
686
687                 /* are we going to list the file- it may be . or .. or a hidden file */
688                 if (entry->d_name[0] == '.') {
689                         if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
690                          && !(all_fmt & DISP_DOT)
691                         ) {
692                                 continue;
693                         }
694                         if (!(all_fmt & DISP_HIDDEN))
695                                 continue;
696                 }
697                 fullname = concat_path_file(path, entry->d_name);
698                 cur = my_stat(fullname, bb_basename(fullname), 0);
699                 if (!cur) {
700                         free(fullname);
701                         continue;
702                 }
703                 cur->fname_allocated = 1;
704                 cur->next = dn;
705                 dn = cur;
706                 nfiles++;
707         }
708         closedir(dir);
709
710         if (dn == NULL)
711                 return NULL;
712
713         /* now that we know how many files there are
714          * allocate memory for an array to hold dnode pointers
715          */
716         *nfiles_p = nfiles;
717         dnp = dnalloc(nfiles);
718         for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
719                 dnp[i] = dn;    /* save pointer to node in array */
720                 dn = dn->next;
721                 if (!dn)
722                         break;
723         }
724
725         return dnp;
726 }
727
728
729 static int print_name(const char *name)
730 {
731         if (option_mask32 & OPT_Q) {
732 #if ENABLE_FEATURE_ASSUME_UNICODE
733                 unsigned len = 2 + bb_mbstrlen(name);
734 #else
735                 unsigned len = 2;
736 #endif
737                 putchar('"');
738                 while (*name) {
739                         if (*name == '"') {
740                                 putchar('\\');
741                                 len++;
742                         }
743                         putchar(*name++);
744                         if (!ENABLE_FEATURE_ASSUME_UNICODE)
745                                 len++;
746                 }
747                 putchar('"');
748                 return len;
749         }
750         /* No -Q: */
751 #if ENABLE_FEATURE_ASSUME_UNICODE
752         fputs(name, stdout);
753         return bb_mbstrlen(name);
754 #else
755         return printf("%s", name);
756 #endif
757 }
758
759
760 static NOINLINE unsigned list_single(const struct dnode *dn)
761 {
762         unsigned column = 0;
763         char *lpath = lpath; /* for compiler */
764 #if ENABLE_FEATURE_LS_TIMESTAMPS
765         char *filetime;
766         time_t ttime, age;
767 #endif
768 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
769         struct stat info;
770         char append;
771 #endif
772
773         /* Never happens:
774         if (dn->fullname == NULL)
775                 return 0;
776         */
777
778 #if ENABLE_FEATURE_LS_TIMESTAMPS
779         ttime = dn->dstat.st_mtime;     /* the default time */
780         if (all_fmt & TIME_ACCESS)
781                 ttime = dn->dstat.st_atime;
782         if (all_fmt & TIME_CHANGE)
783                 ttime = dn->dstat.st_ctime;
784         filetime = ctime(&ttime);
785 #endif
786 #if ENABLE_FEATURE_LS_FILETYPES
787         append = append_char(dn->dstat.st_mode);
788 #endif
789
790         /* Do readlink early, so that if it fails, error message
791          * does not appear *inside* of the "ls -l" line */
792         if (all_fmt & LIST_SYMLINK)
793                 if (S_ISLNK(dn->dstat.st_mode))
794                         lpath = xmalloc_readlink_or_warn(dn->fullname);
795
796         if (all_fmt & LIST_INO)
797                 column += printf("%7lu ", (long) dn->dstat.st_ino);
798         if (all_fmt & LIST_BLOCKS)
799                 column += printf("%4"OFF_FMT"u ", (off_t) dn->dstat.st_blocks >> 1);
800         if (all_fmt & LIST_MODEBITS)
801                 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
802         if (all_fmt & LIST_NLINKS)
803                 column += printf("%4lu ", (long) dn->dstat.st_nlink);
804 #if ENABLE_FEATURE_LS_USERNAME
805         if (all_fmt & LIST_ID_NAME) {
806                 if (option_mask32 & OPT_g) {
807                         column += printf("%-8.8s",
808                                 get_cached_username(dn->dstat.st_uid));
809                 } else {
810                         column += printf("%-8.8s %-8.8s",
811                                 get_cached_username(dn->dstat.st_uid),
812                                 get_cached_groupname(dn->dstat.st_gid));
813                 }
814         }
815 #endif
816         if (all_fmt & LIST_ID_NUMERIC) {
817                 if (option_mask32 & OPT_g)
818                         column += printf("%-8u", (int) dn->dstat.st_uid);
819                 else
820                         column += printf("%-8u %-8u",
821                                         (int) dn->dstat.st_uid,
822                                         (int) dn->dstat.st_gid);
823         }
824         if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
825                 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
826                         column += printf("%4u, %3u ",
827                                         (int) major(dn->dstat.st_rdev),
828                                         (int) minor(dn->dstat.st_rdev));
829                 } else {
830                         if (all_fmt & LS_DISP_HR) {
831                                 column += printf("%9s ",
832                                         /* print st_size, show one fractional, use suffixes */
833                                         make_human_readable_str(dn->dstat.st_size, 1, 0)
834                                 );
835                         } else {
836                                 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
837                         }
838                 }
839         }
840 #if ENABLE_FEATURE_LS_TIMESTAMPS
841         if (all_fmt & LIST_FULLTIME)
842                 column += printf("%24.24s ", filetime);
843         if (all_fmt & LIST_DATE_TIME)
844                 if ((all_fmt & LIST_FULLTIME) == 0) {
845                         /* current_time_t ~== time(NULL) */
846                         age = current_time_t - ttime;
847                         printf("%6.6s ", filetime + 4);
848                         if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
849                                 /* hh:mm if less than 6 months old */
850                                 printf("%5.5s ", filetime + 11);
851                         } else {
852                                 printf(" %4.4s ", filetime + 20);
853                         }
854                         column += 13;
855                 }
856 #endif
857 #if ENABLE_SELINUX
858         if (all_fmt & LIST_CONTEXT) {
859                 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
860                 freecon(dn->sid);
861         }
862 #endif
863         if (all_fmt & LIST_FILENAME) {
864 #if ENABLE_FEATURE_LS_COLOR
865                 if (show_color) {
866                         info.st_mode = 0; /* for fgcolor() */
867                         lstat(dn->fullname, &info);
868                         printf("\033[%u;%um", bold(info.st_mode),
869                                         fgcolor(info.st_mode));
870                 }
871 #endif
872                 column += print_name(dn->name);
873                 if (show_color) {
874                         printf("\033[0m");
875                 }
876         }
877         if (all_fmt & LIST_SYMLINK) {
878                 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
879                         printf(" -> ");
880 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
881 #if ENABLE_FEATURE_LS_COLOR
882                         info.st_mode = 0; /* for fgcolor() */
883 #endif
884                         if (stat(dn->fullname, &info) == 0) {
885                                 append = append_char(info.st_mode);
886                         }
887 #endif
888 #if ENABLE_FEATURE_LS_COLOR
889                         if (show_color) {
890                                 printf("\033[%u;%um", bold(info.st_mode),
891                                            fgcolor(info.st_mode));
892                         }
893 #endif
894                         column += print_name(lpath) + 4;
895                         if (show_color) {
896                                 printf("\033[0m");
897                         }
898                         free(lpath);
899                 }
900         }
901 #if ENABLE_FEATURE_LS_FILETYPES
902         if (all_fmt & LIST_FILETYPE) {
903                 if (append) {
904                         putchar(append);
905                         column++;
906                 }
907         }
908 #endif
909
910         return column;
911 }
912
913
914 int ls_main(int argc UNUSED_PARAM, char **argv)
915 {
916         struct dnode **dnd;
917         struct dnode **dnf;
918         struct dnode **dnp;
919         struct dnode *dn;
920         struct dnode *cur;
921         unsigned opt;
922         unsigned nfiles;
923         unsigned dnfiles;
924         unsigned dndirs;
925         unsigned i;
926 #if ENABLE_FEATURE_LS_COLOR
927         /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
928         /* coreutils 6.10:
929          * # ls --color=BOGUS
930          * ls: invalid argument 'BOGUS' for '--color'
931          * Valid arguments are:
932          * 'always', 'yes', 'force'
933          * 'never', 'no', 'none'
934          * 'auto', 'tty', 'if-tty'
935          * (and substrings: "--color=alwa" work too)
936          */
937         static const char ls_longopts[] ALIGN1 =
938                 "color\0" Optional_argument "\xff"; /* no short equivalent */
939         static const char color_str[] ALIGN1 =
940                 "always\0""yes\0""force\0"
941                 "auto\0""tty\0""if-tty\0";
942         /* need to initialize since --color has _an optional_ argument */
943         const char *color_opt = color_str; /* "always" */
944 #endif
945
946         INIT_G();
947
948         check_unicode_in_env();
949
950         all_fmt = LIST_SHORT |
951                 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
952
953 #if ENABLE_FEATURE_AUTOWIDTH
954         /* obtain the terminal width */
955         get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
956         /* go one less... */
957         terminal_width--;
958 #endif
959
960         /* process options */
961         IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
962 #if ENABLE_FEATURE_AUTOWIDTH
963         opt_complementary = "T+:w+"; /* -T N, -w N */
964         opt = getopt32(argv, ls_options, &tabstops, &terminal_width
965                                 IF_FEATURE_LS_COLOR(, &color_opt));
966 #else
967         opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
968 #endif
969         for (i = 0; opt_flags[i] != (1U<<31); i++) {
970                 if (opt & (1 << i)) {
971                         unsigned flags = opt_flags[i];
972
973                         if (flags & LIST_MASK_TRIGGER)
974                                 all_fmt &= ~LIST_MASK;
975                         if (flags & STYLE_MASK_TRIGGER)
976                                 all_fmt &= ~STYLE_MASK;
977                         if (flags & SORT_MASK_TRIGGER)
978                                 all_fmt &= ~SORT_MASK;
979                         if (flags & DISP_MASK_TRIGGER)
980                                 all_fmt &= ~DISP_MASK;
981                         if (flags & TIME_MASK)
982                                 all_fmt &= ~TIME_MASK;
983                         if (flags & LIST_CONTEXT)
984                                 all_fmt |= STYLE_SINGLE;
985                         /* huh?? opt cannot be 'l' */
986                         //if (LS_DISP_HR && opt == 'l')
987                         //      all_fmt &= ~LS_DISP_HR;
988                         all_fmt |= flags;
989                 }
990         }
991
992 #if ENABLE_FEATURE_LS_COLOR
993         /* find color bit value - last position for short getopt */
994         if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
995                 char *p = getenv("LS_COLORS");
996                 /* LS_COLORS is unset, or (not empty && not "none") ? */
997                 if (!p || (p[0] && strcmp(p, "none") != 0))
998                         show_color = 1;
999         }
1000         if (opt & OPT_color) {
1001                 if (color_opt[0] == 'n')
1002                         show_color = 0;
1003                 else switch (index_in_substrings(color_str, color_opt)) {
1004                 case 3:
1005                 case 4:
1006                 case 5:
1007                         if (isatty(STDOUT_FILENO)) {
1008                 case 0:
1009                 case 1:
1010                 case 2:
1011                                 show_color = 1;
1012                         }
1013                 }
1014         }
1015 #endif
1016
1017         /* sort out which command line options take precedence */
1018         if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1019                 all_fmt &= ~DISP_RECURSIVE;     /* no recurse if listing only dir */
1020         if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1021                 if (all_fmt & TIME_CHANGE)
1022                         all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1023                 if (all_fmt & TIME_ACCESS)
1024                         all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1025         }
1026         if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1027                 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
1028         if (ENABLE_FEATURE_LS_USERNAME)
1029                 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1030                         all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
1031
1032         /* choose a display format */
1033         if (!(all_fmt & STYLE_MASK))
1034                 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1035
1036         argv += optind;
1037         if (!argv[0])
1038                 *--argv = (char*)".";
1039
1040         if (argv[1])
1041                 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1042
1043         /* stuff the command line file names into a dnode array */
1044         dn = NULL;
1045         nfiles = 0;
1046         do {
1047                 /* NB: follow links on command line unless -l or -s */
1048                 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1049                 argv++;
1050                 if (!cur)
1051                         continue;
1052                 cur->fname_allocated = 0;
1053                 cur->next = dn;
1054                 dn = cur;
1055                 nfiles++;
1056         } while (*argv);
1057
1058         /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1059         if (nfiles == 0)
1060                 return exit_code;
1061
1062         /* now that we know how many files there are
1063          * allocate memory for an array to hold dnode pointers
1064          */
1065         dnp = dnalloc(nfiles);
1066         for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1067                 dnp[i] = dn;    /* save pointer to node in array */
1068                 dn = dn->next;
1069                 if (!dn)
1070                         break;
1071         }
1072
1073         if (all_fmt & DISP_NOLIST) {
1074                 dnsort(dnp, nfiles);
1075                 showfiles(dnp, nfiles);
1076         } else {
1077                 dnd = splitdnarray(dnp, SPLIT_DIR);
1078                 dnf = splitdnarray(dnp, SPLIT_FILE);
1079                 dndirs = count_dirs(dnp, SPLIT_DIR);
1080                 dnfiles = nfiles - dndirs;
1081                 if (dnfiles > 0) {
1082                         dnsort(dnf, dnfiles);
1083                         showfiles(dnf, dnfiles);
1084                         if (ENABLE_FEATURE_CLEAN_UP)
1085                                 free(dnf);
1086                 }
1087                 if (dndirs > 0) {
1088                         dnsort(dnd, dndirs);
1089                         showdirs(dnd, dnfiles == 0);
1090                         if (ENABLE_FEATURE_CLEAN_UP)
1091                                 free(dnd);
1092                 }
1093         }
1094         if (ENABLE_FEATURE_CLEAN_UP)
1095                 dfree(dnp);
1096         return exit_code;
1097 }