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