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