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