X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;ds=sidebyside;f=coreutils%2Fls.c;h=14c8beaffa6f043069170006b431e42c7d1c8d27;hb=2ec4f44225f18bc00732cac21f3fde9d3dc2f29f;hp=7bbb19d6cf3dd36c7d1fc6003456be30b1ab06ea;hpb=99912ca733dd960f5589227fd999c86e73c8e894;p=oweals%2Fbusybox.git diff --git a/coreutils/ls.c b/coreutils/ls.c index 7bbb19d6c..14c8beaff 100644 --- a/coreutils/ls.c +++ b/coreutils/ls.c @@ -1,12 +1,11 @@ /* vi: set sw=4 ts=4: */ /* - * tiny-ls.c version 0.1.0: A minimalist 'ls' * Copyright (C) 1996 Brian Candler * - * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ -/* +/* [date unknown. Perhaps before year 2000] * To achieve a small memory footprint, this version of 'ls' doesn't do any * file sorting, and only has the most essential command line switches * (i.e., the ones I couldn't live without :-) All features which involve @@ -17,9 +16,7 @@ * it more portable. * * KNOWN BUGS: - * 1. ls -l of a directory doesn't give "total " header - * 2. ls of a symlink to a directory doesn't list directory contents - * 3. hidden files can make column width too large + * 1. hidden files can make column width too large * * NON-OPTIMAL BEHAVIOUR: * 1. autowidth reads directories twice @@ -27,24 +24,104 @@ * appended, there's no need to stat each one * PORTABILITY: * 1. requires lstat (BSD) - how do you do it without? + * + * [2009-03] + * ls sorts listing now, and supports almost all options. */ -#include -#include "busybox.h" +//usage:#define ls_trivial_usage +//usage: "[-1AaCxd" +//usage: IF_FEATURE_LS_FOLLOWLINKS("LH") +//usage: IF_FEATURE_LS_RECURSIVE("R") +//usage: IF_FEATURE_LS_FILETYPES("Fp") "lins" +//usage: IF_FEATURE_LS_TIMESTAMPS("e") +//usage: IF_FEATURE_HUMAN_READABLE("h") +//usage: IF_FEATURE_LS_SORTFILES("rSXv") +//usage: IF_FEATURE_LS_TIMESTAMPS("ctu") +//usage: IF_SELINUX("kKZ") "]" +//usage: IF_FEATURE_AUTOWIDTH(" [-w WIDTH]") " [FILE]..." +//usage:#define ls_full_usage "\n\n" +//usage: "List directory contents\n" +//usage: "\n -1 One column output" +//usage: "\n -a Include entries which start with ." +//usage: "\n -A Like -a, but exclude . and .." +//usage: "\n -C List by columns" +//usage: "\n -x List by lines" +//usage: "\n -d List directory entries instead of contents" +//usage: IF_FEATURE_LS_FOLLOWLINKS( +//usage: "\n -L Follow symlinks" +//usage: "\n -H Follow symlinks on command line" +//usage: ) +//usage: IF_FEATURE_LS_RECURSIVE( +//usage: "\n -R Recurse" +//usage: ) +//usage: IF_FEATURE_LS_FILETYPES( +//usage: "\n -p Append / to dir entries" +//usage: "\n -F Append indicator (one of */=@|) to entries" +//usage: ) +//usage: "\n -l Long listing format" +//usage: "\n -i List inode numbers" +//usage: "\n -n List numeric UIDs and GIDs instead of names" +//usage: "\n -s List allocated blocks" +//usage: IF_FEATURE_LS_TIMESTAMPS( +//usage: "\n -e List full date and time" +//usage: ) +//usage: IF_FEATURE_HUMAN_READABLE( +//usage: "\n -h List sizes in human readable format (1K 243M 2G)" +//usage: ) +//usage: IF_FEATURE_LS_SORTFILES( +//usage: "\n -r Sort in reverse order" +//usage: "\n -S Sort by size" +//usage: "\n -X Sort by extension" +//usage: "\n -v Sort by version" +//usage: ) +//usage: IF_FEATURE_LS_TIMESTAMPS( +//usage: "\n -c With -l: sort by ctime" +//usage: "\n -t With -l: sort by mtime" +//usage: "\n -u With -l: sort by atime" +//usage: ) +//usage: IF_SELINUX( +//usage: "\n -k List security context" +//usage: "\n -K List security context in long format" +//usage: "\n -Z List security context and permission" +//usage: ) +//usage: IF_FEATURE_AUTOWIDTH( +//usage: "\n -w N Assume the terminal is N columns wide" +//usage: ) +//usage: IF_FEATURE_LS_COLOR( +//usage: "\n --color[={always,never,auto}] Control coloring" +//usage: ) + +#include "libbb.h" +#include "unicode.h" + /* This is a NOEXEC applet. Be very careful! */ -enum { +#if ENABLE_FTPD +/* ftpd uses ls, and without timestamps Mozilla won't understand + * ftpd's LIST output. + */ +# undef CONFIG_FEATURE_LS_TIMESTAMPS +# undef ENABLE_FEATURE_LS_TIMESTAMPS +# undef IF_FEATURE_LS_TIMESTAMPS +# undef IF_NOT_FEATURE_LS_TIMESTAMPS +# define CONFIG_FEATURE_LS_TIMESTAMPS 1 +# define ENABLE_FEATURE_LS_TIMESTAMPS 1 +# define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__ +# define IF_NOT_FEATURE_LS_TIMESTAMPS(...) +#endif + +enum { TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */ -COLUMN_GAP = 2, /* includes the file type char */ -/* what is the overall style of the listing */ -STYLE_COLUMNS = 1 << 21, /* fill columns */ -STYLE_LONG = 2 << 21, /* one record per line, extended info */ -STYLE_SINGLE = 3 << 21, /* one record per line */ -STYLE_MASK = STYLE_SINGLE, +SPLIT_FILE = 0, +SPLIT_DIR = 1, +SPLIT_SUBDIR = 2, + +/* Bits in G.all_fmt: */ /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */ /* what file information will be listed */ @@ -56,159 +133,289 @@ LIST_ID_NAME = 1 << 4, LIST_ID_NUMERIC = 1 << 5, LIST_CONTEXT = 1 << 6, LIST_SIZE = 1 << 7, -LIST_DEV = 1 << 8, -LIST_DATE_TIME = 1 << 9, -LIST_FULLTIME = 1 << 10, -LIST_FILENAME = 1 << 11, -LIST_SYMLINK = 1 << 12, -LIST_FILETYPE = 1 << 13, -LIST_EXEC = 1 << 14, -LIST_MASK = (LIST_EXEC << 1) - 1, +LIST_DATE_TIME = 1 << 8, +LIST_FULLTIME = 1 << 9, +LIST_SYMLINK = 1 << 10, +LIST_FILETYPE = 1 << 11, /* show / suffix for dirs */ +LIST_CLASSIFY = 1 << 12, /* requires LIST_FILETYPE, also show *,|,@,= suffixes */ +LIST_MASK = (LIST_CLASSIFY << 1) - 1, /* what files will be displayed */ -DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */ -DISP_HIDDEN = 1 << 16, /* show filenames starting with . */ -DISP_DOT = 1 << 17, /* show . and .. */ -DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */ -DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */ -DISP_ROWS = 1 << 20, /* print across rows */ +DISP_DIRNAME = 1 << 13, /* 2 or more items? label directories */ +DISP_HIDDEN = 1 << 14, /* show filenames starting with . */ +DISP_DOT = 1 << 15, /* show . and .. */ +DISP_NOLIST = 1 << 16, /* show directory as itself, not contents */ +DISP_RECURSIVE = 1 << 17, /* show directory and everything below it */ +DISP_ROWS = 1 << 18, /* print across rows */ DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1), -/* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */ -SORT_FORWARD = 0, /* sort in reverse order */ -SORT_REVERSE = 1 << 27, /* sort in reverse order */ - -SORT_NAME = 0, /* sort by file name */ -SORT_SIZE = 1 << 28, /* sort by file size */ -SORT_ATIME = 2 << 28, /* sort by last access time */ -SORT_CTIME = 3 << 28, /* sort by last change time */ -SORT_MTIME = 4 << 28, /* sort by last modification time */ -SORT_VERSION = 5 << 28, /* sort by version */ -SORT_EXT = 6 << 28, /* sort by file name extension */ -SORT_DIR = 7 << 28, /* sort by file or directory */ -SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES, +/* what is the overall style of the listing */ +STYLE_COLUMNAR = 1 << 19, /* many records per line */ +STYLE_LONG = 2 << 19, /* one record per line, extended info */ +STYLE_SINGLE = 3 << 19, /* one record per line */ +STYLE_MASK = STYLE_SINGLE, /* which of the three times will be used */ -TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS, -TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS, -TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS, +TIME_CHANGE = (1 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS, +TIME_ACCESS = (2 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS, +TIME_MASK = (3 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS, -FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS, +/* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */ +SORT_REVERSE = 1 << 23, -LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE, +SORT_NAME = 0, /* sort by file name */ +SORT_SIZE = 1 << 24, /* sort by file size */ +SORT_ATIME = 2 << 24, /* sort by last access time */ +SORT_CTIME = 3 << 24, /* sort by last change time */ +SORT_MTIME = 4 << 24, /* sort by last modification time */ +SORT_VERSION = 5 << 24, /* sort by version */ +SORT_EXT = 6 << 24, /* sort by file name extension */ +SORT_DIR = 7 << 24, /* sort by file or directory */ +SORT_MASK = (7 << 24) * ENABLE_FEATURE_LS_SORTFILES, -LIST_SHORT = LIST_FILENAME, LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \ - LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK, - -SPLIT_DIR = 1, -SPLIT_FILE = 0, -SPLIT_SUBDIR = 2, - + LIST_DATE_TIME | LIST_SYMLINK, }; -#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f) -#define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)]) -#define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)]) -#define COLOR(mode) ("\000\043\043\043\042\000\043\043"\ - "\000\000\044\000\043\000\000\040" [TYPEINDEX(mode)]) -#define ATTR(mode) ("\00\00\01\00\01\00\01\00"\ - "\00\00\01\00\01\00\00\01" [TYPEINDEX(mode)]) - -/* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */ -#if ENABLE_FEATURE_LS_COLOR -static int show_color; -/* long option entry used only for --color, which has no short option - * equivalent */ -static const struct option ls_color_opt[] = { - { "color", optional_argument, NULL, 1 }, - { NULL, 0, NULL, 0 } +/* -Cadil1 Std options, busybox always supports */ +/* -gnsxA Std options, busybox always supports */ +/* -Q GNU option, busybox always supports */ +/* -k SELinux option, busybox always supports (ignores if !SELinux) */ +/* Std has -k which means "show sizes in kbytes" */ +/* -LHRctur Std options, busybox optionally supports */ +/* -Fp Std options, busybox optionally supports */ +/* -SXvhTw GNU options, busybox optionally supports */ +/* -T WIDTH Ignored (we don't use tabs on output) */ +/* -KZ SELinux mandated options, busybox optionally supports */ +/* (coreutils 8.4 has no -K, remove it?) */ +/* -e I think we made this one up (looks similar to GNU --full-time) */ +/* We already used up all 32 bits, if we need to add more, candidates for removal: */ +/* -K, -T, -e (add --full-time instead) */ +static const char ls_options[] ALIGN1 = + "Cadil1gnsxQAk" /* 13 opts, total 13 */ + IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */ + IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */ + IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */ + IF_FEATURE_LS_RECURSIVE("R") /* 1, 24 */ + IF_SELINUX("KZ") /* 2, 26 */ + IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 28 */ + IF_FEATURE_HUMAN_READABLE("h") /* 1, 29 */ + IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 31 */ + /* with --color, we use all 32 bits */; +enum { + //OPT_C = (1 << 0), + //OPT_a = (1 << 1), + //OPT_d = (1 << 2), + //OPT_i = (1 << 3), + //OPT_l = (1 << 4), + //OPT_1 = (1 << 5), + OPT_g = (1 << 6), + //OPT_n = (1 << 7), + //OPT_s = (1 << 8), + //OPT_x = (1 << 9), + OPT_Q = (1 << 10), + //OPT_A = (1 << 11), + //OPT_k = (1 << 12), + + OPTBIT_c = 13, + OPTBIT_e, + OPTBIT_t, + OPTBIT_u, + OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS, + OPTBIT_X, /* 18 */ + OPTBIT_r, + OPTBIT_v, + OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES, + OPTBIT_p, /* 22 */ + OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES, + OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE, + OPTBIT_Z, /* 25 */ + OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX, + OPTBIT_H, /* 27 */ + OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS, + OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE, + OPTBIT_w, /* 30 */ + OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH, + + OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS, + OPT_e = (1 << OPTBIT_e) * ENABLE_FEATURE_LS_TIMESTAMPS, + OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS, + OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS, + OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES, + OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES, + OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES, + OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES, + OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES, + OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES, + OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE, + OPT_K = (1 << OPTBIT_K) * ENABLE_SELINUX, + OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX, + OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS, + OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS, + OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE, + OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_AUTOWIDTH, + OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_AUTOWIDTH, + OPT_color = (1 << OPTBIT_color) * ENABLE_FEATURE_LS_COLOR, }; -#else -enum { show_color = 0 }; + +/* TODO: simple toggles may be stored as OPT_xxx bits instead */ +static const uint32_t opt_flags[] = { + STYLE_COLUMNAR, /* C */ + DISP_HIDDEN | DISP_DOT, /* a */ + DISP_NOLIST, /* d */ + LIST_INO, /* i */ + LIST_LONG | STYLE_LONG, /* l */ + STYLE_SINGLE, /* 1 */ + LIST_LONG | STYLE_LONG, /* g (don't show owner) - handled via OPT_g. assumes l */ + LIST_ID_NUMERIC | LIST_LONG | STYLE_LONG, /* n (assumes l) */ + LIST_BLOCKS, /* s */ + DISP_ROWS | STYLE_COLUMNAR, /* x */ + 0, /* Q (quote filename) - handled via OPT_Q */ + DISP_HIDDEN, /* A */ + ENABLE_SELINUX * (LIST_CONTEXT|STYLE_SINGLE), /* k (ignored if !SELINUX) */ +#if ENABLE_FEATURE_LS_TIMESTAMPS + TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */ + LIST_FULLTIME, /* e */ + ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */ + TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */ #endif +#if ENABLE_FEATURE_LS_SORTFILES + SORT_SIZE, /* S */ + SORT_EXT, /* X */ + SORT_REVERSE, /* r */ + SORT_VERSION, /* v */ +#endif +#if ENABLE_FEATURE_LS_FILETYPES + LIST_FILETYPE | LIST_CLASSIFY, /* F */ + LIST_FILETYPE, /* p */ +#endif +#if ENABLE_FEATURE_LS_RECURSIVE + DISP_RECURSIVE, /* R */ +#endif +#if ENABLE_SELINUX + LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME|STYLE_SINGLE, /* K */ + LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT|STYLE_SINGLE, /* Z */ +#endif + (1U << 31) + /* options after Z are not processed through opt_flags */ +}; + /* - * a directory entry and its stat info are stored here + * a directory entry and its stat info */ -struct dnode { /* the basic node */ - char *name; /* the dir entry name */ - char *fullname; /* the dir entry name */ - int allocated; - struct stat dstat; /* the file stat info */ - USE_SELINUX(security_context_t sid;) - struct dnode *next; /* point at the next node */ +struct dnode { + const char *name; /* usually basename, but think "ls -l dir/file" */ + const char *fullname; /* full name (usable for stat etc) */ + struct dnode *dn_next; /* for linked list */ + IF_SELINUX(security_context_t sid;) + smallint fname_allocated; + + /* Used to avoid re-doing [l]stat at printout stage + * if we already collected needed data in scan stage: + */ + mode_t dn_mode_lstat; /* obtained with lstat, or 0 */ + mode_t dn_mode_stat; /* obtained with stat, or 0 */ + +// struct stat dstat; +// struct stat is huge. We don't need it in full. +// At least we don't need st_dev and st_blksize, +// but there are invisible fields as well +// (such as nanosecond-resolution timespamps) +// and padding, which we also don't want to store. +// We also can pre-parse dev_t dn_rdev (in glibc, it's huge). +// On 32-bit uclibc: dnode size went from 112 to 84 bytes. +// + /* Same names as in struct stat, but with dn_ instead of st_ pfx: */ + mode_t dn_mode; /* obtained with lstat OR stat, depending on -L etc */ + off_t dn_size; +#if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES + time_t dn_atime; + time_t dn_mtime; + time_t dn_ctime; +#endif + ino_t dn_ino; + blkcnt_t dn_blocks; + nlink_t dn_nlink; + uid_t dn_uid; + gid_t dn_gid; + int dn_rdev_maj; + int dn_rdev_min; +// dev_t dn_dev; +// blksize_t dn_blksize; }; -typedef struct dnode dnode_t; - -static struct dnode **list_dir(const char *); -static struct dnode **dnalloc(int); -static int list_single(struct dnode *); - -static unsigned all_fmt; +struct globals { +#if ENABLE_FEATURE_LS_COLOR + smallint show_color; +# define G_show_color (G.show_color) +#else +# define G_show_color 0 +#endif + smallint exit_code; + unsigned all_fmt; #if ENABLE_FEATURE_AUTOWIDTH -static unsigned tabstops = COLUMN_GAP; -static unsigned terminal_width = TERMINAL_WIDTH; + unsigned terminal_width; +# define G_terminal_width (G.terminal_width) #else -enum { - tabstops = COLUMN_GAP, - terminal_width = TERMINAL_WIDTH, -}; +# define G_terminal_width TERMINAL_WIDTH #endif +#if ENABLE_FEATURE_LS_TIMESTAMPS + /* Do time() just once. Saves one syscall per file for "ls -l" */ + time_t current_time_t; +#endif +} FIX_ALIASING; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define INIT_G() do { \ + /* we have to zero it out because of NOEXEC */ \ + memset(&G, 0, sizeof(G)); \ + IF_FEATURE_AUTOWIDTH(G_terminal_width = TERMINAL_WIDTH;) \ + IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \ +} while (0) -static int status = EXIT_SUCCESS; -static struct dnode *my_stat(char *fullname, char *name, int force_follow) -{ - struct stat dstat; - struct dnode *cur; - USE_SELINUX(security_context_t sid = NULL;) +/*** Output code ***/ - if ((all_fmt & FOLLOW_LINKS) || force_follow) { -#if ENABLE_SELINUX - if (is_selinux_enabled()) { - getfilecon(fullname, &sid); - } -#endif - if (stat(fullname, &dstat)) { - bb_perror_msg("%s", fullname); - status = EXIT_FAILURE; - return 0; - } - } else { -#if ENABLE_SELINUX - if (is_selinux_enabled()) { - lgetfilecon(fullname, &sid); - } -#endif - if (lstat(fullname, &dstat)) { - bb_perror_msg("%s", fullname); - status = EXIT_FAILURE; - return 0; - } - } - cur = xmalloc(sizeof(struct dnode)); - cur->fullname = fullname; - cur->name = name; - cur->dstat = dstat; - USE_SELINUX(cur->sid = sid;) - return cur; -} +/* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket + * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file + * 3/7:multiplexed char/block device) + * and we use 0 for unknown and 15 for executables (see below) */ +#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f) +/* un fi chr - dir - blk - file - link - sock - - exe */ +#define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)]) +/* 036 black foreground 050 black background + 037 red foreground 051 red background + 040 green foreground 052 green background + 041 brown foreground 053 brown background + 042 blue foreground 054 blue background + 043 magenta (purple) foreground 055 magenta background + 044 cyan (light blue) foreground 056 cyan background + 045 gray foreground 057 white background +*/ +#define COLOR(mode) ( \ + /*un fi chr - dir - blk - file - link - sock - - exe */ \ + "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \ + [TYPEINDEX(mode)]) +/* Select normal (0) [actually "reset all"] or bold (1) + * (other attributes are 2:dim 4:underline 5:blink 7:reverse, + * let's use 7 for "impossible" types, just for fun) + * Note: coreutils 6.9 uses inverted red for setuid binaries. + */ +#define ATTR(mode) ( \ + /*un fi chr - dir - blk - file- link- sock- - exe */ \ + "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \ + [TYPEINDEX(mode)]) #if ENABLE_FEATURE_LS_COLOR +/* mode of zero is interpreted as "unknown" (stat failed) */ static char fgcolor(mode_t mode) { - /* Check wheter the file is existing (if so, color it red!) */ - if (errno == ENOENT) - return '\037'; if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return COLOR(0xF000); /* File is executable ... */ return COLOR(mode); } - -static char bgcolor(mode_t mode) +static char bold(mode_t mode) { if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return ATTR(0xF000); /* File is executable ... */ @@ -216,14 +423,14 @@ static char bgcolor(mode_t mode) } #endif -#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR +#if ENABLE_FEATURE_LS_FILETYPES static char append_char(mode_t mode) { - if (!(all_fmt & LIST_FILETYPE)) + if (!(G.all_fmt & LIST_FILETYPE)) return '\0'; if (S_ISDIR(mode)) return '/'; - if (!(all_fmt & LIST_EXEC)) + if (!(G.all_fmt & LIST_CLASSIFY)) return '\0'; if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return '*'; @@ -231,106 +438,426 @@ static char append_char(mode_t mode) } #endif -#define countdirs(A, B) count_dirs((A), (B), 1) -#define countsubdirs(A, B) count_dirs((A), (B), 0) -static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs) +static unsigned calc_name_len(const char *name) { - int i, dirs; + unsigned len; + uni_stat_t uni_stat; - if (!dn) - return 0; - dirs = 0; - for (i = 0; i < nfiles; i++) { - char *name; - if (!S_ISDIR(dn[i]->dstat.st_mode)) - continue; - name = dn[i]->name; - if (notsubdirs - || name[0]!='.' || (name[1] && (name[1]!='.' || name[2])) - ) { - dirs++; + // TODO: quote tab as \t, etc, if -Q + name = printable_string(&uni_stat, name); + + if (!(option_mask32 & OPT_Q)) { + return uni_stat.unicode_width; + } + + len = 2 + uni_stat.unicode_width; + while (*name) { + if (*name == '"' || *name == '\\') { + len++; + } + name++; + } + return len; +} + +/* Return the number of used columns. + * Note that only STYLE_COLUMNAR uses return value. + * STYLE_SINGLE and STYLE_LONG don't care. + * coreutils 7.2 also supports: + * ls -b (--escape) = octal escapes (although it doesn't look like working) + * ls -N (--literal) = not escape at all + */ +static unsigned print_name(const char *name) +{ + unsigned len; + uni_stat_t uni_stat; + + // TODO: quote tab as \t, etc, if -Q + name = printable_string(&uni_stat, name); + + if (!(option_mask32 & OPT_Q)) { + fputs(name, stdout); + return uni_stat.unicode_width; + } + + len = 2 + uni_stat.unicode_width; + putchar('"'); + while (*name) { + if (*name == '"' || *name == '\\') { + putchar('\\'); + len++; + } + putchar(*name); + name++; + } + putchar('"'); + return len; +} + +/* Return the number of used columns. + * Note that only STYLE_COLUMNAR uses return value, + * STYLE_SINGLE and STYLE_LONG don't care. + */ +static NOINLINE unsigned display_single(const struct dnode *dn) +{ + unsigned column = 0; + char *lpath; +#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR + struct stat statbuf; + char append; +#endif + +#if ENABLE_FEATURE_LS_FILETYPES + append = append_char(dn->dn_mode); +#endif + + /* Do readlink early, so that if it fails, error message + * does not appear *inside* the "ls -l" line */ + lpath = NULL; + if (G.all_fmt & LIST_SYMLINK) + if (S_ISLNK(dn->dn_mode)) + lpath = xmalloc_readlink_or_warn(dn->fullname); + + if (G.all_fmt & LIST_INO) + column += printf("%7llu ", (long long) dn->dn_ino); +//TODO: -h should affect -s too: + if (G.all_fmt & LIST_BLOCKS) + column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1)); + if (G.all_fmt & LIST_MODEBITS) + column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode)); + if (G.all_fmt & LIST_NLINKS) + column += printf("%4lu ", (long) dn->dn_nlink); + if (G.all_fmt & LIST_ID_NUMERIC) { + if (option_mask32 & OPT_g) + column += printf("%-8u ", (int) dn->dn_gid); + else + column += printf("%-8u %-8u ", + (int) dn->dn_uid, + (int) dn->dn_gid); + } +#if ENABLE_FEATURE_LS_USERNAME + else if (G.all_fmt & LIST_ID_NAME) { + if (option_mask32 & OPT_g) { + column += printf("%-8.8s ", + get_cached_groupname(dn->dn_gid)); + } else { + column += printf("%-8.8s %-8.8s ", + get_cached_username(dn->dn_uid), + get_cached_groupname(dn->dn_gid)); + } + } +#endif + if (G.all_fmt & LIST_SIZE) { + if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) { + column += printf("%4u, %3u ", + dn->dn_rdev_maj, + dn->dn_rdev_min); + } else { + if (option_mask32 & OPT_h) { + column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ", + /* print size, show one fractional, use suffixes */ + make_human_readable_str(dn->dn_size, 1, 0) + ); + } else { + column += printf("%9"OFF_FMT"u ", dn->dn_size); + } } } - return dirs; +#if ENABLE_FEATURE_LS_TIMESTAMPS + if (G.all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) { + char *filetime; + const time_t *ttime = &dn->dn_mtime; + if (G.all_fmt & TIME_ACCESS) + ttime = &dn->dn_atime; + if (G.all_fmt & TIME_CHANGE) + ttime = &dn->dn_ctime; + filetime = ctime(ttime); + /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */ + if (G.all_fmt & LIST_FULLTIME) { /* -e */ + /* Note: coreutils 8.4 ls --full-time prints: + * 2009-07-13 17:49:27.000000000 +0200 + */ + column += printf("%.24s ", filetime); + } else { /* LIST_DATE_TIME */ + /* G.current_time_t ~== time(NULL) */ + time_t age = G.current_time_t - *ttime; + if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) { + /* less than 6 months old */ + /* "mmm dd hh:mm " */ + printf("%.12s ", filetime + 4); + } else { + /* "mmm dd yyyy " */ + /* "mmm dd yyyyy " after year 9999 :) */ + strchr(filetime + 20, '\n')[0] = ' '; + printf("%.7s%6s", filetime + 4, filetime + 20); + } + column += 13; + } + } +#endif +#if ENABLE_SELINUX + if (G.all_fmt & LIST_CONTEXT) { + column += printf("%-32s ", dn->sid ? dn->sid : "unknown"); + freecon(dn->sid); + } +#endif + +#if ENABLE_FEATURE_LS_COLOR + if (G_show_color) { + mode_t mode = dn->dn_mode_lstat; + if (!mode) + if (lstat(dn->fullname, &statbuf) == 0) + mode = statbuf.st_mode; + printf("\033[%u;%um", bold(mode), fgcolor(mode)); + } +#endif + column += print_name(dn->name); + if (G_show_color) { + printf("\033[0m"); + } + + if (lpath) { + printf(" -> "); +#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR + if ((G.all_fmt & LIST_FILETYPE) || G_show_color) { + mode_t mode = dn->dn_mode_stat; + if (!mode) + if (stat(dn->fullname, &statbuf) == 0) + mode = statbuf.st_mode; +# if ENABLE_FEATURE_LS_FILETYPES + append = append_char(mode); +# endif +# if ENABLE_FEATURE_LS_COLOR + if (G_show_color) { + printf("\033[%u;%um", bold(mode), fgcolor(mode)); + } +# endif + } +#endif + column += print_name(lpath) + 4; + free(lpath); + if (G_show_color) { + printf("\033[0m"); + } + } +#if ENABLE_FEATURE_LS_FILETYPES + if (G.all_fmt & LIST_FILETYPE) { + if (append) { + putchar(append); + column++; + } + } +#endif + + return column; } -static int countfiles(struct dnode **dnp) +static void display_files(struct dnode **dn, unsigned nfiles) { - int nfiles; + unsigned i, ncols, nrows, row, nc; + unsigned column; + unsigned nexttab; + unsigned column_width = 0; /* used only by STYLE_COLUMNAR */ + + if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */ + ncols = 1; + } else { + /* find the longest file name, use that as the column width */ + for (i = 0; dn[i]; i++) { + int len = calc_name_len(dn[i]->name); + if (column_width < len) + column_width = len; + } + column_width += 1 + + IF_SELINUX( ((G.all_fmt & LIST_CONTEXT) ? 33 : 0) + ) + ((G.all_fmt & LIST_INO) ? 8 : 0) + + ((G.all_fmt & LIST_BLOCKS) ? 5 : 0); + ncols = (unsigned)G_terminal_width / column_width; + } + + if (ncols > 1) { + nrows = nfiles / ncols; + if (nrows * ncols < nfiles) + nrows++; /* round up fractionals */ + } else { + nrows = nfiles; + ncols = 1; + } + + column = 0; + nexttab = 0; + for (row = 0; row < nrows; row++) { + for (nc = 0; nc < ncols; nc++) { + /* reach into the array based on the column and row */ + if (G.all_fmt & DISP_ROWS) + i = (row * ncols) + nc; /* display across row */ + else + i = (nc * nrows) + row; /* display by column */ + if (i < nfiles) { + if (column > 0) { + nexttab -= column; + printf("%*s ", nexttab, ""); + column += nexttab + 1; + } + nexttab = column + column_width; + column += display_single(dn[i]); + } + } + putchar('\n'); + column = 0; + } +} + + +/*** Dir scanning code ***/ + +static struct dnode *my_stat(const char *fullname, const char *name, int force_follow) +{ + struct stat statbuf; struct dnode *cur; - if (dnp == NULL) + cur = xzalloc(sizeof(*cur)); + cur->fullname = fullname; + cur->name = name; + + if ((option_mask32 & OPT_L) || force_follow) { +#if ENABLE_SELINUX + if (is_selinux_enabled()) { + getfilecon(fullname, &cur->sid); + } +#endif + if (stat(fullname, &statbuf)) { + bb_simple_perror_msg(fullname); + G.exit_code = EXIT_FAILURE; + free(cur); + return NULL; + } + cur->dn_mode_stat = statbuf.st_mode; + } else { +#if ENABLE_SELINUX + if (is_selinux_enabled()) { + lgetfilecon(fullname, &cur->sid); + } +#endif + if (lstat(fullname, &statbuf)) { + bb_simple_perror_msg(fullname); + G.exit_code = EXIT_FAILURE; + free(cur); + return NULL; + } + cur->dn_mode_lstat = statbuf.st_mode; + } + + /* cur->dstat = statbuf: */ + cur->dn_mode = statbuf.st_mode ; + cur->dn_size = statbuf.st_size ; +#if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES + cur->dn_atime = statbuf.st_atime ; + cur->dn_mtime = statbuf.st_mtime ; + cur->dn_ctime = statbuf.st_ctime ; +#endif + cur->dn_ino = statbuf.st_ino ; + cur->dn_blocks = statbuf.st_blocks; + cur->dn_nlink = statbuf.st_nlink ; + cur->dn_uid = statbuf.st_uid ; + cur->dn_gid = statbuf.st_gid ; + cur->dn_rdev_maj = major(statbuf.st_rdev); + cur->dn_rdev_min = minor(statbuf.st_rdev); + + return cur; +} + +static unsigned count_dirs(struct dnode **dn, int which) +{ + unsigned dirs, all; + + if (!dn) return 0; - nfiles = 0; - for (cur = dnp[0]; cur->next; cur = cur->next) - nfiles++; - nfiles++; - return nfiles; + + dirs = all = 0; + for (; *dn; dn++) { + const char *name; + + all++; + if (!S_ISDIR((*dn)->dn_mode)) + continue; + + name = (*dn)->name; + if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */ + /* or if it's not . or .. */ + || name[0] != '.' + || (name[1] && (name[1] != '.' || name[2])) + ) { + dirs++; + } + } + return which != SPLIT_FILE ? dirs : all - dirs; } /* get memory to hold an array of pointers */ -static struct dnode **dnalloc(int num) +static struct dnode **dnalloc(unsigned num) { if (num < 1) return NULL; + num++; /* so that we have terminating NULL */ return xzalloc(num * sizeof(struct dnode *)); } #if ENABLE_FEATURE_LS_RECURSIVE -static void dfree(struct dnode **dnp, int nfiles) +static void dfree(struct dnode **dnp) { - int i; + unsigned i; if (dnp == NULL) return; - for (i = 0; i < nfiles; i++) { + for (i = 0; dnp[i]; i++) { struct dnode *cur = dnp[i]; - if (cur->allocated) - free(cur->fullname); /* free the filename */ - free(cur); /* free the dnode */ + if (cur->fname_allocated) + free((char*)cur->fullname); + free(cur); } - free(dnp); /* free the array holding the dnode pointers */ + free(dnp); } #else #define dfree(...) ((void)0) #endif -static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which) +/* Returns NULL-terminated malloced vector of pointers (or NULL) */ +static struct dnode **splitdnarray(struct dnode **dn, int which) { - int dncnt, i, d; + unsigned dncnt, d; struct dnode **dnp; - if (dn == NULL || nfiles < 1) + if (dn == NULL) return NULL; - /* count how many dirs and regular files there are */ - if (which == SPLIT_SUBDIR) - dncnt = countsubdirs(dn, nfiles); - else { - dncnt = countdirs(dn, nfiles); /* assume we are looking for dirs */ - if (which == SPLIT_FILE) - dncnt = nfiles - dncnt; /* looking for files */ - } + /* count how many dirs or files there are */ + dncnt = count_dirs(dn, which); /* allocate a file array and a dir array */ dnp = dnalloc(dncnt); /* copy the entrys into the file or dir array */ - for (d = i = 0; i < nfiles; i++) { - if (S_ISDIR(dn[i]->dstat.st_mode)) { - char *name; - if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) + for (d = 0; *dn; dn++) { + if (S_ISDIR((*dn)->dn_mode)) { + const char *name; + + if (which == SPLIT_FILE) continue; - name = dn[i]->name; - if ((which & SPLIT_DIR) - || name[0]!='.' || (name[1] && (name[1]!='.' || name[2])) + + name = (*dn)->name; + if ((which & SPLIT_DIR) /* any dir... */ + /* ... or not . or .. */ + || name[0] != '.' + || (name[1] && (name[1] != '.' || name[2])) ) { - dnp[d++] = dn[i]; + dnp[d++] = *dn; } - } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) { - dnp[d++] = dn[i]; + } else + if (which == SPLIT_FILE) { + dnp[d++] = *dn; } } return dnp; @@ -341,450 +868,203 @@ static int sortcmp(const void *a, const void *b) { struct dnode *d1 = *(struct dnode **)a; struct dnode *d2 = *(struct dnode **)b; - unsigned sort_opts = all_fmt & SORT_MASK; - int dif; + unsigned sort_opts = G.all_fmt & SORT_MASK; + off_t dif; dif = 0; /* assume SORT_NAME */ // TODO: use pre-initialized function pointer // instead of branch forest if (sort_opts == SORT_SIZE) { - dif = (int) (d2->dstat.st_size - d1->dstat.st_size); - } else if (sort_opts == SORT_ATIME) { - dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime); - } else if (sort_opts == SORT_CTIME) { - dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime); - } else if (sort_opts == SORT_MTIME) { - dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime); - } else if (sort_opts == SORT_DIR) { - dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode); - /* } else if (sort_opts == SORT_VERSION) { */ - /* } else if (sort_opts == SORT_EXT) { */ + dif = (d2->dn_size - d1->dn_size); + } else + if (sort_opts == SORT_ATIME) { + dif = (d2->dn_atime - d1->dn_atime); + } else + if (sort_opts == SORT_CTIME) { + dif = (d2->dn_ctime - d1->dn_ctime); + } else + if (sort_opts == SORT_MTIME) { + dif = (d2->dn_mtime - d1->dn_mtime); + } else + if (sort_opts == SORT_DIR) { + dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode); + } else +#if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1 + if (sort_opts == SORT_VERSION) { + dif = strverscmp(d1->name, d2->name); + } else +#endif + if (sort_opts == SORT_EXT) { + dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.')); } - if (dif == 0) { - /* sort by name - may be a tie_breaker for time or size cmp */ - if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name); - else dif = strcmp(d1->name, d2->name); + /* sort by name, use as tie breaker for other sorts */ + if (ENABLE_LOCALE_SUPPORT) + dif = strcoll(d1->name, d2->name); + else + dif = strcmp(d1->name, d2->name); } - if (all_fmt & SORT_REVERSE) { - dif = -dif; + /* Make dif fit into an int */ + if (sizeof(dif) > sizeof(int)) { + enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) }; + /* shift leaving only "int" worth of bits */ + if (dif != 0) { + dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT); + } } - return dif; + + return (G.all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif; } static void dnsort(struct dnode **dn, int size) { qsort(dn, size, sizeof(*dn), sortcmp); } -#else -#define dnsort(dn, size) ((void)0) -#endif - -static void showfiles(struct dnode **dn, int nfiles) +static void sort_and_display_files(struct dnode **dn, unsigned nfiles) { - int i, ncols, nrows, row, nc; - int column = 0; - int nexttab = 0; - int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */ - - if (dn == NULL || nfiles < 1) - return; - - if (all_fmt & STYLE_LONG) { - ncols = 1; - } else { - /* find the longest file name, use that as the column width */ - for (i = 0; i < nfiles; i++) { - int len = strlen(dn[i]->name); - if (column_width < len) - column_width = len; - } - column_width += tabstops + - USE_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + ) - ((all_fmt & LIST_INO) ? 8 : 0) + - ((all_fmt & LIST_BLOCKS) ? 5 : 0); - ncols = (int) (terminal_width / column_width); - } - - if (ncols > 1) { - nrows = nfiles / ncols; - if (nrows * ncols < nfiles) - nrows++; /* round up fractionals */ - } else { - nrows = nfiles; - ncols = 1; - } - - for (row = 0; row < nrows; row++) { - for (nc = 0; nc < ncols; nc++) { - /* reach into the array based on the column and row */ - i = (nc * nrows) + row; /* assume display by column */ - if (all_fmt & DISP_ROWS) - i = (row * ncols) + nc; /* display across row */ - if (i < nfiles) { - if (column > 0) { - nexttab -= column; - printf("%*s", nexttab, ""); - column += nexttab; - } - nexttab = column + column_width; - column += list_single(dn[i]); - } - } - putchar('\n'); - column = 0; - } + dnsort(dn, nfiles); + display_files(dn, nfiles); } +#else +# define dnsort(dn, size) ((void)0) +# define sort_and_display_files(dn, nfiles) display_files(dn, nfiles) +#endif - -static void showdirs(struct dnode **dn, int ndirs, int first) -{ - int i, nfiles; - struct dnode **subdnp; - int dndirs; - struct dnode **dnd; - - if (dn == NULL || ndirs < 1) - return; - - for (i = 0; i < ndirs; i++) { - if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) { - if (!first) - puts(""); - first = 0; - printf("%s:\n", dn[i]->fullname); - } - subdnp = list_dir(dn[i]->fullname); - nfiles = countfiles(subdnp); - if (nfiles > 0) { - /* list all files at this level */ - dnsort(subdnp, nfiles); - showfiles(subdnp, nfiles); - if (ENABLE_FEATURE_LS_RECURSIVE) { - if (all_fmt & DISP_RECURSIVE) { - /* recursive- list the sub-dirs */ - dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR); - dndirs = countsubdirs(subdnp, nfiles); - if (dndirs > 0) { - dnsort(dnd, dndirs); - showdirs(dnd, dndirs, 0); - /* free the array of dnode pointers to the dirs */ - free(dnd); - } - } - /* free the dnodes and the fullname mem */ - dfree(subdnp, nfiles); - } - } - } -} - - -static struct dnode **list_dir(const char *path) +/* Returns NULL-terminated malloced vector of pointers (or NULL) */ +static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p) { struct dnode *dn, *cur, **dnp; struct dirent *entry; DIR *dir; - int i, nfiles; - - if (path == NULL) - return NULL; + unsigned i, nfiles; - dn = NULL; - nfiles = 0; + *nfiles_p = 0; dir = warn_opendir(path); if (dir == NULL) { - status = EXIT_FAILURE; + G.exit_code = EXIT_FAILURE; return NULL; /* could not open the dir */ } + dn = NULL; + nfiles = 0; while ((entry = readdir(dir)) != NULL) { char *fullname; /* are we going to list the file- it may be . or .. or a hidden file */ if (entry->d_name[0] == '.') { - if ((entry->d_name[1] == 0 || ( - entry->d_name[1] == '.' - && entry->d_name[2] == 0)) - && !(all_fmt & DISP_DOT)) + if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2])) + && !(G.all_fmt & DISP_DOT) + ) { continue; - if (!(all_fmt & DISP_HIDDEN)) + } + if (!(G.all_fmt & DISP_HIDDEN)) continue; } fullname = concat_path_file(path, entry->d_name); - cur = my_stat(fullname, strrchr(fullname, '/') + 1, 0); + cur = my_stat(fullname, bb_basename(fullname), 0); if (!cur) { free(fullname); continue; } - cur->allocated = 1; - cur->next = dn; + cur->fname_allocated = 1; + cur->dn_next = dn; dn = cur; nfiles++; } closedir(dir); + if (dn == NULL) + return NULL; + /* now that we know how many files there are * allocate memory for an array to hold dnode pointers */ - if (dn == NULL) - return NULL; + *nfiles_p = nfiles; dnp = dnalloc(nfiles); - for (i = 0, cur = dn; i < nfiles; i++) { - dnp[i] = cur; /* save pointer to node in array */ - cur = cur->next; + for (i = 0; /* i < nfiles - detected via !dn below */; i++) { + dnp[i] = dn; /* save pointer to node in array */ + dn = dn->dn_next; + if (!dn) + break; } return dnp; } - -#if ENABLE_FEATURE_LS_TIMESTAMPS -/* Do time() just once. Saves one syscall per file for "ls -l" */ -/* Initialized in main() */ -static time_t current_time_t; -#endif - -static int list_single(struct dnode *dn) +#if ENABLE_DESKTOP +/* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html + * If any of the -l, -n, -s options is specified, each list + * of files within the directory shall be preceded by a + * status line indicating the number of file system blocks + * occupied by files in the directory in 512-byte units if + * the -k option is not specified, or 1024-byte units if the + * -k option is specified, rounded up to the next integral + * number of units. + */ +/* by Jorgen Overgaard (jorgen AT antistaten.se) */ +static off_t calculate_blocks(struct dnode **dn) { - int i, column = 0; + uoff_t blocks = 1; + if (dn) { + while (*dn) { + /* st_blocks is in 512 byte blocks */ + blocks += (*dn)->dn_blocks; + dn++; + } + } -#if ENABLE_FEATURE_LS_TIMESTAMPS - char *filetime; - time_t ttime, age; -#endif -#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR - struct stat info; - char append; + /* Even though standard says use 512 byte blocks, coreutils use 1k */ + /* Actually, we round up by calculating (blocks + 1) / 2, + * "+ 1" was done when we initialized blocks to 1 */ + return blocks >> 1; +} #endif - if (dn->fullname == NULL) - return 0; +static void scan_and_display_dirs_recur(struct dnode **dn, int first) +{ + unsigned nfiles; + struct dnode **subdnp; -#if ENABLE_FEATURE_LS_TIMESTAMPS - ttime = dn->dstat.st_mtime; /* the default time */ - if (all_fmt & TIME_ACCESS) - ttime = dn->dstat.st_atime; - if (all_fmt & TIME_CHANGE) - ttime = dn->dstat.st_ctime; - filetime = ctime(&ttime); -#endif -#if ENABLE_FEATURE_LS_FILETYPES - append = append_char(dn->dstat.st_mode); + for (; *dn; dn++) { + if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) { + if (!first) + bb_putchar('\n'); + first = 0; + printf("%s:\n", (*dn)->fullname); + } + subdnp = scan_one_dir((*dn)->fullname, &nfiles); +#if ENABLE_DESKTOP + if ((G.all_fmt & STYLE_MASK) == STYLE_LONG || (G.all_fmt & LIST_BLOCKS)) + printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp)); #endif + if (nfiles > 0) { + /* list all files at this level */ + sort_and_display_files(subdnp, nfiles); - for (i = 0; i <= 31; i++) { - switch (all_fmt & (1 << i)) { - case LIST_INO: - column += printf("%7ld ", (long) dn->dstat.st_ino); - break; - case LIST_BLOCKS: - column += printf("%4"OFF_FMT"d ", (off_t) dn->dstat.st_blocks >> 1); - break; - case LIST_MODEBITS: - column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode)); - break; - case LIST_NLINKS: - column += printf("%4ld ", (long) dn->dstat.st_nlink); - break; - case LIST_ID_NAME: -#if ENABLE_FEATURE_LS_USERNAME - printf("%-8.8s %-8.8s", - get_cached_username(dn->dstat.st_uid), - get_cached_groupname(dn->dstat.st_gid)); - column += 17; - break; -#endif - case LIST_ID_NUMERIC: - column += printf("%-8d %-8d", dn->dstat.st_uid, dn->dstat.st_gid); - break; - case LIST_SIZE: - case LIST_DEV: - if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) { - column += printf("%4d, %3d ", (int) major(dn->dstat.st_rdev), - (int) minor(dn->dstat.st_rdev)); - } else { - if (all_fmt & LS_DISP_HR) { - column += printf("%9s ", - make_human_readable_str(dn->dstat.st_size, 1, 0)); - } else { - column += printf("%9"OFF_FMT"d ", (off_t) dn->dstat.st_size); - } - } - break; -#if ENABLE_FEATURE_LS_TIMESTAMPS - case LIST_FULLTIME: - printf("%24.24s ", filetime); - column += 25; - break; - case LIST_DATE_TIME: - if ((all_fmt & LIST_FULLTIME) == 0) { - /* current_time_t ~== time(NULL) */ - age = current_time_t - ttime; - printf("%6.6s ", filetime + 4); - if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) { - /* hh:mm if less than 6 months old */ - printf("%5.5s ", filetime + 11); - } else { - printf(" %4.4s ", filetime + 20); - } - column += 13; - } - break; -#endif -#if ENABLE_SELINUX - case LIST_CONTEXT: - { - char context[80]; - int len = 0; - - if (dn->sid) { - /* I assume sid initilized with NULL */ - len = strlen(dn->sid) + 1; - safe_strncpy(context, dn->sid, len); - freecon(dn->sid); - } else { - safe_strncpy(context, "unknown", 8); - } - printf("%-32s ", context); - column += MAX(33, len); - } - break; -#endif - case LIST_FILENAME: - errno = 0; -#if ENABLE_FEATURE_LS_COLOR - if (show_color && !lstat(dn->fullname, &info)) { - printf("\033[%d;%dm", bgcolor(info.st_mode), - fgcolor(info.st_mode)); - } -#endif - column += printf("%s", dn->name); - if (show_color) { - printf("\033[0m"); - } - break; - case LIST_SYMLINK: - if (S_ISLNK(dn->dstat.st_mode)) { - char *lpath = xmalloc_readlink_or_warn(dn->fullname); - if (!lpath) break; - printf(" -> "); -#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR - if (!stat(dn->fullname, &info)) { - append = append_char(info.st_mode); - } -#endif -#if ENABLE_FEATURE_LS_COLOR - if (show_color) { - errno = 0; - printf("\033[%d;%dm", bgcolor(info.st_mode), - fgcolor(info.st_mode)); - } -#endif - column += printf("%s", lpath) + 4; - if (show_color) { - printf("\033[0m"); + if (ENABLE_FEATURE_LS_RECURSIVE + && (G.all_fmt & DISP_RECURSIVE) + ) { + struct dnode **dnd; + unsigned dndirs; + /* recursive - list the sub-dirs */ + dnd = splitdnarray(subdnp, SPLIT_SUBDIR); + dndirs = count_dirs(subdnp, SPLIT_SUBDIR); + if (dndirs > 0) { + dnsort(dnd, dndirs); + scan_and_display_dirs_recur(dnd, 0); + /* free the array of dnode pointers to the dirs */ + free(dnd); } - free(lpath); - } - break; -#if ENABLE_FEATURE_LS_FILETYPES - case LIST_FILETYPE: - if (append) { - putchar(append); - column++; } - break; -#endif + /* free the dnodes and the fullname mem */ + dfree(subdnp); } } - - return column; } -/* "[-]Cadil1", POSIX mandated options, busybox always supports */ -/* "[-]gnsx", POSIX non-mandated options, busybox always supports */ -/* "[-]Ak" GNU options, busybox always supports */ -/* "[-]FLRctur", POSIX mandated options, busybox optionally supports */ -/* "[-]p", POSIX non-mandated options, busybox optionally supports */ -/* "[-]SXvThw", GNU options, busybox optionally supports */ -/* "[-]K", SELinux mandated options, busybox optionally supports */ -/* "[-]e", I think we made this one up */ -static const char ls_options[] = "Cadil1gnsxAk" - USE_FEATURE_LS_TIMESTAMPS("cetu") - USE_FEATURE_LS_SORTFILES("SXrv") - USE_FEATURE_LS_FILETYPES("Fp") - USE_FEATURE_LS_FOLLOWLINKS("L") - USE_FEATURE_LS_RECURSIVE("R") - USE_FEATURE_HUMAN_READABLE("h") - USE_SELINUX("K") - USE_FEATURE_AUTOWIDTH("T:w:") - USE_SELINUX("Z"); - -enum { - LIST_MASK_TRIGGER = 0, - STYLE_MASK_TRIGGER = STYLE_MASK, - DISP_MASK_TRIGGER = DISP_ROWS, - SORT_MASK_TRIGGER = SORT_MASK, -}; - -static const unsigned opt_flags[] = { - LIST_SHORT | STYLE_COLUMNS, /* C */ - DISP_HIDDEN | DISP_DOT, /* a */ - DISP_NOLIST, /* d */ - LIST_INO, /* i */ - LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */ - LIST_SHORT | STYLE_SINGLE, /* 1 */ - 0, /* g - ingored */ - LIST_ID_NUMERIC, /* n */ - LIST_BLOCKS, /* s */ - DISP_ROWS, /* x */ - DISP_HIDDEN, /* A */ - ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */ -#if ENABLE_FEATURE_LS_TIMESTAMPS - TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */ - LIST_FULLTIME, /* e */ - ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */ - TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */ -#endif -#if ENABLE_FEATURE_LS_SORTFILES - SORT_SIZE, /* S */ - SORT_EXT, /* X */ - SORT_REVERSE, /* r */ - SORT_VERSION, /* v */ -#endif -#if ENABLE_FEATURE_LS_FILETYPES - LIST_FILETYPE | LIST_EXEC, /* F */ - LIST_FILETYPE, /* p */ -#endif -#if ENABLE_FEATURE_LS_FOLLOWLINKS - FOLLOW_LINKS, /* L */ -#endif -#if ENABLE_FEATURE_LS_RECURSIVE - DISP_RECURSIVE, /* R */ -#endif -#if ENABLE_FEATURE_HUMAN_READABLE - LS_DISP_HR, /* h */ -#endif -#if ENABLE_SELINUX - LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */ -#endif -#if ENABLE_FEATURE_AUTOWIDTH - 0, 0, /* T, w - ignored */ -#endif -#if ENABLE_SELINUX - LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */ -#endif - (1U<<31) -}; - - -/* THIS IS A "SAFE" APPLET, main() MAY BE CALLED INTERNALLY FROM SHELL */ -/* BE CAREFUL! */ -int ls_main(int argc, char **argv); -int ls_main(int argc, char **argv) +int ls_main(int argc UNUSED_PARAM, char **argv) { struct dnode **dnd; struct dnode **dnf; @@ -792,169 +1072,187 @@ int ls_main(int argc, char **argv) struct dnode *dn; struct dnode *cur; unsigned opt; - int nfiles = 0; - int dnfiles; - int dndirs; - int oi; - int ac; - int i; - char **av; - USE_FEATURE_AUTOWIDTH(char *tabstops_str = NULL;) - USE_FEATURE_AUTOWIDTH(char *terminal_width_str = NULL;) - USE_FEATURE_LS_COLOR(char *color_opt;) - -#if ENABLE_FEATURE_LS_TIMESTAMPS - time(¤t_time_t); + unsigned nfiles; + unsigned dnfiles; + unsigned dndirs; + unsigned i; +#if ENABLE_FEATURE_LS_COLOR + /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */ + /* coreutils 6.10: + * # ls --color=BOGUS + * ls: invalid argument 'BOGUS' for '--color' + * Valid arguments are: + * 'always', 'yes', 'force' + * 'never', 'no', 'none' + * 'auto', 'tty', 'if-tty' + * (and substrings: "--color=alwa" work too) + */ + static const char ls_longopts[] ALIGN1 = + "color\0" Optional_argument "\xff"; /* no short equivalent */ + static const char color_str[] ALIGN1 = + "always\0""yes\0""force\0" + "auto\0""tty\0""if-tty\0"; + /* need to initialize since --color has _an optional_ argument */ + const char *color_opt = color_str; /* "always" */ #endif - all_fmt = LIST_SHORT | - (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD)); + INIT_G(); + + init_unicode(); + + if (ENABLE_FEATURE_LS_SORTFILES) + G.all_fmt = SORT_NAME; #if ENABLE_FEATURE_AUTOWIDTH - /* Obtain the terminal width */ - get_terminal_width_height(STDOUT_FILENO, &terminal_width, NULL); - /* Go one less... */ - terminal_width--; + /* obtain the terminal width */ + get_terminal_width_height(STDIN_FILENO, &G_terminal_width, NULL); + /* go one less... */ + G_terminal_width--; #endif /* process options */ - USE_FEATURE_LS_COLOR(applet_long_options = ls_color_opt;) -#if ENABLE_FEATURE_AUTOWIDTH - opt = getopt32(argc, argv, ls_options, &tabstops_str, &terminal_width_str - USE_FEATURE_LS_COLOR(, &color_opt)); - if (tabstops_str) - tabstops = xatou(tabstops_str); - if (terminal_width_str) - terminal_width = xatou(terminal_width_str); -#else - opt = getopt32(argc, argv, ls_options USE_FEATURE_LS_COLOR(, &color_opt)); -#endif - for (i = 0; opt_flags[i] != (1U<<31); i++) { + IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;) + opt_complementary = + /* -e implies -l */ + IF_FEATURE_LS_TIMESTAMPS("el") + /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html: + * in some pairs of opts, only last one takes effect: + */ + IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */ + // ":m-l:l-m" - we don't have -m + IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H") + ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */ + ":C-1:1-C" /* bycols/oneline */ + ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */ + IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */ + /* -w NUM: */ + IF_FEATURE_AUTOWIDTH(":w+"); + opt = getopt32(argv, ls_options + IF_FEATURE_AUTOWIDTH(, NULL, &G_terminal_width) + IF_FEATURE_LS_COLOR(, &color_opt) + ); + for (i = 0; opt_flags[i] != (1U << 31); i++) { if (opt & (1 << i)) { - unsigned flags = opt_flags[i]; - - if (flags & LIST_MASK_TRIGGER) - all_fmt &= ~LIST_MASK; - if (flags & STYLE_MASK_TRIGGER) - all_fmt &= ~STYLE_MASK; - if (flags & SORT_MASK_TRIGGER) - all_fmt &= ~SORT_MASK; - if (flags & DISP_MASK_TRIGGER) - all_fmt &= ~DISP_MASK; + uint32_t flags = opt_flags[i]; + + if (flags & STYLE_MASK) + G.all_fmt &= ~STYLE_MASK; + if (flags & SORT_MASK) + G.all_fmt &= ~SORT_MASK; if (flags & TIME_MASK) - all_fmt &= ~TIME_MASK; - if (flags & LIST_CONTEXT) - all_fmt |= STYLE_SINGLE; - /* huh?? opt cannot be 'l' */ - //if (LS_DISP_HR && opt == 'l') - // all_fmt &= ~LS_DISP_HR; - all_fmt |= flags; + G.all_fmt &= ~TIME_MASK; + + G.all_fmt |= flags; } } #if ENABLE_FEATURE_LS_COLOR - /* find color bit value - last position for short getopt */ + /* set G_show_color = 1/0 */ if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) { char *p = getenv("LS_COLORS"); /* LS_COLORS is unset, or (not empty && not "none") ? */ - if (!p || (p[0] && strcmp(p, "none"))) - show_color = 1; + if (!p || (p[0] && strcmp(p, "none") != 0)) + G_show_color = 1; } - if (opt & (1 << i)) { /* next flag after short options */ - if (!color_opt || !strcmp("always", color_opt)) - show_color = 1; - else if (color_opt && !strcmp("never", color_opt)) - show_color = 0; - else if (color_opt && !strcmp("auto", color_opt) && isatty(STDOUT_FILENO)) - show_color = 1; + if (opt & OPT_color) { + if (color_opt[0] == 'n') + G_show_color = 0; + else switch (index_in_substrings(color_str, color_opt)) { + case 3: + case 4: + case 5: + if (isatty(STDOUT_FILENO)) { + case 0: + case 1: + case 2: + G_show_color = 1; + } + } } #endif /* sort out which command line options take precedence */ - if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST)) - all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */ + if (ENABLE_FEATURE_LS_RECURSIVE && (G.all_fmt & DISP_NOLIST)) + G.all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */ if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) { - if (all_fmt & TIME_CHANGE) - all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME; - if (all_fmt & TIME_ACCESS) - all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME; - } - if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */ - all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC); - if (ENABLE_FEATURE_LS_USERNAME) - if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC)) - all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */ - - /* choose a display format */ - if (!(all_fmt & STYLE_MASK)) - all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE); - - /* - * when there are no cmd line args we have to supply a default "." arg. - * we will create a second argv array, "av" that will hold either - * our created "." arg, or the real cmd line args. The av array - * just holds the pointers- we don't move the date the pointers - * point to. - */ - ac = argc - optind; /* how many cmd line args are left */ - if (ac < 1) { - static const char *const dotdir[] = { "." }; - - av = (char **) dotdir; - ac = 1; - } else { - av = argv + optind; + if (G.all_fmt & TIME_CHANGE) + G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_CTIME; + if (G.all_fmt & TIME_ACCESS) + G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_ATIME; } + if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */ + G.all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME); + + /* choose a display format if one was not already specified by an option */ + if (!(G.all_fmt & STYLE_MASK)) + G.all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE); - /* now, everything is in the av array */ - if (ac > 1) - all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */ + argv += optind; + if (!argv[0]) + *--argv = (char*)"."; + + if (argv[1]) + G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */ /* stuff the command line file names into a dnode array */ dn = NULL; - for (oi = 0; oi < ac; oi++) { - /* ls w/o -l follows links on command line */ - cur = my_stat(av[oi], av[oi], !(all_fmt & STYLE_LONG)); + nfiles = 0; + do { + cur = my_stat(*argv, *argv, + /* follow links on command line unless -l, -s or -F: */ + !((G.all_fmt & STYLE_MASK) == STYLE_LONG + || (G.all_fmt & LIST_BLOCKS) + || (option_mask32 & OPT_F) + ) + /* ... or if -H: */ + || (option_mask32 & OPT_H) + /* ... or if -L, but my_stat always follows links if -L */ + ); + argv++; if (!cur) continue; - cur->allocated = 0; - cur->next = dn; + /*cur->fname_allocated = 0; - already is */ + cur->dn_next = dn; dn = cur; nfiles++; - } + } while (*argv); + + /* nfiles _may_ be 0 here - try "ls doesnt_exist" */ + if (nfiles == 0) + return G.exit_code; /* now that we know how many files there are * allocate memory for an array to hold dnode pointers */ dnp = dnalloc(nfiles); - for (i = 0, cur = dn; i < nfiles; i++) { - dnp[i] = cur; /* save pointer to node in array */ - cur = cur->next; + for (i = 0; /* i < nfiles - detected via !dn below */; i++) { + dnp[i] = dn; /* save pointer to node in array */ + dn = dn->dn_next; + if (!dn) + break; } - if (all_fmt & DISP_NOLIST) { - dnsort(dnp, nfiles); - if (nfiles > 0) - showfiles(dnp, nfiles); + if (G.all_fmt & DISP_NOLIST) { + sort_and_display_files(dnp, nfiles); } else { - dnd = splitdnarray(dnp, nfiles, SPLIT_DIR); - dnf = splitdnarray(dnp, nfiles, SPLIT_FILE); - dndirs = countdirs(dnp, nfiles); + dnd = splitdnarray(dnp, SPLIT_DIR); + dnf = splitdnarray(dnp, SPLIT_FILE); + dndirs = count_dirs(dnp, SPLIT_DIR); dnfiles = nfiles - dndirs; if (dnfiles > 0) { - dnsort(dnf, dnfiles); - showfiles(dnf, dnfiles); + sort_and_display_files(dnf, dnfiles); if (ENABLE_FEATURE_CLEAN_UP) free(dnf); } if (dndirs > 0) { dnsort(dnd, dndirs); - showdirs(dnd, dndirs, dnfiles == 0); + scan_and_display_dirs_recur(dnd, dnfiles == 0); if (ENABLE_FEATURE_CLEAN_UP) free(dnd); } } + if (ENABLE_FEATURE_CLEAN_UP) - dfree(dnp, nfiles); - return status; + dfree(dnp); + return G.exit_code; }