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