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