busybox/coreutils/ls.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * tiny-ls.c version 0.1.0: A minimalist 'ls'
   4 * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
   5 *
   6 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
   7 */
   8
   9/* [date unknown. Perhaps before year 2000]
  10 * To achieve a small memory footprint, this version of 'ls' doesn't do any
  11 * file sorting, and only has the most essential command line switches
  12 * (i.e., the ones I couldn't live without :-) All features which involve
  13 * linking in substantial chunks of libc can be disabled.
  14 *
  15 * Although I don't really want to add new features to this program to
  16 * keep it small, I *am* interested to receive bug fixes and ways to make
  17 * it more portable.
  18 *
  19 * KNOWN BUGS:
  20 * 1. ls -l of a directory doesn't give "total <blocks>" header
  21 * 2. hidden files can make column width too large
  22 *
  23 * NON-OPTIMAL BEHAVIOUR:
  24 * 1. autowidth reads directories twice
  25 * 2. if you do a short directory listing without filetype characters
  26 *    appended, there's no need to stat each one
  27 * PORTABILITY:
  28 * 1. requires lstat (BSD) - how do you do it without?
  29 *
  30 * [2009-03]
  31 * ls sorts listing now, and supports almost all options.
  32 */
  33
  34#include "libbb.h"
  35
  36#if ENABLE_FEATURE_ASSUME_UNICODE
  37#include <wchar.h>
  38#endif
  39
  40/* This is a NOEXEC applet. Be very careful! */
  41
  42
  43#if ENABLE_FTPD
  44/* ftpd uses ls, and without timestamps Mozilla won't understand
  45 * ftpd's LIST output.
  46 */
  47# undef CONFIG_FEATURE_LS_TIMESTAMPS
  48# undef ENABLE_FEATURE_LS_TIMESTAMPS
  49# undef USE_FEATURE_LS_TIMESTAMPS
  50# undef SKIP_FEATURE_LS_TIMESTAMPS
  51# define CONFIG_FEATURE_LS_TIMESTAMPS 1
  52# define ENABLE_FEATURE_LS_TIMESTAMPS 1
  53# define USE_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
  54# define SKIP_FEATURE_LS_TIMESTAMPS(...)
  55#endif
  56
  57
  58enum {
  59
  60TERMINAL_WIDTH  = 80,           /* use 79 if terminal has linefold bug */
  61COLUMN_GAP      = 2,            /* includes the file type char */
  62
  63/* what is the overall style of the listing */
  64STYLE_COLUMNS   = 1 << 21,      /* fill columns */
  65STYLE_LONG      = 2 << 21,      /* one record per line, extended info */
  66STYLE_SINGLE    = 3 << 21,      /* one record per line */
  67STYLE_MASK      = STYLE_SINGLE,
  68
  69/* 51306 lrwxrwxrwx  1 root     root         2 May 11 01:43 /bin/view -> vi* */
  70/* what file information will be listed */
  71LIST_INO        = 1 << 0,
  72LIST_BLOCKS     = 1 << 1,
  73LIST_MODEBITS   = 1 << 2,
  74LIST_NLINKS     = 1 << 3,
  75LIST_ID_NAME    = 1 << 4,
  76LIST_ID_NUMERIC = 1 << 5,
  77LIST_CONTEXT    = 1 << 6,
  78LIST_SIZE       = 1 << 7,
  79//LIST_DEV        = 1 << 8, - unused, synonym to LIST_SIZE
  80LIST_DATE_TIME  = 1 << 9,
  81LIST_FULLTIME   = 1 << 10,
  82LIST_FILENAME   = 1 << 11,
  83LIST_SYMLINK    = 1 << 12,
  84LIST_FILETYPE   = 1 << 13,
  85LIST_EXEC       = 1 << 14,
  86LIST_MASK       = (LIST_EXEC << 1) - 1,
  87
  88/* what files will be displayed */
  89DISP_DIRNAME    = 1 << 15,      /* 2 or more items? label directories */
  90DISP_HIDDEN     = 1 << 16,      /* show filenames starting with . */
  91DISP_DOT        = 1 << 17,      /* show . and .. */
  92DISP_NOLIST     = 1 << 18,      /* show directory as itself, not contents */
  93DISP_RECURSIVE  = 1 << 19,      /* show directory and everything below it */
  94DISP_ROWS       = 1 << 20,      /* print across rows */
  95DISP_MASK       = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
  96
  97/* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
  98SORT_FORWARD    = 0,            /* sort in reverse order */
  99SORT_REVERSE    = 1 << 27,      /* sort in reverse order */
 100
 101SORT_NAME       = 0,            /* sort by file name */
 102SORT_SIZE       = 1 << 28,      /* sort by file size */
 103SORT_ATIME      = 2 << 28,      /* sort by last access time */
 104SORT_CTIME      = 3 << 28,      /* sort by last change time */
 105SORT_MTIME      = 4 << 28,      /* sort by last modification time */
 106SORT_VERSION    = 5 << 28,      /* sort by version */
 107SORT_EXT        = 6 << 28,      /* sort by file name extension */
 108SORT_DIR        = 7 << 28,      /* sort by file or directory */
 109SORT_MASK       = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
 110
 111/* which of the three times will be used */
 112TIME_CHANGE     = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
 113TIME_ACCESS     = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
 114TIME_MASK       = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
 115
 116FOLLOW_LINKS    = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
 117
 118LS_DISP_HR      = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
 119
 120LIST_SHORT      = LIST_FILENAME,
 121LIST_LONG       = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
 122                  LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
 123
 124SPLIT_DIR       = 1,
 125SPLIT_FILE      = 0,
 126SPLIT_SUBDIR    = 2,
 127
 128};
 129
 130/* "[-]Cadil1", POSIX mandated options, busybox always supports */
 131/* "[-]gnsx", POSIX non-mandated options, busybox always supports */
 132/* "[-]Q" GNU option? busybox always supports */
 133/* "[-]Ak" GNU options, busybox always supports */
 134/* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
 135/* "[-]p", POSIX non-mandated options, busybox optionally supports */
 136/* "[-]SXvThw", GNU options, busybox optionally supports */
 137/* "[-]K", SELinux mandated options, busybox optionally supports */
 138/* "[-]e", I think we made this one up */
 139static const char ls_options[] ALIGN1 =
 140        "Cadil1gnsxQAk" /* 13 opts, total 13 */
 141        USE_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
 142        USE_FEATURE_LS_SORTFILES("SXrv")  /* 4, 21 */
 143        USE_FEATURE_LS_FILETYPES("Fp")    /* 2, 23 */
 144        USE_FEATURE_LS_FOLLOWLINKS("L")   /* 1, 24 */
 145        USE_FEATURE_LS_RECURSIVE("R")     /* 1, 25 */
 146        USE_FEATURE_HUMAN_READABLE("h")   /* 1, 26 */
 147        USE_SELINUX("K") /* 1, 27 */
 148        USE_SELINUX("Z") /* 1, 28 */
 149        USE_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
 150        ;
 151enum {
 152        //OPT_C = (1 << 0),
 153        //OPT_a = (1 << 1),
 154        //OPT_d = (1 << 2),
 155        //OPT_i = (1 << 3),
 156        //OPT_l = (1 << 4),
 157        //OPT_1 = (1 << 5),
 158        OPT_g = (1 << 6),
 159        //OPT_n = (1 << 7),
 160        //OPT_s = (1 << 8),
 161        //OPT_x = (1 << 9),
 162        OPT_Q = (1 << 10),
 163        //OPT_A = (1 << 11),
 164        //OPT_k = (1 << 12),
 165};
 166
 167enum {
 168        LIST_MASK_TRIGGER       = 0,
 169        STYLE_MASK_TRIGGER      = STYLE_MASK,
 170        DISP_MASK_TRIGGER       = DISP_ROWS,
 171        SORT_MASK_TRIGGER       = SORT_MASK,
 172};
 173
 174/* TODO: simple toggles may be stored as OPT_xxx bits instead */
 175static const unsigned opt_flags[] = {
 176        LIST_SHORT | STYLE_COLUMNS, /* C */
 177        DISP_HIDDEN | DISP_DOT,     /* a */
 178        DISP_NOLIST,                /* d */
 179        LIST_INO,                   /* i */
 180        LIST_LONG | STYLE_LONG,     /* l - remember LS_DISP_HR in mask! */
 181        LIST_SHORT | STYLE_SINGLE,  /* 1 */
 182        0,                          /* g (don't show group) - handled via OPT_g */
 183        LIST_ID_NUMERIC,            /* n */
 184        LIST_BLOCKS,                /* s */
 185        DISP_ROWS,                  /* x */
 186        0,                          /* Q (quote filename) - handled via OPT_Q */
 187        DISP_HIDDEN,                /* A */
 188        ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
 189#if ENABLE_FEATURE_LS_TIMESTAMPS
 190        TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME),   /* c */
 191        LIST_FULLTIME,              /* e */
 192        ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME,   /* t */
 193        TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME),   /* u */
 194#endif
 195#if ENABLE_FEATURE_LS_SORTFILES
 196        SORT_SIZE,                  /* S */
 197        SORT_EXT,                   /* X */
 198        SORT_REVERSE,               /* r */
 199        SORT_VERSION,               /* v */
 200#endif
 201#if ENABLE_FEATURE_LS_FILETYPES
 202        LIST_FILETYPE | LIST_EXEC,  /* F */
 203        LIST_FILETYPE,              /* p */
 204#endif
 205#if ENABLE_FEATURE_LS_FOLLOWLINKS
 206        FOLLOW_LINKS,               /* L */
 207#endif
 208#if ENABLE_FEATURE_LS_RECURSIVE
 209        DISP_RECURSIVE,             /* R */
 210#endif
 211#if ENABLE_FEATURE_HUMAN_READABLE
 212        LS_DISP_HR,                 /* h */
 213#endif
 214#if ENABLE_SELINUX
 215        LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
 216#endif
 217#if ENABLE_SELINUX
 218        LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
 219#endif
 220        (1U<<31)
 221        /* options after Z are not processed through opt_flags:
 222         * T, w - ignored
 223         */
 224};
 225
 226
 227/*
 228 * a directory entry and its stat info are stored here
 229 */
 230struct dnode {                  /* the basic node */
 231        const char *name;             /* the dir entry name */
 232        const char *fullname;         /* the dir entry name */
 233        int   allocated;
 234        struct stat dstat;      /* the file stat info */
 235        USE_SELINUX(security_context_t sid;)
 236        struct dnode *next;     /* point at the next node */
 237};
 238
 239static struct dnode **list_dir(const char *);
 240static struct dnode **dnalloc(int);
 241static int list_single(const struct dnode *);
 242
 243
 244struct globals {
 245#if ENABLE_FEATURE_LS_COLOR
 246        smallint show_color;
 247#endif
 248        smallint exit_code;
 249        unsigned all_fmt;
 250#if ENABLE_FEATURE_AUTOWIDTH
 251        unsigned tabstops; // = COLUMN_GAP;
 252        unsigned terminal_width; // = TERMINAL_WIDTH;
 253#endif
 254#if ENABLE_FEATURE_LS_TIMESTAMPS
 255        /* Do time() just once. Saves one syscall per file for "ls -l" */
 256        time_t current_time_t;
 257#endif
 258};
 259#define G (*(struct globals*)&bb_common_bufsiz1)
 260#if ENABLE_FEATURE_LS_COLOR
 261#define show_color     (G.show_color    )
 262#else
 263enum { show_color = 0 };
 264#endif
 265#define exit_code      (G.exit_code     )
 266#define all_fmt        (G.all_fmt       )
 267#if ENABLE_FEATURE_AUTOWIDTH
 268#define tabstops       (G.tabstops      )
 269#define terminal_width (G.terminal_width)
 270#else
 271enum {
 272        tabstops = COLUMN_GAP,
 273        terminal_width = TERMINAL_WIDTH,
 274};
 275#endif
 276#define current_time_t (G.current_time_t)
 277/* memset: we have to zero it out because of NOEXEC */
 278#define INIT_G() do { \
 279        memset(&G, 0, sizeof(G)); \
 280        USE_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
 281        USE_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
 282        USE_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
 283} while (0)
 284
 285
 286#if ENABLE_FEATURE_ASSUME_UNICODE
 287/* libbb candidate */
 288static size_t mbstrlen(const char *string)
 289{
 290        size_t width = mbsrtowcs(NULL /*dest*/, &string,
 291                                MAXINT(size_t) /*len*/, NULL /*state*/);
 292        if (width == (size_t)-1)
 293                return strlen(string);
 294        return width;
 295}
 296#else
 297#define mbstrlen(string) strlen(string)
 298#endif
 299
 300
 301static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
 302{
 303        struct stat dstat;
 304        struct dnode *cur;
 305        USE_SELINUX(security_context_t sid = NULL;)
 306
 307        if ((all_fmt & FOLLOW_LINKS) || force_follow) {
 308#if ENABLE_SELINUX
 309                if (is_selinux_enabled())  {
 310                         getfilecon(fullname, &sid);
 311                }
 312#endif
 313                if (stat(fullname, &dstat)) {
 314                        bb_simple_perror_msg(fullname);
 315                        exit_code = EXIT_FAILURE;
 316                        return 0;
 317                }
 318        } else {
 319#if ENABLE_SELINUX
 320                if (is_selinux_enabled()) {
 321                        lgetfilecon(fullname, &sid);
 322                }
 323#endif
 324                if (lstat(fullname, &dstat)) {
 325                        bb_simple_perror_msg(fullname);
 326                        exit_code = EXIT_FAILURE;
 327                        return 0;
 328                }
 329        }
 330
 331        cur = xmalloc(sizeof(struct dnode));
 332        cur->fullname = fullname;
 333        cur->name = name;
 334        cur->dstat = dstat;
 335        USE_SELINUX(cur->sid = sid;)
 336        return cur;
 337}
 338
 339
 340/* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
 341 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
 342 *  3/7:multiplexed char/block device)
 343 * and we use 0 for unknown and 15 for executables (see below) */
 344#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
 345#define TYPECHAR(mode)  ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
 346#define APPCHAR(mode)   ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
 347/* 036 black foreground              050 black background
 348   037 red foreground                051 red background
 349   040 green foreground              052 green background
 350   041 brown foreground              053 brown background
 351   042 blue foreground               054 blue background
 352   043 magenta (purple) foreground   055 magenta background
 353   044 cyan (light blue) foreground  056 cyan background
 354   045 gray foreground               057 white background
 355*/
 356#define COLOR(mode) ( \
 357        /*un  fi  chr     dir     blk     file    link    sock        exe */ \
 358        "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
 359        [TYPEINDEX(mode)])
 360/* Select normal (0) [actually "reset all"] or bold (1)
 361 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
 362 *  let's use 7 for "impossible" types, just for fun)
 363 * Note: coreutils 6.9 uses inverted red for setuid binaries.
 364 */
 365#define ATTR(mode) ( \
 366        /*un fi chr   dir   blk   file  link  sock     exe */ \
 367        "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
 368        [TYPEINDEX(mode)])
 369
 370#if ENABLE_FEATURE_LS_COLOR
 371/* mode of zero is interpreted as "unknown" (stat failed) */
 372static char fgcolor(mode_t mode)
 373{
 374        if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
 375                return COLOR(0xF000);   /* File is executable ... */
 376        return COLOR(mode);
 377}
 378static char bold(mode_t mode)
 379{
 380        if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
 381                return ATTR(0xF000);    /* File is executable ... */
 382        return ATTR(mode);
 383}
 384#endif
 385
 386#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
 387static char append_char(mode_t mode)
 388{
 389        if (!(all_fmt & LIST_FILETYPE))
 390                return '\0';
 391        if (S_ISDIR(mode))
 392                return '/';
 393        if (!(all_fmt & LIST_EXEC))
 394                return '\0';
 395        if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
 396                return '*';
 397        return APPCHAR(mode);
 398}
 399#endif
 400
 401
 402#define countdirs(A, B) count_dirs((A), (B), 1)
 403#define countsubdirs(A, B) count_dirs((A), (B), 0)
 404static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs)
 405{
 406        int i, dirs;
 407
 408        if (!dn)
 409                return 0;
 410        dirs = 0;
 411        for (i = 0; i < nfiles; i++) {
 412                const char *name;
 413                if (!S_ISDIR(dn[i]->dstat.st_mode))
 414                        continue;
 415                name = dn[i]->name;
 416                if (notsubdirs
 417                 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
 418                ) {
 419                        dirs++;
 420                }
 421        }
 422        return dirs;
 423}
 424
 425static int countfiles(struct dnode **dnp)
 426{
 427        int nfiles;
 428        struct dnode *cur;
 429
 430        if (dnp == NULL)
 431                return 0;
 432        nfiles = 0;
 433        for (cur = dnp[0]; cur->next; cur = cur->next)
 434                nfiles++;
 435        nfiles++;
 436        return nfiles;
 437}
 438
 439/* get memory to hold an array of pointers */
 440static struct dnode **dnalloc(int num)
 441{
 442        if (num < 1)
 443                return NULL;
 444
 445        return xzalloc(num * sizeof(struct dnode *));
 446}
 447
 448#if ENABLE_FEATURE_LS_RECURSIVE
 449static void dfree(struct dnode **dnp, int nfiles)
 450{
 451        int i;
 452
 453        if (dnp == NULL)
 454                return;
 455
 456        for (i = 0; i < nfiles; i++) {
 457                struct dnode *cur = dnp[i];
 458                if (cur->allocated)
 459                        free((char*)cur->fullname);     /* free the filename */
 460                free(cur);              /* free the dnode */
 461        }
 462        free(dnp);                      /* free the array holding the dnode pointers */
 463}
 464#else
 465#define dfree(...) ((void)0)
 466#endif
 467
 468static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which)
 469{
 470        int dncnt, i, d;
 471        struct dnode **dnp;
 472
 473        if (dn == NULL || nfiles < 1)
 474                return NULL;
 475
 476        /* count how many dirs and regular files there are */
 477        if (which == SPLIT_SUBDIR)
 478                dncnt = countsubdirs(dn, nfiles);
 479        else {
 480                dncnt = countdirs(dn, nfiles);  /* assume we are looking for dirs */
 481                if (which == SPLIT_FILE)
 482                        dncnt = nfiles - dncnt; /* looking for files */
 483        }
 484
 485        /* allocate a file array and a dir array */
 486        dnp = dnalloc(dncnt);
 487
 488        /* copy the entrys into the file or dir array */
 489        for (d = i = 0; i < nfiles; i++) {
 490                if (S_ISDIR(dn[i]->dstat.st_mode)) {
 491                        const char *name;
 492                        if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
 493                                continue;
 494                        name = dn[i]->name;
 495                        if ((which & SPLIT_DIR)
 496                         || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
 497                        ) {
 498                                dnp[d++] = dn[i];
 499                        }
 500                } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
 501                        dnp[d++] = dn[i];
 502                }
 503        }
 504        return dnp;
 505}
 506
 507#if ENABLE_FEATURE_LS_SORTFILES
 508static int sortcmp(const void *a, const void *b)
 509{
 510        struct dnode *d1 = *(struct dnode **)a;
 511        struct dnode *d2 = *(struct dnode **)b;
 512        unsigned sort_opts = all_fmt & SORT_MASK;
 513        int dif;
 514
 515        dif = 0; /* assume SORT_NAME */
 516        // TODO: use pre-initialized function pointer
 517        // instead of branch forest
 518        if (sort_opts == SORT_SIZE) {
 519                dif = (int) (d2->dstat.st_size - d1->dstat.st_size);
 520        } else if (sort_opts == SORT_ATIME) {
 521                dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime);
 522        } else if (sort_opts == SORT_CTIME) {
 523                dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime);
 524        } else if (sort_opts == SORT_MTIME) {
 525                dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime);
 526        } else if (sort_opts == SORT_DIR) {
 527                dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
 528                /* } else if (sort_opts == SORT_VERSION) { */
 529                /* } else if (sort_opts == SORT_EXT) { */
 530        }
 531
 532        if (dif == 0) {
 533                /* sort by name - may be a tie_breaker for time or size cmp */
 534                if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name);
 535                else dif = strcmp(d1->name, d2->name);
 536        }
 537
 538        if (all_fmt & SORT_REVERSE) {
 539                dif = -dif;
 540        }
 541        return dif;
 542}
 543
 544static void dnsort(struct dnode **dn, int size)
 545{
 546        qsort(dn, size, sizeof(*dn), sortcmp);
 547}
 548#else
 549#define dnsort(dn, size) ((void)0)
 550#endif
 551
 552
 553static void showfiles(struct dnode **dn, int nfiles)
 554{
 555        int i, ncols, nrows, row, nc;
 556        int column = 0;
 557        int nexttab = 0;
 558        int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */
 559
 560        if (dn == NULL || nfiles < 1)
 561                return;
 562
 563        if (all_fmt & STYLE_LONG) {
 564                ncols = 1;
 565        } else {
 566                /* find the longest file name, use that as the column width */
 567                for (i = 0; i < nfiles; i++) {
 568                        int len = mbstrlen(dn[i]->name);
 569                        if (column_width < len)
 570                                column_width = len;
 571                }
 572                column_width += tabstops +
 573                        USE_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
 574                                     ((all_fmt & LIST_INO) ? 8 : 0) +
 575                                     ((all_fmt & LIST_BLOCKS) ? 5 : 0);
 576                ncols = (int) (terminal_width / column_width);
 577        }
 578
 579        if (ncols > 1) {
 580                nrows = nfiles / ncols;
 581                if (nrows * ncols < nfiles)
 582                        nrows++;                /* round up fractionals */
 583        } else {
 584                nrows = nfiles;
 585                ncols = 1;
 586        }
 587
 588        for (row = 0; row < nrows; row++) {
 589                for (nc = 0; nc < ncols; nc++) {
 590                        /* reach into the array based on the column and row */
 591                        i = (nc * nrows) + row; /* assume display by column */
 592                        if (all_fmt & DISP_ROWS)
 593                                i = (row * ncols) + nc; /* display across row */
 594                        if (i < nfiles) {
 595                                if (column > 0) {
 596                                        nexttab -= column;
 597                                        printf("%*s", nexttab, "");
 598                                        column += nexttab;
 599                                }
 600                                nexttab = column + column_width;
 601                                column += list_single(dn[i]);
 602                        }
 603                }
 604                putchar('\n');
 605                column = 0;
 606        }
 607}
 608
 609
 610static void showdirs(struct dnode **dn, int ndirs, int first)
 611{
 612        int i, nfiles;
 613        struct dnode **subdnp;
 614        int dndirs;
 615        struct dnode **dnd;
 616
 617        if (dn == NULL || ndirs < 1)
 618                return;
 619
 620        for (i = 0; i < ndirs; i++) {
 621                if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
 622                        if (!first)
 623                                bb_putchar('\n');
 624                        first = 0;
 625                        printf("%s:\n", dn[i]->fullname);
 626                }
 627                subdnp = list_dir(dn[i]->fullname);
 628                nfiles = countfiles(subdnp);
 629                if (nfiles > 0) {
 630                        /* list all files at this level */
 631                        dnsort(subdnp, nfiles);
 632                        showfiles(subdnp, nfiles);
 633                        if (ENABLE_FEATURE_LS_RECURSIVE) {
 634                                if (all_fmt & DISP_RECURSIVE) {
 635                                        /* recursive- list the sub-dirs */
 636                                        dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR);
 637                                        dndirs = countsubdirs(subdnp, nfiles);
 638                                        if (dndirs > 0) {
 639                                                dnsort(dnd, dndirs);
 640                                                showdirs(dnd, dndirs, 0);
 641                                                /* free the array of dnode pointers to the dirs */
 642                                                free(dnd);
 643                                        }
 644                                }
 645                                /* free the dnodes and the fullname mem */
 646                                dfree(subdnp, nfiles);
 647                        }
 648                }
 649        }
 650}
 651
 652
 653static struct dnode **list_dir(const char *path)
 654{
 655        struct dnode *dn, *cur, **dnp;
 656        struct dirent *entry;
 657        DIR *dir;
 658        int i, nfiles;
 659
 660        if (path == NULL)
 661                return NULL;
 662
 663        dn = NULL;
 664        nfiles = 0;
 665        dir = warn_opendir(path);
 666        if (dir == NULL) {
 667                exit_code = EXIT_FAILURE;
 668                return NULL;    /* could not open the dir */
 669        }
 670        while ((entry = readdir(dir)) != NULL) {
 671                char *fullname;
 672
 673                /* are we going to list the file- it may be . or .. or a hidden file */
 674                if (entry->d_name[0] == '.') {
 675                        if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
 676                         && !(all_fmt & DISP_DOT)
 677                        ) {
 678                                continue;
 679                        }
 680                        if (!(all_fmt & DISP_HIDDEN))
 681                                continue;
 682                }
 683                fullname = concat_path_file(path, entry->d_name);
 684                cur = my_stat(fullname, bb_basename(fullname), 0);
 685                if (!cur) {
 686                        free(fullname);
 687                        continue;
 688                }
 689                cur->allocated = 1;
 690                cur->next = dn;
 691                dn = cur;
 692                nfiles++;
 693        }
 694        closedir(dir);
 695
 696        /* now that we know how many files there are
 697         * allocate memory for an array to hold dnode pointers
 698         */
 699        if (dn == NULL)
 700                return NULL;
 701        dnp = dnalloc(nfiles);
 702        for (i = 0, cur = dn; i < nfiles; i++) {
 703                dnp[i] = cur;   /* save pointer to node in array */
 704                cur = cur->next;
 705        }
 706
 707        return dnp;
 708}
 709
 710
 711static int print_name(const char *name)
 712{
 713        if (option_mask32 & OPT_Q) {
 714#if ENABLE_FEATURE_ASSUME_UNICODE
 715                int len = 2 + mbstrlen(name);
 716#else
 717                int len = 2;
 718#endif
 719                putchar('"');
 720                while (*name) {
 721                        if (*name == '"') {
 722                                putchar('\\');
 723                                len++;
 724                        }
 725                        putchar(*name++);
 726                        if (!ENABLE_FEATURE_ASSUME_UNICODE)
 727                                len++;
 728                }
 729                putchar('"');
 730                return len;
 731        }
 732        /* No -Q: */
 733#if ENABLE_FEATURE_ASSUME_UNICODE
 734        fputs(name, stdout);
 735        return mbstrlen(name);
 736#else
 737        return printf("%s", name);
 738#endif
 739}
 740
 741
 742static int list_single(const struct dnode *dn)
 743{
 744        int column = 0;
 745        char *lpath = lpath; /* for compiler */
 746#if ENABLE_FEATURE_LS_TIMESTAMPS
 747        char *filetime;
 748        time_t ttime, age;
 749#endif
 750#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
 751        struct stat info;
 752        char append;
 753#endif
 754
 755        if (dn->fullname == NULL)
 756                return 0;
 757
 758#if ENABLE_FEATURE_LS_TIMESTAMPS
 759        ttime = dn->dstat.st_mtime;     /* the default time */
 760        if (all_fmt & TIME_ACCESS)
 761                ttime = dn->dstat.st_atime;
 762        if (all_fmt & TIME_CHANGE)
 763                ttime = dn->dstat.st_ctime;
 764        filetime = ctime(&ttime);
 765#endif
 766#if ENABLE_FEATURE_LS_FILETYPES
 767        append = append_char(dn->dstat.st_mode);
 768#endif
 769
 770        /* Do readlink early, so that if it fails, error message
 771         * does not appear *inside* of the "ls -l" line */
 772        if (all_fmt & LIST_SYMLINK)
 773                if (S_ISLNK(dn->dstat.st_mode))
 774                        lpath = xmalloc_readlink_or_warn(dn->fullname);
 775
 776        if (all_fmt & LIST_INO)
 777                column += printf("%7lu ", (long) dn->dstat.st_ino);
 778        if (all_fmt & LIST_BLOCKS)
 779                column += printf("%4"OFF_FMT"u ", (off_t) dn->dstat.st_blocks >> 1);
 780        if (all_fmt & LIST_MODEBITS)
 781                column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
 782        if (all_fmt & LIST_NLINKS)
 783                column += printf("%4lu ", (long) dn->dstat.st_nlink);
 784#if ENABLE_FEATURE_LS_USERNAME
 785        if (all_fmt & LIST_ID_NAME) {
 786                if (option_mask32 & OPT_g) {
 787                        column += printf("%-8.8s",
 788                                get_cached_username(dn->dstat.st_uid));
 789                } else {
 790                        column += printf("%-8.8s %-8.8s",
 791                                get_cached_username(dn->dstat.st_uid),
 792                                get_cached_groupname(dn->dstat.st_gid));
 793                }
 794        }
 795#endif
 796        if (all_fmt & LIST_ID_NUMERIC) {
 797                if (option_mask32 & OPT_g)
 798                        column += printf("%-8u", (int) dn->dstat.st_uid);
 799                else
 800                        column += printf("%-8u %-8u",
 801                                        (int) dn->dstat.st_uid,
 802                                        (int) dn->dstat.st_gid);
 803        }
 804        if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
 805                if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
 806                        column += printf("%4u, %3u ",
 807                                        (int) major(dn->dstat.st_rdev),
 808                                        (int) minor(dn->dstat.st_rdev));
 809                } else {
 810                        if (all_fmt & LS_DISP_HR) {
 811                                column += printf("%9s ",
 812                                        make_human_readable_str(dn->dstat.st_size, 1, 0));
 813                        } else {
 814                                column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
 815                        }
 816                }
 817        }
 818#if ENABLE_FEATURE_LS_TIMESTAMPS
 819        if (all_fmt & LIST_FULLTIME)
 820                column += printf("%24.24s ", filetime);
 821        if (all_fmt & LIST_DATE_TIME)
 822                if ((all_fmt & LIST_FULLTIME) == 0) {
 823                        /* current_time_t ~== time(NULL) */
 824                        age = current_time_t - ttime;
 825                        printf("%6.6s ", filetime + 4);
 826                        if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
 827                                /* hh:mm if less than 6 months old */
 828                                printf("%5.5s ", filetime + 11);
 829                        } else {
 830                                printf(" %4.4s ", filetime + 20);
 831                        }
 832                        column += 13;
 833                }
 834#endif
 835#if ENABLE_SELINUX
 836        if (all_fmt & LIST_CONTEXT) {
 837                column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
 838                freecon(dn->sid);
 839        }
 840#endif
 841        if (all_fmt & LIST_FILENAME) {
 842#if ENABLE_FEATURE_LS_COLOR
 843                if (show_color) {
 844                        info.st_mode = 0; /* for fgcolor() */
 845                        lstat(dn->fullname, &info);
 846                        printf("\033[%u;%um", bold(info.st_mode),
 847                                        fgcolor(info.st_mode));
 848                }
 849#endif
 850                column += print_name(dn->name);
 851                if (show_color) {
 852                        printf("\033[0m");
 853                }
 854        }
 855        if (all_fmt & LIST_SYMLINK) {
 856                if (S_ISLNK(dn->dstat.st_mode) && lpath) {
 857                        printf(" -> ");
 858#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
 859#if ENABLE_FEATURE_LS_COLOR
 860                        info.st_mode = 0; /* for fgcolor() */
 861#endif
 862                        if (stat(dn->fullname, &info) == 0) {
 863                                append = append_char(info.st_mode);
 864                        }
 865#endif
 866#if ENABLE_FEATURE_LS_COLOR
 867                        if (show_color) {
 868                                printf("\033[%u;%um", bold(info.st_mode),
 869                                           fgcolor(info.st_mode));
 870                        }
 871#endif
 872                        column += print_name(lpath) + 4;
 873                        if (show_color) {
 874                                printf("\033[0m");
 875                        }
 876                        free(lpath);
 877                }
 878        }
 879#if ENABLE_FEATURE_LS_FILETYPES
 880        if (all_fmt & LIST_FILETYPE) {
 881                if (append) {
 882                        putchar(append);
 883                        column++;
 884                }
 885        }
 886#endif
 887
 888        return column;
 889}
 890
 891
 892/* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
 893#if ENABLE_FEATURE_LS_COLOR
 894/* long option entry used only for --color, which has no short option
 895 * equivalent */
 896static const char ls_color_opt[] ALIGN1 =
 897        "color\0" Optional_argument "\xff" /* no short equivalent */
 898        ;
 899#endif
 900
 901
 902int ls_main(int argc UNUSED_PARAM, char **argv)
 903{
 904        struct dnode **dnd;
 905        struct dnode **dnf;
 906        struct dnode **dnp;
 907        struct dnode *dn;
 908        struct dnode *cur;
 909        unsigned opt;
 910        int nfiles;
 911        int dnfiles;
 912        int dndirs;
 913        int i;
 914        /* need to initialize since --color has _an optional_ argument */
 915        USE_FEATURE_LS_COLOR(const char *color_opt = "always";)
 916
 917        INIT_G();
 918
 919        all_fmt = LIST_SHORT |
 920                (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
 921
 922#if ENABLE_FEATURE_AUTOWIDTH
 923        /* Obtain the terminal width */
 924        get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
 925        /* Go one less... */
 926        terminal_width--;
 927#endif
 928
 929        /* process options */
 930        USE_FEATURE_LS_COLOR(applet_long_options = ls_color_opt;)
 931#if ENABLE_FEATURE_AUTOWIDTH
 932        opt_complementary = "T+:w+"; /* -T N, -w N */
 933        opt = getopt32(argv, ls_options, &tabstops, &terminal_width
 934                                USE_FEATURE_LS_COLOR(, &color_opt));
 935#else
 936        opt = getopt32(argv, ls_options USE_FEATURE_LS_COLOR(, &color_opt));
 937#endif
 938        for (i = 0; opt_flags[i] != (1U<<31); i++) {
 939                if (opt & (1 << i)) {
 940                        unsigned flags = opt_flags[i];
 941
 942                        if (flags & LIST_MASK_TRIGGER)
 943                                all_fmt &= ~LIST_MASK;
 944                        if (flags & STYLE_MASK_TRIGGER)
 945                                all_fmt &= ~STYLE_MASK;
 946                        if (flags & SORT_MASK_TRIGGER)
 947                                all_fmt &= ~SORT_MASK;
 948                        if (flags & DISP_MASK_TRIGGER)
 949                                all_fmt &= ~DISP_MASK;
 950                        if (flags & TIME_MASK)
 951                                all_fmt &= ~TIME_MASK;
 952                        if (flags & LIST_CONTEXT)
 953                                all_fmt |= STYLE_SINGLE;
 954                        /* huh?? opt cannot be 'l' */
 955                        //if (LS_DISP_HR && opt == 'l')
 956                        //      all_fmt &= ~LS_DISP_HR;
 957                        all_fmt |= flags;
 958                }
 959        }
 960
 961#if ENABLE_FEATURE_LS_COLOR
 962        /* find color bit value - last position for short getopt */
 963        if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
 964                char *p = getenv("LS_COLORS");
 965                /* LS_COLORS is unset, or (not empty && not "none") ? */
 966                if (!p || (p[0] && strcmp(p, "none") != 0))
 967                        show_color = 1;
 968        }
 969        if (opt & (1 << i)) {  /* next flag after short options */
 970                if (strcmp("always", color_opt) == 0)
 971                        show_color = 1;
 972                else if (strcmp("never", color_opt) == 0)
 973                        show_color = 0;
 974                else if (strcmp("auto", color_opt) == 0 && isatty(STDOUT_FILENO))
 975                        show_color = 1;
 976        }
 977#endif
 978
 979        /* sort out which command line options take precedence */
 980        if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
 981                all_fmt &= ~DISP_RECURSIVE;     /* no recurse if listing only dir */
 982        if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
 983                if (all_fmt & TIME_CHANGE)
 984                        all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
 985                if (all_fmt & TIME_ACCESS)
 986                        all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
 987        }
 988        if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
 989                all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
 990        if (ENABLE_FEATURE_LS_USERNAME)
 991                if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
 992                        all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
 993
 994        /* choose a display format */
 995        if (!(all_fmt & STYLE_MASK))
 996                all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
 997
 998        argv += optind;
 999        if (!argv[0])
