Patch from Shaun Jackman to save a few bytes.
[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  *  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 <selinux/selinux.h>   /* for is_selinux_enabled() */
68 #endif
69
70 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
71 #include <time.h>
72 #endif
73
74 /* what is the overall style of the listing */
75 #define STYLE_AUTO      (0)
76 #define STYLE_COLUMNS   (1U<<21)        /* fill columns */
77 #define STYLE_LONG      (2U<<21)        /* one record per line, extended info */
78 #define STYLE_SINGLE    (3U<<21)        /* one record per line */
79
80 #define STYLE_MASK                 STYLE_SINGLE
81 #define STYLE_ONE_RECORD_FLAG      STYLE_LONG
82
83 /* 51306 lrwxrwxrwx  1 root     root         2 May 11 01:43 /bin/view -> vi* */
84 /* what file information will be listed */
85 #define LIST_INO                (1U<<0)
86 #define LIST_BLOCKS             (1U<<1)
87 #define LIST_MODEBITS   (1U<<2)
88 #define LIST_NLINKS             (1U<<3)
89 #define LIST_ID_NAME    (1U<<4)
90 #define LIST_ID_NUMERIC (1U<<5)
91 #define LIST_CONTEXT    (1U<<6)
92 #define LIST_SIZE               (1U<<7)
93 #define LIST_DEV                (1U<<8)
94 #define LIST_DATE_TIME  (1U<<9)
95 #define LIST_FULLTIME   (1U<<10)
96 #define LIST_FILENAME   (1U<<11)
97 #define LIST_SYMLINK    (1U<<12)
98 #define LIST_FILETYPE   (1U<<13)
99 #define LIST_EXEC       (1U<<14)
100
101 #define LIST_MASK       ((LIST_EXEC << 1) - 1)
102
103 /* what files will be displayed */
104 /* TODO -- We may be able to make DISP_NORMAL 0 to save a bit slot. */
105 #define DISP_NORMAL             (1U<<14)        /* show normal filenames */
106 #define DISP_DIRNAME    (1U<<15)        /* 2 or more items? label directories */
107 #define DISP_HIDDEN             (1U<<16)        /* show filenames starting with .  */
108 #define DISP_DOT                (1U<<17)        /* show . and .. */
109 #define DISP_NOLIST             (1U<<18)        /* show directory as itself, not contents */
110 #define DISP_RECURSIVE  (1U<<19)        /* show directory and everything below it */
111 #define DISP_ROWS               (1U<<20)        /* print across rows */
112
113 #define DISP_MASK       (((DISP_ROWS << 1) - 1) & ~(DISP_NORMAL - 1))
114
115 #ifdef CONFIG_FEATURE_LS_SORTFILES
116 /* how will the files be sorted */
117 #define SORT_ORDER_FORWARD   0                  /* sort in reverse order */
118 #define SORT_ORDER_REVERSE   (1U<<27)   /* sort in reverse order */
119
120 #define SORT_NAME      0                        /* sort by file name */
121 #define SORT_SIZE      (1U<<28)         /* sort by file size */
122 #define SORT_ATIME     (2U<<28)         /* sort by last access time */
123 #define SORT_CTIME     (3U<<28)         /* sort by last change time */
124 #define SORT_MTIME     (4U<<28)         /* sort by last modification time */
125 #define SORT_VERSION   (5U<<28)         /* sort by version */
126 #define SORT_EXT       (6U<<28)         /* sort by file name extension */
127 #define SORT_DIR       (7U<<28)         /* sort by file or directory */
128
129 #define SORT_MASK      (7U<<28)
130 #endif
131
132 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
133 /* which of the three times will be used */
134 #define TIME_MOD       0
135 #define TIME_CHANGE    (1U<<23)
136 #define TIME_ACCESS    (1U<<24)
137
138 #define TIME_MASK      (3U<<23)
139 #endif
140
141 #ifdef CONFIG_FEATURE_LS_FOLLOWLINKS
142 #define FOLLOW_LINKS   (1U<<25)
143 #endif
144 #ifdef CONFIG_FEATURE_HUMAN_READABLE
145 #define LS_DISP_HR     (1U<<26)
146 #endif
147
148 #define LIST_SHORT      (LIST_FILENAME)
149 #define LIST_ISHORT     (LIST_INO | LIST_FILENAME)
150 #define LIST_LONG       (LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
151                                                 LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK)
152 #define LIST_ILONG      (LIST_INO | LIST_LONG)
153
154 #define SPLIT_DIR      1
155 #define SPLIT_FILE     0
156 #define SPLIT_SUBDIR   2
157
158 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
159 #define TYPECHAR(mode)  ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
160
161 #if defined(CONFIG_FEATURE_LS_FILETYPES) || defined(CONFIG_FEATURE_LS_COLOR)
162 # define APPCHAR(mode)   ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
163 #endif
164
165 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
166 #ifdef CONFIG_FEATURE_LS_COLOR
167 static int show_color = 0;
168
169 #define COLOR(mode)     ("\000\043\043\043\042\000\043\043"\
170                                         "\000\000\044\000\043\000\000\040" [TYPEINDEX(mode)])
171 #define ATTR(mode)      ("\00\00\01\00\01\00\01\00"\
172                                         "\00\00\01\00\01\00\00\01" [TYPEINDEX(mode)])
173 #endif
174
175 /*
176  * a directory entry and its stat info are stored here
177  */
178 struct dnode {                  /* the basic node */
179         char *name;                     /* the dir entry name */
180         char *fullname;         /* the dir entry name */
181         struct stat dstat;      /* the file stat info */
182 #ifdef CONFIG_SELINUX
183         security_context_t sid;
184 #endif
185         struct dnode *next;     /* point at the next node */
186 };
187 typedef struct dnode dnode_t;
188
189 static struct dnode **list_dir(const char *);
190 static struct dnode **dnalloc(int);
191 static int list_single(struct dnode *);
192
193 static unsigned int all_fmt;
194
195 #ifdef CONFIG_SELINUX
196 static int selinux_enabled= 0;
197 #endif
198
199 #ifdef CONFIG_FEATURE_AUTOWIDTH
200 static int terminal_width = TERMINAL_WIDTH;
201 static unsigned short tabstops = COLUMN_GAP;
202 #else
203 #define tabstops COLUMN_GAP
204 #define terminal_width TERMINAL_WIDTH
205 #endif
206
207 static int status = EXIT_SUCCESS;
208
209 static struct dnode *my_stat(char *fullname, char *name)
210 {
211         struct stat dstat;
212         struct dnode *cur;
213 #ifdef CONFIG_SELINUX
214         security_context_t sid=NULL;
215 #endif
216         int rc;
217
218 #ifdef CONFIG_FEATURE_LS_FOLLOWLINKS
219         if (all_fmt & FOLLOW_LINKS) {
220 #ifdef CONFIG_SELINUX
221                 if (is_selinux_enabled())  {
222                   rc=0; /*  Set the number which means success before hand.  */
223                   rc = getfilecon(fullname,&sid);
224                 }
225 #endif
226                   rc = stat(fullname, &dstat);
227                 if(rc)
228                 {
229                         bb_perror_msg("%s", fullname);
230                         status = EXIT_FAILURE;
231                         return 0;
232                 }
233         } else
234 #endif
235         {
236 #ifdef CONFIG_SELINUX
237                 if  (is_selinux_enabled())  {
238                   rc=0; /*  Set the number which means success before hand.  */
239                   rc = lgetfilecon(fullname,&sid);
240                 }
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[80];
740                                 int len;
741                         
742                                 if (dn->sid) {
743                                   /*  I assume sid initilized with NULL  */
744                                   len = strlen(dn->sid)+1;
745                                   safe_strncpy(context, dn->sid, len);
746                                   freecon(dn->sid);
747                                 }else {
748                                   safe_strncpy(context, "unknown",8);
749                                 }
750                                 printf("%-32s ", context);
751                                 column += MAX(33, len);
752                         }
753                         break;
754 #endif
755                 case LIST_FILENAME:
756 #ifdef CONFIG_FEATURE_LS_COLOR
757                         errno = 0;
758                         if (show_color && !lstat(dn->fullname, &info)) {
759                                 printf("\033[%d;%dm", bgcolor(info.st_mode),
760                                            fgcolor(info.st_mode));
761                         }
762 #endif
763                         column += printf("%s", dn->name);
764 #ifdef CONFIG_FEATURE_LS_COLOR
765                         if (show_color) {
766                                 printf("\033[0m");
767                         }
768 #endif
769                         break;
770                 case LIST_SYMLINK:
771                         if (S_ISLNK(dn->dstat.st_mode)) {
772                                 char *lpath = xreadlink(dn->fullname);
773
774                                 if (lpath) {
775                                         printf(" -> ");
776 #if defined(CONFIG_FEATURE_LS_FILETYPES) || defined (CONFIG_FEATURE_LS_COLOR)
777                                         if (!stat(dn->fullname, &info)) {
778                                                 append = append_char(info.st_mode);
779                                         }
780 #endif
781 #ifdef CONFIG_FEATURE_LS_COLOR
782                                         if (show_color) {
783                                                 errno = 0;
784                                                 printf("\033[%d;%dm", bgcolor(info.st_mode),
785                                                            fgcolor(info.st_mode));
786                                         }
787 #endif
788                                         column += printf("%s", lpath) + 4;
789 #ifdef CONFIG_FEATURE_LS_COLOR
790                                         if (show_color) {
791                                                 printf("\033[0m");
792                                         }
793 #endif
794                                         free(lpath);
795                                 }
796                         }
797                         break;
798 #ifdef CONFIG_FEATURE_LS_FILETYPES
799                 case LIST_FILETYPE:
800                         if (append != '\0') {
801                                 printf("%1c", append);
802                                 column++;
803                         }
804                         break;
805 #endif
806                 }
807         }
808
809         return column;
810 }
811
812 /*----------------------------------------------------------------------*/
813
814 /* "[-]Cadil1", POSIX mandated options, busybox always supports */
815 /* "[-]gnsx", POSIX non-mandated options, busybox always supports */
816 /* "[-]Ak" GNU options, busybox always supports */
817 /* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
818 /* "[-]p", POSIX non-mandated options, busybox optionally supports */
819 /* "[-]SXvThw", GNU options, busybox optionally supports */
820 /* "[-]K", SELinux mandated options, busybox optionally supports */
821 /* "[-]e", I think we made this one up */
822
823 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
824 # define LS_STR_TIMESTAMPS      "cetu"
825 #else
826 # define LS_STR_TIMESTAMPS      ""
827 #endif
828
829 #ifdef CONFIG_FEATURE_LS_SORTFILES
830 # define LS_STR_SORTFILES       "SXrv"
831 #else
832 # define LS_STR_SORTFILES       ""
833 #endif
834
835 #ifdef CONFIG_FEATURE_LS_FILETYPES
836 # define LS_STR_FILETYPES       "Fp"
837 #else
838 # define LS_STR_FILETYPES       ""
839 #endif
840
841 #ifdef CONFIG_FEATURE_LS_FOLLOWLINKS
842 # define LS_STR_FOLLOW_LINKS    "L"
843 #else
844 # define LS_STR_FOLLOW_LINKS    ""
845 #endif
846
847 #ifdef CONFIG_FEATURE_LS_RECURSIVE
848 # define LS_STR_RECURSIVE       "R"
849 #else
850 # define LS_STR_RECURSIVE       ""
851 #endif
852
853 #ifdef CONFIG_FEATURE_HUMAN_READABLE
854 # define LS_STR_HUMAN_READABLE  "h"
855 #else
856 # define LS_STR_HUMAN_READABLE  ""
857 #endif
858
859 #ifdef CONFIG_SELINUX
860 # define LS_STR_SELINUX "K"
861 #else
862 # define LS_STR_SELINUX ""
863 #endif
864
865 #ifdef CONFIG_FEATURE_AUTOWIDTH
866 # define LS_STR_AUTOWIDTH       "T:w:"
867 #else
868 # define LS_STR_AUTOWIDTH       ""
869 #endif
870
871 static const char ls_options[]="Cadil1gnsxAk" \
872         LS_STR_TIMESTAMPS \
873         LS_STR_SORTFILES \
874         LS_STR_FILETYPES \
875         LS_STR_FOLLOW_LINKS \
876         LS_STR_RECURSIVE \
877         LS_STR_HUMAN_READABLE \
878         LS_STR_SELINUX \
879         LS_STR_AUTOWIDTH;
880
881 #define LIST_MASK_TRIGGER       0
882 #define STYLE_MASK_TRIGGER      STYLE_MASK
883 #define SORT_MASK_TRIGGER       SORT_MASK
884 #define DISP_MASK_TRIGGER       DISP_ROWS
885 #define TIME_MASK_TRIGGER       TIME_MASK
886
887 static const unsigned opt_flags[] = {
888         LIST_SHORT | STYLE_COLUMNS,     /* C */
889         DISP_HIDDEN | DISP_DOT,         /* a */
890         DISP_NOLIST,                    /* d */
891         LIST_INO,                       /* i */
892         LIST_LONG | STYLE_LONG,         /* l - remember LS_DISP_HR in mask! */
893         LIST_SHORT | STYLE_SINGLE,      /* 1 */
894         0,                              /* g - ingored */
895         LIST_ID_NUMERIC,                /* n */
896         LIST_BLOCKS,                    /* s */
897         DISP_ROWS,                      /* x */
898         DISP_HIDDEN,                    /* A */
899 #ifdef CONFIG_SELINUX
900         LIST_CONTEXT,                   /* k */
901 #else
902         0,                              /* k - ingored */
903 #endif
904 #ifdef CONFIG_FEATURE_LS_TIMESTAMPS
905 # ifdef CONFIG_FEATURE_LS_SORTFILES
906         TIME_CHANGE | SORT_CTIME,       /* c */
907 # else
908         TIME_CHANGE,                    /* c */
909 # endif
910         LIST_FULLTIME,                  /* e */
911 # ifdef CONFIG_FEATURE_LS_SORTFILES
912         SORT_MTIME,                     /* t */
913 # else
914         0,                              /* t - ignored -- is this correct? */
915 # endif
916 # ifdef CONFIG_FEATURE_LS_SORTFILES
917         TIME_ACCESS | SORT_ATIME,       /* u */
918 # else
919         TIME_ACCESS,                    /* u */
920 # endif
921 #endif
922 #ifdef CONFIG_FEATURE_LS_SORTFILES
923         SORT_SIZE,                      /* S */
924         SORT_EXT,                       /* X */
925         SORT_ORDER_REVERSE,             /* r */
926         SORT_VERSION,                   /* v */
927 #endif
928 #ifdef CONFIG_FEATURE_LS_FILETYPES
929         LIST_FILETYPE | LIST_EXEC,      /* F */
930         LIST_FILETYPE,                  /* p */
931 #endif
932 #ifdef CONFIG_FEATURE_LS_FOLLOWLINKS
933         FOLLOW_LINKS,                   /* L */
934 #endif
935 #ifdef CONFIG_FEATURE_LS_RECURSIVE
936         DISP_RECURSIVE,                 /* R */
937 #endif
938 #ifdef CONFIG_FEATURE_HUMAN_READABLE
939         LS_DISP_HR,                     /* h */
940 #endif
941 #ifdef CONFIG_SELINUX
942         LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
943 #endif
944         (1U<<31)
945 };
946
947
948 /*----------------------------------------------------------------------*/
949
950 extern int ls_main(int argc, char **argv)
951 {
952         struct dnode **dnd;
953         struct dnode **dnf;
954         struct dnode **dnp;
955         struct dnode *dn;
956         struct dnode *cur;
957         long opt;
958         int nfiles = 0;
959         int dnfiles;
960         int dndirs;
961         int oi;
962         int ac;
963         int i;
964         char **av;
965 #ifdef CONFIG_FEATURE_AUTOWIDTH
966         char *tabstops_str = NULL;
967         char *terminal_width_str = NULL;
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 }