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" - 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 */
209 static const char ls_options[] ALIGN1 =
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);) \
351 /*** Output code ***/
354 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
355 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
356 * 3/7:multiplexed char/block device)
357 * and we use 0 for unknown and 15 for executables (see below) */
358 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
359 /* un fi chr - dir - blk - file - link - sock - - exe */
360 #define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)])
361 /* 036 black foreground 050 black background
362 037 red foreground 051 red background
363 040 green foreground 052 green background
364 041 brown foreground 053 brown background
365 042 blue foreground 054 blue background
366 043 magenta (purple) foreground 055 magenta background
367 044 cyan (light blue) foreground 056 cyan background
368 045 gray foreground 057 white background
370 #define COLOR(mode) ( \
371 /*un fi chr - dir - blk - file - link - sock - - exe */ \
372 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
374 /* Select normal (0) [actually "reset all"] or bold (1)
375 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
376 * let's use 7 for "impossible" types, just for fun)
377 * Note: coreutils 6.9 uses inverted red for setuid binaries.
379 #define ATTR(mode) ( \
380 /*un fi chr - dir - blk - file- link- sock- - exe */ \
381 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
384 #if ENABLE_FEATURE_LS_COLOR
385 /* mode of zero is interpreted as "unknown" (stat failed) */
386 static char fgcolor(mode_t mode)
388 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
389 return COLOR(0xF000); /* File is executable ... */
392 static char bold(mode_t mode)
394 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
395 return ATTR(0xF000); /* File is executable ... */
400 #if ENABLE_FEATURE_LS_FILETYPES
401 static char append_char(mode_t mode)
403 if (!(option_mask32 & (OPT_F|OPT_p)))
408 if (!(option_mask32 & OPT_F))
410 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
412 return APPCHAR(mode);
416 static unsigned calc_name_len(const char *name)
421 // TODO: quote tab as \t, etc, if -Q
422 name = printable_string(&uni_stat, name);
424 if (!(option_mask32 & OPT_Q)) {
425 return uni_stat.unicode_width;
428 len = 2 + uni_stat.unicode_width;
430 if (*name == '"' || *name == '\\') {
438 /* Return the number of used columns.
439 * Note that only columnar output uses return value.
440 * -l and -1 modes don't care.
441 * coreutils 7.2 also supports:
442 * ls -b (--escape) = octal escapes (although it doesn't look like working)
443 * ls -N (--literal) = not escape at all
445 static unsigned print_name(const char *name)
450 // TODO: quote tab as \t, etc, if -Q
451 name = printable_string(&uni_stat, name);
453 if (!(option_mask32 & OPT_Q)) {
455 return uni_stat.unicode_width;
458 len = 2 + uni_stat.unicode_width;
461 if (*name == '"' || *name == '\\') {
472 /* Return the number of used columns.
473 * Note that only columnar output uses return value,
474 * -l and -1 modes don't care.
476 static NOINLINE unsigned display_single(const struct dnode *dn)
481 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
486 #if ENABLE_FEATURE_LS_FILETYPES
487 append = append_char(dn->dn_mode);
491 /* Do readlink early, so that if it fails, error message
492 * does not appear *inside* the "ls -l" line */
495 if (S_ISLNK(dn->dn_mode))
496 lpath = xmalloc_readlink_or_warn(dn->fullname);
498 if (opt & OPT_i) /* show inode# */
499 column += printf("%7llu ", (long long) dn->dn_ino);
500 //TODO: -h should affect -s too:
501 if (opt & OPT_s) /* show allocated blocks */
502 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1));
504 /* long listing: show mode */
505 column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode));
506 /* long listing: show number of links */
507 column += printf("%4lu ", (long) dn->dn_nlink);
508 /* long listing: show user/group */
511 column += printf("%-8u ", (int) dn->dn_gid);
513 column += printf("%-8u %-8u ",
517 #if ENABLE_FEATURE_LS_USERNAME
520 column += printf("%-8.8s ",
521 get_cached_groupname(dn->dn_gid));
523 column += printf("%-8.8s %-8.8s ",
524 get_cached_username(dn->dn_uid),
525 get_cached_groupname(dn->dn_gid));
532 column += printf("%-32s ", dn->sid ? dn->sid : "?");
537 /* long listing: show size */
538 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
539 column += printf("%4u, %3u ",
544 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
545 /* print size, show one fractional, use suffixes */
546 make_human_readable_str(dn->dn_size, 1, 0)
549 column += printf("%9"OFF_FMT"u ", dn->dn_size);
552 #if ENABLE_FEATURE_LS_TIMESTAMPS
553 /* long listing: show {m,c,a}time */
554 if (opt & OPT_full_time) { /* --full-time */
555 /* coreutils 8.4 ls --full-time prints:
556 * 2009-07-13 17:49:27.000000000 +0200
557 * we don't show fractional seconds.
559 char buf[sizeof("YYYY-mm-dd HH:MM:SS TIMEZONE")];
560 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %z",
561 localtime(&dn->dn_time));
562 column += printf("%s ", buf);
563 } else { /* ordinary time format */
564 /* G.current_time_t is ~== time(NULL) */
565 char *filetime = ctime(&dn->dn_time);
566 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
567 time_t age = G.current_time_t - dn->dn_time;
568 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
569 /* less than 6 months old */
570 /* "mmm dd hh:mm " */
571 printf("%.12s ", filetime + 4);
574 /* "mmm dd yyyyy " after year 9999 :) */
575 strchr(filetime + 20, '\n')[0] = ' ';
576 printf("%.7s%6s", filetime + 4, filetime + 20);
583 #if ENABLE_FEATURE_LS_COLOR
585 mode_t mode = dn->dn_mode_lstat;
587 if (lstat(dn->fullname, &statbuf) == 0)
588 mode = statbuf.st_mode;
589 printf("\033[%u;%um", bold(mode), fgcolor(mode));
592 column += print_name(dn->name);
599 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
600 if ((opt & (OPT_F|OPT_p))
603 mode_t mode = dn->dn_mode_stat;
605 if (stat(dn->fullname, &statbuf) == 0)
606 mode = statbuf.st_mode;
607 # if ENABLE_FEATURE_LS_FILETYPES
608 append = append_char(mode);
610 # if ENABLE_FEATURE_LS_COLOR
612 printf("\033[%u;%um", bold(mode), fgcolor(mode));
617 column += print_name(lpath) + 4;
623 #if ENABLE_FEATURE_LS_FILETYPES
624 if (opt & (OPT_F|OPT_p)) {
635 static void display_files(struct dnode **dn, unsigned nfiles)
637 unsigned i, ncols, nrows, row, nc;
640 unsigned column_width = 0; /* used only by coulmnal output */
642 if (option_mask32 & (OPT_l|OPT_1)) {
645 /* find the longest file name, use that as the column width */
646 for (i = 0; dn[i]; i++) {
647 int len = calc_name_len(dn[i]->name);
648 if (column_width < len)
652 + ((option_mask32 & OPT_Z) ? 33 : 0) /* context width */
653 + ((option_mask32 & OPT_i) ? 8 : 0) /* inode# width */
654 + ((option_mask32 & OPT_s) ? 5 : 0) /* "alloc block" width */
656 ncols = (unsigned)G_terminal_width / column_width;
660 nrows = nfiles / ncols;
661 if (nrows * ncols < nfiles)
662 nrows++; /* round up fractionals */
670 for (row = 0; row < nrows; row++) {
671 for (nc = 0; nc < ncols; nc++) {
672 /* reach into the array based on the column and row */
673 if (option_mask32 & OPT_x)
674 i = (row * ncols) + nc; /* display across row */
676 i = (nc * nrows) + row; /* display by column */
680 printf("%*s", nexttab, "");
683 nexttab = column + column_width;
684 column += display_single(dn[i]);
693 /*** Dir scanning code ***/
695 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
700 cur = xzalloc(sizeof(*cur));
701 cur->fullname = fullname;
704 if ((option_mask32 & OPT_L) || force_follow) {
706 if (option_mask32 & OPT_Z) {
707 getfilecon(fullname, &cur->sid);
710 if (stat(fullname, &statbuf)) {
711 bb_simple_perror_msg(fullname);
712 G.exit_code = EXIT_FAILURE;
716 cur->dn_mode_stat = statbuf.st_mode;
719 if (option_mask32 & OPT_Z) {
720 lgetfilecon(fullname, &cur->sid);
723 if (lstat(fullname, &statbuf)) {
724 bb_simple_perror_msg(fullname);
725 G.exit_code = EXIT_FAILURE;
729 cur->dn_mode_lstat = statbuf.st_mode;
732 /* cur->dstat = statbuf: */
733 cur->dn_mode = statbuf.st_mode ;
734 cur->dn_size = statbuf.st_size ;
735 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
736 cur->dn_time = statbuf.st_mtime ;
737 if (option_mask32 & OPT_u)
738 cur->dn_time = statbuf.st_atime;
739 if (option_mask32 & OPT_c)
740 cur->dn_time = statbuf.st_ctime;
742 cur->dn_ino = statbuf.st_ino ;
743 cur->dn_blocks = statbuf.st_blocks;
744 cur->dn_nlink = statbuf.st_nlink ;
745 cur->dn_uid = statbuf.st_uid ;
746 cur->dn_gid = statbuf.st_gid ;
747 cur->dn_rdev_maj = major(statbuf.st_rdev);
748 cur->dn_rdev_min = minor(statbuf.st_rdev);
753 static unsigned count_dirs(struct dnode **dn, int which)
765 if (!S_ISDIR((*dn)->dn_mode))
769 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
770 /* or if it's not . or .. */
772 || (name[1] && (name[1] != '.' || name[2]))
777 return which != SPLIT_FILE ? dirs : all - dirs;
780 /* get memory to hold an array of pointers */
781 static struct dnode **dnalloc(unsigned num)
786 num++; /* so that we have terminating NULL */
787 return xzalloc(num * sizeof(struct dnode *));
790 #if ENABLE_FEATURE_LS_RECURSIVE
791 static void dfree(struct dnode **dnp)
798 for (i = 0; dnp[i]; i++) {
799 struct dnode *cur = dnp[i];
800 if (cur->fname_allocated)
801 free((char*)cur->fullname);
807 #define dfree(...) ((void)0)
810 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
811 static struct dnode **splitdnarray(struct dnode **dn, int which)
819 /* count how many dirs or files there are */
820 dncnt = count_dirs(dn, which);
822 /* allocate a file array and a dir array */
823 dnp = dnalloc(dncnt);
825 /* copy the entrys into the file or dir array */
826 for (d = 0; *dn; dn++) {
827 if (S_ISDIR((*dn)->dn_mode)) {
830 if (which == SPLIT_FILE)
834 if ((which & SPLIT_DIR) /* any dir... */
835 /* ... or not . or .. */
837 || (name[1] && (name[1] != '.' || name[2]))
842 if (which == SPLIT_FILE) {
849 #if ENABLE_FEATURE_LS_SORTFILES
850 static int sortcmp(const void *a, const void *b)
852 struct dnode *d1 = *(struct dnode **)a;
853 struct dnode *d2 = *(struct dnode **)b;
854 unsigned opt = option_mask32;
857 dif = 0; /* assume sort by name */
858 // TODO: use pre-initialized function pointer
859 // instead of branch forest
860 if (opt & OPT_dirs_first) {
861 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
863 goto maybe_invert_and_ret;
866 if (opt & OPT_S) { /* sort by size */
867 dif = (d2->dn_size - d1->dn_size);
869 if (opt & OPT_t) { /* sort by time */
870 dif = (d2->dn_time - d1->dn_time);
872 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
873 if (opt & OPT_v) { /* sort by version */
874 dif = strverscmp(d1->name, d2->name);
877 if (opt & OPT_X) { /* sort by extension */
878 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
881 /* sort by name, use as tie breaker for other sorts */
882 if (ENABLE_LOCALE_SUPPORT)
883 dif = strcoll(d1->name, d2->name);
885 dif = strcmp(d1->name, d2->name);
887 /* Make dif fit into an int */
888 if (sizeof(dif) > sizeof(int)) {
889 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
890 /* shift leaving only "int" worth of bits */
891 /* (this requires dif != 0, and here it is nonzero) */
892 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
895 maybe_invert_and_ret:
896 return (opt & OPT_r) ? -(int)dif : (int)dif;
899 static void dnsort(struct dnode **dn, int size)
901 qsort(dn, size, sizeof(*dn), sortcmp);
904 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
907 display_files(dn, nfiles);
910 # define dnsort(dn, size) ((void)0)
911 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
914 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
915 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
917 struct dnode *dn, *cur, **dnp;
918 struct dirent *entry;
923 dir = warn_opendir(path);
925 G.exit_code = EXIT_FAILURE;
926 return NULL; /* could not open the dir */
930 while ((entry = readdir(dir)) != NULL) {
933 /* are we going to list the file- it may be . or .. or a hidden file */
934 if (entry->d_name[0] == '.') {
935 if (!(option_mask32 & (OPT_a|OPT_A)))
936 continue; /* skip all dotfiles if no -a/-A */
937 if (!(option_mask32 & OPT_a)
938 && (!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
940 continue; /* if only -A, skip . and .. but show other dotfiles */
943 fullname = concat_path_file(path, entry->d_name);
944 cur = my_stat(fullname, bb_basename(fullname), 0);
949 cur->fname_allocated = 1;
959 /* now that we know how many files there are
960 * allocate memory for an array to hold dnode pointers
963 dnp = dnalloc(nfiles);
964 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
965 dnp[i] = dn; /* save pointer to node in array */
975 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
976 * If any of the -l, -n, -s options is specified, each list
977 * of files within the directory shall be preceded by a
978 * status line indicating the number of file system blocks
979 * occupied by files in the directory in 512-byte units if
980 * the -k option is not specified, or 1024-byte units if the
981 * -k option is specified, rounded up to the next integral
984 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
985 static off_t calculate_blocks(struct dnode **dn)
990 /* st_blocks is in 512 byte blocks */
991 blocks += (*dn)->dn_blocks;
996 /* Even though standard says use 512 byte blocks, coreutils use 1k */
997 /* Actually, we round up by calculating (blocks + 1) / 2,
998 * "+ 1" was done when we initialized blocks to 1 */
1003 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1006 struct dnode **subdnp;
1009 if (G.show_dirname || (option_mask32 & OPT_R)) {
1013 printf("%s:\n", (*dn)->fullname);
1015 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1017 if (option_mask32 & (OPT_s|OPT_l)) {
1018 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1022 /* list all files at this level */
1023 sort_and_display_files(subdnp, nfiles);
1025 if (ENABLE_FEATURE_LS_RECURSIVE
1026 && (option_mask32 & OPT_R)
1030 /* recursive - list the sub-dirs */
1031 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1032 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1034 dnsort(dnd, dndirs);
1035 scan_and_display_dirs_recur(dnd, 0);
1036 /* free the array of dnode pointers to the dirs */
1040 /* free the dnodes and the fullname mem */
1047 int ls_main(int argc UNUSED_PARAM, char **argv)
1048 { /* ^^^^^^^^^^^^^^^^^ note: if FTPD, argc can be wrong, see ftpd.c */
1059 #if ENABLE_FEATURE_LS_COLOR
1060 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1062 * # ls --color=BOGUS
1063 * ls: invalid argument 'BOGUS' for '--color'
1064 * Valid arguments are:
1065 * 'always', 'yes', 'force'
1066 * 'never', 'no', 'none'
1067 * 'auto', 'tty', 'if-tty'
1068 * (and substrings: "--color=alwa" work too)
1070 static const char ls_longopts[] ALIGN1 =
1071 "full-time\0" No_argument "\xff"
1072 "group-directories-first\0" No_argument "\xfe"
1073 "color\0" Optional_argument "\xfd"
1075 static const char color_str[] ALIGN1 =
1076 "always\0""yes\0""force\0"
1077 "auto\0""tty\0""if-tty\0";
1078 /* need to initialize since --color has _an optional_ argument */
1079 const char *color_opt = color_str; /* "always" */
1086 #if ENABLE_FEATURE_LS_WIDTH
1087 /* obtain the terminal width */
1088 G_terminal_width = get_terminal_width(STDIN_FILENO);
1089 /* go one less... */
1093 /* process options */
1094 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1096 /* -n and -g imply -l */
1098 /* --full-time implies -l */
1099 IF_FEATURE_LS_TIMESTAMPS(IF_LONG_OPTS(":\xff""l"))
1100 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1101 * in some pairs of opts, only last one takes effect:
1103 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1104 // ":m-l:l-m" - we don't have -m
1105 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1106 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1107 ":C-1:1-C" /* bycols/oneline */
1108 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1109 IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */
1111 IF_FEATURE_LS_WIDTH(":w+");
1112 opt = getopt32(argv, ls_options
1113 IF_FEATURE_LS_WIDTH(, /*-T*/ NULL, /*-w*/ &G_terminal_width)
1114 IF_FEATURE_LS_COLOR(, &color_opt)
1116 #if 0 /* option bits debug */
1117 bb_error_msg("opt:0x%08x l:%x H:%x color:%x dirs:%x", opt, OPT_l, OPT_H, OPT_color, OPT_dirs_first);
1118 if (opt & OPT_c ) bb_error_msg("-c");
1119 if (opt & OPT_l ) bb_error_msg("-l");
1120 if (opt & OPT_H ) bb_error_msg("-H");
1121 if (opt & OPT_color ) bb_error_msg("--color");
1122 if (opt & OPT_dirs_first) bb_error_msg("--group-directories-first");
1123 if (opt & OPT_full_time ) bb_error_msg("--full-time");
1129 if (!is_selinux_enabled())
1130 option_mask32 &= ~OPT_Z;
1134 #if ENABLE_FEATURE_LS_COLOR
1135 /* set G_show_color = 1/0 */
1136 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1137 char *p = getenv("LS_COLORS");
1138 /* LS_COLORS is unset, or (not empty && not "none") ? */
1139 if (!p || (p[0] && strcmp(p, "none") != 0))
1142 if (opt & OPT_color) {
1143 if (color_opt[0] == 'n')
1145 else switch (index_in_substrings(color_str, color_opt)) {
1149 if (isatty(STDOUT_FILENO)) {
1159 /* sort out which command line options take precedence */
1160 if (ENABLE_FEATURE_LS_RECURSIVE && (opt & OPT_d))
1161 option_mask32 &= ~OPT_R; /* no recurse if listing only dir */
1162 if (!(opt & OPT_l)) { /* not -l? */
1163 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1164 /* when to sort by time? -t[cu] sorts by time even with -l */
1165 /* (this is achieved by opt_flags[] element for -t) */
1166 /* without -l, bare -c or -u enable sort too */
1167 /* (with -l, bare -c or -u just select which time to show) */
1168 if (opt & (OPT_c|OPT_u)) {
1169 option_mask32 |= OPT_t;
1174 /* choose a display format if one was not already specified by an option */
1175 if (!(option_mask32 & (OPT_l|OPT_1|OPT_x|OPT_C)))
1176 option_mask32 |= (isatty(STDOUT_FILENO) ? OPT_C : OPT_1);
1178 if (ENABLE_FTPD && applet_name[0] == 'f') {
1179 /* ftpd secret backdoor. dirs first are much nicer */
1180 option_mask32 |= OPT_dirs_first;
1185 *--argv = (char*)".";
1188 G.show_dirname = 1; /* 2 or more items? label directories */
1190 /* stuff the command line file names into a dnode array */
1194 cur = my_stat(*argv, *argv,
1195 /* follow links on command line unless -l, -s or -F: */
1196 !(option_mask32 & (OPT_l|OPT_s|OPT_F))
1198 || (option_mask32 & OPT_H)
1199 /* ... or if -L, but my_stat always follows links if -L */
1204 /*cur->fname_allocated = 0; - already is */
1210 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1214 /* now that we know how many files there are
1215 * allocate memory for an array to hold dnode pointers
1217 dnp = dnalloc(nfiles);
1218 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1219 dnp[i] = dn; /* save pointer to node in array */
1225 if (option_mask32 & OPT_d) {
1226 sort_and_display_files(dnp, nfiles);
1228 dnd = splitdnarray(dnp, SPLIT_DIR);
1229 dnf = splitdnarray(dnp, SPLIT_FILE);
1230 dndirs = count_dirs(dnp, SPLIT_DIR);
1231 dnfiles = nfiles - dndirs;
1233 sort_and_display_files(dnf, dnfiles);
1234 if (ENABLE_FEATURE_CLEAN_UP)
1238 dnsort(dnd, dndirs);
1239 scan_and_display_dirs_recur(dnd, dnfiles == 0);
1240 if (ENABLE_FEATURE_CLEAN_UP)
1245 if (ENABLE_FEATURE_CLEAN_UP)