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.
7 /* [date unknown. Perhaps before year 2000]
8 * To achieve a small memory footprint, this version of 'ls' doesn't do any
9 * file sorting, and only has the most essential command line switches
10 * (i.e., the ones I couldn't live without :-) All features which involve
11 * linking in substantial chunks of libc can be disabled.
13 * Although I don't really want to add new features to this program to
14 * keep it small, I *am* interested to receive bug fixes and ways to make
18 * 1. hidden files can make column width too large
20 * NON-OPTIMAL BEHAVIOUR:
21 * 1. autowidth reads directories twice
22 * 2. if you do a short directory listing without filetype characters
23 * appended, there's no need to stat each one
25 * 1. requires lstat (BSD) - how do you do it without?
28 * ls sorts listing now, and supports almost all options.
34 //config: ls is used to list the contents of directories.
36 //config:config FEATURE_LS_FILETYPES
37 //config: bool "Enable filetyping options (-p and -F)"
39 //config: depends on LS
41 //config:config FEATURE_LS_FOLLOWLINKS
42 //config: bool "Enable symlinks dereferencing (-L)"
44 //config: depends on LS
46 //config:config FEATURE_LS_RECURSIVE
47 //config: bool "Enable recursion (-R)"
49 //config: depends on LS
51 //config:config FEATURE_LS_WIDTH
52 //config: bool "Enable -w WIDTH and window size autodetection"
54 //config: depends on LS
56 //config:config FEATURE_LS_SORTFILES
57 //config: bool "Sort the file names"
59 //config: depends on LS
61 //config: Allow ls to sort file names alphabetically.
63 //config:config FEATURE_LS_TIMESTAMPS
64 //config: bool "Show file timestamps"
66 //config: depends on LS
68 //config: Allow ls to display timestamps for files.
70 //config:config FEATURE_LS_USERNAME
71 //config: bool "Show username/groupnames"
73 //config: depends on LS
75 //config: Allow ls to display username/groupname for files.
77 //config:config FEATURE_LS_COLOR
78 //config: bool "Allow use of color to identify file types"
80 //config: depends on LS && LONG_OPTS
82 //config: This enables the --color option to ls.
84 //config:config FEATURE_LS_COLOR_IS_DEFAULT
85 //config: bool "Produce colored ls output by default"
87 //config: depends on FEATURE_LS_COLOR
89 //config: Saying yes here will turn coloring on by default,
90 //config: even if no "--color" option is given to the ls command.
91 //config: This is not recommended, since the colors are not
92 //config: configurable, and the output may not be legible on
93 //config: many output screens.
95 //applet:IF_LS(APPLET_NOEXEC(ls, ls, BB_DIR_BIN, BB_SUID_DROP, ls))
97 //kbuild:lib-$(CONFIG_LS) += ls.o
99 //usage:#define ls_trivial_usage
101 //usage: IF_FEATURE_LS_FOLLOWLINKS("LH")
102 //usage: IF_FEATURE_LS_RECURSIVE("R")
103 //usage: IF_FEATURE_LS_FILETYPES("Fp") "lins"
104 //usage: IF_FEATURE_HUMAN_READABLE("h")
105 //usage: IF_FEATURE_LS_SORTFILES("rSXv")
106 //usage: IF_FEATURE_LS_TIMESTAMPS("ctu")
107 //usage: IF_SELINUX("kZ") "]"
108 //usage: IF_FEATURE_LS_WIDTH(" [-w WIDTH]") " [FILE]..."
109 //usage:#define ls_full_usage "\n\n"
110 //usage: "List directory contents\n"
111 //usage: "\n -1 One column output"
112 //usage: "\n -a Include entries which start with ."
113 //usage: "\n -A Like -a, but exclude . and .."
114 //usage: "\n -C List by columns"
115 //usage: "\n -x List by lines"
116 //usage: "\n -d List directory entries instead of contents"
117 //usage: IF_FEATURE_LS_FOLLOWLINKS(
118 //usage: "\n -L Follow symlinks"
119 //usage: "\n -H Follow symlinks on command line"
121 //usage: IF_FEATURE_LS_RECURSIVE(
122 //usage: "\n -R Recurse"
124 //usage: IF_FEATURE_LS_FILETYPES(
125 //usage: "\n -p Append / to dir entries"
126 //usage: "\n -F Append indicator (one of */=@|) to entries"
128 //usage: "\n -l Long listing format"
129 //usage: "\n -i List inode numbers"
130 //usage: "\n -n List numeric UIDs and GIDs instead of names"
131 //usage: "\n -s List allocated blocks"
132 //usage: IF_FEATURE_LS_TIMESTAMPS(
133 //usage: "\n -lc List ctime"
134 //usage: "\n -lu List atime"
136 //usage: IF_FEATURE_LS_TIMESTAMPS(IF_LONG_OPTS(
137 //usage: "\n --full-time List full date and time"
139 //usage: IF_FEATURE_HUMAN_READABLE(
140 //usage: "\n -h Human readable sizes (1K 243M 2G)"
142 //usage: IF_FEATURE_LS_SORTFILES(
143 //usage: IF_LONG_OPTS(
144 //usage: "\n --group-directories-first"
146 //usage: "\n -S Sort by size"
147 //usage: "\n -X Sort by extension"
148 //usage: "\n -v Sort by version"
150 //usage: IF_FEATURE_LS_TIMESTAMPS(
151 //usage: "\n -t Sort by mtime"
152 //usage: "\n -tc Sort by ctime"
153 //usage: "\n -tu Sort by atime"
155 //usage: "\n -r Reverse sort order"
157 //usage: "\n -Z List security context and permission"
159 //usage: IF_FEATURE_LS_WIDTH(
160 //usage: "\n -w N Format N columns wide"
162 //usage: IF_FEATURE_LS_COLOR(
163 //usage: "\n --color[={always,never,auto}] Control coloring"
167 #include "common_bufsiz.h"
171 /* This is a NOEXEC applet. Be very careful! */
175 /* ftpd uses ls, and without timestamps Mozilla won't understand
176 * ftpd's LIST output.
178 # undef CONFIG_FEATURE_LS_TIMESTAMPS
179 # undef ENABLE_FEATURE_LS_TIMESTAMPS
180 # undef IF_FEATURE_LS_TIMESTAMPS
181 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
182 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
183 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
184 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
185 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
190 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
196 /* Bits in G.all_fmt: */
197 LIST_LONG = 1 << 0, /* long listing (-l and equivalents) */
199 /* what files will be displayed */
200 DISP_DIRNAME = 1 << 1, /* 2 or more items? label directories */
201 DISP_ROWS = 1 << 2, /* print across rows */
203 /* what is the overall style of the listing */
204 STYLE_COLUMNAR = 1 << 3, /* many records per line */
205 STYLE_LONG = 2 << 3, /* one record per line, extended info */
206 STYLE_SINGLE = 3 << 3, /* one record per line */
207 STYLE_MASK = STYLE_SINGLE,
210 /* -Cadi1l Std options, busybox always supports */
211 /* -gnsxA Std options, busybox always supports */
212 /* -Q GNU option, busybox always supports */
213 /* -k Std option, busybox always supports (by ignoring) */
214 /* It means "for -s, show sizes in kbytes" */
215 /* Seems to only affect "POSIXLY_CORRECT=1 ls -sk" */
216 /* since otherwise -s shows kbytes anyway */
217 /* -LHRctur Std options, busybox optionally supports */
218 /* -Fp Std options, busybox optionally supports */
219 /* -SXvhTw GNU options, busybox optionally supports */
220 /* -T WIDTH Ignored (we don't use tabs on output) */
221 /* -Z SELinux mandated option, busybox optionally supports */
222 static const char ls_options[] ALIGN1 =
223 "Cadi1lgnsxAk" /* 12 opts, total 12 */
224 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 14 */
225 IF_FEATURE_LS_RECURSIVE("R") /* 1, 15 */
226 IF_SELINUX("Z") /* 1, 16 */
228 IF_FEATURE_LS_TIMESTAMPS("ctu") /* 3, 20 */
229 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 24 */
230 IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 26 */
231 IF_FEATURE_HUMAN_READABLE("h") /* 1, 27 */
232 IF_FEATURE_LS_WIDTH("T:w:") /* 2, 29 */
250 OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
251 OPTBIT_Z = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
252 OPTBIT_Q = OPTBIT_Z + 1 * ENABLE_SELINUX,
256 OPTBIT_S = OPTBIT_c + 3 * ENABLE_FEATURE_LS_TIMESTAMPS,
260 OPTBIT_L = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
262 OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
263 OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
265 OPTBIT_full_time = OPTBIT_T + 2 * ENABLE_FEATURE_LS_WIDTH,
267 OPTBIT_color, /* 31 */
268 /* with long opts, we use all 32 bits */
270 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
271 OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES,
272 OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE,
273 OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX,
274 OPT_Q = (1 << OPTBIT_Q),
275 OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS,
276 OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS,
277 OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS,
278 OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES,
279 OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES,
280 OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES,
281 OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES,
282 OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS,
283 OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS,
284 OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE,
285 OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_LS_WIDTH,
286 OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_LS_WIDTH,
287 OPT_full_time = (1 << OPTBIT_full_time ) * ENABLE_LONG_OPTS,
288 OPT_dirs_first = (1 << OPTBIT_dirs_first) * ENABLE_LONG_OPTS,
289 OPT_color = (1 << OPTBIT_color ) * ENABLE_FEATURE_LS_COLOR,
292 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
293 static const uint8_t opt_flags[] = {
294 STYLE_COLUMNAR, /* C */
298 STYLE_SINGLE, /* 1 */
299 LIST_LONG | STYLE_LONG, /* l - by keeping it after -1, "ls -l -1" ignores -1 */
300 LIST_LONG | STYLE_LONG, /* g (don't show owner) - handled via OPT_g. assumes l */
301 LIST_LONG | STYLE_LONG, /* n (numeris uid/gid) - handled via OPT_n. assumes l */
303 DISP_ROWS | STYLE_COLUMNAR, /* x */
305 /* options after -x are not processed through opt_flags */
310 * a directory entry and its stat info
313 const char *name; /* usually basename, but think "ls -l dir/file" */
314 const char *fullname; /* full name (usable for stat etc) */
315 struct dnode *dn_next; /* for linked list */
316 IF_SELINUX(security_context_t sid;)
317 smallint fname_allocated;
319 /* Used to avoid re-doing [l]stat at printout stage
320 * if we already collected needed data in scan stage:
322 mode_t dn_mode_lstat; /* obtained with lstat, or 0 */
323 mode_t dn_mode_stat; /* obtained with stat, or 0 */
325 // struct stat dstat;
326 // struct stat is huge. We don't need it in full.
327 // At least we don't need st_dev and st_blksize,
328 // but there are invisible fields as well
329 // (such as nanosecond-resolution timespamps)
330 // and padding, which we also don't want to store.
331 // We also can pre-parse dev_t dn_rdev (in glibc, it's huge).
332 // On 32-bit uclibc: dnode size went from 112 to 84 bytes.
334 /* Same names as in struct stat, but with dn_ instead of st_ pfx: */
335 mode_t dn_mode; /* obtained with lstat OR stat, depending on -L etc */
337 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
348 // blksize_t dn_blksize;
352 #if ENABLE_FEATURE_LS_COLOR
354 # define G_show_color (G.show_color)
356 # define G_show_color 0
360 #if ENABLE_FEATURE_LS_WIDTH
361 unsigned terminal_width;
362 # define G_terminal_width (G.terminal_width)
364 # define G_terminal_width TERMINAL_WIDTH
366 #if ENABLE_FEATURE_LS_TIMESTAMPS
367 /* Do time() just once. Saves one syscall per file for "ls -l" */
368 time_t current_time_t;
371 #define G (*(struct globals*)bb_common_bufsiz1)
372 #define INIT_G() do { \
373 setup_common_bufsiz(); \
374 /* we have to zero it out because of NOEXEC */ \
375 memset(&G, 0, sizeof(G)); \
376 IF_FEATURE_LS_WIDTH(G_terminal_width = TERMINAL_WIDTH;) \
377 IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \
381 /*** Output code ***/
384 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
385 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
386 * 3/7:multiplexed char/block device)
387 * and we use 0 for unknown and 15 for executables (see below) */
388 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
389 /* un fi chr - dir - blk - file - link - sock - - exe */
390 #define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)])
391 /* 036 black foreground 050 black background
392 037 red foreground 051 red background
393 040 green foreground 052 green background
394 041 brown foreground 053 brown background
395 042 blue foreground 054 blue background
396 043 magenta (purple) foreground 055 magenta background
397 044 cyan (light blue) foreground 056 cyan background
398 045 gray foreground 057 white background
400 #define COLOR(mode) ( \
401 /*un fi chr - dir - blk - file - link - sock - - exe */ \
402 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
404 /* Select normal (0) [actually "reset all"] or bold (1)
405 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
406 * let's use 7 for "impossible" types, just for fun)
407 * Note: coreutils 6.9 uses inverted red for setuid binaries.
409 #define ATTR(mode) ( \
410 /*un fi chr - dir - blk - file- link- sock- - exe */ \
411 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
414 #if ENABLE_FEATURE_LS_COLOR
415 /* mode of zero is interpreted as "unknown" (stat failed) */
416 static char fgcolor(mode_t mode)
418 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
419 return COLOR(0xF000); /* File is executable ... */
422 static char bold(mode_t mode)
424 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
425 return ATTR(0xF000); /* File is executable ... */
430 #if ENABLE_FEATURE_LS_FILETYPES
431 static char append_char(mode_t mode)
433 if (!(option_mask32 & (OPT_F|OPT_p)))
438 if (!(option_mask32 & OPT_F))
440 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
442 return APPCHAR(mode);
446 static unsigned calc_name_len(const char *name)
451 // TODO: quote tab as \t, etc, if -Q
452 name = printable_string(&uni_stat, name);
454 if (!(option_mask32 & OPT_Q)) {
455 return uni_stat.unicode_width;
458 len = 2 + uni_stat.unicode_width;
460 if (*name == '"' || *name == '\\') {
468 /* Return the number of used columns.
469 * Note that only STYLE_COLUMNAR uses return value.
470 * STYLE_SINGLE and STYLE_LONG don't care.
471 * coreutils 7.2 also supports:
472 * ls -b (--escape) = octal escapes (although it doesn't look like working)
473 * ls -N (--literal) = not escape at all
475 static unsigned print_name(const char *name)
480 // TODO: quote tab as \t, etc, if -Q
481 name = printable_string(&uni_stat, name);
483 if (!(option_mask32 & OPT_Q)) {
485 return uni_stat.unicode_width;
488 len = 2 + uni_stat.unicode_width;
491 if (*name == '"' || *name == '\\') {
502 /* Return the number of used columns.
503 * Note that only STYLE_COLUMNAR uses return value,
504 * STYLE_SINGLE and STYLE_LONG don't care.
506 static NOINLINE unsigned display_single(const struct dnode *dn)
510 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
515 #if ENABLE_FEATURE_LS_FILETYPES
516 append = append_char(dn->dn_mode);
519 /* Do readlink early, so that if it fails, error message
520 * does not appear *inside* the "ls -l" line */
522 if (G.all_fmt & LIST_LONG)
523 if (S_ISLNK(dn->dn_mode))
524 lpath = xmalloc_readlink_or_warn(dn->fullname);
526 if (option_mask32 & OPT_i) /* show inode# */
527 column += printf("%7llu ", (long long) dn->dn_ino);
528 //TODO: -h should affect -s too:
529 if (option_mask32 & OPT_s) /* show allocated blocks */
530 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1));
531 if (G.all_fmt & LIST_LONG) {
532 /* long listing: show mode */
533 column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode));
534 /* long listing: show number of links */
535 column += printf("%4lu ", (long) dn->dn_nlink);
536 /* long listing: show user/group */
537 if (option_mask32 & OPT_n) {
538 if (option_mask32 & OPT_g)
539 column += printf("%-8u ", (int) dn->dn_gid);
541 column += printf("%-8u %-8u ",
545 #if ENABLE_FEATURE_LS_USERNAME
547 if (option_mask32 & OPT_g) {
548 column += printf("%-8.8s ",
549 get_cached_groupname(dn->dn_gid));
551 column += printf("%-8.8s %-8.8s ",
552 get_cached_username(dn->dn_uid),
553 get_cached_groupname(dn->dn_gid));
559 if (option_mask32 & OPT_Z) {
560 column += printf("%-32s ", dn->sid ? dn->sid : "?");
563 if (G.all_fmt & LIST_LONG) {
565 /* long listing: show size */
566 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
567 column += printf("%4u, %3u ",
571 if (option_mask32 & OPT_h) {
572 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
573 /* print size, show one fractional, use suffixes */
574 make_human_readable_str(dn->dn_size, 1, 0)
577 column += printf("%9"OFF_FMT"u ", dn->dn_size);
580 #if ENABLE_FEATURE_LS_TIMESTAMPS
581 /* long listing: show {m,c,a}time */
582 if (option_mask32 & OPT_full_time) { /* --full-time */
583 /* coreutils 8.4 ls --full-time prints:
584 * 2009-07-13 17:49:27.000000000 +0200
585 * we don't show fractional seconds.
587 char buf[sizeof("YYYY-mm-dd HH:MM:SS TIMEZONE")];
588 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %z",
589 localtime(&dn->dn_time));
590 column += printf("%s ", buf);
591 } else { /* ordinary time format */
592 /* G.current_time_t is ~== time(NULL) */
593 char *filetime = ctime(&dn->dn_time);
594 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
595 time_t age = G.current_time_t - dn->dn_time;
596 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
597 /* less than 6 months old */
598 /* "mmm dd hh:mm " */
599 printf("%.12s ", filetime + 4);
602 /* "mmm dd yyyyy " after year 9999 :) */
603 strchr(filetime + 20, '\n')[0] = ' ';
604 printf("%.7s%6s", filetime + 4, filetime + 20);
611 #if ENABLE_FEATURE_LS_COLOR
613 mode_t mode = dn->dn_mode_lstat;
615 if (lstat(dn->fullname, &statbuf) == 0)
616 mode = statbuf.st_mode;
617 printf("\033[%u;%um", bold(mode), fgcolor(mode));
620 column += print_name(dn->name);
627 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
628 if ((option_mask32 & (OPT_F|OPT_p))
631 mode_t mode = dn->dn_mode_stat;
633 if (stat(dn->fullname, &statbuf) == 0)
634 mode = statbuf.st_mode;
635 # if ENABLE_FEATURE_LS_FILETYPES
636 append = append_char(mode);
638 # if ENABLE_FEATURE_LS_COLOR
640 printf("\033[%u;%um", bold(mode), fgcolor(mode));
645 column += print_name(lpath) + 4;
651 #if ENABLE_FEATURE_LS_FILETYPES
652 if (option_mask32 & (OPT_F|OPT_p)) {
663 static void display_files(struct dnode **dn, unsigned nfiles)
665 unsigned i, ncols, nrows, row, nc;
668 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
670 if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
673 /* find the longest file name, use that as the column width */
674 for (i = 0; dn[i]; i++) {
675 int len = calc_name_len(dn[i]->name);
676 if (column_width < len)
680 + ((option_mask32 & OPT_Z) ? 33 : 0) /* context width */
681 + ((option_mask32 & OPT_i) ? 8 : 0) /* inode# width */
682 + ((option_mask32 & OPT_s) ? 5 : 0) /* "alloc block" width */
684 ncols = (unsigned)G_terminal_width / column_width;
688 nrows = nfiles / ncols;
689 if (nrows * ncols < nfiles)
690 nrows++; /* round up fractionals */
698 for (row = 0; row < nrows; row++) {
699 for (nc = 0; nc < ncols; nc++) {
700 /* reach into the array based on the column and row */
701 if (G.all_fmt & DISP_ROWS)
702 i = (row * ncols) + nc; /* display across row */
704 i = (nc * nrows) + row; /* display by column */
708 printf("%*s", nexttab, "");
711 nexttab = column + column_width;
712 column += display_single(dn[i]);
721 /*** Dir scanning code ***/
723 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
728 cur = xzalloc(sizeof(*cur));
729 cur->fullname = fullname;
732 if ((option_mask32 & OPT_L) || force_follow) {
734 if (option_mask32 & OPT_Z) {
735 getfilecon(fullname, &cur->sid);
738 if (stat(fullname, &statbuf)) {
739 bb_simple_perror_msg(fullname);
740 G.exit_code = EXIT_FAILURE;
744 cur->dn_mode_stat = statbuf.st_mode;
747 if (option_mask32 & OPT_Z) {
748 lgetfilecon(fullname, &cur->sid);
751 if (lstat(fullname, &statbuf)) {
752 bb_simple_perror_msg(fullname);
753 G.exit_code = EXIT_FAILURE;
757 cur->dn_mode_lstat = statbuf.st_mode;
760 /* cur->dstat = statbuf: */
761 cur->dn_mode = statbuf.st_mode ;
762 cur->dn_size = statbuf.st_size ;
763 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
764 cur->dn_time = statbuf.st_mtime ;
765 if (option_mask32 & OPT_u)
766 cur->dn_time = statbuf.st_atime;
767 if (option_mask32 & OPT_c)
768 cur->dn_time = statbuf.st_ctime;
770 cur->dn_ino = statbuf.st_ino ;
771 cur->dn_blocks = statbuf.st_blocks;
772 cur->dn_nlink = statbuf.st_nlink ;
773 cur->dn_uid = statbuf.st_uid ;
774 cur->dn_gid = statbuf.st_gid ;
775 cur->dn_rdev_maj = major(statbuf.st_rdev);
776 cur->dn_rdev_min = minor(statbuf.st_rdev);
781 static unsigned count_dirs(struct dnode **dn, int which)
793 if (!S_ISDIR((*dn)->dn_mode))
797 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
798 /* or if it's not . or .. */
800 || (name[1] && (name[1] != '.' || name[2]))
805 return which != SPLIT_FILE ? dirs : all - dirs;
808 /* get memory to hold an array of pointers */
809 static struct dnode **dnalloc(unsigned num)
814 num++; /* so that we have terminating NULL */
815 return xzalloc(num * sizeof(struct dnode *));
818 #if ENABLE_FEATURE_LS_RECURSIVE
819 static void dfree(struct dnode **dnp)
826 for (i = 0; dnp[i]; i++) {
827 struct dnode *cur = dnp[i];
828 if (cur->fname_allocated)
829 free((char*)cur->fullname);
835 #define dfree(...) ((void)0)
838 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
839 static struct dnode **splitdnarray(struct dnode **dn, int which)
847 /* count how many dirs or files there are */
848 dncnt = count_dirs(dn, which);
850 /* allocate a file array and a dir array */
851 dnp = dnalloc(dncnt);
853 /* copy the entrys into the file or dir array */
854 for (d = 0; *dn; dn++) {
855 if (S_ISDIR((*dn)->dn_mode)) {
858 if (which == SPLIT_FILE)
862 if ((which & SPLIT_DIR) /* any dir... */
863 /* ... or not . or .. */
865 || (name[1] && (name[1] != '.' || name[2]))
870 if (which == SPLIT_FILE) {
877 #if ENABLE_FEATURE_LS_SORTFILES
878 static int sortcmp(const void *a, const void *b)
880 struct dnode *d1 = *(struct dnode **)a;
881 struct dnode *d2 = *(struct dnode **)b;
882 unsigned opt = option_mask32;
885 dif = 0; /* assume sort by name */
886 // TODO: use pre-initialized function pointer
887 // instead of branch forest
888 if (opt & OPT_dirs_first) {
889 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
891 goto maybe_invert_and_ret;
894 if (opt & OPT_S) { /* sort by size */
895 dif = (d2->dn_size - d1->dn_size);
897 if (opt & OPT_t) { /* sort by time */
898 dif = (d2->dn_time - d1->dn_time);
900 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
901 if (opt & OPT_v) { /* sort by version */
902 dif = strverscmp(d1->name, d2->name);
905 if (opt & OPT_X) { /* sort by extension */
906 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
909 /* sort by name, use as tie breaker for other sorts */
910 if (ENABLE_LOCALE_SUPPORT)
911 dif = strcoll(d1->name, d2->name);
913 dif = strcmp(d1->name, d2->name);
915 /* Make dif fit into an int */
916 if (sizeof(dif) > sizeof(int)) {
917 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
918 /* shift leaving only "int" worth of bits */
919 /* (this requires dif != 0, and here it is nonzero) */
920 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
923 maybe_invert_and_ret:
924 return (opt & OPT_r) ? -(int)dif : (int)dif;
927 static void dnsort(struct dnode **dn, int size)
929 qsort(dn, size, sizeof(*dn), sortcmp);
932 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
935 display_files(dn, nfiles);
938 # define dnsort(dn, size) ((void)0)
939 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
942 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
943 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
945 struct dnode *dn, *cur, **dnp;
946 struct dirent *entry;
951 dir = warn_opendir(path);
953 G.exit_code = EXIT_FAILURE;
954 return NULL; /* could not open the dir */
958 while ((entry = readdir(dir)) != NULL) {
961 /* are we going to list the file- it may be . or .. or a hidden file */
962 if (entry->d_name[0] == '.') {
963 if (!(option_mask32 & (OPT_a|OPT_A)))
964 continue; /* skip all dotfiles if no -a/-A */
965 if (!(option_mask32 & OPT_a)
966 && (!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
968 continue; /* if only -A, skip . and .. but show other dotfiles */
971 fullname = concat_path_file(path, entry->d_name);
972 cur = my_stat(fullname, bb_basename(fullname), 0);
977 cur->fname_allocated = 1;
987 /* now that we know how many files there are
988 * allocate memory for an array to hold dnode pointers
991 dnp = dnalloc(nfiles);
992 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
993 dnp[i] = dn; /* save pointer to node in array */
1003 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
1004 * If any of the -l, -n, -s options is specified, each list
1005 * of files within the directory shall be preceded by a
1006 * status line indicating the number of file system blocks
1007 * occupied by files in the directory in 512-byte units if
1008 * the -k option is not specified, or 1024-byte units if the
1009 * -k option is specified, rounded up to the next integral
1012 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
1013 static off_t calculate_blocks(struct dnode **dn)
1018 /* st_blocks is in 512 byte blocks */
1019 blocks += (*dn)->dn_blocks;
1024 /* Even though standard says use 512 byte blocks, coreutils use 1k */
1025 /* Actually, we round up by calculating (blocks + 1) / 2,
1026 * "+ 1" was done when we initialized blocks to 1 */
1031 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1034 struct dnode **subdnp;
1037 if ((G.all_fmt & DISP_DIRNAME)
1038 || (option_mask32 & OPT_R)
1043 printf("%s:\n", (*dn)->fullname);
1045 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1047 if ((G.all_fmt & STYLE_MASK) == STYLE_LONG
1048 || (option_mask32 & OPT_s)
1050 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1054 /* list all files at this level */
1055 sort_and_display_files(subdnp, nfiles);
1057 if (ENABLE_FEATURE_LS_RECURSIVE
1058 && (option_mask32 & OPT_R)
1062 /* recursive - list the sub-dirs */
1063 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1064 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1066 dnsort(dnd, dndirs);
1067 scan_and_display_dirs_recur(dnd, 0);
1068 /* free the array of dnode pointers to the dirs */
1072 /* free the dnodes and the fullname mem */
1079 int ls_main(int argc UNUSED_PARAM, char **argv)
1091 #if ENABLE_FEATURE_LS_COLOR
1092 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1094 * # ls --color=BOGUS
1095 * ls: invalid argument 'BOGUS' for '--color'
1096 * Valid arguments are:
1097 * 'always', 'yes', 'force'
1098 * 'never', 'no', 'none'
1099 * 'auto', 'tty', 'if-tty'
1100 * (and substrings: "--color=alwa" work too)
1102 static const char ls_longopts[] ALIGN1 =
1103 "full-time\0" No_argument "\xff"
1104 "group-directories-first\0" No_argument "\xfe"
1105 "color\0" Optional_argument "\xfd"
1107 static const char color_str[] ALIGN1 =
1108 "always\0""yes\0""force\0"
1109 "auto\0""tty\0""if-tty\0";
1110 /* need to initialize since --color has _an optional_ argument */
1111 const char *color_opt = color_str; /* "always" */
1118 #if ENABLE_FEATURE_LS_WIDTH
1119 /* obtain the terminal width */
1120 G_terminal_width = get_terminal_width(STDIN_FILENO);
1121 /* go one less... */
1125 /* process options */
1126 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1128 /* --full-time implies -l */
1129 IF_FEATURE_LS_TIMESTAMPS(IF_LONG_OPTS("\xff""l"))
1130 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1131 * in some pairs of opts, only last one takes effect:
1133 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1134 // ":m-l:l-m" - we don't have -m
1135 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1136 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1137 ":C-1:1-C" /* bycols/oneline */
1138 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1139 IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */
1141 IF_FEATURE_LS_WIDTH(":w+");
1142 opt = getopt32(argv, ls_options
1143 IF_FEATURE_LS_WIDTH(, NULL, &G_terminal_width)
1144 IF_FEATURE_LS_COLOR(, &color_opt)
1146 #if 0 /* option bits debug */
1147 bb_error_msg("opt:0x%08x l:%x H:%x color:%x dirs:%x", opt, OPT_l, OPT_H, OPT_color, OPT_dirs_first);
1148 if (opt & OPT_c ) bb_error_msg("-c");
1149 if (opt & OPT_l ) bb_error_msg("-l");
1150 if (opt & OPT_H ) bb_error_msg("-H");
1151 if (opt & OPT_color ) bb_error_msg("--color");
1152 if (opt & OPT_dirs_first) bb_error_msg("--group-directories-first");
1153 if (opt & OPT_full_time ) bb_error_msg("--full-time");
1156 for (i = 0; opt_flags[i] != 0xff; i++) {
1157 if (opt & (1 << i)) {
1158 uint32_t flags = opt_flags[i];
1160 if (flags & STYLE_MASK)
1161 G.all_fmt &= ~STYLE_MASK;
1168 if (!is_selinux_enabled())
1169 option_mask32 &= ~OPT_Z;
1173 #if ENABLE_FEATURE_LS_COLOR
1174 /* set G_show_color = 1/0 */
1175 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1176 char *p = getenv("LS_COLORS");
1177 /* LS_COLORS is unset, or (not empty && not "none") ? */
1178 if (!p || (p[0] && strcmp(p, "none") != 0))
1181 if (opt & OPT_color) {
1182 if (color_opt[0] == 'n')
1184 else switch (index_in_substrings(color_str, color_opt)) {
1188 if (isatty(STDOUT_FILENO)) {
1198 /* sort out which command line options take precedence */
1199 if (ENABLE_FEATURE_LS_RECURSIVE && (opt & OPT_d))
1200 option_mask32 &= ~OPT_R; /* no recurse if listing only dir */
1201 if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) { /* not -l? */
1202 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1203 /* when to sort by time? -t[cu] sorts by time even with -l */
1204 /* (this is achieved by opt_flags[] element for -t) */
1205 /* without -l, bare -c or -u enable sort too */
1206 /* (with -l, bare -c or -u just select which time to show) */
1207 if (opt & (OPT_c|OPT_u)) {
1208 option_mask32 |= OPT_t;
1213 /* choose a display format if one was not already specified by an option */
1214 if (!(G.all_fmt & STYLE_MASK))
1215 G.all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1219 *--argv = (char*)".";
1222 G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1224 /* stuff the command line file names into a dnode array */
1228 cur = my_stat(*argv, *argv,
1229 /* follow links on command line unless -l, -s or -F: */
1230 !((G.all_fmt & STYLE_MASK) == STYLE_LONG
1231 || (option_mask32 & (OPT_s|OPT_F))
1234 || (option_mask32 & OPT_H)
1235 /* ... or if -L, but my_stat always follows links if -L */
1240 /*cur->fname_allocated = 0; - already is */
1246 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1250 /* now that we know how many files there are
1251 * allocate memory for an array to hold dnode pointers
1253 dnp = dnalloc(nfiles);
1254 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1255 dnp[i] = dn; /* save pointer to node in array */
1261 if (option_mask32 & OPT_d) {
1262 sort_and_display_files(dnp, nfiles);
1264 dnd = splitdnarray(dnp, SPLIT_DIR);
1265 dnf = splitdnarray(dnp, SPLIT_FILE);
1266 dndirs = count_dirs(dnp, SPLIT_DIR);
1267 dnfiles = nfiles - dndirs;
1269 sort_and_display_files(dnf, dnfiles);
1270 if (ENABLE_FEATURE_CLEAN_UP)
1274 dnsort(dnd, dndirs);
1275 scan_and_display_dirs_recur(dnd, dnfiles == 0);
1276 if (ENABLE_FEATURE_CLEAN_UP)
1281 if (ENABLE_FEATURE_CLEAN_UP)