busybox/coreutils/ls.c
<<
>>
Prefs
   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}
1123