1 /* vi: set sw=4 ts=4: */
3 * tiny-ls.c version 0.1.0: A minimalist 'ls'
4 * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
9 /* [date unknown. Perhaps before year 2000]
10 * To achieve a small memory footprint, this version of 'ls' doesn't do any
11 * file sorting, and only has the most essential command line switches
12 * (i.e., the ones I couldn't live without :-) All features which involve
13 * linking in substantial chunks of libc can be disabled.
15 * Although I don't really want to add new features to this program to
16 * keep it small, I *am* interested to receive bug fixes and ways to make
20 * 1. hidden files can make column width too large
22 * NON-OPTIMAL BEHAVIOUR:
23 * 1. autowidth reads directories twice
24 * 2. if you do a short directory listing without filetype characters
25 * appended, there's no need to stat each one
27 * 1. requires lstat (BSD) - how do you do it without?
30 * ls sorts listing now, and supports almost all options.
33 //usage:#define ls_trivial_usage
35 //usage: IF_FEATURE_LS_FOLLOWLINKS("LH")
36 //usage: IF_FEATURE_LS_RECURSIVE("R")
37 //usage: IF_FEATURE_LS_FILETYPES("Fp") "lins"
38 //usage: IF_FEATURE_LS_TIMESTAMPS("e")
39 //usage: IF_FEATURE_HUMAN_READABLE("h")
40 //usage: IF_FEATURE_LS_SORTFILES("rSXv")
41 //usage: IF_FEATURE_LS_TIMESTAMPS("ctu")
42 //usage: IF_SELINUX("kKZ") "]"
43 //usage: IF_FEATURE_AUTOWIDTH(" -w WIDTH") " [FILE]..."
44 //usage:#define ls_full_usage "\n\n"
45 //usage: "List directory contents\n"
47 //usage: "\n -1 List in a single column"
48 //usage: "\n -A Don't list . and .."
49 //usage: "\n -a Don't hide entries starting with ."
50 //usage: "\n -C List by columns"
51 //usage: "\n -x List by lines"
52 //usage: "\n -d List directory entries instead of contents"
53 //usage: IF_FEATURE_LS_FOLLOWLINKS(
54 //usage: "\n -L Follow symlinks"
55 //usage: "\n -H Follow symlinks on command line only"
57 //usage: IF_FEATURE_LS_RECURSIVE(
58 //usage: "\n -R Recurse"
60 //usage: IF_FEATURE_LS_FILETYPES(
61 //usage: "\n -F Append indicator (one of */=@|) to entries"
62 //usage: "\n -p Append indicator (one of /=@|) to entries"
64 //usage: "\n -l Long listing format"
65 //usage: "\n -i List inode numbers"
66 //usage: "\n -n List numeric UIDs and GIDs instead of names"
67 //usage: "\n -s List the size of each file, in blocks"
68 //usage: IF_FEATURE_LS_TIMESTAMPS(
69 //usage: "\n -e List full date and time"
71 //usage: IF_FEATURE_HUMAN_READABLE(
72 //usage: "\n -h List sizes in human readable format (1K 243M 2G)"
74 //usage: IF_FEATURE_LS_SORTFILES(
75 //usage: "\n -r Sort in reverse order"
76 //usage: "\n -S Sort by file size"
77 //usage: "\n -X Sort by extension"
78 //usage: "\n -v Sort by version"
80 //usage: IF_FEATURE_LS_TIMESTAMPS(
81 //usage: "\n -c With -l: sort by ctime"
82 //usage: "\n -t With -l: sort by modification time"
83 //usage: "\n -u With -l: sort by access time"
86 //usage: "\n -k List security context"
87 //usage: "\n -K List security context in long format"
88 //usage: "\n -Z List security context and permission"
90 //usage: IF_FEATURE_AUTOWIDTH(
91 //usage: "\n -w N Assume the terminal is N columns wide"
93 //usage: IF_FEATURE_LS_COLOR(
94 //usage: "\n --color[={always,never,auto}] Control coloring"
101 /* This is a NOEXEC applet. Be very careful! */
105 /* ftpd uses ls, and without timestamps Mozilla won't understand
106 * ftpd's LIST output.
108 # undef CONFIG_FEATURE_LS_TIMESTAMPS
109 # undef ENABLE_FEATURE_LS_TIMESTAMPS
110 # undef IF_FEATURE_LS_TIMESTAMPS
111 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
112 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
113 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
114 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
115 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
120 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
126 /* Bits in all_fmt: */
128 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
129 /* what file information will be listed */
131 LIST_BLOCKS = 1 << 1,
132 LIST_MODEBITS = 1 << 2,
133 LIST_NLINKS = 1 << 3,
134 LIST_ID_NAME = 1 << 4,
135 LIST_ID_NUMERIC = 1 << 5,
136 LIST_CONTEXT = 1 << 6,
138 LIST_DATE_TIME = 1 << 8,
139 LIST_FULLTIME = 1 << 9,
140 LIST_SYMLINK = 1 << 10,
141 LIST_FILETYPE = 1 << 11,
143 LIST_MASK = (LIST_EXEC << 1) - 1,
145 /* what files will be displayed */
146 DISP_DIRNAME = 1 << 13, /* 2 or more items? label directories */
147 DISP_HIDDEN = 1 << 14, /* show filenames starting with . */
148 DISP_DOT = 1 << 15, /* show . and .. */
149 DISP_NOLIST = 1 << 16, /* show directory as itself, not contents */
150 DISP_RECURSIVE = 1 << 17, /* show directory and everything below it */
151 DISP_ROWS = 1 << 18, /* print across rows */
152 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
154 /* what is the overall style of the listing */
155 STYLE_COLUMNAR = 1 << 19, /* many records per line */
156 STYLE_LONG = 2 << 19, /* one record per line, extended info */
157 STYLE_SINGLE = 3 << 19, /* one record per line */
158 STYLE_MASK = STYLE_SINGLE,
160 /* which of the three times will be used */
161 TIME_CHANGE = (1 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
162 TIME_ACCESS = (2 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
163 TIME_MASK = (3 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS,
165 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
166 SORT_REVERSE = 1 << 23,
168 SORT_NAME = 0, /* sort by file name */
169 SORT_SIZE = 1 << 24, /* sort by file size */
170 SORT_ATIME = 2 << 24, /* sort by last access time */
171 SORT_CTIME = 3 << 24, /* sort by last change time */
172 SORT_MTIME = 4 << 24, /* sort by last modification time */
173 SORT_VERSION = 5 << 24, /* sort by version */
174 SORT_EXT = 6 << 24, /* sort by file name extension */
175 SORT_DIR = 7 << 24, /* sort by file or directory */
176 SORT_MASK = (7 << 24) * ENABLE_FEATURE_LS_SORTFILES,
178 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
179 LIST_DATE_TIME | LIST_SYMLINK,
182 /* -Cadil1 Std options, busybox always supports */
183 /* -gnsxA Std options, busybox always supports */
184 /* -Q GNU option, busybox always supports */
185 /* -k SELinux option, busybox always supports (ignores if !SELinux) */
186 /* Std has -k which means "show sizes in kbytes" */
187 /* -FLHRctur Std options, busybox optionally supports */
188 /* -p Std option, busybox optionally supports */
189 /* Not fully compatible - we show not only '/' but other chars too */
190 /* -SXvhTw GNU options, busybox optionally supports */
191 /* -T TABWIDTH is ignored (we don't use tabs on output) */
192 /* -KZ SELinux mandated options, busybox optionally supports */
193 /* (coreutils 8.4 has no -K, remove it?) */
194 /* -e I think we made this one up (looks similar to GNU --full-time) */
195 /* We already used up all 32 bits, if we need to add more, candidates for removal: */
196 /* -K, -T, -e (add --full-time instead) */
197 static const char ls_options[] ALIGN1 =
198 "Cadil1gnsxQAk" /* 13 opts, total 13 */
199 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
200 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
201 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
202 IF_FEATURE_LS_RECURSIVE("R") /* 1, 24 */
203 IF_SELINUX("KZ") /* 2, 26 */
204 IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 28 */
205 IF_FEATURE_HUMAN_READABLE("h") /* 1, 29 */
206 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 31 */
207 /* with --color, we use all 32 bits */;
227 OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS,
231 OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES,
233 OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES,
234 OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE,
236 OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX,
238 OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS,
239 OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE,
241 OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH,
243 OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS,
244 OPT_e = (1 << OPTBIT_e) * ENABLE_FEATURE_LS_TIMESTAMPS,
245 OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS,
246 OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS,
247 OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES,
248 OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES,
249 OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES,
250 OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES,
251 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
252 OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES,
253 OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE,
254 OPT_K = (1 << OPTBIT_K) * ENABLE_SELINUX,
255 OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX,
256 OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS,
257 OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS,
258 OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE,
259 OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_AUTOWIDTH,
260 OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_AUTOWIDTH,
261 OPT_color = (1 << OPTBIT_color) * ENABLE_FEATURE_LS_COLOR,
264 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
265 static const uint32_t opt_flags[] = {
266 STYLE_COLUMNAR, /* C */
267 DISP_HIDDEN | DISP_DOT, /* a */
270 LIST_LONG | STYLE_LONG, /* l */
271 STYLE_SINGLE, /* 1 */
272 LIST_LONG | STYLE_LONG, /* g (don't show owner) - handled via OPT_g. assumes l */
273 LIST_ID_NUMERIC | LIST_LONG | STYLE_LONG, /* n (assumes l) */
275 DISP_ROWS | STYLE_COLUMNAR, /* x */
276 0, /* Q (quote filename) - handled via OPT_Q */
278 ENABLE_SELINUX * (LIST_CONTEXT|STYLE_SINGLE), /* k (ignored if !SELINUX) */
279 #if ENABLE_FEATURE_LS_TIMESTAMPS
280 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
281 LIST_FULLTIME, /* e */
282 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
283 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
285 #if ENABLE_FEATURE_LS_SORTFILES
288 SORT_REVERSE, /* r */
289 SORT_VERSION, /* v */
291 #if ENABLE_FEATURE_LS_FILETYPES
292 LIST_FILETYPE | LIST_EXEC, /* F */
293 LIST_FILETYPE, /* p */
295 #if ENABLE_FEATURE_LS_RECURSIVE
296 DISP_RECURSIVE, /* R */
299 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME|STYLE_SINGLE, /* K */
300 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT|STYLE_SINGLE, /* Z */
303 /* options after Z are not processed through opt_flags */
308 * a directory entry and its stat info are stored here
311 const char *name; /* the dir entry name */
312 const char *fullname; /* the dir entry name */
313 struct dnode *next; /* point at the next node */
314 smallint fname_allocated;
315 struct stat dstat; /* the file stat info */
316 IF_SELINUX(security_context_t sid;)
320 #if ENABLE_FEATURE_LS_COLOR
325 #if ENABLE_FEATURE_AUTOWIDTH
326 unsigned terminal_width; // = TERMINAL_WIDTH;
328 #if ENABLE_FEATURE_LS_TIMESTAMPS
329 /* Do time() just once. Saves one syscall per file for "ls -l" */
330 time_t current_time_t;
333 #define G (*(struct globals*)&bb_common_bufsiz1)
334 #if ENABLE_FEATURE_LS_COLOR
335 # define show_color (G.show_color )
337 enum { show_color = 0 };
339 #define exit_code (G.exit_code )
340 #define all_fmt (G.all_fmt )
341 #if ENABLE_FEATURE_AUTOWIDTH
342 # define terminal_width (G.terminal_width)
345 terminal_width = TERMINAL_WIDTH,
348 #define current_time_t (G.current_time_t)
349 #define INIT_G() do { \
350 /* we have to zero it out because of NOEXEC */ \
351 memset(&G, 0, sizeof(G)); \
352 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
353 IF_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \
357 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
361 IF_SELINUX(security_context_t sid = NULL;)
363 if ((option_mask32 & OPT_L) || force_follow) {
365 if (is_selinux_enabled()) {
366 getfilecon(fullname, &sid);
369 if (stat(fullname, &dstat)) {
370 bb_simple_perror_msg(fullname);
371 exit_code = EXIT_FAILURE;
376 if (is_selinux_enabled()) {
377 lgetfilecon(fullname, &sid);
380 if (lstat(fullname, &dstat)) {
381 bb_simple_perror_msg(fullname);
382 exit_code = EXIT_FAILURE;
387 cur = xmalloc(sizeof(*cur));
388 cur->fullname = fullname;
391 IF_SELINUX(cur->sid = sid;)
395 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
396 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
397 * 3/7:multiplexed char/block device)
398 * and we use 0 for unknown and 15 for executables (see below) */
399 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
400 #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
401 #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
402 /* 036 black foreground 050 black background
403 037 red foreground 051 red background
404 040 green foreground 052 green background
405 041 brown foreground 053 brown background
406 042 blue foreground 054 blue background
407 043 magenta (purple) foreground 055 magenta background
408 044 cyan (light blue) foreground 056 cyan background
409 045 gray foreground 057 white background
411 #define COLOR(mode) ( \
412 /*un fi chr dir blk file link sock exe */ \
413 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
415 /* Select normal (0) [actually "reset all"] or bold (1)
416 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
417 * let's use 7 for "impossible" types, just for fun)
418 * Note: coreutils 6.9 uses inverted red for setuid binaries.
420 #define ATTR(mode) ( \
421 /*un fi chr dir blk file link sock exe */ \
422 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
425 #if ENABLE_FEATURE_LS_COLOR
426 /* mode of zero is interpreted as "unknown" (stat failed) */
427 static char fgcolor(mode_t mode)
429 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
430 return COLOR(0xF000); /* File is executable ... */
433 static char bold(mode_t mode)
435 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
436 return ATTR(0xF000); /* File is executable ... */
441 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
442 static char append_char(mode_t mode)
444 if (!(all_fmt & LIST_FILETYPE))
448 if (!(all_fmt & LIST_EXEC))
450 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
452 return APPCHAR(mode);
456 static unsigned count_dirs(struct dnode **dn, int which)
468 if (!S_ISDIR((*dn)->dstat.st_mode))
471 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
472 /* or if it's not . or .. */
473 || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
478 return which != SPLIT_FILE ? dirs : all - dirs;
481 /* get memory to hold an array of pointers */
482 static struct dnode **dnalloc(unsigned num)
487 num++; /* so that we have terminating NULL */
488 return xzalloc(num * sizeof(struct dnode *));
491 #if ENABLE_FEATURE_LS_RECURSIVE
492 static void dfree(struct dnode **dnp)
499 for (i = 0; dnp[i]; i++) {
500 struct dnode *cur = dnp[i];
501 if (cur->fname_allocated)
502 free((char*)cur->fullname);
508 #define dfree(...) ((void)0)
511 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
512 static struct dnode **splitdnarray(struct dnode **dn, int which)
520 /* count how many dirs or files there are */
521 dncnt = count_dirs(dn, which);
523 /* allocate a file array and a dir array */
524 dnp = dnalloc(dncnt);
526 /* copy the entrys into the file or dir array */
527 for (d = 0; *dn; dn++) {
528 if (S_ISDIR((*dn)->dstat.st_mode)) {
531 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
534 if ((which & SPLIT_DIR)
535 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
539 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
546 #if ENABLE_FEATURE_LS_SORTFILES
547 static int sortcmp(const void *a, const void *b)
549 struct dnode *d1 = *(struct dnode **)a;
550 struct dnode *d2 = *(struct dnode **)b;
551 unsigned sort_opts = all_fmt & SORT_MASK;
554 dif = 0; /* assume SORT_NAME */
555 // TODO: use pre-initialized function pointer
556 // instead of branch forest
557 if (sort_opts == SORT_SIZE) {
558 dif = (d2->dstat.st_size - d1->dstat.st_size);
559 } else if (sort_opts == SORT_ATIME) {
560 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
561 } else if (sort_opts == SORT_CTIME) {
562 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
563 } else if (sort_opts == SORT_MTIME) {
564 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
565 } else if (sort_opts == SORT_DIR) {
566 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
567 /* } else if (sort_opts == SORT_VERSION) { */
568 /* } else if (sort_opts == SORT_EXT) { */
571 /* sort by name, or tie_breaker for other sorts */
572 if (ENABLE_LOCALE_SUPPORT)
573 dif = strcoll(d1->name, d2->name);
575 dif = strcmp(d1->name, d2->name);
578 /* Make dif fit into an int */
579 if (sizeof(dif) > sizeof(int)) {
580 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
581 /* shift leaving only "int" worth of bits */
583 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
587 return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
590 static void dnsort(struct dnode **dn, int size)
592 qsort(dn, size, sizeof(*dn), sortcmp);
595 #define dnsort(dn, size) ((void)0)
599 static unsigned calc_name_len(const char *name)
604 // TODO: quote tab as \t, etc, if -Q
605 name = printable_string(&uni_stat, name);
607 if (!(option_mask32 & OPT_Q)) {
608 return uni_stat.unicode_width;
611 len = 2 + uni_stat.unicode_width;
613 if (*name == '"' || *name == '\\') {
622 /* Return the number of used columns.
623 * Note that only STYLE_COLUMNAR uses return value.
624 * STYLE_SINGLE and STYLE_LONG don't care.
625 * coreutils 7.2 also supports:
626 * ls -b (--escape) = octal escapes (although it doesn't look like working)
627 * ls -N (--literal) = not escape at all
629 static unsigned print_name(const char *name)
634 // TODO: quote tab as \t, etc, if -Q
635 name = printable_string(&uni_stat, name);
637 if (!(option_mask32 & OPT_Q)) {
639 return uni_stat.unicode_width;
642 len = 2 + uni_stat.unicode_width;
645 if (*name == '"' || *name == '\\') {
656 /* Return the number of used columns.
657 * Note that only STYLE_COLUMNAR uses return value,
658 * STYLE_SINGLE and STYLE_LONG don't care.
660 static NOINLINE unsigned list_single(const struct dnode *dn)
663 char *lpath = lpath; /* for compiler */
664 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
670 if (dn->fullname == NULL)
674 #if ENABLE_FEATURE_LS_FILETYPES
675 append = append_char(dn->dstat.st_mode);
678 /* Do readlink early, so that if it fails, error message
679 * does not appear *inside* the "ls -l" line */
680 if (all_fmt & LIST_SYMLINK)
681 if (S_ISLNK(dn->dstat.st_mode))
682 lpath = xmalloc_readlink_or_warn(dn->fullname);
684 if (all_fmt & LIST_INO)
685 column += printf("%7llu ", (long long) dn->dstat.st_ino);
686 //TODO: -h should affect -s too:
687 if (all_fmt & LIST_BLOCKS)
688 column += printf("%6"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
689 if (all_fmt & LIST_MODEBITS)
690 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
691 if (all_fmt & LIST_NLINKS)
692 column += printf("%4lu ", (long) dn->dstat.st_nlink);
693 if (all_fmt & LIST_ID_NUMERIC) {
694 if (option_mask32 & OPT_g)
695 column += printf("%-8u ", (int) dn->dstat.st_gid);
697 column += printf("%-8u %-8u ",
698 (int) dn->dstat.st_uid,
699 (int) dn->dstat.st_gid);
701 #if ENABLE_FEATURE_LS_USERNAME
702 else if (all_fmt & LIST_ID_NAME) {
703 if (option_mask32 & OPT_g) {
704 column += printf("%-8.8s ",
705 get_cached_groupname(dn->dstat.st_gid));
707 column += printf("%-8.8s %-8.8s ",
708 get_cached_username(dn->dstat.st_uid),
709 get_cached_groupname(dn->dstat.st_gid));
713 if (all_fmt & LIST_SIZE) {
714 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
715 column += printf("%4u, %3u ",
716 (int) major(dn->dstat.st_rdev),
717 (int) minor(dn->dstat.st_rdev));
719 if (option_mask32 & OPT_h) {
720 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
721 /* print st_size, show one fractional, use suffixes */
722 make_human_readable_str(dn->dstat.st_size, 1, 0)
725 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
729 #if ENABLE_FEATURE_LS_TIMESTAMPS
730 if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
732 time_t ttime = dn->dstat.st_mtime;
733 if (all_fmt & TIME_ACCESS)
734 ttime = dn->dstat.st_atime;
735 if (all_fmt & TIME_CHANGE)
736 ttime = dn->dstat.st_ctime;
737 filetime = ctime(&ttime);
738 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
739 if (all_fmt & LIST_FULLTIME) { /* -e */
740 /* Note: coreutils 8.4 ls --full-time prints:
741 * 2009-07-13 17:49:27.000000000 +0200
743 column += printf("%.24s ", filetime);
744 } else { /* LIST_DATE_TIME */
745 /* current_time_t ~== time(NULL) */
746 time_t age = current_time_t - ttime;
747 printf("%.6s ", filetime + 4); /* "Jun 30" */
748 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
749 /* hh:mm if less than 6 months old */
750 printf("%.5s ", filetime + 11);
751 } else { /* year. buggy if year > 9999 ;) */
752 printf(" %.4s ", filetime + 20);
759 if (all_fmt & LIST_CONTEXT) {
760 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
765 #if ENABLE_FEATURE_LS_COLOR
767 info.st_mode = 0; /* for fgcolor() */
768 lstat(dn->fullname, &info);
769 printf("\033[%u;%um", bold(info.st_mode),
770 fgcolor(info.st_mode));
773 column += print_name(dn->name);
778 if (all_fmt & LIST_SYMLINK) {
779 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
781 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
782 #if ENABLE_FEATURE_LS_COLOR
783 info.st_mode = 0; /* for fgcolor() */
785 if (stat(dn->fullname, &info) == 0) {
786 append = append_char(info.st_mode);
789 #if ENABLE_FEATURE_LS_COLOR
791 printf("\033[%u;%um", bold(info.st_mode),
792 fgcolor(info.st_mode));
795 column += print_name(lpath) + 4;
802 #if ENABLE_FEATURE_LS_FILETYPES
803 if (all_fmt & LIST_FILETYPE) {
814 static void showfiles(struct dnode **dn, unsigned nfiles)
816 unsigned i, ncols, nrows, row, nc;
819 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
821 if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
824 /* find the longest file name, use that as the column width */
825 for (i = 0; dn[i]; i++) {
826 int len = calc_name_len(dn[i]->name);
827 if (column_width < len)
831 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
832 ((all_fmt & LIST_INO) ? 8 : 0) +
833 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
834 ncols = (int) (terminal_width / column_width);
838 nrows = nfiles / ncols;
839 if (nrows * ncols < nfiles)
840 nrows++; /* round up fractionals */
848 for (row = 0; row < nrows; row++) {
849 for (nc = 0; nc < ncols; nc++) {
850 /* reach into the array based on the column and row */
851 if (all_fmt & DISP_ROWS)
852 i = (row * ncols) + nc; /* display across row */
854 i = (nc * nrows) + row; /* display by column */
858 printf("%*s ", nexttab, "");
859 column += nexttab + 1;
861 nexttab = column + column_width;
862 column += list_single(dn[i]);
872 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
873 * If any of the -l, -n, -s options is specified, each list
874 * of files within the directory shall be preceded by a
875 * status line indicating the number of file system blocks
876 * occupied by files in the directory in 512-byte units if
877 * the -k option is not specified, or 1024-byte units if the
878 * -k option is specified, rounded up to the next integral
881 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
882 static off_t calculate_blocks(struct dnode **dn)
887 /* st_blocks is in 512 byte blocks */
888 blocks += (*dn)->dstat.st_blocks;
893 /* Even though standard says use 512 byte blocks, coreutils use 1k */
894 /* Actually, we round up by calculating (blocks + 1) / 2,
895 * "+ 1" was done when we initialized blocks to 1 */
901 static struct dnode **list_dir(const char *, unsigned *);
903 static void showdirs(struct dnode **dn, int first)
907 struct dnode **subdnp;
911 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
915 printf("%s:\n", (*dn)->fullname);
917 subdnp = list_dir((*dn)->fullname, &nfiles);
919 if ((all_fmt & STYLE_MASK) == STYLE_LONG)
920 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
923 /* list all files at this level */
924 dnsort(subdnp, nfiles);
925 showfiles(subdnp, nfiles);
926 if (ENABLE_FEATURE_LS_RECURSIVE
927 && (all_fmt & DISP_RECURSIVE)
929 /* recursive - list the sub-dirs */
930 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
931 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
935 /* free the array of dnode pointers to the dirs */
939 /* free the dnodes and the fullname mem */
946 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
947 static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
949 struct dnode *dn, *cur, **dnp;
950 struct dirent *entry;
960 dir = warn_opendir(path);
962 exit_code = EXIT_FAILURE;
963 return NULL; /* could not open the dir */
967 while ((entry = readdir(dir)) != NULL) {
970 /* are we going to list the file- it may be . or .. or a hidden file */
971 if (entry->d_name[0] == '.') {
972 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
973 && !(all_fmt & DISP_DOT)
977 if (!(all_fmt & DISP_HIDDEN))
980 fullname = concat_path_file(path, entry->d_name);
981 cur = my_stat(fullname, bb_basename(fullname), 0);
986 cur->fname_allocated = 1;
996 /* now that we know how many files there are
997 * allocate memory for an array to hold dnode pointers
1000 dnp = dnalloc(nfiles);
1001 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1002 dnp[i] = dn; /* save pointer to node in array */
1012 int ls_main(int argc UNUSED_PARAM, char **argv)
1024 #if ENABLE_FEATURE_LS_COLOR
1025 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1027 * # ls --color=BOGUS
1028 * ls: invalid argument 'BOGUS' for '--color'
1029 * Valid arguments are:
1030 * 'always', 'yes', 'force'
1031 * 'never', 'no', 'none'
1032 * 'auto', 'tty', 'if-tty'
1033 * (and substrings: "--color=alwa" work too)
1035 static const char ls_longopts[] ALIGN1 =
1036 "color\0" Optional_argument "\xff"; /* no short equivalent */
1037 static const char color_str[] ALIGN1 =
1038 "always\0""yes\0""force\0"
1039 "auto\0""tty\0""if-tty\0";
1040 /* need to initialize since --color has _an optional_ argument */
1041 const char *color_opt = color_str; /* "always" */
1048 if (ENABLE_FEATURE_LS_SORTFILES)
1049 all_fmt = SORT_NAME;
1051 #if ENABLE_FEATURE_AUTOWIDTH
1052 /* obtain the terminal width */
1053 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
1054 /* go one less... */
1058 /* process options */
1059 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1063 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1064 * in some pairs of opts, only last one takes effect:
1066 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1067 // ":m-l:l-m" - we don't have -m
1068 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1069 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1070 ":C-1:1-C" /* bycols/oneline */
1071 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1072 ":c-u:u-c" /* mtime/atime */
1074 IF_FEATURE_AUTOWIDTH(":w+");
1075 opt = getopt32(argv, ls_options
1076 IF_FEATURE_AUTOWIDTH(, NULL, &terminal_width)
1077 IF_FEATURE_LS_COLOR(, &color_opt)
1079 for (i = 0; opt_flags[i] != (1U << 31); i++) {
1080 if (opt & (1 << i)) {
1081 uint32_t flags = opt_flags[i];
1083 if (flags & STYLE_MASK)
1084 all_fmt &= ~STYLE_MASK;
1085 if (flags & SORT_MASK)
1086 all_fmt &= ~SORT_MASK;
1087 if (flags & TIME_MASK)
1088 all_fmt &= ~TIME_MASK;
1094 #if ENABLE_FEATURE_LS_COLOR
1095 /* set show_color = 1/0 */
1096 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1097 char *p = getenv("LS_COLORS");
1098 /* LS_COLORS is unset, or (not empty && not "none") ? */
1099 if (!p || (p[0] && strcmp(p, "none") != 0))
1102 if (opt & OPT_color) {
1103 if (color_opt[0] == 'n')
1105 else switch (index_in_substrings(color_str, color_opt)) {
1109 if (isatty(STDOUT_FILENO)) {
1119 /* sort out which command line options take precedence */
1120 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1121 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1122 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1123 if (all_fmt & TIME_CHANGE)
1124 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1125 if (all_fmt & TIME_ACCESS)
1126 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1128 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */
1129 all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1131 /* choose a display format if one was not already specified by an option */
1132 if (!(all_fmt & STYLE_MASK))
1133 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1137 *--argv = (char*)".";
1140 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1142 /* stuff the command line file names into a dnode array */
1146 cur = my_stat(*argv, *argv,
1147 /* follow links on command line unless -l, -s or -F: */
1148 !((all_fmt & (STYLE_LONG|LIST_BLOCKS)) || (option_mask32 & OPT_F))
1150 || (option_mask32 & OPT_H)
1155 cur->fname_allocated = 0;
1161 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1165 /* now that we know how many files there are
1166 * allocate memory for an array to hold dnode pointers
1168 dnp = dnalloc(nfiles);
1169 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1170 dnp[i] = dn; /* save pointer to node in array */
1176 if (all_fmt & DISP_NOLIST) {
1177 dnsort(dnp, nfiles);
1178 showfiles(dnp, nfiles);
1180 dnd = splitdnarray(dnp, SPLIT_DIR);
1181 dnf = splitdnarray(dnp, SPLIT_FILE);
1182 dndirs = count_dirs(dnp, SPLIT_DIR);
1183 dnfiles = nfiles - dndirs;
1185 dnsort(dnf, dnfiles);
1186 showfiles(dnf, dnfiles);
1187 if (ENABLE_FEATURE_CLEAN_UP)
1191 dnsort(dnd, dndirs);
1192 showdirs(dnd, dnfiles == 0);
1193 if (ENABLE_FEATURE_CLEAN_UP)
1197 if (ENABLE_FEATURE_CLEAN_UP)