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