1000                *--argv = (char*)".";
1001
1002        if (argv[1])
1003                all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1004
1005        /* stuff the command line file names into a dnode array */
1006        dn = NULL;
1007        nfiles = 0;
1008        do {
1009                /* NB: follow links on command line unless -l or -s */
1010                cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1011                argv++;
1012                if (!cur)
1013                        continue;
1014                cur->allocated = 0;
1015                cur->next = dn;
1016                dn = cur;
1017                nfiles++;
1018        } while (*argv);
1019
1020        /* now that we know how many files there are
1021         * allocate memory for an array to hold dnode pointers
1022         */
1023        dnp = dnalloc(nfiles);
1024        for (i = 0, cur = dn; i < nfiles; i++) {
1025                dnp[i] = cur;   /* save pointer to node in array */
1026                cur = cur->next;
1027        }
1028
1029        if (all_fmt & DISP_NOLIST) {
1030                dnsort(dnp, nfiles);
1031                if (nfiles > 0)
1032                        showfiles(dnp, nfiles);
1033        } else {
1034                dnd = splitdnarray(dnp, nfiles, SPLIT_DIR);
1035                dnf = splitdnarray(dnp, nfiles, SPLIT_FILE);
1036                dndirs = countdirs(dnp, nfiles);
1037                dnfiles = nfiles - dndirs;
1038                if (dnfiles > 0) {
1039                        dnsort(dnf, dnfiles);
1040                        showfiles(dnf, dnfiles);
1041                        if (ENABLE_FEATURE_CLEAN_UP)
1042                                free(dnf);
1043                }
1044                if (dndirs > 0) {
1045                        dnsort(dnd, dndirs);
1046                        showdirs(dnd, dndirs, dnfiles == 0);
1047                        if (ENABLE_FEATURE_CLEAN_UP)
1048                                free(dnd);
1049                }
1050        }
1051        if (ENABLE_FEATURE_CLEAN_UP)
1052                dfree(dnp, nfiles);
1053        return exit_code;
1054}
1055