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