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