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