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