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//TODO: -h should affect -s too:
 502        if (opt & OPT_s) /* show allocated blocks */
 503                column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1));
 504        if (opt & OPT_l) {
 505                /* long listing: show mode */
 506                char modestr[12];
 507                column += printf("%-10s ", bb_mode_string(modestr, dn->dn_mode));
 508                /* long listing: show number of links */
 509                column += printf("%4lu ", (long) dn->dn_nlink);
 510                /* long listing: show user/group */
 511                if (opt & OPT_n) {
 512                        if (opt & OPT_g)
 513                                column += printf("%-8u ", (int) dn->dn_gid);
 514                        else
 515                                column += printf("%-8u %-8u ",
 516                                                (int) dn->dn_uid,
 517                                                (int) dn->dn_gid);
 518                }
 519#if ENABLE_FEATURE_LS_USERNAME
 520                else {
 521                        if (opt & OPT_g) {
 522                                column += printf("%-8.8s ",
 523                                        get_cached_groupname(dn->dn_gid));
 524                        } else {
 525                                column += printf("%-8.8s %-8.8s ",
 526                                        get_cached_username(dn->dn_uid),
 527                                        get_cached_groupname(dn->dn_gid));
 528                        }
 529                }
 530#endif
 531#if ENABLE_SELINUX
 532        }
 533        if (opt & OPT_Z) {
 534                column += printf("%-32s ", dn->sid ? dn->sid : "?");
 535                freecon(dn->sid);
 536        }
 537        if (opt & OPT_l) {
 538#endif
 539                /* long listing: show size */
 540                if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
 541                        column += printf("%4u, %3u ",
 542                                        dn->dn_rdev_maj,
 543                                        dn->dn_rdev_min);
 544                } else {
 545                        if (opt & OPT_h) {
 546                                column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
 547                                        /* print size, show one fractional, use suffixes */
 548                                        make_human_readable_str(dn->dn_size, 1, 0)
 549                                );
 550                        } else {
 551                                column += printf("%9"OFF_FMT"u ", dn->dn_size);
 552                        }
 553                }
 554#if ENABLE_FEATURE_LS_TIMESTAMPS
 555                /* long listing: show {m,c,a}time */
 556                if (opt & OPT_full_time) { /* --full-time */
 557                        /* coreutils 8.4 ls --full-time prints:
 558                         * 2009-07-13 17:49:27.000000000 +0200
 559                         * we don't show fractional seconds.
 560                         */
 561                        char buf[sizeof("YYYY-mm-dd HH:MM:SS TIMEZONE")];
 562                        strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %z",
 563                                        localtime(&dn->dn_time));
 564                        column += printf("%s ", buf);
 565                } else { /* ordinary time format */
 566                        /* G.current_time_t is ~== time(NULL) */
 567                        char *filetime = ctime(&dn->dn_time);
 568                        /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
 569                        time_t age = G.current_time_t - dn->dn_time;
 570                        if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
 571                                /* less than 6 months old */
 572                                /* "mmm dd hh:mm " */
 573                                printf("%.12s ", filetime + 4);
 574                        } else {
 575                                /* "mmm dd  yyyy " */
 576                                /* "mmm dd yyyyy " after year 9999 :) */
 577                                strchr(filetime + 20, '\n')[0] = ' ';
 578                                printf("%.7s%6s", filetime + 4, filetime + 20);
 579                        }
 580                        column += 13;
 581                }
 582#endif
 583        }
 584
 585#if ENABLE_FEATURE_LS_COLOR
 586        if (G_show_color) {
 587                mode_t mode = dn->dn_mode_lstat;
 588                if (!mode)
 589                        if (lstat(dn->fullname, &statbuf) == 0)
 590                                mode = statbuf.st_mode;
 591                printf(ESC"[%u;%um", bold(mode), fgcolor(mode));
 592        }
 593#endif
 594        column += print_name(dn->name);
 595        if (G_show_color) {
 596                printf(ESC"[m");
 597        }
 598
 599        if (lpath) {
 600                printf(" -> ");
 601#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
 602                if ((opt & (OPT_F|OPT_p))
 603                 || G_show_color
 604                ) {
 605                        mode_t mode = dn->dn_mode_stat;
 606                        if (!mode)
 607                                if (stat(dn->fullname, &statbuf) == 0)
 608                                        mode = statbuf.st_mode;
 609# if ENABLE_FEATURE_LS_FILETYPES
 610                        append = append_char(mode);
 611# endif
 612# if ENABLE_FEATURE_LS_COLOR
 613                        if (G_show_color) {
 614                                printf(ESC"[%u;%um", bold(mode), fgcolor(mode));
 615                        }
 616# endif
 617                }
 618#endif
 619                column += print_name(lpath) + 4;
 620                free(lpath);
 621                if (G_show_color) {
 622                        printf(ESC"[m");
 623                }
 624        }
 625#if ENABLE_FEATURE_LS_FILETYPES
 626        if (opt & (OPT_F|OPT_p)) {
 627                if (append) {
 628                        putchar(append);
 629                        column++;
 630                }
 631        }
 632#endif
 633
 634        return column;
 635}
 636
 637static void display_files(struct dnode **dn, unsigned nfiles)
 638{
 639        unsigned i, ncols, nrows, row, nc;
 640        unsigned column;
 641        unsigned nexttab;
 642        unsigned column_width = 0; /* used only by coulmnal output */
 643
 644        if (option_mask32 & (OPT_l|OPT_1)) {
 645                ncols = 1;
 646        } else {
 647                /* find the longest file name, use that as the column width */
 648                for (i = 0; dn[i]; i++) {
 649                        int len = calc_name_len(dn[i]->name);
 650                        if (column_width < len)
 651                                column_width = len;
 652                }
 653                column_width += 2
 654                        + ((option_mask32 & OPT_Z) ? 33 : 0) /* context width */
 655                        + ((option_mask32 & OPT_i) ? 8 : 0) /* inode# width */
 656                        + ((option_mask32 & OPT_s) ? 5 : 0) /* "alloc block" width */
 657                ;
 658                ncols = (unsigned)G_terminal_width / column_width;
 659        }
 660
 661        if (ncols > 1) {
 662                nrows = nfiles / ncols;
 663                if (nrows * ncols < nfiles)
 664                        nrows++;                /* round up fractionals */
 665        } else {
 666                nrows = nfiles;
 667                ncols = 1;
 668        }
 669
 670        column = 0;
 671        nexttab = 0;
 672        for (row = 0; row < nrows; row++) {
 673                for (nc = 0; nc < ncols; nc++) {
 674                        /* reach into the array based on the column and row */
 675                        if (option_mask32 & OPT_x)
 676                                i = (row * ncols) + nc; /* display across row */
 677                        else
 678                                i = (nc * nrows) + row; /* display by column */
 679                        if (i < nfiles) {
 680                                if (column > 0) {
 681                                        nexttab -= column;
 682                                        printf("%*s", nexttab, "");
 683                                        column += nexttab;
 684                                }
 685                                nexttab = column + column_width;
 686                                column += display_single(dn[i]);
 687                        }
 688                }
 689                putchar('\n');
 690                column = 0;
 691        }
 692}
 693
 694
 695/*** Dir scanning code ***/
 696
 697static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
 698{
 699        struct stat statbuf;
 700        struct dnode *cur;
 701
 702        cur = xzalloc(sizeof(*cur));
 703        cur->fullname = fullname;
 704        cur->name = name;
 705
 706        if ((option_mask32 & OPT_L) || force_follow) {
 707#if ENABLE_SELINUX
 708                if (option_mask32 & OPT_Z) {
 709                        getfilecon(fullname, &cur->sid);
 710                }
 711#endif
 712                if (stat(fullname, &statbuf)) {
 713                        bb_simple_perror_msg(fullname);
 714                        G.exit_code = EXIT_FAILURE;
 715                        free(cur);
 716                        return NULL;
 717                }
 718                cur->dn_mode_stat = statbuf.st_mode;
 719        } else {
 720#if ENABLE_SELINUX
 721                if (option_mask32 & OPT_Z) {
 722                        lgetfilecon(fullname, &cur->sid);
 723                }
 724#endif
 725                if (lstat(fullname, &statbuf)) {
 726                        bb_simple_perror_msg(fullname);
 727                        G.exit_code = EXIT_FAILURE;
 728                        free(cur);
 729                        return NULL;
 730                }
 731                cur->dn_mode_lstat = statbuf.st_mode;
 732        }
 733
 734        /* cur->dstat = statbuf: */
 735        cur->dn_mode   = statbuf.st_mode  ;
 736        cur->dn_size   = statbuf.st_size  ;
 737#if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
 738        cur->dn_time   = statbuf.st_mtime ;
 739        if (option_mask32 & OPT_u)
 740                cur->dn_time = statbuf.st_atime;
 741        if (option_mask32 & OPT_c)
 742                cur->dn_time = statbuf.st_ctime;
 743#endif
 744        cur->dn_ino    = statbuf.st_ino   ;
 745        cur->dn_blocks = statbuf.st_blocks;
 746        cur->dn_nlink  = statbuf.st_nlink ;
 747        cur->dn_uid    = statbuf.st_uid   ;
 748        cur->dn_gid    = statbuf.st_gid   ;
 749        cur->dn_rdev_maj = major(statbuf.st_rdev);
 750        cur->dn_rdev_min = minor(statbuf.st_rdev);
 751
 752        return cur;
 753}
 754
 755static unsigned count_dirs(struct dnode **dn, int which)
 756{
 757        unsigned dirs, all;
 758
 759        if (!dn)
 760                return 0;
 761
 762        dirs = all = 0;
 763        for (; *dn; dn++) {
 764                const char *name;
 765
 766                all++;
 767                if (!S_ISDIR((*dn)->dn_mode))
 768                        continue;
 769
 770                name = (*dn)->name;
 771                if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
 772                 /* or if it's not . or .. */
 773                 || name[0] != '.'
 774                 || (name[1] && (name[1] != '.' || name[2]))
 775                ) {
 776                        dirs++;
 777                }
 778        }
 779        return which != SPLIT_FILE ? dirs : all - dirs;
 780}
 781
 782/* get memory to hold an array of pointers */
 783static struct dnode **dnalloc(unsigned num)
 784{
 785        if (num < 1)
 786                return NULL;
 787
 788        num++; /* so that we have terminating NULL */
 789        return xzalloc(num * sizeof(struct dnode *));
 790}
 791
 792#if ENABLE_FEATURE_LS_RECURSIVE
 793static void dfree(struct dnode **dnp)
 794{
 795        unsigned i;
 796
 797        if (dnp == NULL)
 798                return;
 799
 800        for (i = 0; dnp[i]; i++) {
 801                struct dnode *cur = dnp[i];
 802                if (cur->fname_allocated)
 803                        free((char*)cur->fullname);
 804                free(cur);
 805        }
 806        free(dnp);
 807}
 808#else
 809#define dfree(...) ((void)0)
 810#endif
 811
 812/* Returns NULL-terminated malloced vector of pointers (or NULL) */
 813static struct dnode **splitdnarray(struct dnode **dn, int which)
 814{
 815        unsigned dncnt, d;
 816        struct dnode **dnp;
 817
 818        if (dn == NULL)
 819                return NULL;
 820
 821        /* count how many dirs or files there are */
 822        dncnt = count_dirs(dn, which);
 823
 824        /* allocate a file array and a dir array */
 825        dnp = dnalloc(dncnt);
 826
 827        /* copy the entrys into the file or dir array */
 828        for (d = 0; *dn; dn++) {
 829                if (S_ISDIR((*dn)->dn_mode)) {
 830                        const char *name;
 831
 832                        if (which == SPLIT_FILE)
 833                                continue;
 834
 835                        name = (*dn)->name;
 836                        if ((which & SPLIT_DIR) /* any dir... */
 837                        /* ... or not . or .. */
 838                         || name[0] != '.'
 839                         || (name[1] && (name[1] != '.' || name[2]))
 840                        ) {
 841                                dnp[d++] = *dn;
 842                        }
 843                } else
 844                if (which == SPLIT_FILE) {
 845                        dnp[d++] = *dn;
 846                }
 847        }
 848        return dnp;
 849}
 850
 851#if ENABLE_FEATURE_LS_SORTFILES
 852static int sortcmp(const void *a, const void *b)
 853{
 854        struct dnode *d1 = *(struct dnode **)a;
 855        struct dnode *d2 = *(struct dnode **)b;
 856        unsigned opt = option_mask32;
 857        off_t dif;
 858
 859        dif = 0; /* assume sort by name */
 860        // TODO: use pre-initialized function pointer
 861        // instead of branch forest
 862        if (opt & OPT_dirs_first) {
 863                dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
 864                if (dif != 0)
 865                        goto maybe_invert_and_ret;
 866        }
 867
 868        if (opt & OPT_S) { /* sort by size */
 869                dif = (d2->dn_size - d1->dn_size);
 870        } else
 871        if (opt & OPT_t) { /* sort by time */
 872                dif = (d2->dn_time - d1->dn_time);
 873        } else
 874#if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
 875        if (opt & OPT_v) { /* sort by version */
 876                dif = strverscmp(d1->name, d2->name);
 877        } else
 878#endif
 879        if (opt & OPT_X) { /* sort by extension */
 880                dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
 881        }
 882        if (dif == 0) {
 883                /* sort by name, use as tie breaker for other sorts */
 884                if (ENABLE_LOCALE_SUPPORT)
 885                        dif = strcoll(d1->name, d2->name);
 886                else
 887                        dif = strcmp(d1->name, d2->name);
 888        } else {
 889                /* Make dif fit into an int */
 890                if (sizeof(dif) > sizeof(int)) {
 891                        enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
 892                        /* shift leaving only "int" worth of bits */
 893                        /* (this requires dif != 0, and here it is nonzero) */
 894                        dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
 895                }
 896        }
 897 maybe_invert_and_ret:
 898        return (opt & OPT_r) ? -(int)dif : (int)dif;
 899}
 900
 901static void dnsort(struct dnode **dn, int size)
 902{
 903        qsort(dn, size, sizeof(*dn), sortcmp);
 904}
 905
 906static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
 907{
 908        dnsort(dn, nfiles);
 909        display_files(dn, nfiles);
 910}
 911#else
 912# define dnsort(dn, size) ((void)0)
 913# define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
 914#endif
 915
 916/* Returns NULL-terminated malloced vector of pointers (or NULL) */
 917static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
 918{
 919        struct dnode *dn, *cur, **dnp;
 920        struct dirent *entry;
 921        DIR *dir;
 922        unsigned i, nfiles;
 923
 924        *nfiles_p = 0;
 925        dir = warn_opendir(path);
 926        if (dir == NULL) {
 927                G.exit_code = EXIT_FAILURE;
 928                return NULL;    /* could not open the dir */
 929        }
 930        dn = NULL;
 931        nfiles = 0;
 932        while ((entry = readdir(dir)) != NULL) {
 933                char *fullname;
 934
 935                /* are we going to list the file- it may be . or .. or a hidden file */
 936                if (entry->d_name[0] == '.') {
 937                        if (!(option_mask32 & (OPT_a|OPT_A)))
 938                                continue; /* skip all dotfiles if no -a/-A */
 939                        if (!(option_mask32 & OPT_a)
 940                         && (!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
 941                        ) {
 942                                continue; /* if only -A, skip . and .. but show other dotfiles */
 943                        }
 944                }
 945                fullname = concat_path_file(path, entry->d_name);
 946                cur = my_stat(fullname, bb_basename(fullname), 0);
 947                if (!cur) {
 948                        free(fullname);
 949                        continue;
 950                }
 951                cur->fname_allocated = 1;
 952                cur->dn_next = dn;
 953                dn = cur;
 954                nfiles++;
 955        }
 956        closedir(dir);
 957
 958        if (dn == NULL)
 959                return NULL;
 960
 961        /* now that we know how many files there are
 962         * allocate memory for an array to hold dnode pointers
 963         */
 964        *nfiles_p = nfiles;
 965        dnp = dnalloc(nfiles);
 966        for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
 967                dnp[i] = dn;    /* save pointer to node in array */
 968                dn = dn->dn_next;
 969                if (!dn)
 970                        break;
 971        }
 972
 973        return dnp;
 974}
 975
 976#if ENABLE_DESKTOP
 977/* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
 978 * If any of the -l, -n, -s options is specified, each list
 979 * of files within the directory shall be preceded by a
 980 * status line indicating the number of file system blocks
 981 * occupied by files in the directory in 512-byte units if
 982 * the -k option is not specified, or 1024-byte units if the
 983 * -k option is specified, rounded up to the next integral
 984 * number of units.
 985 */
 986/* by Jorgen Overgaard (jorgen AT antistaten.se) */
 987static off_t calculate_blocks(struct dnode **dn)
 988{
 989        uoff_t blocks = 1;
 990        if (dn) {
 991                while (*dn) {
 992                        /* st_blocks is in 512 byte blocks */
 993                        blocks += (*dn)->dn_blocks;
 994                        dn++;
 995                }
 996        }
 997
 998        /* Even though standard says use 512 byte blocks, coreutils use 1k */
 999        /* Actually, we round up by calculating (blocks + 1) / 2,
1000         * "+ 1" was done when we initialized blocks to 1 */
1001        return blocks >> 1;
1002}
1003#endif
1004
1005static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1006{
1007        unsigned nfiles;
1008        struct dnode **subdnp;
1009
1010        for (; *dn; dn++) {
1011                if (G.show_dirname || (option_mask32 & OPT_R)) {
1012                        if (!first)
1013                                bb_putchar('\n');
1014                        first = 0;
1015                        printf("%s:\n", (*dn)->fullname);
1016                }
1017                subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1018#if ENABLE_DESKTOP
1019                if (option_mask32 & (OPT_s|OPT_l)) {
1020                        if (option_mask32 & OPT_h) {
1021                                printf("total %-"HUMAN_READABLE_MAX_WIDTH_STR"s\n",
1022                                        /* print size, no fractions, use suffixes */
1023                                        make_human_readable_str(calculate_blocks(subdnp) * 1024,
1024                                                                0, 0)
1025                                );
1026                        } else {
1027                                printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1028                        }
1029                }
1030#endif
1031                if (nfiles > 0) {
1032                        /* list all files at this level */
1033                        sort_and_display_files(subdnp, nfiles);
1034
1035                        if (ENABLE_FEATURE_LS_RECURSIVE
1036                         && (option_mask32 & OPT_R)
1037                        ) {
1038                                struct dnode **dnd;
1039                                unsigned dndirs;
1040                                /* recursive - list the sub-dirs */
1041                                dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1042                                dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1043                                if (dndirs > 0) {
1044                                        dnsort(dnd, dndirs);
1045                                        scan_and_display_dirs_recur(dnd, 0);
1046                                        /* free the array of dnode pointers to the dirs */
1047                                        free(dnd);
1048                                }
1049                        }
1050                        /* free the dnodes and the fullname mem */
1051                        dfree(subdnp);
1052                }
1053        }
1054}
1055
1056
1057int ls_main(int argc UNUSED_PARAM, char **argv)
1058{       /*      ^^^^^^^^^^^^^^^^^ note: if FTPD, argc can be wrong, see ftpd.c */
1059        struct dnode **dnd;
1060        struct dnode **dnf;
1061        struct dnode **dnp;
1062        struct dnode *dn;
1063        struct dnode *cur;
1064        unsigned opt;
1065        unsigned nfiles;
1066        unsigned dnfiles;
1067        unsigned dndirs;
1068        unsigned i;
1069#if ENABLE_FEATURE_LS_COLOR
1070        /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1071        /* coreutils 6.10:
1072         * # ls --color=BOGUS
1073         * ls: invalid argument 'BOGUS' for '--color'
1074         * Valid arguments are:
1075         * 'always', 'yes', 'force'
1076         * 'never', 'no', 'none'
1077         * 'auto', 'tty', 'if-tty'
1078         * (and substrings: "--color=alwa" work too)
1079         */
1080        static const char color_str[] ALIGN1 =
1081                "always\0""yes\0""force\0"
1082                "auto\0""tty\0""if-tty\0";
1083        /* need to initialize since --color has _an optional_ argument */
1084        const char *color_opt = color_str; /* "always" */
1085#endif
1086#if ENABLE_LONG_OPTS
1087        static const char ls_longopts[] ALIGN1 =
1088                "full-time\0" No_argument "\xff"
1089                "group-directories-first\0" No_argument "\xfe"
1090                IF_FEATURE_LS_COLOR("color\0" Optional_argument "\xfd")
1091        ;
1092#endif
1093
1094        INIT_G();
1095
1096        init_unicode();
1097
1098#if ENABLE_FEATURE_LS_WIDTH
1099        /* obtain the terminal width */
1100        G_terminal_width = get_terminal_width(STDIN_FILENO);
1101        /* go one less... */
1102        G_terminal_width--;
1103#endif
1104
1105        /* process options */
1106        opt = getopt32long(argv, "^"
1107                ls_options
1108                        "\0"
1109                        /* -n and -g imply -l */
1110                        "nl:gl"
1111                        /* --full-time implies -l */
1112                        IF_FEATURE_LS_TIMESTAMPS(IF_LONG_OPTS(":\xff""l"))
1113                        /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1114                         * in some pairs of opts, only last one takes effect:
1115                         */
1116                        IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1117                        // ":m-l:l-m" - we don't have -m
1118                        IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1119                        ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1120                        ":C-1:1-C" /* bycols/oneline */
1121                        ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1122                        IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */
1123                        /* -w NUM: */
1124                        IF_FEATURE_LS_WIDTH(":w+")
1125                , ls_longopts
1126                IF_FEATURE_LS_WIDTH(, /*-T*/ NULL, /*-w*/ &G_terminal_width)
1127                IF_FEATURE_LS_COLOR(, &color_opt)
1128        );
1129#if 0 /* option bits debug */
1130        bb_error_msg("opt:0x%08x l:%x H:%x color:%x dirs:%x", opt, OPT_l, OPT_H, OPT_color, OPT_dirs_first);
1131        if (opt & OPT_c         ) bb_error_msg("-c");
1132        if (opt & OPT_l         ) bb_error_msg("-l");
1133        if (opt & OPT_H         ) bb_error_msg("-H");
1134        if (opt & OPT_color     ) bb_error_msg("--color");
1135        if (opt & OPT_dirs_first) bb_error_msg("--group-directories-first");
1136        if (opt & OPT_full_time ) bb_error_msg("--full-time");
1137        exit(0);
1138#endif
1139
1140#if ENABLE_SELINUX
1141        if (opt & OPT_Z) {
1142                if (!is_selinux_enabled())
1143                        option_mask32 &= ~OPT_Z;
1144        }
1145#endif
1146
1147#if ENABLE_FEATURE_LS_COLOR
1148        /* set G_show_color = 1/0 */
1149        if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && !is_TERM_dumb()) {
1150                char *p = getenv("LS_COLORS");
1151                /* LS_COLORS is unset, or (not empty && not "none") ? */
1152                if (!p || (p[0] && strcmp(p, "none") != 0)) {
1153                        if (isatty(STDOUT_FILENO)) {
1154                                /* check isatty() last because it's expensive (syscall) */
1155                                G_show_color = 1;
1156                        }
1157                }
1158        }
1159        if (opt & OPT_color) {
1160                if (color_opt[0] == 'n')
1161                        G_show_color = 0;
1162                else switch (index_in_substrings(color_str, color_opt)) {
1163                case 3:
1164                case 4:
1165                case 5:
1166                        if (!is_TERM_dumb() && isatty(STDOUT_FILENO)) {
1167                case 0:
1168                case 1:
1169                case 2:
1170                                G_show_color = 1;
1171                        }
1172                }
1173        }
1174#endif
1175
1176        /* sort out which command line options take precedence */
1177        if (ENABLE_FEATURE_LS_RECURSIVE && (opt & OPT_d))
1178                option_mask32 &= ~OPT_R;        /* no recurse if listing only dir */
1179        if (!(opt & OPT_l)) { /* not -l? */
1180                if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1181                        /* when to sort by time? -t[cu] sorts by time even with -l */
1182                        /* (this is achieved by opt_flags[] element for -t) */
1183                        /* without -l, bare -c or -u enable sort too */
1184                        /* (with -l, bare -c or -u just select which time to show) */
1185                        if (opt & (OPT_c|OPT_u)) {
1186                                option_mask32 |= OPT_t;
1187                        }
1188                }
1189        }
1190
1191        /* choose a display format if one was not already specified by an option */
1192        if (!(option_mask32 & (OPT_l|OPT_1|OPT_x|OPT_C)))
1193                option_mask32 |= (isatty(STDOUT_FILENO) ? OPT_C : OPT_1);
1194
1195        if (ENABLE_FTPD && applet_name[0] == 'f') {
1196                /* ftpd secret backdoor. dirs first are much nicer */
1197                option_mask32 |= OPT_dirs_first;
1198        }
1199
1200        argv += optind;
1201        if (!argv[0])
1202                *--argv = (char*)".";
1203
1204        if (argv[1])
1205                G.show_dirname = 1; /* 2 or more items? label directories */
1206
1207        /* stuff the command line file names into a dnode array */
1208        dn = NULL;
1209        nfiles = 0;
1210        do {
1211                cur = my_stat(*argv, *argv,
1212                        /* follow links on command line unless -l, -i, -s or -F: */
1213                        !(option_mask32 & (OPT_l|OPT_i|OPT_s|OPT_F))
1214                        /* ... or if -H: */
1215                        || (option_mask32 & OPT_H)
1216                        /* ... or if -L, but my_stat always follows links if -L */
1217                );
1218                argv++;
1219                if (!cur)
1220                        continue;
1221                /*cur->fname_allocated = 0; - already is */
1222                cur->dn_next = dn;
1223                dn = cur;
1224                nfiles++;
1225        } while (*argv);
1226
1227        /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1228        if (nfiles == 0)
1229                return G.exit_code;
1230
1231        /* now that we know how many files there are
1232         * allocate memory for an array to hold dnode pointers
1233         */
1234        dnp = dnalloc(nfiles);
1235        for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1236                dnp[i] = dn;    /* save pointer to node in array */
1237                dn = dn->dn_next;
1238                if (!dn)
1239                        break;
1240        }
1241
1242        if (option_mask32 & OPT_d) {
1243                sort_and_display_files(dnp, nfiles);
1244        } else {
1245                dnd = splitdnarray(dnp, SPLIT_DIR);
1246                dnf = splitdnarray(dnp, SPLIT_FILE);
1247                dndirs = count_dirs(dnp, SPLIT_DIR);
1248                dnfiles = nfiles - dndirs;
1249                if (dnfiles > 0) {
1250                        sort_and_display_files(dnf, dnfiles);
1251                        if (ENABLE_FEATURE_CLEAN_UP)
1252                                free(dnf);
1253                }
1254                if (dndirs > 0) {
1255                        dnsort(dnd, dndirs);
1256                        scan_and_display_dirs_recur(dnd, dnfiles == 0);
1257                        if (ENABLE_FEATURE_CLEAN_UP)
1258                                free(dnd);
1259                }
1260        }
1261
1262        if (ENABLE_FEATURE_CLEAN_UP)
1263                dfree(dnp);
1264        return G.exit_code;
1265}
1266