mkfs_ext2: fix bad comment. no code changes
[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 struct globals {
245 #if ENABLE_FEATURE_LS_COLOR
246         smallint show_color;
247 #endif
248         smallint exit_code;
249         unsigned all_fmt;
250 #if ENABLE_FEATURE_AUTOWIDTH
251         unsigned tabstops; // = COLUMN_GAP;
252         unsigned terminal_width; // = TERMINAL_WIDTH;
253 #endif
254 #if ENABLE_FEATURE_LS_TIMESTAMPS
255         /* Do time() just once. Saves one syscall per file for "ls -l" */
256         time_t current_time_t;
257 #endif
258 };
259 #define G (*(struct globals*)&bb_common_bufsiz1)
260 #if ENABLE_FEATURE_LS_COLOR
261 # define show_color     (G.show_color    )
262 #else
263 enum { show_color = 0 };
264 #endif
265 #define exit_code       (G.exit_code     )
266 #define all_fmt         (G.all_fmt       )
267 #if ENABLE_FEATURE_AUTOWIDTH
268 # define tabstops       (G.tabstops      )
269 # define terminal_width (G.terminal_width)
270 #else
271 enum {
272         tabstops = COLUMN_GAP,
273         terminal_width = TERMINAL_WIDTH,
274 };
275 #endif
276 #define current_time_t (G.current_time_t)
277 #define INIT_G() do { \
278         /* we have to zero it out because of NOEXEC */ \
279         memset(&G, 0, sizeof(G)); \
280         IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
281         IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
282         IF_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
283 } while (0)
284
285
286 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
287 {
288         struct stat dstat;
289         struct dnode *cur;
290         IF_SELINUX(security_context_t sid = NULL;)
291
292         if ((all_fmt & FOLLOW_LINKS) || force_follow) {
293 #if ENABLE_SELINUX
294                 if (is_selinux_enabled())  {
295                          getfilecon(fullname, &sid);
296                 }
297 #endif
298                 if (stat(fullname, &dstat)) {
299                         bb_simple_perror_msg(fullname);
300                         exit_code = EXIT_FAILURE;
301                         return 0;
302                 }
303         } else {
304 #if ENABLE_SELINUX
305                 if (is_selinux_enabled()) {
306                         lgetfilecon(fullname, &sid);
307                 }
308 #endif
309                 if (lstat(fullname, &dstat)) {
310                         bb_simple_perror_msg(fullname);
311                         exit_code = EXIT_FAILURE;
312                         return 0;
313                 }
314         }
315
316         cur = xmalloc(sizeof(*cur));
317         cur->fullname = fullname;
318         cur->name = name;
319         cur->dstat = dstat;
320         IF_SELINUX(cur->sid = sid;)
321         return cur;
322 }
323
324 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
325  * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
326  *  3/7:multiplexed char/block device)
327  * and we use 0 for unknown and 15 for executables (see below) */
328 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
329 #define TYPECHAR(mode)  ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
330 #define APPCHAR(mode)   ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
331 /* 036 black foreground              050 black background
332    037 red foreground                051 red background
333    040 green foreground              052 green background
334    041 brown foreground              053 brown background
335    042 blue foreground               054 blue background
336    043 magenta (purple) foreground   055 magenta background
337    044 cyan (light blue) foreground  056 cyan background
338    045 gray foreground               057 white background
339 */
340 #define COLOR(mode) ( \
341         /*un  fi  chr     dir     blk     file    link    sock        exe */ \
342         "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
343         [TYPEINDEX(mode)])
344 /* Select normal (0) [actually "reset all"] or bold (1)
345  * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
346  *  let's use 7 for "impossible" types, just for fun)
347  * Note: coreutils 6.9 uses inverted red for setuid binaries.
348  */
349 #define ATTR(mode) ( \
350         /*un fi chr   dir   blk   file  link  sock     exe */ \
351         "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
352         [TYPEINDEX(mode)])
353
354 #if ENABLE_FEATURE_LS_COLOR
355 /* mode of zero is interpreted as "unknown" (stat failed) */
356 static char fgcolor(mode_t mode)
357 {
358         if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
359                 return COLOR(0xF000);   /* File is executable ... */
360         return COLOR(mode);
361 }
362 static char bold(mode_t mode)
363 {
364         if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
365                 return ATTR(0xF000);    /* File is executable ... */
366         return ATTR(mode);
367 }
368 #endif
369
370 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
371 static char append_char(mode_t mode)
372 {
373         if (!(all_fmt & LIST_FILETYPE))
374                 return '\0';
375         if (S_ISDIR(mode))
376                 return '/';
377         if (!(all_fmt & LIST_EXEC))
378                 return '\0';
379         if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
380                 return '*';
381         return APPCHAR(mode);
382 }
383 #endif
384
385 static unsigned count_dirs(struct dnode **dn, int which)
386 {
387         unsigned dirs, all;
388
389         if (!dn)
390                 return 0;
391
392         dirs = all = 0;
393         for (; *dn; dn++) {
394                 const char *name;
395
396                 all++;
397                 if (!S_ISDIR((*dn)->dstat.st_mode))
398                         continue;
399                 name = (*dn)->name;
400                 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
401                  /* or if it's not . or .. */
402                  || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
403                 ) {
404                         dirs++;
405                 }
406         }
407         return which != SPLIT_FILE ? dirs : all - dirs;
408 }
409
410 /* get memory to hold an array of pointers */
411 static struct dnode **dnalloc(unsigned num)
412 {
413         if (num < 1)
414                 return NULL;
415
416         num++; /* so that we have terminating NULL */
417         return xzalloc(num * sizeof(struct dnode *));
418 }
419
420 #if ENABLE_FEATURE_LS_RECURSIVE
421 static void dfree(struct dnode **dnp)
422 {
423         unsigned i;
424
425         if (dnp == NULL)
426                 return;
427
428         for (i = 0; dnp[i]; i++) {
429                 struct dnode *cur = dnp[i];
430                 if (cur->fname_allocated)
431                         free((char*)cur->fullname);
432                 free(cur);
433         }
434         free(dnp);
435 }
436 #else
437 #define dfree(...) ((void)0)
438 #endif
439
440 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
441 static struct dnode **splitdnarray(struct dnode **dn, int which)
442 {
443         unsigned dncnt, d;
444         struct dnode **dnp;
445
446         if (dn == NULL)
447                 return NULL;
448
449         /* count how many dirs or files there are */
450         dncnt = count_dirs(dn, which);
451
452         /* allocate a file array and a dir array */
453         dnp = dnalloc(dncnt);
454
455         /* copy the entrys into the file or dir array */
456         for (d = 0; *dn; dn++) {
457                 if (S_ISDIR((*dn)->dstat.st_mode)) {
458                         const char *name;
459
460                         if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
461                                 continue;
462                         name = (*dn)->name;
463                         if ((which & SPLIT_DIR)
464                          || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
465                         ) {
466                                 dnp[d++] = *dn;
467                         }
468                 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
469                         dnp[d++] = *dn;
470                 }
471         }
472         return dnp;
473 }
474
475 #if ENABLE_FEATURE_LS_SORTFILES
476 static int sortcmp(const void *a, const void *b)
477 {
478         struct dnode *d1 = *(struct dnode **)a;
479         struct dnode *d2 = *(struct dnode **)b;
480         unsigned sort_opts = all_fmt & SORT_MASK;
481         off_t dif;
482
483         dif = 0; /* assume SORT_NAME */
484         // TODO: use pre-initialized function pointer
485         // instead of branch forest
486         if (sort_opts == SORT_SIZE) {
487                 dif = (d2->dstat.st_size - d1->dstat.st_size);
488         } else if (sort_opts == SORT_ATIME) {
489                 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
490         } else if (sort_opts == SORT_CTIME) {
491                 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
492         } else if (sort_opts == SORT_MTIME) {
493                 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
494         } else if (sort_opts == SORT_DIR) {
495                 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
496                 /* } else if (sort_opts == SORT_VERSION) { */
497                 /* } else if (sort_opts == SORT_EXT) { */
498         }
499         if (dif == 0) {
500                 /* sort by name, or tie_breaker for other sorts */
501                 if (ENABLE_LOCALE_SUPPORT)
502                         dif = strcoll(d1->name, d2->name);
503                 else
504                         dif = strcmp(d1->name, d2->name);
505         }
506
507         /* Make dif fit into an int */
508         if (sizeof(dif) > sizeof(int)) {
509                 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
510                 /* shift leaving only "int" worth of bits */
511                 if (dif != 0) {
512                         dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
513                 }
514         }
515
516         return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
517 }
518
519 static void dnsort(struct dnode **dn, int size)
520 {
521         qsort(dn, size, sizeof(*dn), sortcmp);
522 }
523 #else
524 #define dnsort(dn, size) ((void)0)
525 #endif
526
527
528 static unsigned calc_name_len(const char *name)
529 {
530         unsigned len;
531         uni_stat_t uni_stat;
532
533         // TODO: quote tab as \t, etc, if -Q
534         name = printable_string(&uni_stat, name);
535
536         if (!(option_mask32 & OPT_Q)) {
537                 return uni_stat.unicode_width;
538         }
539
540         len = 2 + uni_stat.unicode_width;
541         while (*name) {
542                 if (*name == '"' || *name == '\\') {
543                         len++;
544                 }
545                 name++;
546         }
547         return len;
548 }
549
550
551 /* Return the number of used columns.
552  * Note that only STYLE_COLUMNS uses return value.
553  * STYLE_SINGLE and STYLE_LONG don't care.
554  * coreutils 7.2 also supports:
555  * ls -b (--escape) = octal escapes (although it doesn't look like working)
556  * ls -N (--literal) = not escape at all
557  */
558 static unsigned print_name(const char *name)
559 {
560         unsigned len;
561         uni_stat_t uni_stat;
562
563         // TODO: quote tab as \t, etc, if -Q
564         name = printable_string(&uni_stat, name);
565
566         if (!(option_mask32 & OPT_Q)) {
567                 fputs(name, stdout);
568                 return uni_stat.unicode_width;
569         }
570
571         len = 2 + uni_stat.unicode_width;
572         putchar('"');
573         while (*name) {
574                 if (*name == '"' || *name == '\\') {
575                         putchar('\\');
576                         len++;
577                 }
578                 putchar(*name++);
579         }
580         putchar('"');
581         return len;
582 }
583
584 /* Return the number of used columns.
585  * Note that only STYLE_COLUMNS uses return value,
586  * STYLE_SINGLE and STYLE_LONG don't care.
587  */
588 static NOINLINE unsigned list_single(const struct dnode *dn)
589 {
590         unsigned column = 0;
591         char *lpath = lpath; /* for compiler */
592 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
593         struct stat info;
594         char append;
595 #endif
596
597         /* Never happens:
598         if (dn->fullname == NULL)
599                 return 0;
600         */
601
602 #if ENABLE_FEATURE_LS_FILETYPES
603         append = append_char(dn->dstat.st_mode);
604 #endif
605
606         /* Do readlink early, so that if it fails, error message
607          * does not appear *inside* the "ls -l" line */
608         if (all_fmt & LIST_SYMLINK)
609                 if (S_ISLNK(dn->dstat.st_mode))
610                         lpath = xmalloc_readlink_or_warn(dn->fullname);
611
612         if (all_fmt & LIST_INO)
613                 column += printf("%7llu ", (long long) dn->dstat.st_ino);
614         if (all_fmt & LIST_BLOCKS)
615                 column += printf("%4"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
616         if (all_fmt & LIST_MODEBITS)
617                 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
618         if (all_fmt & LIST_NLINKS)
619                 column += printf("%4lu ", (long) dn->dstat.st_nlink);
620 #if ENABLE_FEATURE_LS_USERNAME
621         if (all_fmt & LIST_ID_NAME) {
622                 if (option_mask32 & OPT_g) {
623                         column += printf("%-8.8s ",
624                                 get_cached_username(dn->dstat.st_uid));
625                 } else {
626                         column += printf("%-8.8s %-8.8s ",
627                                 get_cached_username(dn->dstat.st_uid),
628                                 get_cached_groupname(dn->dstat.st_gid));
629                 }
630         }
631 #endif
632         if (all_fmt & LIST_ID_NUMERIC) {
633                 if (option_mask32 & OPT_g)
634                         column += printf("%-8u ", (int) dn->dstat.st_uid);
635                 else
636                         column += printf("%-8u %-8u ",
637                                         (int) dn->dstat.st_uid,
638                                         (int) dn->dstat.st_gid);
639         }
640         if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
641                 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
642                         column += printf("%4u, %3u ",
643                                         (int) major(dn->dstat.st_rdev),
644                                         (int) minor(dn->dstat.st_rdev));
645                 } else {
646                         if (all_fmt & LS_DISP_HR) {
647                                 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
648                                         /* print st_size, show one fractional, use suffixes */
649                                         make_human_readable_str(dn->dstat.st_size, 1, 0)
650                                 );
651                         } else {
652                                 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
653                         }
654                 }
655         }
656 #if ENABLE_FEATURE_LS_TIMESTAMPS
657         if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
658                 char *filetime;
659                 time_t ttime = dn->dstat.st_mtime;
660                 if (all_fmt & TIME_ACCESS)
661                         ttime = dn->dstat.st_atime;
662                 if (all_fmt & TIME_CHANGE)
663                         ttime = dn->dstat.st_ctime;
664                 filetime = ctime(&ttime);
665                 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
666                 if (all_fmt & LIST_FULLTIME)
667                         column += printf("%.24s ", filetime);
668                 else { /* LIST_DATE_TIME */
669                         /* current_time_t ~== time(NULL) */
670                         time_t age = current_time_t - ttime;
671                         printf("%.6s ", filetime + 4); /* "Jun 30" */
672                         if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
673                                 /* hh:mm if less than 6 months old */
674                                 printf("%.5s ", filetime + 11);
675                         } else { /* year. buggy if year > 9999 ;) */
676                                 printf(" %.4s ", filetime + 20);
677                         }
678                         column += 13;
679                 }
680         }
681 #endif
682 #if ENABLE_SELINUX
683         if (all_fmt & LIST_CONTEXT) {
684                 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
685                 freecon(dn->sid);
686         }
687 #endif
688         if (all_fmt & LIST_FILENAME) {
689 #if ENABLE_FEATURE_LS_COLOR
690                 if (show_color) {
691                         info.st_mode = 0; /* for fgcolor() */
692                         lstat(dn->fullname, &info);
693                         printf("\033[%u;%um", bold(info.st_mode),
694                                         fgcolor(info.st_mode));
695                 }
696 #endif
697                 column += print_name(dn->name);
698                 if (show_color) {
699                         printf("\033[0m");
700                 }
701         }
702         if (all_fmt & LIST_SYMLINK) {
703                 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
704                         printf(" -> ");
705 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
706 #if ENABLE_FEATURE_LS_COLOR
707                         info.st_mode = 0; /* for fgcolor() */
708 #endif
709                         if (stat(dn->fullname, &info) == 0) {
710                                 append = append_char(info.st_mode);
711                         }
712 #endif
713 #if ENABLE_FEATURE_LS_COLOR
714                         if (show_color) {
715                                 printf("\033[%u;%um", bold(info.st_mode),
716                                            fgcolor(info.st_mode));
717                         }
718 #endif
719                         column += print_name(lpath) + 4;
720                         if (show_color) {
721                                 printf("\033[0m");
722                         }
723                         free(lpath);
724                 }
725         }
726 #if ENABLE_FEATURE_LS_FILETYPES
727         if (all_fmt & LIST_FILETYPE) {
728                 if (append) {
729                         putchar(append);
730                         column++;
731                 }
732         }
733 #endif
734
735         return column;
736 }
737
738 static void showfiles(struct dnode **dn, unsigned nfiles)
739 {
740         unsigned i, ncols, nrows, row, nc;
741         unsigned column = 0;
742         unsigned nexttab = 0;
743         unsigned column_width = 0; /* used only by STYLE_COLUMNS */
744
745         if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
746                 ncols = 1;
747         } else {
748                 /* find the longest file name, use that as the column width */
749                 for (i = 0; dn[i]; i++) {
750                         int len = calc_name_len(dn[i]->name);
751                         if (column_width < len)
752                                 column_width = len;
753                 }
754                 column_width += tabstops +
755                         IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
756                                 ((all_fmt & LIST_INO) ? 8 : 0) +
757                                 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
758                 ncols = (int) (terminal_width / column_width);
759         }
760
761         if (ncols > 1) {
762                 nrows = nfiles / ncols;
763                 if (nrows * ncols < nfiles)
764                         nrows++;                /* round up fractionals */
765         } else {
766                 nrows = nfiles;
767                 ncols = 1;
768         }
769
770         for (row = 0; row < nrows; row++) {
771                 for (nc = 0; nc < ncols; nc++) {
772                         /* reach into the array based on the column and row */
773                         if (all_fmt & DISP_ROWS)
774                                 i = (row * ncols) + nc; /* display across row */
775                         else
776                                 i = (nc * nrows) + row; /* display by column */
777                         if (i < nfiles) {
778                                 if (column > 0) {
779                                         nexttab -= column;
780                                         printf("%*s", nexttab, "");
781                                         column += nexttab;
782                                 }
783                                 nexttab = column + column_width;
784                                 column += list_single(dn[i]);
785                         }
786                 }
787                 putchar('\n');
788                 column = 0;
789         }
790 }
791
792
793 #if ENABLE_DESKTOP
794 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
795  * If any of the -l, -n, -s options is specified, each list
796  * of files within the directory shall be preceded by a
797  * status line indicating the number of file system blocks
798  * occupied by files in the directory in 512-byte units if
799  * the -k option is not specified, or 1024-byte units if the
800  * -k option is specified, rounded up to the next integral
801  * number of units.
802  */
803 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
804 static off_t calculate_blocks(struct dnode **dn)
805 {
806         uoff_t blocks = 1;
807         if (dn) {
808                 while (*dn) {
809                         /* st_blocks is in 512 byte blocks */
810                         blocks += (*dn)->dstat.st_blocks;
811                         dn++;
812                 }
813         }
814
815         /* Even though standard says use 512 byte blocks, coreutils use 1k */
816         /* Actually, we round up by calculating (blocks + 1) / 2,
817          * "+ 1" was done when we initialized blocks to 1 */
818         return blocks >> 1;
819 }
820 #endif
821
822
823 static struct dnode **list_dir(const char *, unsigned *);
824
825 static void showdirs(struct dnode **dn, int first)
826 {
827         unsigned nfiles;
828         unsigned dndirs;
829         struct dnode **subdnp;
830         struct dnode **dnd;
831
832         /* Never happens:
833         if (dn == NULL || ndirs < 1) {
834                 return;
835         }
836         */
837
838         for (; *dn; dn++) {
839                 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
840                         if (!first)
841                                 bb_putchar('\n');
842                         first = 0;
843                         printf("%s:\n", (*dn)->fullname);
844                 }
845                 subdnp = list_dir((*dn)->fullname, &nfiles);
846 #if ENABLE_DESKTOP
847                 if ((all_fmt & STYLE_MASK) == STYLE_LONG)
848                         printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
849 #endif
850                 if (nfiles > 0) {
851                         /* list all files at this level */
852                         dnsort(subdnp, nfiles);
853                         showfiles(subdnp, nfiles);
854                         if (ENABLE_FEATURE_LS_RECURSIVE
855                          && (all_fmt & DISP_RECURSIVE)
856                         ) {
857                                 /* recursive - list the sub-dirs */
858                                 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
859                                 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
860                                 if (dndirs > 0) {
861                                         dnsort(dnd, dndirs);
862                                         showdirs(dnd, 0);
863                                         /* free the array of dnode pointers to the dirs */
864                                         free(dnd);
865                                 }
866                         }
867                         /* free the dnodes and the fullname mem */
868                         dfree(subdnp);
869                 }
870         }
871 }
872
873
874 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
875 static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
876 {
877         struct dnode *dn, *cur, **dnp;
878         struct dirent *entry;
879         DIR *dir;
880         unsigned i, nfiles;
881
882         /* Never happens:
883         if (path == NULL)
884                 return NULL;
885         */
886
887         *nfiles_p = 0;
888         dir = warn_opendir(path);
889         if (dir == NULL) {
890                 exit_code = EXIT_FAILURE;
891                 return NULL;    /* could not open the dir */
892         }
893         dn = NULL;
894         nfiles = 0;
895         while ((entry = readdir(dir)) != NULL) {
896                 char *fullname;
897
898                 /* are we going to list the file- it may be . or .. or a hidden file */
899                 if (entry->d_name[0] == '.') {
900                         if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
901                          && !(all_fmt & DISP_DOT)
902                         ) {
903                                 continue;
904                         }
905                         if (!(all_fmt & DISP_HIDDEN))
906                                 continue;
907                 }
908                 fullname = concat_path_file(path, entry->d_name);
909                 cur = my_stat(fullname, bb_basename(fullname), 0);
910                 if (!cur) {
911                         free(fullname);
912                         continue;
913                 }
914                 cur->fname_allocated = 1;
915                 cur->next = dn;
916                 dn = cur;
917                 nfiles++;
918         }
919         closedir(dir);
920
921         if (dn == NULL)
922                 return NULL;
923
924         /* now that we know how many files there are
925          * allocate memory for an array to hold dnode pointers
926          */
927         *nfiles_p = nfiles;
928         dnp = dnalloc(nfiles);
929         for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
930                 dnp[i] = dn;    /* save pointer to node in array */
931                 dn = dn->next;
932                 if (!dn)
933                         break;
934         }
935
936         return dnp;
937 }
938
939
940 int ls_main(int argc UNUSED_PARAM, char **argv)
941 {
942         struct dnode **dnd;
943         struct dnode **dnf;
944         struct dnode **dnp;
945         struct dnode *dn;
946         struct dnode *cur;
947         unsigned opt;
948         unsigned nfiles;
949         unsigned dnfiles;
950         unsigned dndirs;
951         unsigned i;
952 #if ENABLE_FEATURE_LS_COLOR
953         /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
954         /* coreutils 6.10:
955          * # ls --color=BOGUS
956          * ls: invalid argument 'BOGUS' for '--color'
957          * Valid arguments are:
958          * 'always', 'yes', 'force'
959          * 'never', 'no', 'none'
960          * 'auto', 'tty', 'if-tty'
961          * (and substrings: "--color=alwa" work too)
962          */
963         static const char ls_longopts[] ALIGN1 =
964                 "color\0" Optional_argument "\xff"; /* no short equivalent */
965         static const char color_str[] ALIGN1 =
966                 "always\0""yes\0""force\0"
967                 "auto\0""tty\0""if-tty\0";
968         /* need to initialize since --color has _an optional_ argument */
969         const char *color_opt = color_str; /* "always" */
970 #endif
971
972         INIT_G();
973
974         init_unicode();
975
976         all_fmt = LIST_SHORT |
977                 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
978
979 #if ENABLE_FEATURE_AUTOWIDTH
980         /* obtain the terminal width */
981         get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
982         /* go one less... */
983         terminal_width--;
984 #endif
985
986         /* process options */
987         IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
988 #if ENABLE_FEATURE_AUTOWIDTH
989         opt_complementary = "T+:w+"; /* -T N, -w N */
990         opt = getopt32(argv, ls_options, &tabstops, &terminal_width
991                                 IF_FEATURE_LS_COLOR(, &color_opt));
992 #else
993         opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
994 #endif
995         for (i = 0; opt_flags[i] != (1U<<31); i++) {
996                 if (opt & (1 << i)) {
997                         unsigned flags = opt_flags[i];
998
999                         if (flags & LIST_MASK_TRIGGER)
1000                                 all_fmt &= ~LIST_MASK;
1001                         if (flags & STYLE_MASK_TRIGGER)
1002                                 all_fmt &= ~STYLE_MASK;
1003                         if (flags & SORT_MASK_TRIGGER)
1004                                 all_fmt &= ~SORT_MASK;
1005                         if (flags & DISP_MASK_TRIGGER)
1006                                 all_fmt &= ~DISP_MASK;
1007                         if (flags & TIME_MASK)
1008                                 all_fmt &= ~TIME_MASK;
1009                         if (flags & LIST_CONTEXT)
1010                                 all_fmt |= STYLE_SINGLE;
1011                         /* huh?? opt cannot be 'l' */
1012                         //if (LS_DISP_HR && opt == 'l')
1013                         //      all_fmt &= ~LS_DISP_HR;
1014                         all_fmt |= flags;
1015                 }
1016         }
1017
1018 #if ENABLE_FEATURE_LS_COLOR
1019         /* find color bit value - last position for short getopt */
1020         if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1021                 char *p = getenv("LS_COLORS");
1022                 /* LS_COLORS is unset, or (not empty && not "none") ? */
1023                 if (!p || (p[0] && strcmp(p, "none") != 0))
1024                         show_color = 1;
1025         }
1026         if (opt & OPT_color) {
1027                 if (color_opt[0] == 'n')
1028                         show_color = 0;
1029                 else switch (index_in_substrings(color_str, color_opt)) {
1030                 case 3:
1031                 case 4:
1032                 case 5:
1033                         if (isatty(STDOUT_FILENO)) {
1034                 case 0:
1035                 case 1:
1036                 case 2:
1037                                 show_color = 1;
1038                         }
1039                 }
1040         }
1041 #endif
1042
1043         /* sort out which command line options take precedence */
1044         if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1045                 all_fmt &= ~DISP_RECURSIVE;     /* no recurse if listing only dir */
1046         if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1047                 if (all_fmt & TIME_CHANGE)
1048                         all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1049                 if (all_fmt & TIME_ACCESS)
1050                         all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1051         }
1052         if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1053                 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
1054         if (ENABLE_FEATURE_LS_USERNAME)
1055                 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1056                         all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
1057
1058         /* choose a display format */
1059         if (!(all_fmt & STYLE_MASK))
1060                 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1061
1062         argv += optind;
1063         if (!argv[0])
1064                 *--argv = (char*)".";
1065
1066         if (argv[1])
1067                 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1068
1069         /* stuff the command line file names into a dnode array */
1070         dn = NULL;
1071         nfiles = 0;
1072         do {
1073                 /* NB: follow links on command line unless -l or -s */
1074                 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
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 }