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