hexdump: accept hex numbers in -n, closes 16195
[busybox-git.git] / coreutils / ls.c
blobcc809b7979566ee6f46ffaaedb28abc939a178b5
1 /* vi: set sw=4 ts=4: */
2 /*
3 * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
5 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
6 */
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
15 * it more portable.
17 * KNOWN BUGS:
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
24 * PORTABILITY:
25 * 1. requires lstat (BSD) - how do you do it without?
27 * [2009-03]
28 * ls sorts listing now, and supports almost all options.
30 //config:config LS
31 //config: bool "ls (14 kb)"
32 //config: default y
33 //config: help
34 //config: ls is used to list the contents of directories.
35 //config:
36 //config:config FEATURE_LS_FILETYPES
37 //config: bool "Enable filetyping options (-p and -F)"
38 //config: default y
39 //config: depends on LS
40 //config:
41 //config:config FEATURE_LS_FOLLOWLINKS
42 //config: bool "Enable symlinks dereferencing (-L)"
43 //config: default y
44 //config: depends on LS
45 //config:
46 //config:config FEATURE_LS_RECURSIVE
47 //config: bool "Enable recursion (-R)"
48 //config: default y
49 //config: depends on LS
50 //config:
51 //config:config FEATURE_LS_WIDTH
52 //config: bool "Enable -w WIDTH and window size autodetection"
53 //config: default y
54 //config: depends on LS
55 //config:
56 //config:config FEATURE_LS_SORTFILES
57 //config: bool "Sort the file names"
58 //config: default y
59 //config: depends on LS
60 //config: help
61 //config: Allow ls to sort file names alphabetically.
62 //config:
63 //config:config FEATURE_LS_TIMESTAMPS
64 //config: bool "Show file timestamps"
65 //config: default y
66 //config: depends on LS
67 //config: help
68 //config: Allow ls to display timestamps for files.
69 //config:
70 //config:config FEATURE_LS_USERNAME
71 //config: bool "Show username/groupnames"
72 //config: default y
73 //config: depends on LS
74 //config: help
75 //config: Allow ls to display username/groupname for files.
76 //config:
77 //config:config FEATURE_LS_COLOR
78 //config: bool "Allow use of color to identify file types"
79 //config: default y
80 //config: depends on LS && LONG_OPTS
81 //config: help
82 //config: This enables the --color option to ls.
83 //config:
84 //config:config FEATURE_LS_COLOR_IS_DEFAULT
85 //config: bool "Produce colored ls output by default"
86 //config: default y
87 //config: depends on FEATURE_LS_COLOR
88 //config: help
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
100 //usage: "[-1AaCxd"
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"
120 //usage: )
121 //usage: IF_FEATURE_LS_RECURSIVE(
122 //usage: "\n -R Recurse"
123 //usage: )
124 //usage: IF_FEATURE_LS_FILETYPES(
125 //usage: "\n -p Append / to directory names"
126 //usage: "\n -F Append indicator (one of */=@|) to names"
127 //usage: )
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"
135 //usage: )
136 //usage: IF_FEATURE_LS_TIMESTAMPS(IF_LONG_OPTS(
137 //usage: "\n --full-time List full date/time"
138 //usage: ))
139 //usage: IF_FEATURE_HUMAN_READABLE(
140 //usage: "\n -h Human readable sizes (1K 243M 2G)"
141 //usage: )
142 //usage: IF_FEATURE_LS_SORTFILES(
143 //usage: IF_LONG_OPTS(
144 //usage: "\n --group-directories-first"
145 //usage: )
146 //usage: "\n -S Sort by size"
147 //usage: "\n -X Sort by extension"
148 //usage: "\n -v Sort by version"
149 //usage: )
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"
154 //usage: )
155 //usage: "\n -r Reverse sort order"
156 //usage: IF_SELINUX(
157 //usage: "\n -Z List security context and permission"
158 //usage: )
159 //usage: IF_FEATURE_LS_WIDTH(
160 //usage: "\n -w N Format N columns wide"
161 //usage: )
162 //usage: IF_FEATURE_LS_COLOR(
163 //usage: "\n --color[={always,never,auto}]"
164 //usage: )
166 #include "libbb.h"
167 #include "common_bufsiz.h"
168 #include "unicode.h"
171 /* This is a NOEXEC applet. Be very careful! */
174 #if ENABLE_FTPD
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(...)
186 #endif
189 enum {
190 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
192 SPLIT_FILE = 0,
193 SPLIT_DIR = 1,
194 SPLIT_SUBDIR = 2,
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 #define ls_options \
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 */ \
214 "Q" /* 1, 17 */ \
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 */
221 enum {
222 OPT_C = (1 << 0),
223 OPT_a = (1 << 1),
224 OPT_d = (1 << 2),
225 OPT_i = (1 << 3),
226 OPT_1 = (1 << 4),
227 OPT_l = (1 << 5),
228 OPT_g = (1 << 6),
229 OPT_n = (1 << 7),
230 OPT_s = (1 << 8),
231 OPT_x = (1 << 9),
232 OPT_A = (1 << 10),
233 //OPT_k = (1 << 11),
235 OPTBIT_F = 12,
236 OPTBIT_p, /* 13 */
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,
240 OPTBIT_c, /* 17 */
241 OPTBIT_t, /* 18 */
242 OPTBIT_u, /* 19 */
243 OPTBIT_S = OPTBIT_c + 3 * ENABLE_FEATURE_LS_TIMESTAMPS,
244 OPTBIT_X, /* 21 */
245 OPTBIT_r, /* 22 */
246 OPTBIT_v, /* 23 */
247 OPTBIT_L = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
248 OPTBIT_H, /* 25 */
249 OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
250 OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
251 OPTBIT_w, /* 28 */
252 OPTBIT_full_time = OPTBIT_T + 2 * ENABLE_FEATURE_LS_WIDTH,
253 OPTBIT_dirs_first,
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
282 struct dnode {
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 */
306 off_t dn_size;
307 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
308 time_t dn_time;
309 #endif
310 ino_t dn_ino;
311 blkcnt_t dn_blocks;
312 nlink_t dn_nlink;
313 uid_t dn_uid;
314 gid_t dn_gid;
315 int dn_rdev_maj;
316 int dn_rdev_min;
317 // dev_t dn_dev;
318 // blksize_t dn_blksize;
321 struct globals {
322 #if ENABLE_FEATURE_LS_COLOR
323 smallint show_color;
324 # define G_show_color (G.show_color)
325 #else
326 # define G_show_color 0
327 #endif
328 smallint exit_code;
329 smallint show_dirname;
330 #if ENABLE_FEATURE_LS_WIDTH
331 unsigned terminal_width;
332 # define G_terminal_width (G.terminal_width)
333 #else
334 # define G_terminal_width TERMINAL_WIDTH
335 #endif
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;
339 #endif
340 } FIX_ALIASING;
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);) \
348 } while (0)
350 #define ESC "\033"
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" \
375 [TYPEINDEX(mode)])
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" \
384 [TYPEINDEX(mode)])
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 ... */
392 return COLOR(mode);
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 ... */
398 return ATTR(mode);
400 #endif
402 #if ENABLE_FEATURE_LS_FILETYPES
403 static char append_char(mode_t mode)
405 if (!(option_mask32 & (OPT_F|OPT_p)))
406 return '\0';
408 if (S_ISDIR(mode))
409 return '/';
410 if (!(option_mask32 & OPT_F))
411 return '\0';
412 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
413 return '*';
414 return APPCHAR(mode);
416 #endif
418 static unsigned calc_name_len(const char *name)
420 unsigned len;
421 uni_stat_t uni_stat;
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;
431 while (*name) {
432 if (*name == '"' || *name == '\\') {
433 len++;
435 name++;
437 return len;
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)
449 unsigned len;
450 uni_stat_t uni_stat;
452 // TODO: quote tab as \t, etc, if -Q
453 name = printable_string2(&uni_stat, name);
455 if (!(option_mask32 & OPT_Q)) {
456 fputs_stdout(name);
457 return uni_stat.unicode_width;
460 len = 2 + uni_stat.unicode_width;
461 putchar('"');
462 while (*name) {
463 if (*name == '"' || *name == '\\') {
464 putchar('\\');
465 len++;
467 putchar(*name);
468 name++;
470 putchar('"');
471 return len;
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)
480 unsigned column = 0;
481 char *lpath;
482 int opt;
483 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
484 struct stat statbuf;
485 #endif
486 #if ENABLE_FEATURE_LS_FILETYPES
487 char append = append_char(dn->dn_mode);
488 #endif
490 opt = option_mask32;
492 /* Do readlink early, so that if it fails, error message
493 * does not appear *inside* the "ls -l" line */
494 lpath = NULL;
495 if (opt & OPT_l)
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 */
502 if (opt & OPT_h) {
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)
507 } else {
508 column += printf("%6"OFF_FMT"u ", (off_t)(dn->dn_blocks >> 1));
511 if (opt & OPT_l) {
512 /* long listing: show mode */
513 char modestr[12];
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 */
518 if (opt & OPT_n) {
519 if (opt & OPT_g)
520 column += printf("%-8u ", (int) dn->dn_gid);
521 else
522 column += printf("%-8u %-8u ",
523 (int) dn->dn_uid,
524 (int) dn->dn_gid);
526 #if ENABLE_FEATURE_LS_USERNAME
527 else {
528 if (opt & OPT_g) {
529 column += printf("%-8s ",
530 get_cached_groupname(dn->dn_gid));
531 } else {
532 column += printf("%-8s %-8s ",
533 get_cached_username(dn->dn_uid),
534 get_cached_groupname(dn->dn_gid));
537 #endif
538 #if ENABLE_SELINUX
540 if (opt & OPT_Z) {
541 column += printf("%-32s ", dn->sid ? dn->sid : "?");
542 freecon(dn->sid);
544 if (opt & OPT_l) {
545 #endif
546 /* long listing: show size */
547 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
548 column += printf("%4u, %3u ",
549 dn->dn_rdev_maj,
550 dn->dn_rdev_min);
551 } else {
552 if (opt & OPT_h) {
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)
557 } else {
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);
581 } else {
582 /* "mmm dd yyyy " */
583 /* "mmm dd yyyyy " after year 9999 :) */
584 strchr(filetime + 20, '\n')[0] = ' ';
585 printf("%.7s%6s", filetime + 4, filetime + 20);
587 column += 13;
589 #endif
592 #if ENABLE_FEATURE_LS_COLOR
593 if (G_show_color) {
594 mode_t mode = dn->dn_mode_lstat;
595 if (!mode)
596 if (lstat(dn->fullname, &statbuf) == 0)
597 mode = statbuf.st_mode;
598 printf(ESC"[%u;%um", bold(mode), fgcolor(mode));
600 #endif
601 column += print_name(dn->name);
602 if (G_show_color) {
603 printf(ESC"[m");
606 if (lpath) {
607 printf(" -> ");
608 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
609 if ((opt & (OPT_F|OPT_p))
610 || G_show_color
612 mode_t mode = dn->dn_mode_stat;
613 if (!mode)
614 if (stat(dn->fullname, &statbuf) == 0)
615 mode = statbuf.st_mode;
616 # if ENABLE_FEATURE_LS_FILETYPES
617 append = append_char(mode);
618 # endif
619 # if ENABLE_FEATURE_LS_COLOR
620 if (G_show_color) {
621 printf(ESC"[%u;%um", bold(mode), fgcolor(mode));
623 # endif
625 #endif
626 column += print_name(lpath) + 4;
627 free(lpath);
628 if (G_show_color) {
629 printf(ESC"[m");
632 #if ENABLE_FEATURE_LS_FILETYPES
633 if (opt & (OPT_F|OPT_p)) {
634 if (append) {
635 putchar(append);
636 column++;
639 #endif
641 return column;
644 static void display_files(struct dnode **dn, unsigned nfiles)
646 unsigned i, ncols, nrows, row, nc;
647 unsigned column;
648 unsigned nexttab;
649 unsigned column_width = 0; /* used only by coulmnal output */
651 if (option_mask32 & (OPT_l|OPT_1)) {
652 ncols = 1;
653 } else {
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)
658 column_width = len;
660 column_width += 2
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;
668 if (ncols > 1) {
669 nrows = nfiles / ncols;
670 if (nrows * ncols < nfiles)
671 nrows++; /* round up fractionals */
672 } else {
673 nrows = nfiles;
674 ncols = 1;
677 column = 0;
678 nexttab = 0;
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 */
684 else
685 i = (nc * nrows) + row; /* display by column */
686 if (i < nfiles) {
687 if (column > 0) {
688 nexttab -= column;
689 printf("%*s", nexttab, "");
690 column += nexttab;
692 nexttab = column + column_width;
693 column += display_single(dn[i]);
696 putchar('\n');
697 column = 0;
702 /*** Dir scanning code ***/
704 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
706 struct stat statbuf;
707 struct dnode *cur;
709 cur = xzalloc(sizeof(*cur));
710 cur->fullname = fullname;
711 cur->name = name;
713 if ((option_mask32 & OPT_L) || force_follow) {
714 #if ENABLE_SELINUX
715 if (option_mask32 & OPT_Z) {
716 getfilecon(fullname, &cur->sid);
718 #endif
719 if (stat(fullname, &statbuf)) {
720 bb_simple_perror_msg(fullname);
721 G.exit_code = EXIT_FAILURE;
722 free(cur);
723 return NULL;
725 cur->dn_mode_stat = statbuf.st_mode;
726 } else {
727 #if ENABLE_SELINUX
728 if (option_mask32 & OPT_Z) {
729 lgetfilecon(fullname, &cur->sid);
731 #endif
732 if (lstat(fullname, &statbuf)) {
733 bb_simple_perror_msg(fullname);
734 G.exit_code = EXIT_FAILURE;
735 free(cur);
736 return NULL;
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;
750 #endif
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);
759 return cur;
762 static unsigned count_dirs(struct dnode **dn, int which)
764 unsigned dirs, all;
766 if (!dn)
767 return 0;
769 dirs = all = 0;
770 for (; *dn; dn++) {
771 const char *name;
773 all++;
774 if (!S_ISDIR((*dn)->dn_mode))
775 continue;
777 name = (*dn)->name;
778 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
779 /* or if it's not . or .. */
780 || name[0] != '.'
781 || (name[1] && (name[1] != '.' || name[2]))
783 dirs++;
786 return which != SPLIT_FILE ? dirs : all - dirs;
789 /* get memory to hold an array of pointers */
790 static struct dnode **dnalloc(unsigned num)
792 if (num < 1)
793 return NULL;
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)
802 unsigned i;
804 if (dnp == NULL)
805 return;
807 for (i = 0; dnp[i]; i++) {
808 struct dnode *cur = dnp[i];
809 if (cur->fname_allocated)
810 free((char*)cur->fullname);
811 free(cur);
813 free(dnp);
815 #else
816 #define dfree(...) ((void)0)
817 #endif
819 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
820 static struct dnode **splitdnarray(struct dnode **dn, int which)
822 unsigned dncnt, d;
823 struct dnode **dnp;
825 if (dn == NULL)
826 return NULL;
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)) {
837 const char *name;
839 if (which == SPLIT_FILE)
840 continue;
842 name = (*dn)->name;
843 if ((which & SPLIT_DIR) /* any dir... */
844 /* ... or not . or .. */
845 || name[0] != '.'
846 || (name[1] && (name[1] != '.' || name[2]))
848 dnp[d++] = *dn;
850 } else
851 if (which == SPLIT_FILE) {
852 dnp[d++] = *dn;
855 return dnp;
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;
864 off_t dif;
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);
871 if (dif != 0)
872 goto maybe_invert_and_ret;
875 if (opt & OPT_S) { /* sort by size */
876 dif = (d2->dn_size - d1->dn_size);
877 } else
878 if (opt & OPT_t) { /* sort by time */
879 dif = (d2->dn_time - d1->dn_time);
880 } else
881 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
882 if (opt & OPT_v) { /* sort by version */
883 dif = strverscmp(d1->name, d2->name);
884 } else
885 #endif
886 if (opt & OPT_X) { /* sort by extension */
887 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
889 if (dif == 0) {
890 /* sort by name, use as tie breaker for other sorts */
891 if (ENABLE_LOCALE_SUPPORT)
892 dif = strcoll(d1->name, d2->name);
893 else
894 dif = strcmp(d1->name, d2->name);
895 } else {
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)
915 dnsort(dn, nfiles);
916 display_files(dn, nfiles);
918 #else
919 # define dnsort(dn, size) ((void)0)
920 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
921 #endif
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;
928 DIR *dir;
929 unsigned i, nfiles;
931 *nfiles_p = 0;
932 dir = warn_opendir(path);
933 if (dir == NULL) {
934 G.exit_code = EXIT_FAILURE;
935 return NULL; /* could not open the dir */
937 dn = NULL;
938 nfiles = 0;
939 while ((entry = readdir(dir)) != NULL) {
940 char *fullname;
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);
954 if (!cur) {
955 free(fullname);
956 continue;
958 cur->fname_allocated = 1;
959 cur->dn_next = dn;
960 dn = cur;
961 nfiles++;
963 closedir(dir);
965 if (dn == NULL)
966 return NULL;
968 /* now that we know how many files there are
969 * allocate memory for an array to hold dnode pointers
971 *nfiles_p = nfiles;
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 */
975 dn = dn->dn_next;
976 if (!dn)
977 break;
980 return dnp;
983 #if ENABLE_DESKTOP
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
991 * number of units.
993 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
994 static off_t calculate_blocks(struct dnode **dn)
996 uoff_t blocks = 1;
997 if (dn) {
998 while (*dn) {
999 /* st_blocks is in 512 byte blocks */
1000 blocks += (*dn)->dn_blocks;
1001 dn++;
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 */
1008 return blocks >> 1;
1010 #endif
1012 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1014 unsigned nfiles;
1015 struct dnode **subdnp;
1017 for (; *dn; dn++) {
1018 if (G.show_dirname || (option_mask32 & OPT_R)) {
1019 if (!first)
1020 bb_putchar('\n');
1021 first = 0;
1022 printf("%s:\n", (*dn)->fullname);
1024 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1025 #if ENABLE_DESKTOP
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,
1031 0, 0)
1033 } else {
1034 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1037 #endif
1038 if (nfiles > 0) {
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)
1045 struct dnode **dnd;
1046 unsigned dndirs;
1047 /* recursive - list the sub-dirs */
1048 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1049 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1050 if (dndirs > 0) {
1051 dnsort(dnd, dndirs);
1052 scan_and_display_dirs_recur(dnd, 0);
1053 /* free the array of dnode pointers to the dirs */
1054 free(dnd);
1057 /* free the dnodes and the fullname mem */
1058 dfree(subdnp);
1064 int ls_main(int argc UNUSED_PARAM, char **argv)
1065 { /* ^^^^^^^^^^^^^^^^^ note: if FTPD, argc can be wrong, see ftpd.c */
1066 struct dnode **dnd;
1067 struct dnode **dnf;
1068 struct dnode **dnp;
1069 struct dnode *dn;
1070 struct dnode *cur;
1071 unsigned opt;
1072 unsigned nfiles;
1073 unsigned dnfiles;
1074 unsigned dndirs;
1075 unsigned i;
1076 #if ENABLE_FEATURE_LS_COLOR
1077 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1078 /* coreutils 6.10:
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" */
1092 #endif
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")
1099 #endif
1101 INIT_G();
1103 init_unicode();
1105 #if ENABLE_FEATURE_LS_WIDTH
1106 /* obtain the terminal width */
1107 G_terminal_width = get_terminal_width(STDIN_FILENO);
1108 /* go one less... */
1109 G_terminal_width--;
1110 #endif
1112 /* process options */
1113 opt = getopt32long(argv, "^"
1114 ls_options
1115 "\0"
1116 /* -n and -g imply -l */
1117 "nl:gl"
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 */
1130 /* -w NUM: */
1131 IF_FEATURE_LS_WIDTH(":w+")
1132 , ls_longopts
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");
1144 exit(0);
1145 #endif
1147 #if ENABLE_SELINUX
1148 if (opt & OPT_Z) {
1149 if (!is_selinux_enabled())
1150 option_mask32 &= ~OPT_Z;
1152 #endif
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) */
1162 G_show_color = 1;
1166 if (opt & OPT_color) {
1167 if (color_opt[0] == 'n')
1168 G_show_color = 0;
1169 else switch (index_in_substrings(color_str, color_opt)) {
1170 case 3:
1171 case 4:
1172 case 5:
1173 if (!is_TERM_dumb() && isatty(STDOUT_FILENO)) {
1174 case 0:
1175 case 1:
1176 case 2:
1177 G_show_color = 1;
1181 #endif
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;
1207 argv += optind;
1208 if (!argv[0])
1209 *--argv = (char*)".";
1211 if (argv[1])
1212 G.show_dirname = 1; /* 2 or more items? label directories */
1214 /* stuff the command line file names into a dnode array */
1215 dn = NULL;
1216 nfiles = 0;
1217 do {
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))
1221 /* ... or if -H: */
1222 || (option_mask32 & OPT_H)
1223 /* ... or if -L, but my_stat always follows links if -L */
1225 argv++;
1226 if (!cur)
1227 continue;
1228 /*cur->fname_allocated = 0; - already is */
1229 cur->dn_next = dn;
1230 dn = cur;
1231 nfiles++;
1232 } while (*argv);
1234 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1235 if (nfiles == 0)
1236 return G.exit_code;
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 */
1244 dn = dn->dn_next;
1245 if (!dn)
1246 break;
1249 if (option_mask32 & OPT_d) {
1250 sort_and_display_files(dnp, nfiles);
1251 } else {
1252 dnd = splitdnarray(dnp, SPLIT_DIR);
1253 dnf = splitdnarray(dnp, SPLIT_FILE);
1254 dndirs = count_dirs(dnp, SPLIT_DIR);
1255 dnfiles = nfiles - dndirs;
1256 if (dnfiles > 0) {
1257 sort_and_display_files(dnf, dnfiles);
1258 if (ENABLE_FEATURE_CLEAN_UP)
1259 free(dnf);
1261 if (dndirs > 0) {
1262 dnsort(dnd, dndirs);
1263 scan_and_display_dirs_recur(dnd, dnfiles == 0);
1264 if (ENABLE_FEATURE_CLEAN_UP)
1265 free(dnd);
1269 if (ENABLE_FEATURE_CLEAN_UP)
1270 dfree(dnp);
1271 return G.exit_code;