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