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