1 /* vi: set sw=4 ts=4: */
3 * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
5 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8 /* [date unknown. Perhaps before year 2000]
9 * To achieve a small memory footprint, this version of 'ls' doesn't do any
10 * file sorting, and only has the most essential command line switches
11 * (i.e., the ones I couldn't live without :-) All features which involve
12 * linking in substantial chunks of libc can be disabled.
14 * Although I don't really want to add new features to this program to
15 * keep it small, I *am* interested to receive bug fixes and ways to make
19 * 1. hidden files can make column width too large
21 * NON-OPTIMAL BEHAVIOUR:
22 * 1. autowidth reads directories twice
23 * 2. if you do a short directory listing without filetype characters
24 * appended, there's no need to stat each one
26 * 1. requires lstat (BSD) - how do you do it without?
29 * ls sorts listing now, and supports almost all options.
32 //usage:#define ls_trivial_usage
34 //usage: IF_FEATURE_LS_FOLLOWLINKS("LH")
35 //usage: IF_FEATURE_LS_RECURSIVE("R")
36 //usage: IF_FEATURE_LS_FILETYPES("Fp") "lins"
37 //usage: IF_FEATURE_LS_TIMESTAMPS("e")
38 //usage: IF_FEATURE_HUMAN_READABLE("h")
39 //usage: IF_FEATURE_LS_SORTFILES("rSXv")
40 //usage: IF_FEATURE_LS_TIMESTAMPS("ctu")
41 //usage: IF_SELINUX("kKZ") "]"
42 //usage: IF_FEATURE_AUTOWIDTH(" [-w WIDTH]") " [FILE]..."
43 //usage:#define ls_full_usage "\n\n"
44 //usage: "List directory contents\n"
46 //usage: "\n -1 One column output"
47 //usage: "\n -a Include entries which start with ."
48 //usage: "\n -A Like -a, but exclude . and .."
49 //usage: "\n -C List by columns"
50 //usage: "\n -x List by lines"
51 //usage: "\n -d List directory entries instead of contents"
52 //usage: IF_FEATURE_LS_FOLLOWLINKS(
53 //usage: "\n -L Follow symlinks"
54 //usage: "\n -H Follow symlinks on command line"
56 //usage: IF_FEATURE_LS_RECURSIVE(
57 //usage: "\n -R Recurse"
59 //usage: IF_FEATURE_LS_FILETYPES(
60 //usage: "\n -p Append / to dir entries"
61 //usage: "\n -F Append indicator (one of */=@|) to entries"
63 //usage: "\n -l Long listing format"
64 //usage: "\n -i List inode numbers"
65 //usage: "\n -n List numeric UIDs and GIDs instead of names"
66 //usage: "\n -s List allocated blocks"
67 //usage: IF_FEATURE_LS_TIMESTAMPS(
68 //usage: "\n -e List full date and time"
70 //usage: IF_FEATURE_HUMAN_READABLE(
71 //usage: "\n -h List sizes in human readable format (1K 243M 2G)"
73 //usage: IF_FEATURE_LS_SORTFILES(
74 //usage: "\n -r Sort in reverse order"
75 //usage: "\n -S Sort by size"
76 //usage: "\n -X Sort by extension"
77 //usage: "\n -v Sort by version"
79 //usage: IF_FEATURE_LS_TIMESTAMPS(
80 //usage: "\n -c With -l: sort by ctime"
81 //usage: "\n -t With -l: sort by mtime"
82 //usage: "\n -u With -l: sort by atime"
85 //usage: "\n -k List security context"
86 //usage: "\n -K List security context in long format"
87 //usage: "\n -Z List security context and permission"
89 //usage: IF_FEATURE_AUTOWIDTH(
90 //usage: "\n -w N Assume the terminal is N columns wide"
92 //usage: IF_FEATURE_LS_COLOR(
93 //usage: "\n --color[={always,never,auto}] Control coloring"
100 /* This is a NOEXEC applet. Be very careful! */
104 /* ftpd uses ls, and without timestamps Mozilla won't understand
105 * ftpd's LIST output.
107 # undef CONFIG_FEATURE_LS_TIMESTAMPS
108 # undef ENABLE_FEATURE_LS_TIMESTAMPS
109 # undef IF_FEATURE_LS_TIMESTAMPS
110 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
111 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
112 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
113 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
114 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
119 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
125 /* Bits in all_fmt: */
127 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
128 /* what file information will be listed */
130 LIST_BLOCKS = 1 << 1,
131 LIST_MODEBITS = 1 << 2,
132 LIST_NLINKS = 1 << 3,
133 LIST_ID_NAME = 1 << 4,
134 LIST_ID_NUMERIC = 1 << 5,
135 LIST_CONTEXT = 1 << 6,
137 LIST_DATE_TIME = 1 << 8,
138 LIST_FULLTIME = 1 << 9,
139 LIST_SYMLINK = 1 << 10,
140 LIST_FILETYPE = 1 << 11, /* show / suffix for dirs */
141 LIST_CLASSIFY = 1 << 12, /* requires LIST_FILETYPE, also show *,|,@,= suffixes */
142 LIST_MASK = (LIST_CLASSIFY << 1) - 1,
144 /* what files will be displayed */
145 DISP_DIRNAME = 1 << 13, /* 2 or more items? label directories */
146 DISP_HIDDEN = 1 << 14, /* show filenames starting with . */
147 DISP_DOT = 1 << 15, /* show . and .. */
148 DISP_NOLIST = 1 << 16, /* show directory as itself, not contents */
149 DISP_RECURSIVE = 1 << 17, /* show directory and everything below it */
150 DISP_ROWS = 1 << 18, /* print across rows */
151 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
153 /* what is the overall style of the listing */
154 STYLE_COLUMNAR = 1 << 19, /* many records per line */
155 STYLE_LONG = 2 << 19, /* one record per line, extended info */
156 STYLE_SINGLE = 3 << 19, /* one record per line */
157 STYLE_MASK = STYLE_SINGLE,
159 /* which of the three times will be used */
160 TIME_CHANGE = (1 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
161 TIME_ACCESS = (2 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
162 TIME_MASK = (3 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
164 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
165 SORT_REVERSE = 1 << 23,
167 SORT_NAME = 0, /* sort by file name */
168 SORT_SIZE = 1 << 24, /* sort by file size */
169 SORT_ATIME = 2 << 24, /* sort by last access time */
170 SORT_CTIME = 3 << 24, /* sort by last change time */
171 SORT_MTIME = 4 << 24, /* sort by last modification time */
172 SORT_VERSION = 5 << 24, /* sort by version */
173 SORT_EXT = 6 << 24, /* sort by file name extension */
174 SORT_DIR = 7 << 24, /* sort by file or directory */
175 SORT_MASK = (7 << 24) * ENABLE_FEATURE_LS_SORTFILES,
177 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
178 LIST_DATE_TIME | LIST_SYMLINK,
181 /* -Cadil1 Std options, busybox always supports */
182 /* -gnsxA Std options, busybox always supports */
183 /* -Q GNU option, busybox always supports */
184 /* -k SELinux option, busybox always supports (ignores if !SELinux) */
185 /* Std has -k which means "show sizes in kbytes" */
186 /* -FLHRctur Std options, busybox optionally supports */
187 /* -p Std option, busybox optionally supports */
188 /* Not fully compatible - we show not only '/' but other chars too */
189 /* -SXvhTw GNU options, busybox optionally supports */
190 /* -T TABWIDTH is ignored (we don't use tabs on output) */
191 /* -KZ SELinux mandated options, busybox optionally supports */
192 /* (coreutils 8.4 has no -K, remove it?) */
193 /* -e I think we made this one up (looks similar to GNU --full-time) */
194 /* We already used up all 32 bits, if we need to add more, candidates for removal: */
195 /* -K, -T, -e (add --full-time instead) */
196 static const char ls_options[] ALIGN1 =
197 "Cadil1gnsxQAk" /* 13 opts, total 13 */
198 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
199 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
200 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
201 IF_FEATURE_LS_RECURSIVE("R") /* 1, 24 */
202 IF_SELINUX("KZ") /* 2, 26 */
203 IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 28 */
204 IF_FEATURE_HUMAN_READABLE("h") /* 1, 29 */
205 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 31 */
206 /* with --color, we use all 32 bits */;
226 OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS,
230 OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
232 OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
233 OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
235 OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX,
237 OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
238 OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
240 OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH,
242 OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS,
243 OPT_e = (1 << OPTBIT_e) * ENABLE_FEATURE_LS_TIMESTAMPS,
244 OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS,
245 OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS,
246 OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES,
247 OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES,
248 OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES,
249 OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES,
250 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
251 OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES,
252 OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE,
253 OPT_K = (1 << OPTBIT_K) * ENABLE_SELINUX,
254 OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX,
255 OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS,
256 OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS,
257 OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE,
258 OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_AUTOWIDTH,
259 OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_AUTOWIDTH,
260 OPT_color = (1 << OPTBIT_color) * ENABLE_FEATURE_LS_COLOR,
263 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
264 static const uint32_t opt_flags[] = {
265 STYLE_COLUMNAR, /* C */
266 DISP_HIDDEN | DISP_DOT, /* a */
269 LIST_LONG | STYLE_LONG, /* l */
270 STYLE_SINGLE, /* 1 */
271 LIST_LONG | STYLE_LONG, /* g (don't show owner) - handled via OPT_g. assumes l */
272 LIST_ID_NUMERIC | LIST_LONG | STYLE_LONG, /* n (assumes l) */
274 DISP_ROWS | STYLE_COLUMNAR, /* x */
275 0, /* Q (quote filename) - handled via OPT_Q */
277 ENABLE_SELINUX * (LIST_CONTEXT|STYLE_SINGLE), /* k (ignored if !SELINUX) */
278 #if ENABLE_FEATURE_LS_TIMESTAMPS
279 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
280 LIST_FULLTIME, /* e */
281 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
282 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
284 #if ENABLE_FEATURE_LS_SORTFILES
287 SORT_REVERSE, /* r */
288 SORT_VERSION, /* v */
290 #if ENABLE_FEATURE_LS_FILETYPES
291 LIST_FILETYPE | LIST_CLASSIFY, /* F */
292 LIST_FILETYPE, /* p */
294 #if ENABLE_FEATURE_LS_RECURSIVE
295 DISP_RECURSIVE, /* R */
298 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME|STYLE_SINGLE, /* K */
299 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT|STYLE_SINGLE, /* Z */
302 /* options after Z are not processed through opt_flags */
307 * a directory entry and its stat info are stored here
310 const char *name; /* the dir entry name */
311 const char *fullname; /* the dir entry name */
312 struct dnode *next; /* point at the next node */
313 smallint fname_allocated;
314 struct stat dstat; /* the file stat info */
315 IF_SELINUX(security_context_t sid;)
319 #if ENABLE_FEATURE_LS_COLOR
324 #if ENABLE_FEATURE_AUTOWIDTH
325 unsigned terminal_width; // = TERMINAL_WIDTH;
327 #if ENABLE_FEATURE_LS_TIMESTAMPS
328 /* Do time() just once. Saves one syscall per file for "ls -l" */
329 time_t current_time_t;
332 #define G (*(struct globals*)&bb_common_bufsiz1)
333 #if ENABLE_FEATURE_LS_COLOR
334 # define show_color (G.show_color )
336 enum { show_color = 0 };
338 #define exit_code (G.exit_code )
339 #define all_fmt (G.all_fmt )
340 #if ENABLE_FEATURE_AUTOWIDTH
341 # define terminal_width (G.terminal_width)
344 terminal_width = TERMINAL_WIDTH,
347 #define current_time_t (G.current_time_t)
348 #define INIT_G() do { \
349 /* we have to zero it out because of NOEXEC */ \
350 memset(&G, 0, sizeof(G)); \
351 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
352 IF_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \
356 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
360 IF_SELINUX(security_context_t sid = NULL;)
362 if ((option_mask32 & OPT_L) || force_follow) {
364 if (is_selinux_enabled()) {
365 getfilecon(fullname, &sid);
368 if (stat(fullname, &dstat)) {
369 bb_simple_perror_msg(fullname);
370 exit_code = EXIT_FAILURE;
375 if (is_selinux_enabled()) {
376 lgetfilecon(fullname, &sid);
379 if (lstat(fullname, &dstat)) {
380 bb_simple_perror_msg(fullname);
381 exit_code = EXIT_FAILURE;
386 cur = xmalloc(sizeof(*cur));
387 cur->fullname = fullname;
390 IF_SELINUX(cur->sid = sid;)
394 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
395 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
396 * 3/7:multiplexed char/block device)
397 * and we use 0 for unknown and 15 for executables (see below) */
398 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
399 /* un fi chr - dir - blk - file - link - sock - - exe */
400 #define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)])
401 /* 036 black foreground 050 black background
402 037 red foreground 051 red background
403 040 green foreground 052 green background
404 041 brown foreground 053 brown background
405 042 blue foreground 054 blue background
406 043 magenta (purple) foreground 055 magenta background
407 044 cyan (light blue) foreground 056 cyan background
408 045 gray foreground 057 white background
410 #define COLOR(mode) ( \
411 /*un fi chr - dir - blk - file - link - sock - - exe */ \
412 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
414 /* Select normal (0) [actually "reset all"] or bold (1)
415 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
416 * let's use 7 for "impossible" types, just for fun)
417 * Note: coreutils 6.9 uses inverted red for setuid binaries.
419 #define ATTR(mode) ( \
420 /*un fi chr - dir - blk - file- link- sock- - exe */ \
421 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
424 #if ENABLE_FEATURE_LS_COLOR
425 /* mode of zero is interpreted as "unknown" (stat failed) */
426 static char fgcolor(mode_t mode)
428 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
429 return COLOR(0xF000); /* File is executable ... */
432 static char bold(mode_t mode)
434 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
435 return ATTR(0xF000); /* File is executable ... */
440 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
441 static char append_char(mode_t mode)
443 if (!(all_fmt & LIST_FILETYPE))
447 if (!(all_fmt & LIST_CLASSIFY))
449 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
451 return APPCHAR(mode);
455 static unsigned count_dirs(struct dnode **dn, int which)
467 if (!S_ISDIR((*dn)->dstat.st_mode))
470 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
471 /* or if it's not . or .. */
472 || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
477 return which != SPLIT_FILE ? dirs : all - dirs;
480 /* get memory to hold an array of pointers */
481 static struct dnode **dnalloc(unsigned num)
486 num++; /* so that we have terminating NULL */
487 return xzalloc(num * sizeof(struct dnode *));
490 #if ENABLE_FEATURE_LS_RECURSIVE
491 static void dfree(struct dnode **dnp)
498 for (i = 0; dnp[i]; i++) {
499 struct dnode *cur = dnp[i];
500 if (cur->fname_allocated)
501 free((char*)cur->fullname);
507 #define dfree(...) ((void)0)
510 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
511 static struct dnode **splitdnarray(struct dnode **dn, int which)
519 /* count how many dirs or files there are */
520 dncnt = count_dirs(dn, which);
522 /* allocate a file array and a dir array */
523 dnp = dnalloc(dncnt);
525 /* copy the entrys into the file or dir array */
526 for (d = 0; *dn; dn++) {
527 if (S_ISDIR((*dn)->dstat.st_mode)) {
530 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
533 if ((which & SPLIT_DIR)
534 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
538 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
545 #if ENABLE_FEATURE_LS_SORTFILES
546 static int sortcmp(const void *a, const void *b)
548 struct dnode *d1 = *(struct dnode **)a;
549 struct dnode *d2 = *(struct dnode **)b;
550 unsigned sort_opts = all_fmt & SORT_MASK;
553 dif = 0; /* assume SORT_NAME */
554 // TODO: use pre-initialized function pointer
555 // instead of branch forest
556 if (sort_opts == SORT_SIZE) {
557 dif = (d2->dstat.st_size - d1->dstat.st_size);
558 } else if (sort_opts == SORT_ATIME) {
559 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
560 } else if (sort_opts == SORT_CTIME) {
561 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
562 } else if (sort_opts == SORT_MTIME) {
563 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
564 } else if (sort_opts == SORT_DIR) {
565 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
566 /* } else if (sort_opts == SORT_VERSION) { */
567 /* } else if (sort_opts == SORT_EXT) { */
570 /* sort by name, or tie_breaker for other sorts */
571 if (ENABLE_LOCALE_SUPPORT)
572 dif = strcoll(d1->name, d2->name);
574 dif = strcmp(d1->name, d2->name);
577 /* Make dif fit into an int */
578 if (sizeof(dif) > sizeof(int)) {
579 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
580 /* shift leaving only "int" worth of bits */
582 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
586 return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
589 static void dnsort(struct dnode **dn, int size)
591 qsort(dn, size, sizeof(*dn), sortcmp);
594 #define dnsort(dn, size) ((void)0)
598 static unsigned calc_name_len(const char *name)
603 // TODO: quote tab as \t, etc, if -Q
604 name = printable_string(&uni_stat, name);
606 if (!(option_mask32 & OPT_Q)) {
607 return uni_stat.unicode_width;
610 len = 2 + uni_stat.unicode_width;
612 if (*name == '"' || *name == '\\') {
621 /* Return the number of used columns.
622 * Note that only STYLE_COLUMNAR uses return value.
623 * STYLE_SINGLE and STYLE_LONG don't care.
624 * coreutils 7.2 also supports:
625 * ls -b (--escape) = octal escapes (although it doesn't look like working)
626 * ls -N (--literal) = not escape at all
628 static unsigned print_name(const char *name)
633 // TODO: quote tab as \t, etc, if -Q
634 name = printable_string(&uni_stat, name);
636 if (!(option_mask32 & OPT_Q)) {
638 return uni_stat.unicode_width;
641 len = 2 + uni_stat.unicode_width;
644 if (*name == '"' || *name == '\\') {
655 /* Return the number of used columns.
656 * Note that only STYLE_COLUMNAR uses return value,
657 * STYLE_SINGLE and STYLE_LONG don't care.
659 static NOINLINE unsigned list_single(const struct dnode *dn)
663 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
668 #if ENABLE_FEATURE_LS_FILETYPES
669 append = append_char(dn->dstat.st_mode);
672 /* Do readlink early, so that if it fails, error message
673 * does not appear *inside* the "ls -l" line */
675 if (all_fmt & LIST_SYMLINK)
676 if (S_ISLNK(dn->dstat.st_mode))
677 lpath = xmalloc_readlink_or_warn(dn->fullname);
679 if (all_fmt & LIST_INO)
680 column += printf("%7llu ", (long long) dn->dstat.st_ino);
681 //TODO: -h should affect -s too:
682 if (all_fmt & LIST_BLOCKS)
683 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
684 if (all_fmt & LIST_MODEBITS)
685 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
686 if (all_fmt & LIST_NLINKS)
687 column += printf("%4lu ", (long) dn->dstat.st_nlink);
688 if (all_fmt & LIST_ID_NUMERIC) {
689 if (option_mask32 & OPT_g)
690 column += printf("%-8u ", (int) dn->dstat.st_gid);
692 column += printf("%-8u %-8u ",
693 (int) dn->dstat.st_uid,
694 (int) dn->dstat.st_gid);
696 #if ENABLE_FEATURE_LS_USERNAME
697 else if (all_fmt & LIST_ID_NAME) {
698 if (option_mask32 & OPT_g) {
699 column += printf("%-8.8s ",
700 get_cached_groupname(dn->dstat.st_gid));
702 column += printf("%-8.8s %-8.8s ",
703 get_cached_username(dn->dstat.st_uid),
704 get_cached_groupname(dn->dstat.st_gid));
708 if (all_fmt & LIST_SIZE) {
709 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
710 column += printf("%4u, %3u ",
711 (int) major(dn->dstat.st_rdev),
712 (int) minor(dn->dstat.st_rdev));
714 if (option_mask32 & OPT_h) {
715 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
716 /* print st_size, show one fractional, use suffixes */
717 make_human_readable_str(dn->dstat.st_size, 1, 0)
720 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
724 #if ENABLE_FEATURE_LS_TIMESTAMPS
725 if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
727 time_t ttime = dn->dstat.st_mtime;
728 if (all_fmt & TIME_ACCESS)
729 ttime = dn->dstat.st_atime;
730 if (all_fmt & TIME_CHANGE)
731 ttime = dn->dstat.st_ctime;
732 filetime = ctime(&ttime);
733 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
734 if (all_fmt & LIST_FULLTIME) { /* -e */
735 /* Note: coreutils 8.4 ls --full-time prints:
736 * 2009-07-13 17:49:27.000000000 +0200
738 column += printf("%.24s ", filetime);
739 } else { /* LIST_DATE_TIME */
740 /* current_time_t ~== time(NULL) */
741 time_t age = current_time_t - ttime;
742 printf("%.6s ", filetime + 4); /* "Jun 30" */
743 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
744 /* hh:mm if less than 6 months old */
745 printf("%.5s ", filetime + 11);
746 } else { /* year. buggy if year > 9999 ;) */
747 printf(" %.4s ", filetime + 20);
754 if (all_fmt & LIST_CONTEXT) {
755 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
760 #if ENABLE_FEATURE_LS_COLOR
763 lstat(dn->fullname, &info);
764 printf("\033[%u;%um", bold(info.st_mode),
765 fgcolor(info.st_mode));
768 column += print_name(dn->name);
775 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
777 stat(dn->fullname, &info);
778 # if ENABLE_FEATURE_LS_FILETYPES
779 append = append_char(info.st_mode);
782 #if ENABLE_FEATURE_LS_COLOR
784 printf("\033[%u;%um", bold(info.st_mode),
785 fgcolor(info.st_mode));
788 column += print_name(lpath) + 4;
794 #if ENABLE_FEATURE_LS_FILETYPES
795 if (all_fmt & LIST_FILETYPE) {
806 static void showfiles(struct dnode **dn, unsigned nfiles)
808 unsigned i, ncols, nrows, row, nc;
811 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
813 if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
816 /* find the longest file name, use that as the column width */
817 for (i = 0; dn[i]; i++) {
818 int len = calc_name_len(dn[i]->name);
819 if (column_width < len)
823 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
824 ((all_fmt & LIST_INO) ? 8 : 0) +
825 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
826 ncols = (int) (terminal_width / column_width);
830 nrows = nfiles / ncols;
831 if (nrows * ncols < nfiles)
832 nrows++; /* round up fractionals */
840 for (row = 0; row < nrows; row++) {
841 for (nc = 0; nc < ncols; nc++) {
842 /* reach into the array based on the column and row */
843 if (all_fmt & DISP_ROWS)
844 i = (row * ncols) + nc; /* display across row */
846 i = (nc * nrows) + row; /* display by column */
850 printf("%*s ", nexttab, "");
851 column += nexttab + 1;
853 nexttab = column + column_width;
854 column += list_single(dn[i]);
864 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
865 * If any of the -l, -n, -s options is specified, each list
866 * of files within the directory shall be preceded by a
867 * status line indicating the number of file system blocks
868 * occupied by files in the directory in 512-byte units if
869 * the -k option is not specified, or 1024-byte units if the
870 * -k option is specified, rounded up to the next integral
873 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
874 static off_t calculate_blocks(struct dnode **dn)
879 /* st_blocks is in 512 byte blocks */
880 blocks += (*dn)->dstat.st_blocks;
885 /* Even though standard says use 512 byte blocks, coreutils use 1k */
886 /* Actually, we round up by calculating (blocks + 1) / 2,
887 * "+ 1" was done when we initialized blocks to 1 */
893 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
894 static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
896 struct dnode *dn, *cur, **dnp;
897 struct dirent *entry;
902 dir = warn_opendir(path);
904 exit_code = EXIT_FAILURE;
905 return NULL; /* could not open the dir */
909 while ((entry = readdir(dir)) != NULL) {
912 /* are we going to list the file- it may be . or .. or a hidden file */
913 if (entry->d_name[0] == '.') {
914 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
915 && !(all_fmt & DISP_DOT)
919 if (!(all_fmt & DISP_HIDDEN))
922 fullname = concat_path_file(path, entry->d_name);
923 cur = my_stat(fullname, bb_basename(fullname), 0);
928 cur->fname_allocated = 1;
938 /* now that we know how many files there are
939 * allocate memory for an array to hold dnode pointers
942 dnp = dnalloc(nfiles);
943 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
944 dnp[i] = dn; /* save pointer to node in array */
954 static void showdirs(struct dnode **dn, int first)
957 struct dnode **subdnp;
960 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
964 printf("%s:\n", (*dn)->fullname);
966 subdnp = list_dir((*dn)->fullname, &nfiles);
968 if ((all_fmt & STYLE_MASK) == STYLE_LONG)
969 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
972 /* list all files at this level */
973 dnsort(subdnp, nfiles);
974 showfiles(subdnp, nfiles);
975 if (ENABLE_FEATURE_LS_RECURSIVE
976 && (all_fmt & DISP_RECURSIVE)
980 /* recursive - list the sub-dirs */
981 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
982 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
986 /* free the array of dnode pointers to the dirs */
990 /* free the dnodes and the fullname mem */
997 int ls_main(int argc UNUSED_PARAM, char **argv)
1009 #if ENABLE_FEATURE_LS_COLOR
1010 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1012 * # ls --color=BOGUS
1013 * ls: invalid argument 'BOGUS' for '--color'
1014 * Valid arguments are:
1015 * 'always', 'yes', 'force'
1016 * 'never', 'no', 'none'
1017 * 'auto', 'tty', 'if-tty'
1018 * (and substrings: "--color=alwa" work too)
1020 static const char ls_longopts[] ALIGN1 =
1021 "color\0" Optional_argument "\xff"; /* no short equivalent */
1022 static const char color_str[] ALIGN1 =
1023 "always\0""yes\0""force\0"
1024 "auto\0""tty\0""if-tty\0";
1025 /* need to initialize since --color has _an optional_ argument */
1026 const char *color_opt = color_str; /* "always" */
1033 if (ENABLE_FEATURE_LS_SORTFILES)
1034 all_fmt = SORT_NAME;
1036 #if ENABLE_FEATURE_AUTOWIDTH
1037 /* obtain the terminal width */
1038 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
1039 /* go one less... */
1043 /* process options */
1044 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1048 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1049 * in some pairs of opts, only last one takes effect:
1051 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1052 // ":m-l:l-m" - we don't have -m
1053 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1054 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1055 ":C-1:1-C" /* bycols/oneline */
1056 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1057 ":c-u:u-c" /* mtime/atime */
1059 IF_FEATURE_AUTOWIDTH(":w+");
1060 opt = getopt32(argv, ls_options
1061 IF_FEATURE_AUTOWIDTH(, NULL, &terminal_width)
1062 IF_FEATURE_LS_COLOR(, &color_opt)
1064 for (i = 0; opt_flags[i] != (1U << 31); i++) {
1065 if (opt & (1 << i)) {
1066 uint32_t flags = opt_flags[i];
1068 if (flags & STYLE_MASK)
1069 all_fmt &= ~STYLE_MASK;
1070 if (flags & SORT_MASK)
1071 all_fmt &= ~SORT_MASK;
1072 if (flags & TIME_MASK)
1073 all_fmt &= ~TIME_MASK;
1079 #if ENABLE_FEATURE_LS_COLOR
1080 /* set show_color = 1/0 */
1081 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1082 char *p = getenv("LS_COLORS");
1083 /* LS_COLORS is unset, or (not empty && not "none") ? */
1084 if (!p || (p[0] && strcmp(p, "none") != 0))
1087 if (opt & OPT_color) {
1088 if (color_opt[0] == 'n')
1090 else switch (index_in_substrings(color_str, color_opt)) {
1094 if (isatty(STDOUT_FILENO)) {
1104 /* sort out which command line options take precedence */
1105 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1106 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1107 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1108 if (all_fmt & TIME_CHANGE)
1109 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1110 if (all_fmt & TIME_ACCESS)
1111 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1113 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */
1114 all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1116 /* choose a display format if one was not already specified by an option */
1117 if (!(all_fmt & STYLE_MASK))
1118 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1122 *--argv = (char*)".";
1125 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1127 /* stuff the command line file names into a dnode array */
1131 cur = my_stat(*argv, *argv,
1132 /* follow links on command line unless -l, -s or -F: */
1133 !((all_fmt & STYLE_MASK) == STYLE_LONG
1134 || (all_fmt & LIST_BLOCKS)
1135 || (option_mask32 & OPT_F)
1138 || (option_mask32 & OPT_H)
1139 /* ... or if -L, but my_stat always follows links if -L */
1144 cur->fname_allocated = 0;
1150 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1154 /* now that we know how many files there are
1155 * allocate memory for an array to hold dnode pointers
1157 dnp = dnalloc(nfiles);
1158 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1159 dnp[i] = dn; /* save pointer to node in array */
1165 if (all_fmt & DISP_NOLIST) {
1166 dnsort(dnp, nfiles);
1167 showfiles(dnp, nfiles);
1169 dnd = splitdnarray(dnp, SPLIT_DIR);
1170 dnf = splitdnarray(dnp, SPLIT_FILE);
1171 dndirs = count_dirs(dnp, SPLIT_DIR);
1172 dnfiles = nfiles - dndirs;
1174 dnsort(dnf, dnfiles);
1175 showfiles(dnf, dnfiles);
1176 if (ENABLE_FEATURE_CLEAN_UP)
1180 dnsort(dnd, dndirs);
1181 showdirs(dnd, dnfiles == 0);
1182 if (ENABLE_FEATURE_CLEAN_UP)
1186 if (ENABLE_FEATURE_CLEAN_UP)