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 names starting 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 names, not 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 directory names"
126 //usage: "\n -F Append indicator (one of */=@|) to names"
128 //usage: "\n -l Long 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/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}]"
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 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_string2(&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_string2(&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 if (opt
& OPT_s
) { /* show allocated blocks */
503 column
+= printf("%"HUMAN_READABLE_MAX_WIDTH_STR
"s ",
504 /* print size, show one fractional, use suffixes */
505 make_human_readable_str((off_t
)dn
->dn_blocks
<< 9, 1, 0)
508 column
+= printf("%6"OFF_FMT
"u ", (off_t
)(dn
->dn_blocks
>> 1));
512 /* long listing: show mode */
514 column
+= printf("%-10s ", bb_mode_string(modestr
, dn
->dn_mode
));
515 /* long listing: show number of links */
516 column
+= printf("%4lu ", (long) dn
->dn_nlink
);
517 /* long listing: show user/group */
520 column
+= printf("%-8u ", (int) dn
->dn_gid
);
522 column
+= printf("%-8u %-8u ",
526 #if ENABLE_FEATURE_LS_USERNAME
529 column
+= printf("%-8s ",
530 get_cached_groupname(dn
->dn_gid
));
532 column
+= printf("%-8s %-8s ",
533 get_cached_username(dn
->dn_uid
),
534 get_cached_groupname(dn
->dn_gid
));
541 column
+= printf("%-32s ", dn
->sid
? dn
->sid
: "?");
546 /* long listing: show size */
547 if (S_ISBLK(dn
->dn_mode
) || S_ISCHR(dn
->dn_mode
)) {
548 column
+= printf("%4u, %3u ",
553 column
+= printf("%"HUMAN_READABLE_MAX_WIDTH_STR
"s ",
554 /* print size, show one fractional, use suffixes */
555 make_human_readable_str(dn
->dn_size
, 1, 0)
558 column
+= printf("%9"OFF_FMT
"u ", dn
->dn_size
);
561 #if ENABLE_FEATURE_LS_TIMESTAMPS
562 /* long listing: show {m,c,a}time */
563 if (opt
& OPT_full_time
) { /* --full-time */
564 /* coreutils 8.4 ls --full-time prints:
565 * 2009-07-13 17:49:27.000000000 +0200
566 * we don't show fractional seconds.
568 char buf
[sizeof("YYYY-mm-dd HH:MM:SS TIMEZONE")];
569 strftime(buf
, sizeof(buf
), "%Y-%m-%d %H:%M:%S %z",
570 localtime(&dn
->dn_time
));
571 column
+= printf("%s ", buf
);
572 } else { /* ordinary time format */
573 /* G.current_time_t is ~== time(NULL) */
574 char *filetime
= ctime(&dn
->dn_time
);
575 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
576 time_t age
= G
.current_time_t
- dn
->dn_time
;
577 if (age
< 3600L * 24 * 365 / 2 && age
> -15 * 60) {
578 /* less than 6 months old */
579 /* "mmm dd hh:mm " */
580 printf("%.12s ", filetime
+ 4);
583 /* "mmm dd yyyyy " after year 9999 :) */
584 strchr(filetime
+ 20, '\n')[0] = ' ';
585 printf("%.7s%6s", filetime
+ 4, filetime
+ 20);
592 #if ENABLE_FEATURE_LS_COLOR
594 mode_t mode
= dn
->dn_mode_lstat
;
596 if (lstat(dn
->fullname
, &statbuf
) == 0)
597 mode
= statbuf
.st_mode
;
598 printf(ESC
"[%u;%um", bold(mode
), fgcolor(mode
));
601 column
+= print_name(dn
->name
);
608 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
609 if ((opt
& (OPT_F
|OPT_p
))
612 mode_t mode
= dn
->dn_mode_stat
;
614 if (stat(dn
->fullname
, &statbuf
) == 0)
615 mode
= statbuf
.st_mode
;
616 # if ENABLE_FEATURE_LS_FILETYPES
617 append
= append_char(mode
);
619 # if ENABLE_FEATURE_LS_COLOR
621 printf(ESC
"[%u;%um", bold(mode
), fgcolor(mode
));
626 column
+= print_name(lpath
) + 4;
632 #if ENABLE_FEATURE_LS_FILETYPES
633 if (opt
& (OPT_F
|OPT_p
)) {
644 static void display_files(struct dnode
**dn
, unsigned nfiles
)
646 unsigned i
, ncols
, nrows
, row
, nc
;
649 unsigned column_width
= 0; /* used only by coulmnal output */
651 if (option_mask32
& (OPT_l
|OPT_1
)) {
654 /* find the longest file name, use that as the column width */
655 for (i
= 0; dn
[i
]; i
++) {
656 int len
= calc_name_len(dn
[i
]->name
);
657 if (column_width
< len
)
661 + ((option_mask32
& OPT_Z
) ? 33 : 0) /* context width */
662 + ((option_mask32
& OPT_i
) ? 8 : 0) /* inode# width */
663 + ((option_mask32
& OPT_s
) ? 5 : 0) /* "alloc block" width */
665 ncols
= (unsigned)G_terminal_width
/ column_width
;
669 nrows
= nfiles
/ ncols
;
670 if (nrows
* ncols
< nfiles
)
671 nrows
++; /* round up fractionals */
679 for (row
= 0; row
< nrows
; row
++) {
680 for (nc
= 0; nc
< ncols
; nc
++) {
681 /* reach into the array based on the column and row */
682 if (option_mask32
& OPT_x
)
683 i
= (row
* ncols
) + nc
; /* display across row */
685 i
= (nc
* nrows
) + row
; /* display by column */
689 printf("%*s", nexttab
, "");
692 nexttab
= column
+ column_width
;
693 column
+= display_single(dn
[i
]);
702 /*** Dir scanning code ***/
704 static struct dnode
*my_stat(const char *fullname
, const char *name
, int force_follow
)
709 cur
= xzalloc(sizeof(*cur
));
710 cur
->fullname
= fullname
;
713 if ((option_mask32
& OPT_L
) || force_follow
) {
715 if (option_mask32
& OPT_Z
) {
716 getfilecon(fullname
, &cur
->sid
);
719 if (stat(fullname
, &statbuf
)) {
720 bb_simple_perror_msg(fullname
);
721 G
.exit_code
= EXIT_FAILURE
;
725 cur
->dn_mode_stat
= statbuf
.st_mode
;
728 if (option_mask32
& OPT_Z
) {
729 lgetfilecon(fullname
, &cur
->sid
);
732 if (lstat(fullname
, &statbuf
)) {
733 bb_simple_perror_msg(fullname
);
734 G
.exit_code
= EXIT_FAILURE
;
738 cur
->dn_mode_lstat
= statbuf
.st_mode
;
741 /* cur->dstat = statbuf: */
742 cur
->dn_mode
= statbuf
.st_mode
;
743 cur
->dn_size
= statbuf
.st_size
;
744 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
745 cur
->dn_time
= statbuf
.st_mtime
;
746 if (option_mask32
& OPT_u
)
747 cur
->dn_time
= statbuf
.st_atime
;
748 if (option_mask32
& OPT_c
)
749 cur
->dn_time
= statbuf
.st_ctime
;
751 cur
->dn_ino
= statbuf
.st_ino
;
752 cur
->dn_blocks
= statbuf
.st_blocks
;
753 cur
->dn_nlink
= statbuf
.st_nlink
;
754 cur
->dn_uid
= statbuf
.st_uid
;
755 cur
->dn_gid
= statbuf
.st_gid
;
756 cur
->dn_rdev_maj
= major(statbuf
.st_rdev
);
757 cur
->dn_rdev_min
= minor(statbuf
.st_rdev
);
762 static unsigned count_dirs(struct dnode
**dn
, int which
)
774 if (!S_ISDIR((*dn
)->dn_mode
))
778 if (which
!= SPLIT_SUBDIR
/* if not requested to skip . / .. */
779 /* or if it's not . or .. */
781 || (name
[1] && (name
[1] != '.' || name
[2]))
786 return which
!= SPLIT_FILE
? dirs
: all
- dirs
;
789 /* get memory to hold an array of pointers */
790 static struct dnode
**dnalloc(unsigned num
)
795 num
++; /* so that we have terminating NULL */
796 return xzalloc(num
* sizeof(struct dnode
*));
799 #if ENABLE_FEATURE_LS_RECURSIVE
800 static void dfree(struct dnode
**dnp
)
807 for (i
= 0; dnp
[i
]; i
++) {
808 struct dnode
*cur
= dnp
[i
];
809 if (cur
->fname_allocated
)
810 free((char*)cur
->fullname
);
816 #define dfree(...) ((void)0)
819 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
820 static struct dnode
**splitdnarray(struct dnode
**dn
, int which
)
828 /* count how many dirs or files there are */
829 dncnt
= count_dirs(dn
, which
);
831 /* allocate a file array and a dir array */
832 dnp
= dnalloc(dncnt
);
834 /* copy the entrys into the file or dir array */
835 for (d
= 0; *dn
; dn
++) {
836 if (S_ISDIR((*dn
)->dn_mode
)) {
839 if (which
== SPLIT_FILE
)
843 if ((which
& SPLIT_DIR
) /* any dir... */
844 /* ... or not . or .. */
846 || (name
[1] && (name
[1] != '.' || name
[2]))
851 if (which
== SPLIT_FILE
) {
858 #if ENABLE_FEATURE_LS_SORTFILES
859 static int sortcmp(const void *a
, const void *b
)
861 struct dnode
*d1
= *(struct dnode
**)a
;
862 struct dnode
*d2
= *(struct dnode
**)b
;
863 unsigned opt
= option_mask32
;
866 dif
= 0; /* assume sort by name */
867 // TODO: use pre-initialized function pointer
868 // instead of branch forest
869 if (opt
& OPT_dirs_first
) {
870 dif
= S_ISDIR(d2
->dn_mode
) - S_ISDIR(d1
->dn_mode
);
872 goto maybe_invert_and_ret
;
875 if (opt
& OPT_S
) { /* sort by size */
876 dif
= (d2
->dn_size
- d1
->dn_size
);
878 if (opt
& OPT_t
) { /* sort by time */
879 dif
= (d2
->dn_time
- d1
->dn_time
);
881 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
882 if (opt
& OPT_v
) { /* sort by version */
883 dif
= strverscmp(d1
->name
, d2
->name
);
886 if (opt
& OPT_X
) { /* sort by extension */
887 dif
= strcmp(strchrnul(d1
->name
, '.'), strchrnul(d2
->name
, '.'));
890 /* sort by name, use as tie breaker for other sorts */
891 if (ENABLE_LOCALE_SUPPORT
)
892 dif
= strcoll(d1
->name
, d2
->name
);
894 dif
= strcmp(d1
->name
, d2
->name
);
896 /* Make dif fit into an int */
897 if (sizeof(dif
) > sizeof(int)) {
898 enum { BITS_TO_SHIFT
= 8 * (sizeof(dif
) - sizeof(int)) };
899 /* shift leaving only "int" worth of bits */
900 /* (this requires dif != 0, and here it is nonzero) */
901 dif
= 1 | (int)((uoff_t
)dif
>> BITS_TO_SHIFT
);
904 maybe_invert_and_ret
:
905 return (opt
& OPT_r
) ? -(int)dif
: (int)dif
;
908 static void dnsort(struct dnode
**dn
, int size
)
910 qsort(dn
, size
, sizeof(*dn
), sortcmp
);
913 static void sort_and_display_files(struct dnode
**dn
, unsigned nfiles
)
916 display_files(dn
, nfiles
);
919 # define dnsort(dn, size) ((void)0)
920 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
923 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
924 static struct dnode
**scan_one_dir(const char *path
, unsigned *nfiles_p
)
926 struct dnode
*dn
, *cur
, **dnp
;
927 struct dirent
*entry
;
932 dir
= warn_opendir(path
);
934 G
.exit_code
= EXIT_FAILURE
;
935 return NULL
; /* could not open the dir */
939 while ((entry
= readdir(dir
)) != NULL
) {
942 /* are we going to list the file- it may be . or .. or a hidden file */
943 if (entry
->d_name
[0] == '.') {
944 if (!(option_mask32
& (OPT_a
|OPT_A
)))
945 continue; /* skip all dotfiles if no -a/-A */
946 if (!(option_mask32
& OPT_a
)
947 && (!entry
->d_name
[1] || (entry
->d_name
[1] == '.' && !entry
->d_name
[2]))
949 continue; /* if only -A, skip . and .. but show other dotfiles */
952 fullname
= concat_path_file(path
, entry
->d_name
);
953 cur
= my_stat(fullname
, bb_basename(fullname
), 0);
958 cur
->fname_allocated
= 1;
968 /* now that we know how many files there are
969 * allocate memory for an array to hold dnode pointers
972 dnp
= dnalloc(nfiles
);
973 for (i
= 0; /* i < nfiles - detected via !dn below */; i
++) {
974 dnp
[i
] = dn
; /* save pointer to node in array */
984 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
985 * If any of the -l, -n, -s options is specified, each list
986 * of files within the directory shall be preceded by a
987 * status line indicating the number of file system blocks
988 * occupied by files in the directory in 512-byte units if
989 * the -k option is not specified, or 1024-byte units if the
990 * -k option is specified, rounded up to the next integral
993 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
994 static off_t
calculate_blocks(struct dnode
**dn
)
999 /* st_blocks is in 512 byte blocks */
1000 blocks
+= (*dn
)->dn_blocks
;
1005 /* Even though standard says use 512 byte blocks, coreutils use 1k */
1006 /* Actually, we round up by calculating (blocks + 1) / 2,
1007 * "+ 1" was done when we initialized blocks to 1 */
1012 static void scan_and_display_dirs_recur(struct dnode
**dn
, int first
)
1015 struct dnode
**subdnp
;
1018 if (G
.show_dirname
|| (option_mask32
& OPT_R
)) {
1022 printf("%s:\n", (*dn
)->fullname
);
1024 subdnp
= scan_one_dir((*dn
)->fullname
, &nfiles
);
1026 if (option_mask32
& (OPT_s
|OPT_l
)) {
1027 if (option_mask32
& OPT_h
) {
1028 printf("total %-"HUMAN_READABLE_MAX_WIDTH_STR
"s\n",
1029 /* print size, no fractions, use suffixes */
1030 make_human_readable_str(calculate_blocks(subdnp
) * 1024,
1034 printf("total %"OFF_FMT
"u\n", calculate_blocks(subdnp
));
1039 /* list all files at this level */
1040 sort_and_display_files(subdnp
, nfiles
);
1042 if (ENABLE_FEATURE_LS_RECURSIVE
1043 && (option_mask32
& OPT_R
)
1047 /* recursive - list the sub-dirs */
1048 dnd
= splitdnarray(subdnp
, SPLIT_SUBDIR
);
1049 dndirs
= count_dirs(subdnp
, SPLIT_SUBDIR
);
1051 dnsort(dnd
, dndirs
);
1052 scan_and_display_dirs_recur(dnd
, 0);
1053 /* free the array of dnode pointers to the dirs */
1057 /* free the dnodes and the fullname mem */
1064 int ls_main(int argc UNUSED_PARAM
, char **argv
)
1065 { /* ^^^^^^^^^^^^^^^^^ note: if FTPD, argc can be wrong, see ftpd.c */
1076 #if ENABLE_FEATURE_LS_COLOR
1077 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1079 * # ls --color=BOGUS
1080 * ls: invalid argument 'BOGUS' for '--color'
1081 * Valid arguments are:
1082 * 'always', 'yes', 'force'
1083 * 'never', 'no', 'none'
1084 * 'auto', 'tty', 'if-tty'
1085 * (and substrings: "--color=alwa" work too)
1087 static const char color_str
[] ALIGN1
=
1088 "always\0""yes\0""force\0"
1089 "auto\0""tty\0""if-tty\0";
1090 /* need to initialize since --color has _an optional_ argument */
1091 const char *color_opt
= color_str
; /* "always" */
1093 #if ENABLE_LONG_OPTS
1094 static const char ls_longopts
[] ALIGN1
=
1095 "full-time\0" No_argument
"\xff"
1096 "group-directories-first\0" No_argument
"\xfe"
1097 IF_FEATURE_LS_COLOR("color\0" Optional_argument
"\xfd")
1105 #if ENABLE_FEATURE_LS_WIDTH
1106 /* obtain the terminal width */
1107 G_terminal_width
= get_terminal_width(STDIN_FILENO
);
1108 /* go one less... */
1112 /* process options */
1113 opt
= getopt32long(argv
, "^"
1116 /* -n and -g imply -l */
1118 /* --full-time implies -l */
1119 IF_FEATURE_LS_TIMESTAMPS(IF_LONG_OPTS(":\xff""l"))
1120 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1121 * in some pairs of opts, only last one takes effect:
1123 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1124 // ":m-l:l-m" - we don't have -m
1125 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1126 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1127 ":C-1:1-C" /* bycols/oneline */
1128 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1129 IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */
1131 IF_FEATURE_LS_WIDTH(":w+")
1133 IF_FEATURE_LS_WIDTH(, /*-T*/ NULL
, /*-w*/ &G_terminal_width
)
1134 IF_FEATURE_LS_COLOR(, &color_opt
)
1136 #if 0 /* option bits debug */
1137 bb_error_msg("opt:0x%08x l:%x H:%x color:%x dirs:%x", opt
, OPT_l
, OPT_H
, OPT_color
, OPT_dirs_first
);
1138 if (opt
& OPT_c
) bb_error_msg("-c");
1139 if (opt
& OPT_l
) bb_error_msg("-l");
1140 if (opt
& OPT_H
) bb_error_msg("-H");
1141 if (opt
& OPT_color
) bb_error_msg("--color");
1142 if (opt
& OPT_dirs_first
) bb_error_msg("--group-directories-first");
1143 if (opt
& OPT_full_time
) bb_error_msg("--full-time");
1149 if (!is_selinux_enabled())
1150 option_mask32
&= ~OPT_Z
;
1154 #if ENABLE_FEATURE_LS_COLOR
1155 /* set G_show_color = 1/0 */
1156 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT
&& !is_TERM_dumb()) {
1157 char *p
= getenv("LS_COLORS");
1158 /* LS_COLORS is unset, or (not empty && not "none") ? */
1159 if (!p
|| (p
[0] && strcmp(p
, "none") != 0)) {
1160 if (isatty(STDOUT_FILENO
)) {
1161 /* check isatty() last because it's expensive (syscall) */
1166 if (opt
& OPT_color
) {
1167 if (color_opt
[0] == 'n')
1169 else switch (index_in_substrings(color_str
, color_opt
)) {
1173 if (!is_TERM_dumb() && isatty(STDOUT_FILENO
)) {
1183 /* sort out which command line options take precedence */
1184 if (ENABLE_FEATURE_LS_RECURSIVE
&& (opt
& OPT_d
))
1185 option_mask32
&= ~OPT_R
; /* no recurse if listing only dir */
1186 if (!(opt
& OPT_l
)) { /* not -l? */
1187 if (ENABLE_FEATURE_LS_TIMESTAMPS
&& ENABLE_FEATURE_LS_SORTFILES
) {
1188 /* when to sort by time? -t[cu] sorts by time even with -l */
1189 /* (this is achieved by opt_flags[] element for -t) */
1190 /* without -l, bare -c or -u enable sort too */
1191 /* (with -l, bare -c or -u just select which time to show) */
1192 if (opt
& (OPT_c
|OPT_u
)) {
1193 option_mask32
|= OPT_t
;
1198 /* choose a display format if one was not already specified by an option */
1199 if (!(option_mask32
& (OPT_l
|OPT_1
|OPT_x
|OPT_C
)))
1200 option_mask32
|= (isatty(STDOUT_FILENO
) ? OPT_C
: OPT_1
);
1202 if (ENABLE_FTPD
&& applet_name
[0] == 'f') {
1203 /* ftpd secret backdoor. dirs first are much nicer */
1204 option_mask32
|= OPT_dirs_first
;
1209 *--argv
= (char*)".";
1212 G
.show_dirname
= 1; /* 2 or more items? label directories */
1214 /* stuff the command line file names into a dnode array */
1218 cur
= my_stat(*argv
, *argv
,
1219 /* follow links on command line unless -l, -i, -s or -F: */
1220 !(option_mask32
& (OPT_l
|OPT_i
|OPT_s
|OPT_F
))
1222 || (option_mask32
& OPT_H
)
1223 /* ... or if -L, but my_stat always follows links if -L */
1228 /*cur->fname_allocated = 0; - already is */
1234 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1238 /* now that we know how many files there are
1239 * allocate memory for an array to hold dnode pointers
1241 dnp
= dnalloc(nfiles
);
1242 for (i
= 0; /* i < nfiles - detected via !dn below */; i
++) {
1243 dnp
[i
] = dn
; /* save pointer to node in array */
1249 if (option_mask32
& OPT_d
) {
1250 sort_and_display_files(dnp
, nfiles
);
1252 dnd
= splitdnarray(dnp
, SPLIT_DIR
);
1253 dnf
= splitdnarray(dnp
, SPLIT_FILE
);
1254 dndirs
= count_dirs(dnp
, SPLIT_DIR
);
1255 dnfiles
= nfiles
- dndirs
;
1257 sort_and_display_files(dnf
, dnfiles
);
1258 if (ENABLE_FEATURE_CLEAN_UP
)
1262 dnsort(dnd
, dndirs
);
1263 scan_and_display_dirs_recur(dnd
, dnfiles
== 0);
1264 if (ENABLE_FEATURE_CLEAN_UP
)
1269 if (ENABLE_FEATURE_CLEAN_UP
)