Convert a chunk of usage.h to USE_ and SKIP_ (more to do there), and fix a
[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 /*
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 enum {
33         TERMINAL_WIDTH = 80,    /* use 79 if terminal has linefold bug */
34         COLUMN_GAP = 2,         /* includes the file type char */
35 };
36
37 /************************************************************************/
38
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <stdio.h>
42 #include <unistd.h>
43 #include <dirent.h>
44 #include <errno.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <stdlib.h>
48 #include <fcntl.h>
49 #include <signal.h>
50 #include <termios.h>
51 #include <getopt.h> /* struct option */
52 #include <sys/ioctl.h>
53 #include <sys/sysmacros.h>     /* major() and minor() */
54 #include "busybox.h"
55 #ifdef CONFIG_SELINUX
56 #include <selinux/selinux.h>   /* for is_selinux_enabled() */
57 #endif
58
59 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
60 #include <time.h>
61 #endif
62
63 /* what is the overall style of the listing */
64 #define STYLE_AUTO      (0)
65 #define STYLE_COLUMNS   (1U<<21)        /* fill columns */
66 #define STYLE_LONG      (2U<<21)        /* one record per line, extended info */
67 #define STYLE_SINGLE    (3U<<21)        /* one record per line */
68
69 #define STYLE_MASK                 STYLE_SINGLE
70 #define STYLE_ONE_RECORD_FLAG      STYLE_LONG
71
72 /* 51306 lrwxrwxrwx  1 root     root         2 May 11 01:43 /bin/view -> vi* */
73 /* what file information will be listed */
74 #define LIST_INO        (1U<<0)
75 #define LIST_BLOCKS     (1U<<1)
76 #define LIST_MODEBITS   (1U<<2)
77 #define LIST_NLINKS     (1U<<3)
78 #define LIST_ID_NAME    (1U<<4)
79 #define LIST_ID_NUMERIC (1U<<5)
80 #define LIST_CONTEXT    (1U<<6)
81 #define LIST_SIZE       (1U<<7)
82 #define LIST_DEV        (1U<<8)
83 #define LIST_DATE_TIME  (1U<<9)
84 #define LIST_FULLTIME   (1U<<10)
85 #define LIST_FILENAME   (1U<<11)
86 #define LIST_SYMLINK    (1U<<12)
87 #define LIST_FILETYPE   (1U<<13)
88 #define LIST_EXEC       (1U<<14)
89
90 #define LIST_MASK       ((LIST_EXEC << 1) - 1)
91
92 /* what files will be displayed */
93 /* TODO -- We may be able to make DISP_NORMAL 0 to save a bit slot. */
94 #define DISP_NORMAL     (1U<<14)        /* show normal filenames */
95 #define DISP_DIRNAME    (1U<<15)        /* 2 or more items? label directories */
96 #define DISP_HIDDEN     (1U<<16)        /* show filenames starting with .  */
97 #define DISP_DOT        (1U<<17)        /* show . and .. */
98 #define DISP_NOLIST     (1U<<18)        /* show directory as itself, not contents */
99 #define DISP_RECURSIVE  (1U<<19)        /* show directory and everything below it */
100 #define DISP_ROWS       (1U<<20)        /* print across rows */
101
102 #define DISP_MASK       (((DISP_ROWS << 1) - 1) & ~(DISP_NORMAL - 1))
103
104 #ifdef CONFIG_FEATURE_LS_SORTFILES
105 /* how will the files be sorted */
106 #define SORT_ORDER_FORWARD   0          /* sort in reverse order */
107 #define SORT_ORDER_REVERSE   (1U<<27)   /* sort in reverse order */
108
109 #define SORT_NAME      0                /* sort by file name */
110 #define SORT_SIZE      (1U<<28)         /* sort by file size */
111 #define SORT_ATIME     (2U<<28)         /* sort by last access time */
112 #define SORT_CTIME     (3U<<28)         /* sort by last change time */
113 #define SORT_MTIME     (4U<<28)         /* sort by last modification time */
114 #define SORT_VERSION   (5U<<28)         /* sort by version */
115 #define SORT_EXT       (6U<<28)         /* sort by file name extension */
116 #define SORT_DIR       (7U<<28)         /* sort by file or directory */
117
118 #define SORT_MASK      (7U<<28)
119 #endif
120
121 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
122 /* which of the three times will be used */
123 #define TIME_MOD       0
124 #define TIME_CHANGE    (1U<<23)
125 #define TIME_ACCESS    (1U<<24)
126
127 #define TIME_MASK      (3U<<23)
128 #endif
129
130 #ifdef CONFIG_FEATURE_LS_FOLLOWLINKS
131 #define FOLLOW_LINKS   (1U<<25)
132 #endif
133 #ifdef CONFIG_FEATURE_HUMAN_READABLE
134 #define LS_DISP_HR     (1U<<26)
135 #endif
136
137 #define LIST_SHORT      (LIST_FILENAME)
138 #define LIST_ISHORT     (LIST_INO | LIST_FILENAME)
139 #define LIST_LONG       (LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
140                                                 LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK)
141 #define LIST_ILONG      (LIST_INO | LIST_LONG)
142
143 #define SPLIT_DIR      1
144 #define SPLIT_FILE     0
145 #define SPLIT_SUBDIR   2
146
147 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
148 #define TYPECHAR(mode)  ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
149
150 #if defined(CONFIG_FEATURE_LS_FILETYPES) || defined(CONFIG_FEATURE_LS_COLOR)
151 # define APPCHAR(mode)   ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
152 #endif
153
154 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
155 #ifdef CONFIG_FEATURE_LS_COLOR
156
157 static int show_color = 0;
158
159 /* long option entry used only for --color, which has no short option
160  * equivalent.  */
161 static const struct option ls_color_opt[] =
162 {
163         {"color", optional_argument, NULL, 1},
164         {NULL, 0, NULL, 0}
165 };
166
167 #define COLOR(mode)     ("\000\043\043\043\042\000\043\043"\
168                          "\000\000\044\000\043\000\000\040" [TYPEINDEX(mode)])
169 #define ATTR(mode)      ("\00\00\01\00\01\00\01\00"\
170                          "\00\00\01\00\01\00\00\01" [TYPEINDEX(mode)])
171 #endif
172
173 /*
174  * a directory entry and its stat info are stored here
175  */
176 struct dnode {                  /* the basic node */
177         char *name;             /* the dir entry name */
178         char *fullname;         /* the dir entry name */
179         int   allocated;
180         struct stat dstat;      /* the file stat info */
181 #ifdef CONFIG_SELINUX
182         security_context_t sid;
183 #endif
184         struct dnode *next;     /* point at the next node */
185 };
186 typedef struct dnode dnode_t;
187
188 static struct dnode **list_dir(const char *);
189 static struct dnode **dnalloc(int);
190 static int list_single(struct dnode *);
191
192 static unsigned int all_fmt;
193
194 #ifdef CONFIG_FEATURE_AUTOWIDTH
195 static int terminal_width = TERMINAL_WIDTH;
196 static unsigned short tabstops = COLUMN_GAP;
197 #else
198 #define tabstops COLUMN_GAP
199 #define terminal_width TERMINAL_WIDTH
200 #endif
201
202 static int status = EXIT_SUCCESS;
203
204 static struct dnode *my_stat(char *fullname, char *name)
205 {
206         struct stat dstat;
207         struct dnode *cur;
208 #ifdef CONFIG_SELINUX
209         security_context_t sid=NULL;
210 #endif
211         int rc;
212
213 #ifdef CONFIG_FEATURE_LS_FOLLOWLINKS
214         if (all_fmt & FOLLOW_LINKS) {
215 #ifdef CONFIG_SELINUX
216                 if (is_selinux_enabled())  {
217                   rc=0; /*  Set the number which means success before hand.  */
218                   rc = getfilecon(fullname,&sid);
219                 }
220 #endif
221                 rc = stat(fullname, &dstat);
222                 if(rc) {
223                         bb_perror_msg("%s", fullname);
224                         status = EXIT_FAILURE;
225                         return 0;
226                 }
227         } else
228 #endif
229         {
230 #ifdef CONFIG_SELINUX
231                 if  (is_selinux_enabled())  {
232                   rc=0; /*  Set the number which means success before hand.  */
233                   rc = lgetfilecon(fullname,&sid);
234                 }
235 #endif
236                 rc = lstat(fullname, &dstat);
237                 if(rc) {
238                         bb_perror_msg("%s", fullname);
239                         status = EXIT_FAILURE;
240                         return 0;
241                 }
242         }
243
244         cur = (struct dnode *) xmalloc(sizeof(struct dnode));
245         cur->fullname = fullname;
246         cur->name = name;
247         cur->dstat = dstat;
248 #ifdef CONFIG_SELINUX
249         cur->sid = sid;
250 #endif
251         return cur;
252 }
253
254 /*----------------------------------------------------------------------*/
255 #ifdef CONFIG_FEATURE_LS_COLOR
256 static char fgcolor(mode_t mode)
257 {
258         /* Check wheter the file is existing (if so, color it red!) */
259         if (errno == ENOENT) {
260                 return '\037';
261         }
262         if (LIST_EXEC && S_ISREG(mode)
263                 && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
264                 return COLOR(0xF000);   /* File is executable ... */
265         return COLOR(mode);
266 }
267
268 /*----------------------------------------------------------------------*/
269 static char bgcolor(mode_t mode)
270 {
271         if (LIST_EXEC && S_ISREG(mode)
272                 && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
273                 return ATTR(0xF000);    /* File is executable ... */
274         return ATTR(mode);
275 }
276 #endif
277
278 /*----------------------------------------------------------------------*/
279 #if defined(CONFIG_FEATURE_LS_FILETYPES) || defined(CONFIG_FEATURE_LS_COLOR)
280 static char append_char(mode_t mode)
281 {
282         if (!(all_fmt & LIST_FILETYPE))
283                 return '\0';
284         if ((all_fmt & LIST_EXEC) && S_ISREG(mode)
285                 && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
286                 return '*';
287         return APPCHAR(mode);
288 }
289 #endif
290
291 /*----------------------------------------------------------------------*/
292
293 #define countdirs(A,B) count_dirs((A), (B), 1)
294 #define countsubdirs(A,B) count_dirs((A), (B), 0)
295
296 static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs)
297 {
298         int i, dirs;
299
300         if (dn == NULL || nfiles < 1)
301                 return (0);
302         dirs = 0;
303         for (i = 0; i < nfiles; i++) {
304                 if (S_ISDIR(dn[i]->dstat.st_mode)
305                         && (notsubdirs ||
306                         ((dn[i]->name[0] != '.') || (dn[i]->name[1]
307                                                 && ((dn[i]->name[1] != '.')
308                                                 || dn[i]->name[2])))))
309                         dirs++;
310         }
311         return (dirs);
312 }
313
314 static int countfiles(struct dnode **dnp)
315 {
316         int nfiles;
317         struct dnode *cur;
318
319         if (dnp == NULL)
320                 return (0);
321         nfiles = 0;
322         for (cur = dnp[0]; cur->next != NULL; cur = cur->next)
323                 nfiles++;
324         nfiles++;
325         return (nfiles);
326 }
327
328 /* get memory to hold an array of pointers */
329 static struct dnode **dnalloc(int num)
330 {
331         struct dnode **p;
332
333         if (num < 1)
334                 return (NULL);
335
336         p = (struct dnode **) xcalloc((size_t) num, (size_t) (sizeof(struct dnode *)));
337         return (p);
338 }
339
340 #ifdef CONFIG_FEATURE_LS_RECURSIVE
341 static void dfree(struct dnode **dnp)
342 {
343         struct dnode *cur, *next;
344
345         if (dnp == NULL)
346                 return;
347
348         cur = dnp[0];
349         while (cur != NULL) {
350                 if(cur->allocated)
351                         free(cur->fullname);    /* free the filename */
352                 next = cur->next;
353                 free(cur);              /* free the dnode */
354                 cur = next;
355         }
356         free(dnp);                      /* free the array holding the dnode pointers */
357 }
358 #endif
359
360 static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which)
361 {
362         int dncnt, i, d;
363         struct dnode **dnp;
364
365         if (dn == NULL || nfiles < 1)
366                 return (NULL);
367
368         /* count how many dirs and regular files there are */
369         if (which == SPLIT_SUBDIR)
370                 dncnt = countsubdirs(dn, nfiles);
371         else {
372                 dncnt = countdirs(dn, nfiles);  /* assume we are looking for dirs */
373                 if (which == SPLIT_FILE)
374                         dncnt = nfiles - dncnt; /* looking for files */
375         }
376
377         /* allocate a file array and a dir array */
378         dnp = dnalloc(dncnt);
379
380         /* copy the entrys into the file or dir array */
381         for (d = i = 0; i < nfiles; i++) {
382                 if (S_ISDIR(dn[i]->dstat.st_mode)) {
383                         if (which & (SPLIT_DIR|SPLIT_SUBDIR)) {
384                                 if ((which & SPLIT_DIR)
385                                         || ((dn[i]->name[0] != '.')
386                                                 || (dn[i]->name[1]
387                                                         && ((dn[i]->name[1] != '.')
388                                                                 || dn[i]->name[2])))) {
389                                                                         dnp[d++] = dn[i];
390                                                                 }
391                         }
392                 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
393                         dnp[d++] = dn[i];
394                 }
395         }
396         return (dnp);
397 }
398
399 /*----------------------------------------------------------------------*/
400 #ifdef CONFIG_FEATURE_LS_SORTFILES
401 static int sortcmp(struct dnode *d1, struct dnode *d2)
402 {
403         unsigned int sort_opts = all_fmt & SORT_MASK;
404         int dif;
405
406         dif = 0;                        /* assume SORT_NAME */
407         if (sort_opts == SORT_SIZE) {
408                 dif = (int) (d2->dstat.st_size - d1->dstat.st_size);
409         } else if (sort_opts == SORT_ATIME) {
410                 dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime);
411         } else if (sort_opts == SORT_CTIME) {
412                 dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime);
413         } else if (sort_opts == SORT_MTIME) {
414                 dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime);
415         } else if (sort_opts == SORT_DIR) {
416                 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
417                 /* } else if (sort_opts == SORT_VERSION) { */
418                 /* } else if (sort_opts == SORT_EXT) { */
419         }
420
421         if (dif == 0) {
422                 /* sort by name- may be a tie_breaker for time or size cmp */
423 #ifdef CONFIG_LOCALE_SUPPORT
424                 dif = strcoll(d1->name, d2->name);
425 #else
426                 dif = strcmp(d1->name, d2->name);
427 #endif
428         }
429
430         if (all_fmt & SORT_ORDER_REVERSE) {
431                 dif = -dif;
432         }
433         return (dif);
434 }
435
436 /*----------------------------------------------------------------------*/
437 static void shellsort(struct dnode **dn, int size)
438 {
439         struct dnode *temp;
440         int gap, i, j;
441
442         /* shell short the array */
443         if (dn == NULL || size < 2)
444                 return;
445
446         for (gap = size / 2; gap > 0; gap /= 2) {
447                 for (i = gap; i < size; i++) {
448                         for (j = i - gap; j >= 0; j -= gap) {
449                                 if (sortcmp(dn[j], dn[j + gap]) <= 0)
450                                         break;
451                                 /* they are out of order, swap them */
452                                 temp = dn[j];
453                                 dn[j] = dn[j + gap];
454                                 dn[j + gap] = temp;
455                         }
456                 }
457         }
458 }
459 #endif
460
461 /*----------------------------------------------------------------------*/
462 static void showfiles(struct dnode **dn, int nfiles)
463 {
464         int i, ncols, nrows, row, nc;
465         int column = 0;
466         int nexttab = 0;
467         int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */
468
469         if (dn == NULL || nfiles < 1)
470                 return;
471
472         if (all_fmt & STYLE_ONE_RECORD_FLAG) {
473                 ncols = 1;
474         } else {
475                 /* find the longest file name-  use that as the column width */
476                 for (i = 0; i < nfiles; i++) {
477                         int len = strlen(dn[i]->name) +
478 #ifdef CONFIG_SELINUX
479                         ((all_fmt & LIST_CONTEXT) ? 33 : 0) +
480 #endif
481                         ((all_fmt & LIST_INO) ? 8 : 0) +
482                         ((all_fmt & LIST_BLOCKS) ? 5 : 0);
483                         if (column_width < len)
484                                 column_width = len;
485                 }
486                 column_width += tabstops;
487                 ncols = (int) (terminal_width / column_width);
488         }
489
490         if (ncols > 1) {
491                 nrows = nfiles / ncols;
492                 if ((nrows * ncols) < nfiles)
493                         nrows++;                /* round up fractionals */
494         } else {
495                 nrows = nfiles;
496                 ncols = 1;
497         }
498
499         for (row = 0; row < nrows; row++) {
500                 for (nc = 0; nc < ncols; nc++) {
501                         /* reach into the array based on the column and row */
502                         i = (nc * nrows) + row; /* assume display by column */
503                         if (all_fmt & DISP_ROWS)
504                                 i = (row * ncols) + nc; /* display across row */
505                         if (i < nfiles) {
506                                 if (column > 0) {
507                                         nexttab -= column;
508                                         while (nexttab--) {
509                                                 putchar(' ');
510                                                 column++;
511                                         }
512                         }
513                                 nexttab = column + column_width;
514                                 column += list_single(dn[i]);
515                 }
516                 }
517                 putchar('\n');
518                 column = 0;
519         }
520 }
521
522 /*----------------------------------------------------------------------*/
523 static void showdirs(struct dnode **dn, int ndirs, int first)
524 {
525         int i, nfiles;
526         struct dnode **subdnp;
527
528 #ifdef CONFIG_FEATURE_LS_RECURSIVE
529         int dndirs;
530         struct dnode **dnd;
531 #endif
532
533         if (dn == NULL || ndirs < 1)
534                 return;
535
536         for (i = 0; i < ndirs; i++) {
537                 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
538                         if (!first)
539                                 printf("\n");
540                         first = 0;
541                         printf("%s:\n", dn[i]->fullname);
542                 }
543                 subdnp = list_dir(dn[i]->fullname);
544                 nfiles = countfiles(subdnp);
545                 if (nfiles > 0) {
546                         /* list all files at this level */
547 #ifdef CONFIG_FEATURE_LS_SORTFILES
548                         shellsort(subdnp, nfiles);
549 #endif
550                         showfiles(subdnp, nfiles);
551 #ifdef CONFIG_FEATURE_LS_RECURSIVE
552                         if (all_fmt & DISP_RECURSIVE) {
553                                 /* recursive- list the sub-dirs */
554                                 dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR);
555                                 dndirs = countsubdirs(subdnp, nfiles);
556                                 if (dndirs > 0) {
557 #ifdef CONFIG_FEATURE_LS_SORTFILES
558                                         shellsort(dnd, dndirs);
559 #endif
560                                         showdirs(dnd, dndirs, 0);
561                                         free(dnd);      /* free the array of dnode pointers to the dirs */
562                                 }
563                         }
564                         dfree(subdnp);  /* free the dnodes and the fullname mem */
565 #endif
566                 }
567         }
568 }
569
570 /*----------------------------------------------------------------------*/
571 static struct dnode **list_dir(const char *path)
572 {
573         struct dnode *dn, *cur, **dnp;
574         struct dirent *entry;
575         DIR *dir;
576         int i, nfiles;
577
578         if (path == NULL)
579                 return (NULL);
580
581         dn = NULL;
582         nfiles = 0;
583         dir = bb_opendir(path);
584         if (dir == NULL) {
585                 status = EXIT_FAILURE;
586                 return (NULL);  /* could not open the dir */
587         }
588         while ((entry = readdir(dir)) != NULL) {
589                 char *fullname;
590
591                 /* are we going to list the file- it may be . or .. or a hidden file */
592                 if (entry->d_name[0] == '.') {
593                         if ((entry->d_name[1] == 0 || (
594                                 entry->d_name[1] == '.'
595                                 && entry->d_name[2] == 0))
596                                         && !(all_fmt & DISP_DOT))
597                         continue;
598                         if (!(all_fmt & DISP_HIDDEN))
599                         continue;
600                 }
601                 fullname = concat_path_file(path, entry->d_name);
602                 cur = my_stat(fullname, strrchr(fullname, '/') + 1);
603                 if (!cur)
604                         continue;
605                 cur->allocated = 1;
606                 cur->next = dn;
607                 dn = cur;
608                 nfiles++;
609         }
610         closedir(dir);
611
612         /* now that we know how many files there are
613            ** allocate memory for an array to hold dnode pointers
614          */
615         if (dn == NULL)
616                 return (NULL);
617         dnp = dnalloc(nfiles);
618         for (i = 0, cur = dn; i < nfiles; i++) {
619                 dnp[i] = cur;   /* save pointer to node in array */
620                 cur = cur->next;
621         }
622
623         return (dnp);
624 }
625
626 /*----------------------------------------------------------------------*/
627 static int list_single(struct dnode *dn)
628 {
629         int i, column = 0;
630
631 #ifdef CONFIG_FEATURE_LS_USERNAME
632         char scratch[16];
633 #endif
634 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
635         char *filetime;
636         time_t ttime, age;
637 #endif
638 #if defined(CONFIG_FEATURE_LS_FILETYPES) || defined (CONFIG_FEATURE_LS_COLOR)
639         struct stat info;
640         char append;
641 #endif
642
643         if (dn->fullname == NULL)
644                 return (0);
645
646 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
647         ttime = dn->dstat.st_mtime;     /* the default time */
648         if (all_fmt & TIME_ACCESS)
649                 ttime = dn->dstat.st_atime;
650         if (all_fmt & TIME_CHANGE)
651                 ttime = dn->dstat.st_ctime;
652         filetime = ctime(&ttime);
653 #endif
654 #ifdef CONFIG_FEATURE_LS_FILETYPES
655         append = append_char(dn->dstat.st_mode);
656 #endif
657
658         for (i = 0; i <= 31; i++) {
659                 switch (all_fmt & (1 << i)) {
660                 case LIST_INO:
661                         column += printf("%7ld ", (long int) dn->dstat.st_ino);
662                         break;
663                 case LIST_BLOCKS:
664 #if _FILE_OFFSET_BITS == 64
665                         column += printf("%4lld ", (long long)dn->dstat.st_blocks >> 1);
666 #else
667                         column += printf("%4ld ", dn->dstat.st_blocks >> 1);
668 #endif
669                         break;
670                 case LIST_MODEBITS:
671                         column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
672                         break;
673                 case LIST_NLINKS:
674                         column += printf("%4ld ", (long) dn->dstat.st_nlink);
675                         break;
676                 case LIST_ID_NAME:
677 #ifdef CONFIG_FEATURE_LS_USERNAME
678                         bb_getpwuid(scratch, dn->dstat.st_uid, sizeof(scratch));
679                         printf("%-8.8s ", scratch);
680                         bb_getgrgid(scratch, dn->dstat.st_gid, sizeof(scratch));
681                         printf("%-8.8s", scratch);
682                         column += 17;
683                         break;
684 #endif
685                 case LIST_ID_NUMERIC:
686                         column += printf("%-8d %-8d", dn->dstat.st_uid, dn->dstat.st_gid);
687                         break;
688                 case LIST_SIZE:
689                 case LIST_DEV:
690                         if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
691                                 column += printf("%4d, %3d ", (int) major(dn->dstat.st_rdev),
692                                            (int) minor(dn->dstat.st_rdev));
693                         } else {
694 #ifdef CONFIG_FEATURE_HUMAN_READABLE
695                                 if (all_fmt & LS_DISP_HR) {
696                                         column += printf("%9s ",
697                                                         make_human_readable_str(dn->dstat.st_size, 1, 0));
698                                 } else
699 #endif
700                                 {
701 #if _FILE_OFFSET_BITS == 64
702                                         column += printf("%9lld ", (long long) dn->dstat.st_size);
703 #else
704                                         column += printf("%9ld ", dn->dstat.st_size);
705 #endif
706                                 }
707                         }
708                         break;
709 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
710                 case LIST_FULLTIME:
711                         printf("%24.24s ", filetime);
712                         column += 25;
713                         break;
714                 case LIST_DATE_TIME:
715                         if ((all_fmt & LIST_FULLTIME) == 0) {
716                                 age = time(NULL) - ttime;
717                                 printf("%6.6s ", filetime + 4);
718                                 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
719                                         /* hh:mm if less than 6 months old */
720                                         printf("%5.5s ", filetime + 11);
721                                 } else {
722                                         printf(" %4.4s ", filetime + 20);
723                                 }
724                                 column += 13;
725                         }
726                         break;
727 #endif
728 #ifdef CONFIG_SELINUX
729                 case LIST_CONTEXT:
730                         {
731                                 char context[80];
732                                 int len = 0;
733
734                                 if (dn->sid) {
735                                   /*  I assume sid initilized with NULL  */
736                                   len = strlen(dn->sid)+1;
737                                   safe_strncpy(context, dn->sid, len);
738                                   freecon(dn->sid);
739                                 }else {
740                                   safe_strncpy(context, "unknown", 8);
741                                 }
742                                 printf("%-32s ", context);
743                                 column += MAX(33, len);
744                         }
745                         break;
746 #endif
747                 case LIST_FILENAME:
748 #ifdef CONFIG_FEATURE_LS_COLOR
749                         errno = 0;
750                         if (show_color && !lstat(dn->fullname, &info)) {
751                                 printf("\033[%d;%dm", bgcolor(info.st_mode),
752                                            fgcolor(info.st_mode));
753                         }
754 #endif
755                         column += printf("%s", dn->name);
756 #ifdef CONFIG_FEATURE_LS_COLOR
757                         if (show_color) {
758                                 printf("\033[0m");
759                         }
760 #endif
761                         break;
762                 case LIST_SYMLINK:
763                         if (S_ISLNK(dn->dstat.st_mode)) {
764                                 char *lpath = xreadlink(dn->fullname);
765
766                                 if (lpath) {
767                                         printf(" -> ");
768 #if defined(CONFIG_FEATURE_LS_FILETYPES) || defined (CONFIG_FEATURE_LS_COLOR)
769                                         if (!stat(dn->fullname, &info)) {
770                                                 append = append_char(info.st_mode);
771                                         }
772 #endif
773 #ifdef CONFIG_FEATURE_LS_COLOR
774                                         if (show_color) {
775                                                 errno = 0;
776                                                 printf("\033[%d;%dm", bgcolor(info.st_mode),
777                                                            fgcolor(info.st_mode));
778                                         }
779 #endif
780                                         column += printf("%s", lpath) + 4;
781 #ifdef CONFIG_FEATURE_LS_COLOR
782                                         if (show_color) {
783                                                 printf("\033[0m");
784                                         }
785 #endif
786                                         free(lpath);
787                                 }
788                         }
789                         break;
790 #ifdef CONFIG_FEATURE_LS_FILETYPES
791                 case LIST_FILETYPE:
792                         if (append != '\0') {
793                                 printf("%1c", append);
794                                 column++;
795                         }
796                         break;
797 #endif
798                 }
799         }
800
801         return column;
802 }
803
804 /*----------------------------------------------------------------------*/
805
806 /* "[-]Cadil1", POSIX mandated options, busybox always supports */
807 /* "[-]gnsx", POSIX non-mandated options, busybox always supports */
808 /* "[-]Ak" GNU options, busybox always supports */
809 /* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
810 /* "[-]p", POSIX non-mandated options, busybox optionally supports */
811 /* "[-]SXvThw", GNU options, busybox optionally supports */
812 /* "[-]K", SELinux mandated options, busybox optionally supports */
813 /* "[-]e", I think we made this one up */
814
815 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
816 # define LS_STR_TIMESTAMPS      "cetu"
817 #else
818 # define LS_STR_TIMESTAMPS      ""
819 #endif
820
821 #ifdef CONFIG_FEATURE_LS_SORTFILES
822 # define LS_STR_SORTFILES       "SXrv"
823 #else
824 # define LS_STR_SORTFILES       ""
825 #endif
826
827 #ifdef CONFIG_FEATURE_LS_FILETYPES
828 # define LS_STR_FILETYPES       "Fp"
829 #else
830 # define LS_STR_FILETYPES       ""
831 #endif
832
833 #ifdef CONFIG_FEATURE_LS_FOLLOWLINKS
834 # define LS_STR_FOLLOW_LINKS    "L"
835 #else
836 # define LS_STR_FOLLOW_LINKS    ""
837 #endif
838
839 #ifdef CONFIG_FEATURE_LS_RECURSIVE
840 # define LS_STR_RECURSIVE       "R"
841 #else
842 # define LS_STR_RECURSIVE       ""
843 #endif
844
845 #ifdef CONFIG_FEATURE_HUMAN_READABLE
846 # define LS_STR_HUMAN_READABLE  "h"
847 #else
848 # define LS_STR_HUMAN_READABLE  ""
849 #endif
850
851 #ifdef CONFIG_SELINUX
852 # define LS_STR_SELINUX "K"
853 #else
854 # define LS_STR_SELINUX ""
855 #endif
856
857 #ifdef CONFIG_FEATURE_AUTOWIDTH
858 # define LS_STR_AUTOWIDTH       "T:w:"
859 #else
860 # define LS_STR_AUTOWIDTH       ""
861 #endif
862
863 static const char ls_options[]="Cadil1gnsxAk" \
864         LS_STR_TIMESTAMPS \
865         LS_STR_SORTFILES \
866         LS_STR_FILETYPES \
867         LS_STR_FOLLOW_LINKS \
868         LS_STR_RECURSIVE \
869         LS_STR_HUMAN_READABLE \
870         LS_STR_SELINUX \
871         LS_STR_AUTOWIDTH;
872
873 #define LIST_MASK_TRIGGER       0
874 #define STYLE_MASK_TRIGGER      STYLE_MASK
875 #define SORT_MASK_TRIGGER       SORT_MASK
876 #define DISP_MASK_TRIGGER       DISP_ROWS
877 #define TIME_MASK_TRIGGER       TIME_MASK
878
879 static const unsigned opt_flags[] = {
880         LIST_SHORT | STYLE_COLUMNS,     /* C */
881         DISP_HIDDEN | DISP_DOT,         /* a */
882         DISP_NOLIST,                            /* d */
883         LIST_INO,                                       /* i */
884         LIST_LONG | STYLE_LONG,         /* l - remember LS_DISP_HR in mask! */
885         LIST_SHORT | STYLE_SINGLE,      /* 1 */
886         0,                                                      /* g - ingored */
887         LIST_ID_NUMERIC,                        /* n */
888         LIST_BLOCKS,                            /* s */
889         DISP_ROWS,                                      /* x */
890         DISP_HIDDEN,                            /* A */
891 #ifdef CONFIG_SELINUX
892         LIST_CONTEXT,                           /* k */
893 #else
894         0,                                                      /* k - ingored */
895 #endif
896 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
897 # ifdef CONFIG_FEATURE_LS_SORTFILES
898         TIME_CHANGE | SORT_CTIME,       /* c */
899 # else
900         TIME_CHANGE,                            /* c */
901 # endif
902         LIST_FULLTIME,                          /* e */
903 # ifdef CONFIG_FEATURE_LS_SORTFILES
904         SORT_MTIME,                                     /* t */
905 # else
906         0,                                                      /* t - ignored -- is this correct? */
907 # endif
908 # ifdef CONFIG_FEATURE_LS_SORTFILES
909         TIME_ACCESS | SORT_ATIME,       /* u */
910 # else
911         TIME_ACCESS,                            /* u */
912 # endif
913 #endif
914 #ifdef CONFIG_FEATURE_LS_SORTFILES
915         SORT_SIZE,                                      /* S */
916         SORT_EXT,                                       /* X */
917         SORT_ORDER_REVERSE,                     /* r */
918         SORT_VERSION,                           /* v */
919 #endif
920 #ifdef CONFIG_FEATURE_LS_FILETYPES
921         LIST_FILETYPE | LIST_EXEC,      /* F */
922         LIST_FILETYPE,                          /* p */
923 #endif
924 #ifdef CONFIG_FEATURE_LS_FOLLOWLINKS
925         FOLLOW_LINKS,                           /* L */
926 #endif
927 #ifdef CONFIG_FEATURE_LS_RECURSIVE
928         DISP_RECURSIVE,                         /* R */
929 #endif
930 #ifdef CONFIG_FEATURE_HUMAN_READABLE
931         LS_DISP_HR,                                     /* h */
932 #endif
933 #ifdef CONFIG_SELINUX
934         LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
935 #endif
936 #ifdef CONFIG_FEATURE_AUTOWIDTH
937        0, 0,                    /* T, w - ignored */
938 #endif
939         (1U<<31)
940 };
941
942
943 /*----------------------------------------------------------------------*/
944
945 int ls_main(int argc, char **argv)
946 {
947         struct dnode **dnd;
948         struct dnode **dnf;
949         struct dnode **dnp;
950         struct dnode *dn;
951         struct dnode *cur;
952         long opt;
953         int nfiles = 0;
954         int dnfiles;
955         int dndirs;
956         int oi;
957         int ac;
958         int i;
959         char **av;
960 #ifdef CONFIG_FEATURE_AUTOWIDTH
961         char *tabstops_str = NULL;
962         char *terminal_width_str = NULL;
963 #endif
964 #ifdef CONFIG_FEATURE_LS_COLOR
965         char *color_opt;
966 #endif
967
968         all_fmt = LIST_SHORT | DISP_NORMAL | STYLE_AUTO
969 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
970                 | TIME_MOD
971 #endif
972 #ifdef CONFIG_FEATURE_LS_SORTFILES
973                 | SORT_NAME | SORT_ORDER_FORWARD
974 #endif
975                 ;
976
977 #ifdef CONFIG_FEATURE_AUTOWIDTH
978         /* Obtain the terminal width.  */
979         get_terminal_width_height(STDOUT_FILENO, &terminal_width, NULL);
980         /* Go one less... */
981         terminal_width--;
982 #endif
983
984 #ifdef CONFIG_FEATURE_LS_COLOR
985         bb_applet_long_options = ls_color_opt;
986 #endif
987
988         /* process options */
989 #ifdef CONFIG_FEATURE_AUTOWIDTH
990         opt = bb_getopt_ulflags(argc, argv, ls_options, &tabstops_str, &terminal_width_str
991 #ifdef CONFIG_FEATURE_LS_COLOR
992                 , &color_opt
993 #endif
994                 );
995         if (tabstops_str) {
996                 tabstops = atoi(tabstops_str);
997         }
998         if (terminal_width_str) {
999                 terminal_width = atoi(terminal_width_str);
1000         }
1001 #else
1002         opt = bb_getopt_ulflags(argc, argv, ls_options
1003 #ifdef CONFIG_FEATURE_LS_COLOR
1004                 , &color_opt
1005 #endif
1006                 );
1007 #endif
1008         for (i = 0; opt_flags[i] != (1U<<31); i++) {
1009                 if (opt & (1 << i)) {
1010                         unsigned int flags = opt_flags[i];
1011
1012                         if (flags & LIST_MASK_TRIGGER) {
1013                                 all_fmt &= ~LIST_MASK;
1014                         }
1015                         if (flags & STYLE_MASK_TRIGGER) {
1016                                 all_fmt &= ~STYLE_MASK;
1017                         }
1018 #ifdef CONFIG_FEATURE_LS_SORTFILES
1019                         if (flags & SORT_MASK_TRIGGER) {
1020                                 all_fmt &= ~SORT_MASK;
1021                         }
1022 #endif
1023                         if (flags & DISP_MASK_TRIGGER) {
1024                                 all_fmt &= ~DISP_MASK;
1025                         }
1026 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
1027                         if (flags & TIME_MASK_TRIGGER) {
1028                                 all_fmt &= ~TIME_MASK;
1029                         }
1030 #endif
1031                         if (flags & LIST_CONTEXT) {
1032                                 all_fmt |= STYLE_SINGLE;
1033                         }
1034 #ifdef CONFIG_FEATURE_HUMAN_READABLE
1035                         if (opt == 'l') {
1036                                 all_fmt &= ~LS_DISP_HR;
1037                         }
1038 #endif
1039                         all_fmt |= flags;
1040                 }
1041         }
1042
1043 #ifdef CONFIG_FEATURE_LS_COLOR
1044         {
1045                 /* find color bit value - last position for short getopt */
1046
1047 #if CONFIG_FEATURE_LS_COLOR_IS_DEFAULT
1048                 char *p;
1049
1050                 if ((p = getenv ("LS_COLORS")) != NULL &&
1051                         (*p == '\0' || (strcmp(p, "none") == 0))) {
1052                         ;
1053                 } else if (isatty(STDOUT_FILENO)) {
1054                         show_color = 1;
1055                 }
1056 #endif
1057
1058                 if((opt & (1 << i))) {  /* next flag after short options */
1059                         if (color_opt == NULL || strcmp("always", color_opt) == 0)
1060                                 show_color = 1;
1061                         else if (color_opt != NULL && strcmp("never", color_opt) == 0)
1062                                 show_color = 0;
1063                         else if (color_opt != NULL && strcmp("auto", color_opt) == 0 && isatty(STDOUT_FILENO))
1064                                 show_color = 1;
1065                 }
1066         }
1067 #endif
1068
1069         /* sort out which command line options take precedence */
1070 #ifdef CONFIG_FEATURE_LS_RECURSIVE
1071         if (all_fmt & DISP_NOLIST)
1072                 all_fmt &= ~DISP_RECURSIVE;     /* no recurse if listing only dir */
1073 #endif
1074 #if defined (CONFIG_FEATURE_LS_TIMESTAMPS) && defined (CONFIG_FEATURE_LS_SORTFILES)
1075         if (all_fmt & TIME_CHANGE)
1076                 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1077         if (all_fmt & TIME_ACCESS)
1078                 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1079 #endif
1080         if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1081                 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
1082 #ifdef CONFIG_FEATURE_LS_USERNAME
1083         if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1084                 all_fmt &= ~LIST_ID_NAME;       /* don't list names if numeric uid */
1085 #endif
1086
1087         /* choose a display format */
1088         if ((all_fmt & STYLE_MASK) == STYLE_AUTO)
1089 #if STYLE_AUTO != 0
1090                 all_fmt = (all_fmt & ~STYLE_MASK)
1091                                 | (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1092 #else
1093                 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1094 #endif
1095
1096         /*
1097          * when there are no cmd line args we have to supply a default "." arg.
1098          * we will create a second argv array, "av" that will hold either
1099          * our created "." arg, or the real cmd line args.  The av array
1100          * just holds the pointers- we don't move the date the pointers
1101          * point to.
1102          */
1103         ac = argc - optind;     /* how many cmd line args are left */
1104         if (ac < 1) {
1105                 static const char * const dotdir[] = { "." };
1106
1107                 av = (char **) dotdir;
1108                 ac = 1;
1109         } else {
1110                 av = argv + optind;
1111         }
1112
1113         /* now, everything is in the av array */
1114         if (ac > 1)
1115                 all_fmt |= DISP_DIRNAME;        /* 2 or more items? label directories */
1116
1117         /* stuff the command line file names into an dnode array */
1118         dn = NULL;
1119         for (oi = 0; oi < ac; oi++) {
1120                 cur = my_stat(av[oi], av[oi]);
1121                 if (!cur)
1122                         continue;
1123                 cur->allocated = 0;
1124                 cur->next = dn;
1125                 dn = cur;
1126                 nfiles++;
1127         }
1128
1129         /* now that we know how many files there are
1130            ** allocate memory for an array to hold dnode pointers
1131          */
1132         dnp = dnalloc(nfiles);
1133         for (i = 0, cur = dn; i < nfiles; i++) {
1134                 dnp[i] = cur;   /* save pointer to node in array */
1135                 cur = cur->next;
1136         }
1137
1138         if (all_fmt & DISP_NOLIST) {
1139 #ifdef CONFIG_FEATURE_LS_SORTFILES
1140                 shellsort(dnp, nfiles);
1141 #endif
1142                 if (nfiles > 0)
1143                         showfiles(dnp, nfiles);
1144         } else {
1145                 dnd = splitdnarray(dnp, nfiles, SPLIT_DIR);
1146                 dnf = splitdnarray(dnp, nfiles, SPLIT_FILE);
1147                 dndirs = countdirs(dnp, nfiles);
1148                 dnfiles = nfiles - dndirs;
1149                 if (dnfiles > 0) {
1150 #ifdef CONFIG_FEATURE_LS_SORTFILES
1151                         shellsort(dnf, dnfiles);
1152 #endif
1153                         showfiles(dnf, dnfiles);
1154                 }
1155                 if (dndirs > 0) {
1156 #ifdef CONFIG_FEATURE_LS_SORTFILES
1157                         shellsort(dnd, dndirs);
1158 #endif
1159                         showdirs(dnd, dndirs, dnfiles == 0);
1160                 }
1161         }
1162         return (status);
1163 }