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.
31 //config: bool "ls (14 kb)"
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" - don't show, this is a default anyway
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 */
197 /* -Cadi1l Std options, busybox always supports */
198 /* -gnsxA Std options, busybox always supports */
199 /* -Q GNU option, busybox always supports */
200 /* -k Std option, busybox always supports (by ignoring) */
201 /* It means "for -s, show sizes in kbytes" */
202 /* Seems to only affect "POSIXLY_CORRECT=1 ls -sk" */
203 /* since otherwise -s shows kbytes anyway */
204 /* -LHRctur Std options, busybox optionally supports */
205 /* -Fp Std options, busybox optionally supports */
206 /* -SXvhTw GNU options, busybox optionally supports */
207 /* -T WIDTH Ignored (we don't use tabs on output) */
208 /* -Z SELinux mandated option, busybox optionally supports */
210 "Cadi1lgnsxAk" /* 12 opts, total 12 */ \
211 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 14 */ \
212 IF_FEATURE_LS_RECURSIVE("R") /* 1, 15 */ \
213 IF_SELINUX("Z") /* 1, 16 */ \
215 IF_FEATURE_LS_TIMESTAMPS("ctu") /* 3, 20 */ \
216 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 24 */ \
217 IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 26 */ \
218 IF_FEATURE_HUMAN_READABLE("h") /* 1, 27 */ \
219 IF_FEATURE_LS_WIDTH("T:w:") /* 2, 29 */
237 OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
238 OPTBIT_Z = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
239 OPTBIT_Q = OPTBIT_Z + 1 * ENABLE_SELINUX,
243 OPTBIT_S = OPTBIT_c + 3 * ENABLE_FEATURE_LS_TIMESTAMPS,
247 OPTBIT_L = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
249 OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
250 OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
252 OPTBIT_full_time = OPTBIT_T + 2 * ENABLE_FEATURE_LS_WIDTH,
254 OPTBIT_color, /* 31 */
255 /* with long opts, we use all 32 bits */
257 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
258 OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES,
259 OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE,
260 OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX,
261 OPT_Q = (1 << OPTBIT_Q),
262 OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS,
263 OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS,
264 OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS,
265 OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES,
266 OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES,
267 OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES,
268 OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES,
269 OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS,
270 OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS,
271 OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE,
272 OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_LS_WIDTH,
273 OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_LS_WIDTH,
274 OPT_full_time = (1 << OPTBIT_full_time ) * ENABLE_LONG_OPTS,
275 OPT_dirs_first = (1 << OPTBIT_dirs_first) * ENABLE_LONG_OPTS,
276 OPT_color = (1 << OPTBIT_color ) * ENABLE_FEATURE_LS_COLOR,
280 * a directory entry and its stat info
283 const char *name; /* usually basename, but think "ls -l dir/file" */
284 const char *fullname; /* full name (usable for stat etc) */
285 struct dnode *dn_next; /* for linked list */
286 IF_SELINUX(security_context_t sid;)
287 smallint fname_allocated;
289 /* Used to avoid re-doing [l]stat at printout stage
290 * if we already collected needed data in scan stage:
292 mode_t dn_mode_lstat; /* obtained with lstat, or 0 */
293 mode_t dn_mode_stat; /* obtained with stat, or 0 */
295 // struct stat dstat;
296 // struct stat is huge. We don't need it in full.
297 // At least we don't need st_dev and st_blksize,
298 // but there are invisible fields as well
299 // (such as nanosecond-resolution timespamps)
300 // and padding, which we also don't want to store.
301 // We also can pre-parse dev_t dn_rdev (in glibc, it's huge).
302 // On 32-bit uclibc: dnode size went from 112 to 84 bytes.
304 /* Same names as in struct stat, but with dn_ instead of st_ pfx: */
305 mode_t dn_mode; /* obtained with lstat OR stat, depending on -L etc */
307 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
318 // blksize_t dn_blksize;
322 #if ENABLE_FEATURE_LS_COLOR
324 # define G_show_color (G.show_color)
326 # define G_show_color 0
329 smallint show_dirname;
330 #if ENABLE_FEATURE_LS_WIDTH
331 unsigned terminal_width;
332 # define G_terminal_width (G.terminal_width)
334 # define G_terminal_width TERMINAL_WIDTH
336 #if ENABLE_FEATURE_LS_TIMESTAMPS
337 /* Do time() just once. Saves one syscall per file for "ls -l" */
338 time_t current_time_t;
341 #define G (*(struct globals*)bb_common_bufsiz1)
342 #define INIT_G() do { \
343 setup_common_bufsiz(); \
344 /* we have to zero it out because of NOEXEC */ \
345 memset(&G, 0, sizeof(G)); \
346 IF_FEATURE_LS_WIDTH(G_terminal_width = TERMINAL_WIDTH;) \
347 IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \
353 /*** Output code ***/
356 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
357 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
358 * 3/7:multiplexed char/block device)
359 * and we use 0 for unknown and 15 for executables (see below) */
360 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
361 /* un fi chr - dir - blk - file - link - sock - - exe */
362 #define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)])
363 /* 036 black foreground 050 black background
364 037 red foreground 051 red background
365 040 green foreground 052 green background
366 041 brown foreground 053 brown background
367 042 blue foreground 054 blue background
368 043 magenta (purple) foreground 055 magenta background
369 044 cyan (light blue) foreground 056 cyan background
370 045 gray foreground 057 white background
372 #define COLOR(mode) ( \
373 /*un fi chr - dir - blk - file - link - sock - - exe */ \
374 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
376 /* Select normal (0) [actually "reset all"] or bold (1)
377 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
378 * let's use 7 for "impossible" types, just for fun)
379 * Note: coreutils 6.9 uses inverted red for setuid binaries.
381 #define ATTR(mode) ( \
382 /*un fi chr - dir - blk - file- link- sock- - exe */ \
383 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
386 #if ENABLE_FEATURE_LS_COLOR
387 /* mode of zero is interpreted as "unknown" (stat failed) */
388 static char fgcolor(mode_t mode)
390 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
391 return COLOR(0xF000); /* File is executable ... */
394 static char bold(mode_t mode)
396 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
397 return ATTR(0xF000); /* File is executable ... */
402 #if ENABLE_FEATURE_LS_FILETYPES
403 static char append_char(mode_t mode)
405 if (!(option_mask32 & (OPT_F|OPT_p)))
410 if (!(option_mask32 & OPT_F))
412 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
414 return APPCHAR(mode);
418 static unsigned calc_name_len(const char *name)
423 // TODO: quote tab as \t, etc, if -Q
424 name = printable_string(&uni_stat, name);
426 if (!(option_mask32 & OPT_Q)) {
427 return uni_stat.unicode_width;
430 len = 2 + uni_stat.unicode_width;
432 if (*name == '"' || *name == '\\') {
440 /* Return the number of used columns.
441 * Note that only columnar output uses return value.
442 * -l and -1 modes don't care.
443 * coreutils 7.2 also supports:
444 * ls -b (--escape) = octal escapes (although it doesn't look like working)
445 * ls -N (--literal) = not escape at all
447 static unsigned print_name(const char *name)
452 // TODO: quote tab as \t, etc, if -Q
453 name = printable_string(&uni_stat, name);
455 if (!(option_mask32 & OPT_Q)) {
457 return uni_stat.unicode_width;
460 len = 2 + uni_stat.unicode_width;
463 if (*name == '"' || *name == '\\') {
474 /* Return the number of used columns.
475 * Note that only columnar output uses return value,
476 * -l and -1 modes don't care.
478 static NOINLINE unsigned display_single(const struct dnode *dn)
483 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
486 #if ENABLE_FEATURE_LS_FILETYPES
487 char append = append_char(dn->dn_mode);
492 /* Do readlink early, so that if it fails, error message
493 * does not appear *inside* the "ls -l" line */
496 if (S_ISLNK(dn->dn_mode))
497 lpath = xmalloc_readlink_or_warn(dn->fullname);
499 if (opt & OPT_i) /* show inode# */
500 column += printf("%7llu ", (long long) dn->dn_ino);
501 //TODO: -h should affect -s too:
502 if (opt & OPT_s) /* show allocated blocks */
503 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1));
505 /* long listing: show mode */
506 column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode));
507 /* long listing: show number of links */
508 column += printf("%4lu ", (long) dn->dn_nlink);
509 /* long listing: show user/group */
512 column += printf("%-8u ", (int) dn->dn_gid);
514 column += printf("%-8u %-8u ",
518 #if ENABLE_FEATURE_LS_USERNAME
521 column += printf("%-8.8s ",
522 get_cached_groupname(dn->dn_gid));
524 column += printf("%-8.8s %-8.8s ",
525 get_cached_username(dn->dn_uid),
526 get_cached_groupname(dn->dn_gid));
533 column += printf("%-32s ", dn->sid ? dn->sid : "?");
538 /* long listing: show size */
539 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
540 column += printf("%4u, %3u ",
545 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
546 /* print size, show one fractional, use suffixes */
547 make_human_readable_str(dn->dn_size, 1, 0)
550 column += printf("%9"OFF_FMT"u ", dn->dn_size);
553 #if ENABLE_FEATURE_LS_TIMESTAMPS
554 /* long listing: show {m,c,a}time */
555 if (opt & OPT_full_time) { /* --full-time */
556 /* coreutils 8.4 ls --full-time prints:
557 * 2009-07-13 17:49:27.000000000 +0200
558 * we don't show fractional seconds.
560 char buf[sizeof("YYYY-mm-dd HH:MM:SS TIMEZONE")];
561 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %z",
562 localtime(&dn->dn_time));
563 column += printf("%s ", buf);
564 } else { /* ordinary time format */
565 /* G.current_time_t is ~== time(NULL) */
566 char *filetime = ctime(&dn->dn_time);
567 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
568 time_t age = G.current_time_t - dn->dn_time;
569 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
570 /* less than 6 months old */
571 /* "mmm dd hh:mm " */
572 printf("%.12s ", filetime + 4);
575 /* "mmm dd yyyyy " after year 9999 :) */
576 strchr(filetime + 20, '\n')[0] = ' ';
577 printf("%.7s%6s", filetime + 4, filetime + 20);
584 #if ENABLE_FEATURE_LS_COLOR
586 mode_t mode = dn->dn_mode_lstat;
588 if (lstat(dn->fullname, &statbuf) == 0)
589 mode = statbuf.st_mode;
590 printf(ESC"[%u;%um", bold(mode), fgcolor(mode));
593 column += print_name(dn->name);
600 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
601 if ((opt & (OPT_F|OPT_p))
604 mode_t mode = dn->dn_mode_stat;
606 if (stat(dn->fullname, &statbuf) == 0)
607 mode = statbuf.st_mode;
608 # if ENABLE_FEATURE_LS_FILETYPES
609 append = append_char(mode);
611 # if ENABLE_FEATURE_LS_COLOR
613 printf(ESC"[%u;%um", bold(mode), fgcolor(mode));
618 column += print_name(lpath) + 4;
624 #if ENABLE_FEATURE_LS_FILETYPES
625 if (opt & (OPT_F|OPT_p)) {
636 static void display_files(struct dnode **dn, unsigned nfiles)
638 unsigned i, ncols, nrows, row, nc;
641 unsigned column_width = 0; /* used only by coulmnal output */
643 if (option_mask32 & (OPT_l|OPT_1)) {
646 /* find the longest file name, use that as the column width */
647 for (i = 0; dn[i]; i++) {
648 int len = calc_name_len(dn[i]->name);
649 if (column_width < len)
653 + ((option_mask32 & OPT_Z) ? 33 : 0) /* context width */
654 + ((option_mask32 & OPT_i) ? 8 : 0) /* inode# width */
655 + ((option_mask32 & OPT_s) ? 5 : 0) /* "alloc block" width */
657 ncols = (unsigned)G_terminal_width / column_width;
661 nrows = nfiles / ncols;
662 if (nrows * ncols < nfiles)
663 nrows++; /* round up fractionals */
671 for (row = 0; row < nrows; row++) {
672 for (nc = 0; nc < ncols; nc++) {
673 /* reach into the array based on the column and row */
674 if (option_mask32 & OPT_x)
675 i = (row * ncols) + nc; /* display across row */
677 i = (nc * nrows) + row; /* display by column */
681 printf("%*s", nexttab, "");
684 nexttab = column + column_width;
685 column += display_single(dn[i]);
694 /*** Dir scanning code ***/
696 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
701 cur = xzalloc(sizeof(*cur));
702 cur->fullname = fullname;
705 if ((option_mask32 & OPT_L) || force_follow) {
707 if (option_mask32 & OPT_Z) {
708 getfilecon(fullname, &cur->sid);
711 if (stat(fullname, &statbuf)) {
712 bb_simple_perror_msg(fullname);
713 G.exit_code = EXIT_FAILURE;
717 cur->dn_mode_stat = statbuf.st_mode;
720 if (option_mask32 & OPT_Z) {
721 lgetfilecon(fullname, &cur->sid);
724 if (lstat(fullname, &statbuf)) {
725 bb_simple_perror_msg(fullname);
726 G.exit_code = EXIT_FAILURE;
730 cur->dn_mode_lstat = statbuf.st_mode;
733 /* cur->dstat = statbuf: */
734 cur->dn_mode = statbuf.st_mode ;
735 cur->dn_size = statbuf.st_size ;
736 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
737 cur->dn_time = statbuf.st_mtime ;
738 if (option_mask32 & OPT_u)
739 cur->dn_time = statbuf.st_atime;
740 if (option_mask32 & OPT_c)
741 cur->dn_time = statbuf.st_ctime;
743 cur->dn_ino = statbuf.st_ino ;
744 cur->dn_blocks = statbuf.st_blocks;
745 cur->dn_nlink = statbuf.st_nlink ;
746 cur->dn_uid = statbuf.st_uid ;
747 cur->dn_gid = statbuf.st_gid ;
748 cur->dn_rdev_maj = major(statbuf.st_rdev);
749 cur->dn_rdev_min = minor(statbuf.st_rdev);
754 static unsigned count_dirs(struct dnode **dn, int which)
766 if (!S_ISDIR((*dn)->dn_mode))
770 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
771 /* or if it's not . or .. */
773 || (name[1] && (name[1] != '.' || name[2]))
778 return which != SPLIT_FILE ? dirs : all - dirs;
781 /* get memory to hold an array of pointers */
782 static struct dnode **dnalloc(unsigned num)
787 num++; /* so that we have terminating NULL */
788 return xzalloc(num * sizeof(struct dnode *));
791 #if ENABLE_FEATURE_LS_RECURSIVE
792 static void dfree(struct dnode **dnp)
799 for (i = 0; dnp[i]; i++) {
800 struct dnode *cur = dnp[i];
801 if (cur->fname_allocated)
802 free((char*)cur->fullname);
808 #define dfree(...) ((void)0)
811 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
812 static struct dnode **splitdnarray(struct dnode **dn, int which)
820 /* count how many dirs or files there are */
821 dncnt = count_dirs(dn, which);
823 /* allocate a file array and a dir array */
824 dnp = dnalloc(dncnt);
826 /* copy the entrys into the file or dir array */
827 for (d = 0; *dn; dn++) {
828 if (S_ISDIR((*dn)->dn_mode)) {
831 if (which == SPLIT_FILE)
835 if ((which & SPLIT_DIR) /* any dir... */
836 /* ... or not . or .. */
838 || (name[1] && (name[1] != '.' || name[2]))
843 if (which == SPLIT_FILE) {
850 #if ENABLE_FEATURE_LS_SORTFILES
851 static int sortcmp(const void *a, const void *b)
853 struct dnode *d1 = *(struct dnode **)a;
854 struct dnode *d2 = *(struct dnode **)b;
855 unsigned opt = option_mask32;
858 dif = 0; /* assume sort by name */
859 // TODO: use pre-initialized function pointer
860 // instead of branch forest
861 if (opt & OPT_dirs_first) {
862 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
864 goto maybe_invert_and_ret;
867 if (opt & OPT_S) { /* sort by size */
868 dif = (d2->dn_size - d1->dn_size);
870 if (opt & OPT_t) { /* sort by time */
871 dif = (d2->dn_time - d1->dn_time);
873 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
874 if (opt & OPT_v) { /* sort by version */
875 dif = strverscmp(d1->name, d2->name);
878 if (opt & OPT_X) { /* sort by extension */
879 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
882 /* sort by name, use as tie breaker for other sorts */
883 if (ENABLE_LOCALE_SUPPORT)
884 dif = strcoll(d1->name, d2->name);
886 dif = strcmp(d1->name, d2->name);
888 /* Make dif fit into an int */
889 if (sizeof(dif) > sizeof(int)) {
890 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
891 /* shift leaving only "int" worth of bits */
892 /* (this requires dif != 0, and here it is nonzero) */
893 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
896 maybe_invert_and_ret:
897 return (opt & OPT_r) ? -(int)dif : (int)dif;
900 static void dnsort(struct dnode **dn, int size)
902 qsort(dn, size, sizeof(*dn), sortcmp);
905 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
908 display_files(dn, nfiles);
911 # define dnsort(dn, size) ((void)0)
912 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
915 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
916 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
918 struct dnode *dn, *cur, **dnp;
919 struct dirent *entry;
924 dir = warn_opendir(path);
926 G.exit_code = EXIT_FAILURE;
927 return NULL; /* could not open the dir */
931 while ((entry = readdir(dir)) != NULL) {
934 /* are we going to list the file- it may be . or .. or a hidden file */
935 if (entry->d_name[0] == '.') {
936 if (!(option_mask32 & (OPT_a|OPT_A)))
937 continue; /* skip all dotfiles if no -a/-A */
938 if (!(option_mask32 & OPT_a)
939 && (!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
941 continue; /* if only -A, skip . and .. but show other dotfiles */
944 fullname = concat_path_file(path, entry->d_name);
945 cur = my_stat(fullname, bb_basename(fullname), 0);
950 cur->fname_allocated = 1;
960 /* now that we know how many files there are
961 * allocate memory for an array to hold dnode pointers
964 dnp = dnalloc(nfiles);
965 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
966 dnp[i] = dn; /* save pointer to node in array */
976 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
977 * If any of the -l, -n, -s options is specified, each list
978 * of files within the directory shall be preceded by a
979 * status line indicating the number of file system blocks
980 * occupied by files in the directory in 512-byte units if
981 * the -k option is not specified, or 1024-byte units if the
982 * -k option is specified, rounded up to the next integral
985 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
986 static off_t calculate_blocks(struct dnode **dn)
991 /* st_blocks is in 512 byte blocks */
992 blocks += (*dn)->dn_blocks;
997 /* Even though standard says use 512 byte blocks, coreutils use 1k */
998 /* Actually, we round up by calculating (blocks + 1) / 2,
999 * "+ 1" was done when we initialized blocks to 1 */
1004 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1007 struct dnode **subdnp;
1010 if (G.show_dirname || (option_mask32 & OPT_R)) {
1014 printf("%s:\n", (*dn)->fullname);
1016 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1018 if (option_mask32 & (OPT_s|OPT_l)) {
1019 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1023 /* list all files at this level */
1024 sort_and_display_files(subdnp, nfiles);
1026 if (ENABLE_FEATURE_LS_RECURSIVE
1027 && (option_mask32 & OPT_R)
1031 /* recursive - list the sub-dirs */
1032 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1033 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1035 dnsort(dnd, dndirs);
1036 scan_and_display_dirs_recur(dnd, 0);
1037 /* free the array of dnode pointers to the dirs */
1041 /* free the dnodes and the fullname mem */
1048 int ls_main(int argc UNUSED_PARAM, char **argv)
1049 { /* ^^^^^^^^^^^^^^^^^ note: if FTPD, argc can be wrong, see ftpd.c */
1060 #if ENABLE_FEATURE_LS_COLOR
1061 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1063 * # ls --color=BOGUS
1064 * ls: invalid argument 'BOGUS' for '--color'
1065 * Valid arguments are:
1066 * 'always', 'yes', 'force'
1067 * 'never', 'no', 'none'
1068 * 'auto', 'tty', 'if-tty'
1069 * (and substrings: "--color=alwa" work too)
1071 static const char color_str[] ALIGN1 =
1072 "always\0""yes\0""force\0"
1073 "auto\0""tty\0""if-tty\0";
1074 /* need to initialize since --color has _an optional_ argument */
1075 const char *color_opt = color_str; /* "always" */
1077 #if ENABLE_LONG_OPTS
1078 static const char ls_longopts[] ALIGN1 =
1079 "full-time\0" No_argument "\xff"
1080 "group-directories-first\0" No_argument "\xfe"
1081 "color\0" Optional_argument "\xfd"
1089 #if ENABLE_FEATURE_LS_WIDTH
1090 /* obtain the terminal width */
1091 G_terminal_width = get_terminal_width(STDIN_FILENO);
1092 /* go one less... */
1096 /* process options */
1097 opt = getopt32long(argv, "^"
1100 /* -n and -g imply -l */
1102 /* --full-time implies -l */
1103 IF_FEATURE_LS_TIMESTAMPS(IF_LONG_OPTS(":\xff""l"))
1104 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1105 * in some pairs of opts, only last one takes effect:
1107 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1108 // ":m-l:l-m" - we don't have -m
1109 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1110 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1111 ":C-1:1-C" /* bycols/oneline */
1112 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1113 IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */
1115 IF_FEATURE_LS_WIDTH(":w+")
1117 IF_FEATURE_LS_WIDTH(, /*-T*/ NULL, /*-w*/ &G_terminal_width)
1118 IF_FEATURE_LS_COLOR(, &color_opt)
1120 #if 0 /* option bits debug */
1121 bb_error_msg("opt:0x%08x l:%x H:%x color:%x dirs:%x", opt, OPT_l, OPT_H, OPT_color, OPT_dirs_first);
1122 if (opt & OPT_c ) bb_error_msg("-c");
1123 if (opt & OPT_l ) bb_error_msg("-l");
1124 if (opt & OPT_H ) bb_error_msg("-H");
1125 if (opt & OPT_color ) bb_error_msg("--color");
1126 if (opt & OPT_dirs_first) bb_error_msg("--group-directories-first");
1127 if (opt & OPT_full_time ) bb_error_msg("--full-time");
1133 if (!is_selinux_enabled())
1134 option_mask32 &= ~OPT_Z;
1138 #if ENABLE_FEATURE_LS_COLOR
1139 /* set G_show_color = 1/0 */
1140 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1141 char *p = getenv("LS_COLORS");
1142 /* LS_COLORS is unset, or (not empty && not "none") ? */
1143 if (!p || (p[0] && strcmp(p, "none") != 0))
1146 if (opt & OPT_color) {
1147 if (color_opt[0] == 'n')
1149 else switch (index_in_substrings(color_str, color_opt)) {
1153 if (isatty(STDOUT_FILENO)) {
1163 /* sort out which command line options take precedence */
1164 if (ENABLE_FEATURE_LS_RECURSIVE && (opt & OPT_d))
1165 option_mask32 &= ~OPT_R; /* no recurse if listing only dir */
1166 if (!(opt & OPT_l)) { /* not -l? */
1167 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1168 /* when to sort by time? -t[cu] sorts by time even with -l */
1169 /* (this is achieved by opt_flags[] element for -t) */
1170 /* without -l, bare -c or -u enable sort too */
1171 /* (with -l, bare -c or -u just select which time to show) */
1172 if (opt & (OPT_c|OPT_u)) {
1173 option_mask32 |= OPT_t;
1178 /* choose a display format if one was not already specified by an option */
1179 if (!(option_mask32 & (OPT_l|OPT_1|OPT_x|OPT_C)))
1180 option_mask32 |= (isatty(STDOUT_FILENO) ? OPT_C : OPT_1);
1182 if (ENABLE_FTPD && applet_name[0] == 'f') {
1183 /* ftpd secret backdoor. dirs first are much nicer */
1184 option_mask32 |= OPT_dirs_first;
1189 *--argv = (char*)".";
1192 G.show_dirname = 1; /* 2 or more items? label directories */
1194 /* stuff the command line file names into a dnode array */
1198 cur = my_stat(*argv, *argv,
1199 /* follow links on command line unless -l, -s or -F: */
1200 !(option_mask32 & (OPT_l|OPT_s|OPT_F))
1202 || (option_mask32 & OPT_H)
1203 /* ... or if -L, but my_stat always follows links if -L */
1208 /*cur->fname_allocated = 0; - already is */
1214 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1218 /* now that we know how many files there are
1219 * allocate memory for an array to hold dnode pointers
1221 dnp = dnalloc(nfiles);
1222 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1223 dnp[i] = dn; /* save pointer to node in array */
1229 if (option_mask32 & OPT_d) {
1230 sort_and_display_files(dnp, nfiles);
1232 dnd = splitdnarray(dnp, SPLIT_DIR);
1233 dnf = splitdnarray(dnp, SPLIT_FILE);
1234 dndirs = count_dirs(dnp, SPLIT_DIR);
1235 dnfiles = nfiles - dndirs;
1237 sort_and_display_files(dnf, dnfiles);
1238 if (ENABLE_FEATURE_CLEAN_UP)
1242 dnsort(dnd, dndirs);
1243 scan_and_display_dirs_recur(dnd, dnfiles == 0);
1244 if (ENABLE_FEATURE_CLEAN_UP)
1249 if (ENABLE_FEATURE_CLEAN_UP)