source: branches/3.2/mindi-busybox/coreutils/ls.c @ 3186

Last change on this file since 3186 was 2725, checked in by bruno, 9 years ago
  • Update mindi-busybox to 1.18.3 to avoid problems with the tar command which is now failing on recent versions with busybox 1.7.3
File size: 30.2 KB
Line 
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 source tree.
7 */
8
9/* [date unknown. Perhaps before year 2000]
10 * To achieve a small memory footprint, this version of 'ls' doesn't do any
11 * file sorting, and only has the most essential command line switches
12 * (i.e., the ones I couldn't live without :-) All features which involve
13 * linking in substantial chunks of libc can be disabled.
14 *
15 * Although I don't really want to add new features to this program to
16 * keep it small, I *am* interested to receive bug fixes and ways to make
17 * it more portable.
18 *
19 * KNOWN BUGS:
20 * 1. hidden files can make column width too large
21 *
22 * NON-OPTIMAL BEHAVIOUR:
23 * 1. autowidth reads directories twice
24 * 2. if you do a short directory listing without filetype characters
25 *    appended, there's no need to stat each one
26 * PORTABILITY:
27 * 1. requires lstat (BSD) - how do you do it without?
28 *
29 * [2009-03]
30 * ls sorts listing now, and supports almost all options.
31 */
32#include "libbb.h"
33#include "unicode.h"
34
35
36/* This is a NOEXEC applet. Be very careful! */
37
38
39#if ENABLE_FTPD
40/* ftpd uses ls, and without timestamps Mozilla won't understand
41 * ftpd's LIST output.
42 */
43# undef CONFIG_FEATURE_LS_TIMESTAMPS
44# undef ENABLE_FEATURE_LS_TIMESTAMPS
45# undef IF_FEATURE_LS_TIMESTAMPS
46# undef IF_NOT_FEATURE_LS_TIMESTAMPS
47# define CONFIG_FEATURE_LS_TIMESTAMPS 1
48# define ENABLE_FEATURE_LS_TIMESTAMPS 1
49# define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
50# define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
51#endif
52
53
54enum {
55TERMINAL_WIDTH  = 80,           /* use 79 if terminal has linefold bug */
56COLUMN_GAP      = 2,            /* includes the file type char */
57
58/* what is the overall style of the listing */
59STYLE_COLUMNS   = 1 << 21,      /* fill columns */
60STYLE_LONG      = 2 << 21,      /* one record per line, extended info */
61STYLE_SINGLE    = 3 << 21,      /* one record per line */
62STYLE_MASK      = STYLE_SINGLE,
63
64/* 51306 lrwxrwxrwx  1 root     root         2 May 11 01:43 /bin/view -> vi* */
65/* what file information will be listed */
66LIST_INO        = 1 << 0,
67LIST_BLOCKS     = 1 << 1,
68LIST_MODEBITS   = 1 << 2,
69LIST_NLINKS     = 1 << 3,
70LIST_ID_NAME    = 1 << 4,
71LIST_ID_NUMERIC = 1 << 5,
72LIST_CONTEXT    = 1 << 6,
73LIST_SIZE       = 1 << 7,
74//LIST_DEV        = 1 << 8, - unused, synonym to LIST_SIZE
75LIST_DATE_TIME  = 1 << 9,
76LIST_FULLTIME   = 1 << 10,
77LIST_FILENAME   = 1 << 11,
78LIST_SYMLINK    = 1 << 12,
79LIST_FILETYPE   = 1 << 13,
80LIST_EXEC       = 1 << 14,
81LIST_MASK       = (LIST_EXEC << 1) - 1,
82
83/* what files will be displayed */
84DISP_DIRNAME    = 1 << 15,      /* 2 or more items? label directories */
85DISP_HIDDEN     = 1 << 16,      /* show filenames starting with . */
86DISP_DOT        = 1 << 17,      /* show . and .. */
87DISP_NOLIST     = 1 << 18,      /* show directory as itself, not contents */
88DISP_RECURSIVE  = 1 << 19,      /* show directory and everything below it */
89DISP_ROWS       = 1 << 20,      /* print across rows */
90DISP_MASK       = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
91
92/* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
93SORT_FORWARD    = 0,            /* sort in reverse order */
94SORT_REVERSE    = 1 << 27,      /* sort in reverse order */
95
96SORT_NAME       = 0,            /* sort by file name */
97SORT_SIZE       = 1 << 28,      /* sort by file size */
98SORT_ATIME      = 2 << 28,      /* sort by last access time */
99SORT_CTIME      = 3 << 28,      /* sort by last change time */
100SORT_MTIME      = 4 << 28,      /* sort by last modification time */
101SORT_VERSION    = 5 << 28,      /* sort by version */
102SORT_EXT        = 6 << 28,      /* sort by file name extension */
103SORT_DIR        = 7 << 28,      /* sort by file or directory */
104SORT_MASK       = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
105
106/* which of the three times will be used */
107TIME_CHANGE     = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
108TIME_ACCESS     = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
109TIME_MASK       = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
110
111FOLLOW_LINKS    = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
112
113LS_DISP_HR      = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
114
115LIST_SHORT      = LIST_FILENAME,
116LIST_LONG       = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
117                  LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
118
119SPLIT_DIR       = 1,
120SPLIT_FILE      = 0,
121SPLIT_SUBDIR    = 2,
122};
123
124/* "[-]Cadil1", POSIX mandated options, busybox always supports */
125/* "[-]gnsx", POSIX non-mandated options, busybox always supports */
126/* "[-]Q" GNU option? busybox always supports */
127/* "[-]Ak" GNU options, busybox always supports */
128/* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
129/* "[-]p", POSIX non-mandated options, busybox optionally supports */
130/* "[-]SXvThw", GNU options, busybox optionally supports */
131/* "[-]K", SELinux mandated options, busybox optionally supports */
132/* "[-]e", I think we made this one up */
133static const char ls_options[] ALIGN1 =
134    "Cadil1gnsxQAk" /* 13 opts, total 13 */
135    IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
136    IF_FEATURE_LS_SORTFILES("SXrv")  /* 4, 21 */
137    IF_FEATURE_LS_FILETYPES("Fp")    /* 2, 23 */
138    IF_FEATURE_LS_FOLLOWLINKS("L")   /* 1, 24 */
139    IF_FEATURE_LS_RECURSIVE("R")     /* 1, 25 */
140    IF_FEATURE_HUMAN_READABLE("h")   /* 1, 26 */
141    IF_SELINUX("KZ") /* 2, 28 */
142    IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
143    ;
144enum {
145    //OPT_C = (1 << 0),
146    //OPT_a = (1 << 1),
147    //OPT_d = (1 << 2),
148    //OPT_i = (1 << 3),
149    //OPT_l = (1 << 4),
150    //OPT_1 = (1 << 5),
151    OPT_g = (1 << 6),
152    //OPT_n = (1 << 7),
153    //OPT_s = (1 << 8),
154    //OPT_x = (1 << 9),
155    OPT_Q = (1 << 10),
156    //OPT_A = (1 << 11),
157    //OPT_k = (1 << 12),
158    OPTBIT_color = 13
159        + 4 * ENABLE_FEATURE_LS_TIMESTAMPS
160        + 4 * ENABLE_FEATURE_LS_SORTFILES
161        + 2 * ENABLE_FEATURE_LS_FILETYPES
162        + 1 * ENABLE_FEATURE_LS_FOLLOWLINKS
163        + 1 * ENABLE_FEATURE_LS_RECURSIVE
164        + 1 * ENABLE_FEATURE_HUMAN_READABLE
165        + 2 * ENABLE_SELINUX
166        + 2 * ENABLE_FEATURE_AUTOWIDTH,
167    OPT_color = 1 << OPTBIT_color,
168};
169
170enum {
171    LIST_MASK_TRIGGER   = 0,
172    STYLE_MASK_TRIGGER  = STYLE_MASK,
173    DISP_MASK_TRIGGER   = DISP_ROWS,
174    SORT_MASK_TRIGGER   = SORT_MASK,
175};
176
177/* TODO: simple toggles may be stored as OPT_xxx bits instead */
178static const unsigned opt_flags[] = {
179    LIST_SHORT | STYLE_COLUMNS, /* C */
180    DISP_HIDDEN | DISP_DOT,     /* a */
181    DISP_NOLIST,                /* d */
182    LIST_INO,                   /* i */
183    LIST_LONG | STYLE_LONG,     /* l - remember LS_DISP_HR in mask! */
184    LIST_SHORT | STYLE_SINGLE,  /* 1 */
185    0,                          /* g (don't show owner) - handled via OPT_g */
186    LIST_ID_NUMERIC,            /* n */
187    LIST_BLOCKS,                /* s */
188    DISP_ROWS,                  /* x */
189    0,                          /* Q (quote filename) - handled via OPT_Q */
190    DISP_HIDDEN,                /* A */
191    ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
192#if ENABLE_FEATURE_LS_TIMESTAMPS
193    TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME),   /* c */
194    LIST_FULLTIME,              /* e */
195    ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME,   /* t */
196    TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME),   /* u */
197#endif
198#if ENABLE_FEATURE_LS_SORTFILES
199    SORT_SIZE,                  /* S */
200    SORT_EXT,                   /* X */
201    SORT_REVERSE,               /* r */
202    SORT_VERSION,               /* v */
203#endif
204#if ENABLE_FEATURE_LS_FILETYPES
205    LIST_FILETYPE | LIST_EXEC,  /* F */
206    LIST_FILETYPE,              /* p */
207#endif
208#if ENABLE_FEATURE_LS_FOLLOWLINKS
209    FOLLOW_LINKS,               /* L */
210#endif
211#if ENABLE_FEATURE_LS_RECURSIVE
212    DISP_RECURSIVE,             /* R */
213#endif
214#if ENABLE_FEATURE_HUMAN_READABLE
215    LS_DISP_HR,                 /* h */
216#endif
217#if ENABLE_SELINUX
218    LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
219#endif
220#if ENABLE_SELINUX
221    LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
222#endif
223    (1U<<31)
224    /* options after Z are not processed through opt_flags:
225     * T, w - ignored
226     */
227};
228
229
230/*
231 * a directory entry and its stat info are stored here
232 */
233struct dnode {
234    const char *name;       /* the dir entry name */
235    const char *fullname;   /* the dir entry name */
236    struct dnode *next;     /* point at the next node */
237    smallint fname_allocated;
238    struct stat dstat;      /* the file stat info */
239    IF_SELINUX(security_context_t sid;)
240};
241
242struct globals {
243#if ENABLE_FEATURE_LS_COLOR
244    smallint show_color;
245#endif
246    smallint exit_code;
247    unsigned all_fmt;
248#if ENABLE_FEATURE_AUTOWIDTH
249    unsigned tabstops; // = COLUMN_GAP;
250    unsigned terminal_width; // = TERMINAL_WIDTH;
251#endif
252#if ENABLE_FEATURE_LS_TIMESTAMPS
253    /* Do time() just once. Saves one syscall per file for "ls -l" */
254    time_t current_time_t;
255#endif
256} FIX_ALIASING;
257#define G (*(struct globals*)&bb_common_bufsiz1)
258#if ENABLE_FEATURE_LS_COLOR
259# define show_color     (G.show_color    )
260#else
261enum { show_color = 0 };
262#endif
263#define exit_code       (G.exit_code     )
264#define all_fmt         (G.all_fmt       )
265#if ENABLE_FEATURE_AUTOWIDTH
266# define tabstops       (G.tabstops      )
267# define terminal_width (G.terminal_width)
268#else
269enum {
270    tabstops = COLUMN_GAP,
271    terminal_width = TERMINAL_WIDTH,
272};
273#endif
274#define current_time_t (G.current_time_t)
275#define INIT_G() do { \
276    /* we have to zero it out because of NOEXEC */ \
277    memset(&G, 0, sizeof(G)); \
278    IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
279    IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
280    IF_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
281} while (0)
282
283
284static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
285{
286    struct stat dstat;
287    struct dnode *cur;
288    IF_SELINUX(security_context_t sid = NULL;)
289
290    if ((all_fmt & FOLLOW_LINKS) || force_follow) {
291#if ENABLE_SELINUX
292        if (is_selinux_enabled())  {
293             getfilecon(fullname, &sid);
294        }
295#endif
296        if (stat(fullname, &dstat)) {
297            bb_simple_perror_msg(fullname);
298            exit_code = EXIT_FAILURE;
299            return 0;
300        }
301    } else {
302#if ENABLE_SELINUX
303        if (is_selinux_enabled()) {
304            lgetfilecon(fullname, &sid);
305        }
306#endif
307        if (lstat(fullname, &dstat)) {
308            bb_simple_perror_msg(fullname);
309            exit_code = EXIT_FAILURE;
310            return 0;
311        }
312    }
313
314    cur = xmalloc(sizeof(*cur));
315    cur->fullname = fullname;
316    cur->name = name;
317    cur->dstat = dstat;
318    IF_SELINUX(cur->sid = sid;)
319    return cur;
320}
321
322/* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
323 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
324 *  3/7:multiplexed char/block device)
325 * and we use 0 for unknown and 15 for executables (see below) */
326#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
327#define TYPECHAR(mode)  ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
328#define APPCHAR(mode)   ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
329/* 036 black foreground              050 black background
330   037 red foreground                051 red background
331   040 green foreground              052 green background
332   041 brown foreground              053 brown background
333   042 blue foreground               054 blue background
334   043 magenta (purple) foreground   055 magenta background
335   044 cyan (light blue) foreground  056 cyan background
336   045 gray foreground               057 white background
337*/
338#define COLOR(mode) ( \
339    /*un  fi  chr     dir     blk     file    link    sock        exe */ \
340    "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
341    [TYPEINDEX(mode)])
342/* Select normal (0) [actually "reset all"] or bold (1)
343 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
344 *  let's use 7 for "impossible" types, just for fun)
345 * Note: coreutils 6.9 uses inverted red for setuid binaries.
346 */
347#define ATTR(mode) ( \
348    /*un fi chr   dir   blk   file  link  sock     exe */ \
349    "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
350    [TYPEINDEX(mode)])
351
352#if ENABLE_FEATURE_LS_COLOR
353/* mode of zero is interpreted as "unknown" (stat failed) */
354static char fgcolor(mode_t mode)
355{
356    if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
357        return COLOR(0xF000);   /* File is executable ... */
358    return COLOR(mode);
359}
360static char bold(mode_t mode)
361{
362    if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
363        return ATTR(0xF000);    /* File is executable ... */
364    return ATTR(mode);
365}
366#endif
367
368#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
369static char append_char(mode_t mode)
370{
371    if (!(all_fmt & LIST_FILETYPE))
372        return '\0';
373    if (S_ISDIR(mode))
374        return '/';
375    if (!(all_fmt & LIST_EXEC))
376        return '\0';
377    if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
378        return '*';
379    return APPCHAR(mode);
380}
381#endif
382
383static unsigned count_dirs(struct dnode **dn, int which)
384{
385    unsigned dirs, all;
386
387    if (!dn)
388        return 0;
389
390    dirs = all = 0;
391    for (; *dn; dn++) {
392        const char *name;
393
394        all++;
395        if (!S_ISDIR((*dn)->dstat.st_mode))
396            continue;
397        name = (*dn)->name;
398        if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
399         /* or if it's not . or .. */
400         || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
401        ) {
402            dirs++;
403        }
404    }
405    return which != SPLIT_FILE ? dirs : all - dirs;
406}
407
408/* get memory to hold an array of pointers */
409static struct dnode **dnalloc(unsigned num)
410{
411    if (num < 1)
412        return NULL;
413
414    num++; /* so that we have terminating NULL */
415    return xzalloc(num * sizeof(struct dnode *));
416}
417
418#if ENABLE_FEATURE_LS_RECURSIVE
419static void dfree(struct dnode **dnp)
420{
421    unsigned i;
422
423    if (dnp == NULL)
424        return;
425
426    for (i = 0; dnp[i]; i++) {
427        struct dnode *cur = dnp[i];
428        if (cur->fname_allocated)
429            free((char*)cur->fullname);
430        free(cur);
431    }
432    free(dnp);
433}
434#else
435#define dfree(...) ((void)0)
436#endif
437
438/* Returns NULL-terminated malloced vector of pointers (or NULL) */
439static struct dnode **splitdnarray(struct dnode **dn, int which)
440{
441    unsigned dncnt, d;
442    struct dnode **dnp;
443
444    if (dn == NULL)
445        return NULL;
446
447    /* count how many dirs or files there are */
448    dncnt = count_dirs(dn, which);
449
450    /* allocate a file array and a dir array */
451    dnp = dnalloc(dncnt);
452
453    /* copy the entrys into the file or dir array */
454    for (d = 0; *dn; dn++) {
455        if (S_ISDIR((*dn)->dstat.st_mode)) {
456            const char *name;
457
458            if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
459                continue;
460            name = (*dn)->name;
461            if ((which & SPLIT_DIR)
462             || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
463            ) {
464                dnp[d++] = *dn;
465            }
466        } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
467            dnp[d++] = *dn;
468        }
469    }
470    return dnp;
471}
472
473#if ENABLE_FEATURE_LS_SORTFILES
474static int sortcmp(const void *a, const void *b)
475{
476    struct dnode *d1 = *(struct dnode **)a;
477    struct dnode *d2 = *(struct dnode **)b;
478    unsigned sort_opts = all_fmt & SORT_MASK;
479    off_t dif;
480
481    dif = 0; /* assume SORT_NAME */
482    // TODO: use pre-initialized function pointer
483    // instead of branch forest
484    if (sort_opts == SORT_SIZE) {
485        dif = (d2->dstat.st_size - d1->dstat.st_size);
486    } else if (sort_opts == SORT_ATIME) {
487        dif = (d2->dstat.st_atime - d1->dstat.st_atime);
488    } else if (sort_opts == SORT_CTIME) {
489        dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
490    } else if (sort_opts == SORT_MTIME) {
491        dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
492    } else if (sort_opts == SORT_DIR) {
493        dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
494        /* } else if (sort_opts == SORT_VERSION) { */
495        /* } else if (sort_opts == SORT_EXT) { */
496    }
497    if (dif == 0) {
498        /* sort by name, or tie_breaker for other sorts */
499        if (ENABLE_LOCALE_SUPPORT)
500            dif = strcoll(d1->name, d2->name);
501        else
502            dif = strcmp(d1->name, d2->name);
503    }
504
505    /* Make dif fit into an int */
506    if (sizeof(dif) > sizeof(int)) {
507        enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
508        /* shift leaving only "int" worth of bits */
509        if (dif != 0) {
510            dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
511        }
512    }
513
514    return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
515}
516
517static void dnsort(struct dnode **dn, int size)
518{
519    qsort(dn, size, sizeof(*dn), sortcmp);
520}
521#else
522#define dnsort(dn, size) ((void)0)
523#endif
524
525
526static unsigned calc_name_len(const char *name)
527{
528    unsigned len;
529    uni_stat_t uni_stat;
530
531    // TODO: quote tab as \t, etc, if -Q
532    name = printable_string(&uni_stat, name);
533
534    if (!(option_mask32 & OPT_Q)) {
535        return uni_stat.unicode_width;
536    }
537
538    len = 2 + uni_stat.unicode_width;
539    while (*name) {
540        if (*name == '"' || *name == '\\') {
541            len++;
542        }
543        name++;
544    }
545    return len;
546}
547
548
549/* Return the number of used columns.
550 * Note that only STYLE_COLUMNS uses return value.
551 * STYLE_SINGLE and STYLE_LONG don't care.
552 * coreutils 7.2 also supports:
553 * ls -b (--escape) = octal escapes (although it doesn't look like working)
554 * ls -N (--literal) = not escape at all
555 */
556static unsigned print_name(const char *name)
557{
558    unsigned len;
559    uni_stat_t uni_stat;
560
561    // TODO: quote tab as \t, etc, if -Q
562    name = printable_string(&uni_stat, name);
563
564    if (!(option_mask32 & OPT_Q)) {
565        fputs(name, stdout);
566        return uni_stat.unicode_width;
567    }
568
569    len = 2 + uni_stat.unicode_width;
570    putchar('"');
571    while (*name) {
572        if (*name == '"' || *name == '\\') {
573            putchar('\\');
574            len++;
575        }
576        putchar(*name);
577        name++;
578    }
579    putchar('"');
580    return len;
581}
582
583/* Return the number of used columns.
584 * Note that only STYLE_COLUMNS uses return value,
585 * STYLE_SINGLE and STYLE_LONG don't care.
586 */
587static NOINLINE unsigned list_single(const struct dnode *dn)
588{
589    unsigned column = 0;
590    char *lpath = lpath; /* for compiler */
591#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
592    struct stat info;
593    char append;
594#endif
595
596    /* Never happens:
597    if (dn->fullname == NULL)
598        return 0;
599    */
600
601#if ENABLE_FEATURE_LS_FILETYPES
602    append = append_char(dn->dstat.st_mode);
603#endif
604
605    /* Do readlink early, so that if it fails, error message
606     * does not appear *inside* the "ls -l" line */
607    if (all_fmt & LIST_SYMLINK)
608        if (S_ISLNK(dn->dstat.st_mode))
609            lpath = xmalloc_readlink_or_warn(dn->fullname);
610
611    if (all_fmt & LIST_INO)
612        column += printf("%7llu ", (long long) dn->dstat.st_ino);
613    if (all_fmt & LIST_BLOCKS)
614        column += printf("%4"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
615    if (all_fmt & LIST_MODEBITS)
616        column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
617    if (all_fmt & LIST_NLINKS)
618        column += printf("%4lu ", (long) dn->dstat.st_nlink);
619#if ENABLE_FEATURE_LS_USERNAME
620    if (all_fmt & LIST_ID_NAME) {
621        if (option_mask32 & OPT_g) {
622            column += printf("%-8.8s ",
623                get_cached_groupname(dn->dstat.st_gid));
624        } else {
625            column += printf("%-8.8s %-8.8s ",
626                get_cached_username(dn->dstat.st_uid),
627                get_cached_groupname(dn->dstat.st_gid));
628        }
629    }
630#endif
631    if (all_fmt & LIST_ID_NUMERIC) {
632        if (option_mask32 & OPT_g)
633            column += printf("%-8u ", (int) dn->dstat.st_gid);
634        else
635            column += printf("%-8u %-8u ",
636                    (int) dn->dstat.st_uid,
637                    (int) dn->dstat.st_gid);
638    }
639    if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
640        if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
641            column += printf("%4u, %3u ",
642                    (int) major(dn->dstat.st_rdev),
643                    (int) minor(dn->dstat.st_rdev));
644        } else {
645            if (all_fmt & LS_DISP_HR) {
646                column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
647                    /* print st_size, show one fractional, use suffixes */
648                    make_human_readable_str(dn->dstat.st_size, 1, 0)
649                );
650            } else {
651                column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
652            }
653        }
654    }
655#if ENABLE_FEATURE_LS_TIMESTAMPS
656    if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
657        char *filetime;
658        time_t ttime = dn->dstat.st_mtime;
659        if (all_fmt & TIME_ACCESS)
660            ttime = dn->dstat.st_atime;
661        if (all_fmt & TIME_CHANGE)
662            ttime = dn->dstat.st_ctime;
663        filetime = ctime(&ttime);
664        /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
665        if (all_fmt & LIST_FULLTIME)
666            column += printf("%.24s ", filetime);
667        else { /* LIST_DATE_TIME */
668            /* current_time_t ~== time(NULL) */
669            time_t age = current_time_t - ttime;
670            printf("%.6s ", filetime + 4); /* "Jun 30" */
671            if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
672                /* hh:mm if less than 6 months old */
673                printf("%.5s ", filetime + 11);
674            } else { /* year. buggy if year > 9999 ;) */
675                printf(" %.4s ", filetime + 20);
676            }
677            column += 13;
678        }
679    }
680#endif
681#if ENABLE_SELINUX
682    if (all_fmt & LIST_CONTEXT) {
683        column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
684        freecon(dn->sid);
685    }
686#endif
687    if (all_fmt & LIST_FILENAME) {
688#if ENABLE_FEATURE_LS_COLOR
689        if (show_color) {
690            info.st_mode = 0; /* for fgcolor() */
691            lstat(dn->fullname, &info);
692            printf("\033[%u;%um", bold(info.st_mode),
693                    fgcolor(info.st_mode));
694        }
695#endif
696        column += print_name(dn->name);
697        if (show_color) {
698            printf("\033[0m");
699        }
700    }
701    if (all_fmt & LIST_SYMLINK) {
702        if (S_ISLNK(dn->dstat.st_mode) && lpath) {
703            printf(" -> ");
704#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
705#if ENABLE_FEATURE_LS_COLOR
706            info.st_mode = 0; /* for fgcolor() */
707#endif
708            if (stat(dn->fullname, &info) == 0) {
709                append = append_char(info.st_mode);
710            }
711#endif
712#if ENABLE_FEATURE_LS_COLOR
713            if (show_color) {
714                printf("\033[%u;%um", bold(info.st_mode),
715                       fgcolor(info.st_mode));
716            }
717#endif
718            column += print_name(lpath) + 4;
719            if (show_color) {
720                printf("\033[0m");
721            }
722            free(lpath);
723        }
724    }
725#if ENABLE_FEATURE_LS_FILETYPES
726    if (all_fmt & LIST_FILETYPE) {
727        if (append) {
728            putchar(append);
729            column++;
730        }
731    }
732#endif
733
734    return column;
735}
736
737static void showfiles(struct dnode **dn, unsigned nfiles)
738{
739    unsigned i, ncols, nrows, row, nc;
740    unsigned column = 0;
741    unsigned nexttab = 0;
742    unsigned column_width = 0; /* used only by STYLE_COLUMNS */
743
744    if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
745        ncols = 1;
746    } else {
747        /* find the longest file name, use that as the column width */
748        for (i = 0; dn[i]; i++) {
749            int len = calc_name_len(dn[i]->name);
750            if (column_width < len)
751                column_width = len;
752        }
753        column_width += tabstops +
754            IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
755                ((all_fmt & LIST_INO) ? 8 : 0) +
756                ((all_fmt & LIST_BLOCKS) ? 5 : 0);
757        ncols = (int) (terminal_width / column_width);
758    }
759
760    if (ncols > 1) {
761        nrows = nfiles / ncols;
762        if (nrows * ncols < nfiles)
763            nrows++;                /* round up fractionals */
764    } else {
765        nrows = nfiles;
766        ncols = 1;
767    }
768
769    for (row = 0; row < nrows; row++) {
770        for (nc = 0; nc < ncols; nc++) {
771            /* reach into the array based on the column and row */
772            if (all_fmt & DISP_ROWS)
773                i = (row * ncols) + nc; /* display across row */
774            else
775                i = (nc * nrows) + row; /* display by column */
776            if (i < nfiles) {
777                if (column > 0) {
778                    nexttab -= column;
779                    printf("%*s", nexttab, "");
780                    column += nexttab;
781                }
782                nexttab = column + column_width;
783                column += list_single(dn[i]);
784            }
785        }
786        putchar('\n');
787        column = 0;
788    }
789}
790
791
792#if ENABLE_DESKTOP
793/* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
794 * If any of the -l, -n, -s options is specified, each list
795 * of files within the directory shall be preceded by a
796 * status line indicating the number of file system blocks
797 * occupied by files in the directory in 512-byte units if
798 * the -k option is not specified, or 1024-byte units if the
799 * -k option is specified, rounded up to the next integral
800 * number of units.
801 */
802/* by Jorgen Overgaard (jorgen AT antistaten.se) */
803static off_t calculate_blocks(struct dnode **dn)
804{
805    uoff_t blocks = 1;
806    if (dn) {
807        while (*dn) {
808            /* st_blocks is in 512 byte blocks */
809            blocks += (*dn)->dstat.st_blocks;
810            dn++;
811        }
812    }
813
814    /* Even though standard says use 512 byte blocks, coreutils use 1k */
815    /* Actually, we round up by calculating (blocks + 1) / 2,
816     * "+ 1" was done when we initialized blocks to 1 */
817    return blocks >> 1;
818}
819#endif
820
821
822static struct dnode **list_dir(const char *, unsigned *);
823
824static void showdirs(struct dnode **dn, int first)
825{
826    unsigned nfiles;
827    unsigned dndirs;
828    struct dnode **subdnp;
829    struct dnode **dnd;
830
831    /* Never happens:
832    if (dn == NULL || ndirs < 1) {
833        return;
834    }
835    */
836
837    for (; *dn; dn++) {
838        if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
839            if (!first)
840                bb_putchar('\n');
841            first = 0;
842            printf("%s:\n", (*dn)->fullname);
843        }
844        subdnp = list_dir((*dn)->fullname, &nfiles);
845#if ENABLE_DESKTOP
846        if ((all_fmt & STYLE_MASK) == STYLE_LONG)
847            printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
848#endif
849        if (nfiles > 0) {
850            /* list all files at this level */
851            dnsort(subdnp, nfiles);
852            showfiles(subdnp, nfiles);
853            if (ENABLE_FEATURE_LS_RECURSIVE
854             && (all_fmt & DISP_RECURSIVE)
855            ) {
856                /* recursive - list the sub-dirs */
857                dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
858                dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
859                if (dndirs > 0) {
860                    dnsort(dnd, dndirs);
861                    showdirs(dnd, 0);
862                    /* free the array of dnode pointers to the dirs */
863                    free(dnd);
864                }
865            }
866            /* free the dnodes and the fullname mem */
867            dfree(subdnp);
868        }
869    }
870}
871
872
873/* Returns NULL-terminated malloced vector of pointers (or NULL) */
874static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
875{
876    struct dnode *dn, *cur, **dnp;
877    struct dirent *entry;
878    DIR *dir;
879    unsigned i, nfiles;
880
881    /* Never happens:
882    if (path == NULL)
883        return NULL;
884    */
885
886    *nfiles_p = 0;
887    dir = warn_opendir(path);
888    if (dir == NULL) {
889        exit_code = EXIT_FAILURE;
890        return NULL;    /* could not open the dir */
891    }
892    dn = NULL;
893    nfiles = 0;
894    while ((entry = readdir(dir)) != NULL) {
895        char *fullname;
896
897        /* are we going to list the file- it may be . or .. or a hidden file */
898        if (entry->d_name[0] == '.') {
899            if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
900             && !(all_fmt & DISP_DOT)
901            ) {
902                continue;
903            }
904            if (!(all_fmt & DISP_HIDDEN))
905                continue;
906        }
907        fullname = concat_path_file(path, entry->d_name);
908        cur = my_stat(fullname, bb_basename(fullname), 0);
909        if (!cur) {
910            free(fullname);
911            continue;
912        }
913        cur->fname_allocated = 1;
914        cur->next = dn;
915        dn = cur;
916        nfiles++;
917    }
918    closedir(dir);
919
920    if (dn == NULL)
921        return NULL;
922
923    /* now that we know how many files there are
924     * allocate memory for an array to hold dnode pointers
925     */
926    *nfiles_p = nfiles;
927    dnp = dnalloc(nfiles);
928    for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
929        dnp[i] = dn;    /* save pointer to node in array */
930        dn = dn->next;
931        if (!dn)
932            break;
933    }
934
935    return dnp;
936}
937
938
939int ls_main(int argc UNUSED_PARAM, char **argv)
940{
941    struct dnode **dnd;
942    struct dnode **dnf;
943    struct dnode **dnp;
944    struct dnode *dn;
945    struct dnode *cur;
946    unsigned opt;
947    unsigned nfiles;
948    unsigned dnfiles;
949    unsigned dndirs;
950    unsigned i;
951#if ENABLE_FEATURE_LS_COLOR
952    /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
953    /* coreutils 6.10:
954     * # ls --color=BOGUS
955     * ls: invalid argument 'BOGUS' for '--color'
956     * Valid arguments are:
957     * 'always', 'yes', 'force'
958     * 'never', 'no', 'none'
959     * 'auto', 'tty', 'if-tty'
960     * (and substrings: "--color=alwa" work too)
961     */
962    static const char ls_longopts[] ALIGN1 =
963        "color\0" Optional_argument "\xff"; /* no short equivalent */
964    static const char color_str[] ALIGN1 =
965        "always\0""yes\0""force\0"
966        "auto\0""tty\0""if-tty\0";
967    /* need to initialize since --color has _an optional_ argument */
968    const char *color_opt = color_str; /* "always" */
969#endif
970
971    INIT_G();
972
973    init_unicode();
974
975    all_fmt = LIST_SHORT |
976        (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
977
978#if ENABLE_FEATURE_AUTOWIDTH
979    /* obtain the terminal width */
980    get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
981    /* go one less... */
982    terminal_width--;
983#endif
984
985    /* process options */
986    IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
987#if ENABLE_FEATURE_AUTOWIDTH
988    opt_complementary = "T+:w+"; /* -T N, -w N */
989    opt = getopt32(argv, ls_options, &tabstops, &terminal_width
990                IF_FEATURE_LS_COLOR(, &color_opt));
991#else
992    opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
993#endif
994    for (i = 0; opt_flags[i] != (1U<<31); i++) {
995        if (opt & (1 << i)) {
996            unsigned flags = opt_flags[i];
997
998            if (flags & LIST_MASK_TRIGGER)
999                all_fmt &= ~LIST_MASK;
1000            if (flags & STYLE_MASK_TRIGGER)
1001                all_fmt &= ~STYLE_MASK;
1002            if (flags & SORT_MASK_TRIGGER)
1003                all_fmt &= ~SORT_MASK;
1004            if (flags & DISP_MASK_TRIGGER)
1005                all_fmt &= ~DISP_MASK;
1006            if (flags & TIME_MASK)
1007                all_fmt &= ~TIME_MASK;
1008            if (flags & LIST_CONTEXT)
1009                all_fmt |= STYLE_SINGLE;
1010            /* huh?? opt cannot be 'l' */
1011            //if (LS_DISP_HR && opt == 'l')
1012            //  all_fmt &= ~LS_DISP_HR;
1013            all_fmt |= flags;
1014        }
1015    }
1016
1017#if ENABLE_FEATURE_LS_COLOR
1018    /* find color bit value - last position for short getopt */
1019    if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1020        char *p = getenv("LS_COLORS");
1021        /* LS_COLORS is unset, or (not empty && not "none") ? */
1022        if (!p || (p[0] && strcmp(p, "none") != 0))
1023            show_color = 1;
1024    }
1025    if (opt & OPT_color) {
1026        if (color_opt[0] == 'n')
1027            show_color = 0;
1028        else switch (index_in_substrings(color_str, color_opt)) {
1029        case 3:
1030        case 4:
1031        case 5:
1032            if (isatty(STDOUT_FILENO)) {
1033        case 0:
1034        case 1:
1035        case 2:
1036                show_color = 1;
1037            }
1038        }
1039    }
1040#endif
1041
1042    /* sort out which command line options take precedence */
1043    if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1044        all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1045    if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1046        if (all_fmt & TIME_CHANGE)
1047            all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1048        if (all_fmt & TIME_ACCESS)
1049            all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1050    }
1051    if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1052        all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
1053    if (ENABLE_FEATURE_LS_USERNAME)
1054        if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1055            all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
1056
1057    /* choose a display format */
1058    if (!(all_fmt & STYLE_MASK))
1059        all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1060
1061    argv += optind;
1062    if (!argv[0])
1063        *--argv = (char*)".";
1064
1065    if (argv[1])
1066        all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1067
1068    /* stuff the command line file names into a dnode array */
1069    dn = NULL;
1070    nfiles = 0;
1071    do {
1072        /* NB: follow links on command line unless -l or -s */
1073        cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1074        argv++;
1075        if (!cur)
1076            continue;
1077        cur->fname_allocated = 0;
1078        cur->next = dn;
1079        dn = cur;
1080        nfiles++;
1081    } while (*argv);
1082
1083    /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1084    if (nfiles == 0)
1085        return exit_code;
1086
1087    /* now that we know how many files there are
1088     * allocate memory for an array to hold dnode pointers
1089     */
1090    dnp = dnalloc(nfiles);
1091    for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1092        dnp[i] = dn;    /* save pointer to node in array */
1093        dn = dn->next;
1094        if (!dn)
1095            break;
1096    }
1097
1098    if (all_fmt & DISP_NOLIST) {
1099        dnsort(dnp, nfiles);
1100        showfiles(dnp, nfiles);
1101    } else {
1102        dnd = splitdnarray(dnp, SPLIT_DIR);
1103        dnf = splitdnarray(dnp, SPLIT_FILE);
1104        dndirs = count_dirs(dnp, SPLIT_DIR);
1105        dnfiles = nfiles - dndirs;
1106        if (dnfiles > 0) {
1107            dnsort(dnf, dnfiles);
1108            showfiles(dnf, dnfiles);
1109            if (ENABLE_FEATURE_CLEAN_UP)
1110                free(dnf);
1111        }
1112        if (dndirs > 0) {
1113            dnsort(dnd, dndirs);
1114            showdirs(dnd, dnfiles == 0);
1115            if (ENABLE_FEATURE_CLEAN_UP)
1116                free(dnd);
1117        }
1118    }
1119    if (ENABLE_FEATURE_CLEAN_UP)
1120        dfree(dnp);
1121    return exit_code;
1122}
Note: See TracBrowser for help on using the repository browser.