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