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("L")
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 List entries pointed to by symlinks"
56 //usage: IF_FEATURE_LS_RECURSIVE(
57 //usage: "\n -R Recurse"
59 //usage: IF_FEATURE_LS_FILETYPES(
60 //usage: "\n -F Append indicator (one of */=@|) to entries"
61 //usage: "\n -p Append indicator (one of /=@|) to entries"
63 //usage: "\n -l Long listing format"
64 //usage: "\n -i List inode numbers"
65 //usage: "\n -n List numeric UIDs and GIDs instead of names"
66 //usage: "\n -s List the size of each file, in blocks"
67 //usage: IF_FEATURE_LS_TIMESTAMPS(
68 //usage: "\n -e List full date and time"
70 //usage: IF_FEATURE_HUMAN_READABLE(
71 //usage: "\n -h List sizes in human readable format (1K 243M 2G)"
73 //usage: IF_FEATURE_LS_SORTFILES(
74 //usage: "\n -r Sort in reverse order"
75 //usage: "\n -S Sort by file size"
76 //usage: "\n -X Sort by extension"
77 //usage: "\n -v Sort by version"
79 //usage: IF_FEATURE_LS_TIMESTAMPS(
80 //usage: "\n -c With -l: sort by ctime"
81 //usage: "\n -t With -l: sort by modification time"
82 //usage: "\n -u With -l: sort by access time"
85 //usage: "\n -k List security context"
86 //usage: "\n -K List security context in long format"
87 //usage: "\n -Z List security context and permission"
89 //usage: IF_FEATURE_AUTOWIDTH(
90 //usage: "\n -w N Assume the terminal is N columns wide"
92 //usage: IF_FEATURE_LS_COLOR(
93 //usage: "\n --color[={always,never,auto}] Control coloring"
100 /* This is a NOEXEC applet. Be very careful! */
104 /* ftpd uses ls, and without timestamps Mozilla won't understand
105 * ftpd's LIST output.
107 # undef CONFIG_FEATURE_LS_TIMESTAMPS
108 # undef ENABLE_FEATURE_LS_TIMESTAMPS
109 # undef IF_FEATURE_LS_TIMESTAMPS
110 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
111 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
112 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
113 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
114 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
119 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
121 /* what is the overall style of the listing */
122 STYLE_COLUMNAR = 1 << 21, /* many records per line */
123 STYLE_LONG = 2 << 21, /* one record per line, extended info */
124 STYLE_SINGLE = 3 << 21, /* one record per line */
125 STYLE_MASK = STYLE_SINGLE,
127 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
128 /* what file information will be listed */
130 LIST_BLOCKS = 1 << 1,
131 LIST_MODEBITS = 1 << 2,
132 LIST_NLINKS = 1 << 3,
133 LIST_ID_NAME = 1 << 4,
134 LIST_ID_NUMERIC = 1 << 5,
135 LIST_CONTEXT = 1 << 6,
137 //LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE
138 LIST_DATE_TIME = 1 << 9,
139 LIST_FULLTIME = 1 << 10,
140 LIST_FILENAME = 1 << 11,
141 LIST_SYMLINK = 1 << 12,
142 LIST_FILETYPE = 1 << 13,
144 LIST_MASK = (LIST_EXEC << 1) - 1,
146 /* what files will be displayed */
147 DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */
148 DISP_HIDDEN = 1 << 16, /* show filenames starting with . */
149 DISP_DOT = 1 << 17, /* show . and .. */
150 DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */
151 DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */
152 DISP_ROWS = 1 << 20, /* print across rows */
153 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
155 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
156 SORT_FORWARD = 0, /* sort in reverse order */
157 SORT_REVERSE = 1 << 27, /* sort in reverse order */
159 SORT_NAME = 0, /* sort by file name */
160 SORT_SIZE = 1 << 28, /* sort by file size */
161 SORT_ATIME = 2 << 28, /* sort by last access time */
162 SORT_CTIME = 3 << 28, /* sort by last change time */
163 SORT_MTIME = 4 << 28, /* sort by last modification time */
164 SORT_VERSION = 5 << 28, /* sort by version */
165 SORT_EXT = 6 << 28, /* sort by file name extension */
166 SORT_DIR = 7 << 28, /* sort by file or directory */
167 SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
169 /* which of the three times will be used */
170 TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
171 TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
172 TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
174 FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
176 LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
178 LIST_SHORT = LIST_FILENAME,
179 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
180 LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
187 /* -Cadil1 Std options, busybox always supports */
188 /* -gnsxA Std options, busybox always supports */
189 /* -Q GNU option? busybox always supports */
190 /* -k SELinux option, busybox always supports (ignores if !SELinux) */
191 /* Std has -k which means "show sizes in kbytes" */
192 /* -FLRctur Std options, busybox optionally supports */
193 /* -p Std option, busybox optionally supports */
194 /* Not fully compatible - we show not only '/' but other chars too */
195 /* -SXvhTw GNU options, busybox optionally supports */
196 /* -T TABWIDTH is ignored (we don't use tabs on output) */
197 /* -K SELinux mandated options, busybox optionally supports */
198 /* -e I think we made this one up (looks similar to GNU --full-time) */
199 /* Std opts we do not support: */
200 /* -H Follow the links on command line only */
201 static const char ls_options[] ALIGN1 =
202 "Cadil1gnsxQAk" /* 13 opts, total 13 */
203 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
204 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
205 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
206 IF_FEATURE_LS_FOLLOWLINKS("L") /* 1, 24 */
207 IF_FEATURE_LS_RECURSIVE("R") /* 1, 25 */
208 IF_FEATURE_HUMAN_READABLE("h") /* 1, 26 */
209 IF_SELINUX("KZ") /* 2, 28 */
210 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
227 + 4 * ENABLE_FEATURE_LS_TIMESTAMPS
228 + 4 * ENABLE_FEATURE_LS_SORTFILES,
229 OPTBIT_color = OPTBIT_F
230 + 2 * ENABLE_FEATURE_LS_FILETYPES
231 + 1 * ENABLE_FEATURE_LS_FOLLOWLINKS
232 + 1 * ENABLE_FEATURE_LS_RECURSIVE
233 + 1 * ENABLE_FEATURE_HUMAN_READABLE
235 + 2 * ENABLE_FEATURE_AUTOWIDTH,
236 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
237 OPT_color = (1 << OPTBIT_color),
240 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
241 static const unsigned opt_flags[] = {
242 LIST_SHORT | STYLE_COLUMNAR, /* C */
243 DISP_HIDDEN | DISP_DOT, /* a */
246 LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
247 LIST_SHORT | STYLE_SINGLE, /* 1 */
248 0, /* g (don't show owner) - handled via OPT_g */
249 LIST_ID_NUMERIC, /* n */
251 LIST_SHORT | DISP_ROWS | STYLE_COLUMNAR, /* x */
252 0, /* Q (quote filename) - handled via OPT_Q */
254 ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
255 #if ENABLE_FEATURE_LS_TIMESTAMPS
256 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
257 LIST_FULLTIME, /* e */
258 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
259 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
261 #if ENABLE_FEATURE_LS_SORTFILES
264 SORT_REVERSE, /* r */
265 SORT_VERSION, /* v */
267 #if ENABLE_FEATURE_LS_FILETYPES
268 LIST_FILETYPE | LIST_EXEC, /* F */
269 LIST_FILETYPE, /* p */
271 #if ENABLE_FEATURE_LS_FOLLOWLINKS
272 FOLLOW_LINKS, /* L */
274 #if ENABLE_FEATURE_LS_RECURSIVE
275 DISP_RECURSIVE, /* R */
277 #if ENABLE_FEATURE_HUMAN_READABLE
281 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
282 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
285 /* options after Z are not processed through opt_flags:
292 * a directory entry and its stat info are stored here
295 const char *name; /* the dir entry name */
296 const char *fullname; /* the dir entry name */
297 struct dnode *next; /* point at the next node */
298 smallint fname_allocated;
299 struct stat dstat; /* the file stat info */
300 IF_SELINUX(security_context_t sid;)
304 #if ENABLE_FEATURE_LS_COLOR
309 #if ENABLE_FEATURE_AUTOWIDTH
310 unsigned terminal_width; // = TERMINAL_WIDTH;
312 #if ENABLE_FEATURE_LS_TIMESTAMPS
313 /* Do time() just once. Saves one syscall per file for "ls -l" */
314 time_t current_time_t;
317 #define G (*(struct globals*)&bb_common_bufsiz1)
318 #if ENABLE_FEATURE_LS_COLOR
319 # define show_color (G.show_color )
321 enum { show_color = 0 };
323 #define exit_code (G.exit_code )
324 #define all_fmt (G.all_fmt )
325 #if ENABLE_FEATURE_AUTOWIDTH
326 # define terminal_width (G.terminal_width)
329 terminal_width = TERMINAL_WIDTH,
332 #define current_time_t (G.current_time_t)
333 #define INIT_G() do { \
334 /* we have to zero it out because of NOEXEC */ \
335 memset(&G, 0, sizeof(G)); \
336 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
337 IF_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \
341 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
345 IF_SELINUX(security_context_t sid = NULL;)
347 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
349 if (is_selinux_enabled()) {
350 getfilecon(fullname, &sid);
353 if (stat(fullname, &dstat)) {
354 bb_simple_perror_msg(fullname);
355 exit_code = EXIT_FAILURE;
360 if (is_selinux_enabled()) {
361 lgetfilecon(fullname, &sid);
364 if (lstat(fullname, &dstat)) {
365 bb_simple_perror_msg(fullname);
366 exit_code = EXIT_FAILURE;
371 cur = xmalloc(sizeof(*cur));
372 cur->fullname = fullname;
375 IF_SELINUX(cur->sid = sid;)
379 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
380 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
381 * 3/7:multiplexed char/block device)
382 * and we use 0 for unknown and 15 for executables (see below) */
383 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
384 #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
385 #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
386 /* 036 black foreground 050 black background
387 037 red foreground 051 red background
388 040 green foreground 052 green background
389 041 brown foreground 053 brown background
390 042 blue foreground 054 blue background
391 043 magenta (purple) foreground 055 magenta background
392 044 cyan (light blue) foreground 056 cyan background
393 045 gray foreground 057 white background
395 #define COLOR(mode) ( \
396 /*un fi chr dir blk file link sock exe */ \
397 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
399 /* Select normal (0) [actually "reset all"] or bold (1)
400 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
401 * let's use 7 for "impossible" types, just for fun)
402 * Note: coreutils 6.9 uses inverted red for setuid binaries.
404 #define ATTR(mode) ( \
405 /*un fi chr dir blk file link sock exe */ \
406 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
409 #if ENABLE_FEATURE_LS_COLOR
410 /* mode of zero is interpreted as "unknown" (stat failed) */
411 static char fgcolor(mode_t mode)
413 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
414 return COLOR(0xF000); /* File is executable ... */
417 static char bold(mode_t mode)
419 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
420 return ATTR(0xF000); /* File is executable ... */
425 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
426 static char append_char(mode_t mode)
428 if (!(all_fmt & LIST_FILETYPE))
432 if (!(all_fmt & LIST_EXEC))
434 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
436 return APPCHAR(mode);
440 static unsigned count_dirs(struct dnode **dn, int which)
452 if (!S_ISDIR((*dn)->dstat.st_mode))
455 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
456 /* or if it's not . or .. */
457 || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
462 return which != SPLIT_FILE ? dirs : all - dirs;
465 /* get memory to hold an array of pointers */
466 static struct dnode **dnalloc(unsigned num)
471 num++; /* so that we have terminating NULL */
472 return xzalloc(num * sizeof(struct dnode *));
475 #if ENABLE_FEATURE_LS_RECURSIVE
476 static void dfree(struct dnode **dnp)
483 for (i = 0; dnp[i]; i++) {
484 struct dnode *cur = dnp[i];
485 if (cur->fname_allocated)
486 free((char*)cur->fullname);
492 #define dfree(...) ((void)0)
495 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
496 static struct dnode **splitdnarray(struct dnode **dn, int which)
504 /* count how many dirs or files there are */
505 dncnt = count_dirs(dn, which);
507 /* allocate a file array and a dir array */
508 dnp = dnalloc(dncnt);
510 /* copy the entrys into the file or dir array */
511 for (d = 0; *dn; dn++) {
512 if (S_ISDIR((*dn)->dstat.st_mode)) {
515 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
518 if ((which & SPLIT_DIR)
519 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
523 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
530 #if ENABLE_FEATURE_LS_SORTFILES
531 static int sortcmp(const void *a, const void *b)
533 struct dnode *d1 = *(struct dnode **)a;
534 struct dnode *d2 = *(struct dnode **)b;
535 unsigned sort_opts = all_fmt & SORT_MASK;
538 dif = 0; /* assume SORT_NAME */
539 // TODO: use pre-initialized function pointer
540 // instead of branch forest
541 if (sort_opts == SORT_SIZE) {
542 dif = (d2->dstat.st_size - d1->dstat.st_size);
543 } else if (sort_opts == SORT_ATIME) {
544 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
545 } else if (sort_opts == SORT_CTIME) {
546 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
547 } else if (sort_opts == SORT_MTIME) {
548 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
549 } else if (sort_opts == SORT_DIR) {
550 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
551 /* } else if (sort_opts == SORT_VERSION) { */
552 /* } else if (sort_opts == SORT_EXT) { */
555 /* sort by name, or tie_breaker for other sorts */
556 if (ENABLE_LOCALE_SUPPORT)
557 dif = strcoll(d1->name, d2->name);
559 dif = strcmp(d1->name, d2->name);
562 /* Make dif fit into an int */
563 if (sizeof(dif) > sizeof(int)) {
564 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
565 /* shift leaving only "int" worth of bits */
567 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
571 return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
574 static void dnsort(struct dnode **dn, int size)
576 qsort(dn, size, sizeof(*dn), sortcmp);
579 #define dnsort(dn, size) ((void)0)
583 static unsigned calc_name_len(const char *name)
588 // TODO: quote tab as \t, etc, if -Q
589 name = printable_string(&uni_stat, name);
591 if (!(option_mask32 & OPT_Q)) {
592 return uni_stat.unicode_width;
595 len = 2 + uni_stat.unicode_width;
597 if (*name == '"' || *name == '\\') {
606 /* Return the number of used columns.
607 * Note that only STYLE_COLUMNAR uses return value.
608 * STYLE_SINGLE and STYLE_LONG don't care.
609 * coreutils 7.2 also supports:
610 * ls -b (--escape) = octal escapes (although it doesn't look like working)
611 * ls -N (--literal) = not escape at all
613 static unsigned print_name(const char *name)
618 // TODO: quote tab as \t, etc, if -Q
619 name = printable_string(&uni_stat, name);
621 if (!(option_mask32 & OPT_Q)) {
623 return uni_stat.unicode_width;
626 len = 2 + uni_stat.unicode_width;
629 if (*name == '"' || *name == '\\') {
640 /* Return the number of used columns.
641 * Note that only STYLE_COLUMNAR uses return value,
642 * STYLE_SINGLE and STYLE_LONG don't care.
644 static NOINLINE unsigned list_single(const struct dnode *dn)
647 char *lpath = lpath; /* for compiler */
648 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
654 if (dn->fullname == NULL)
658 #if ENABLE_FEATURE_LS_FILETYPES
659 append = append_char(dn->dstat.st_mode);
662 /* Do readlink early, so that if it fails, error message
663 * does not appear *inside* the "ls -l" line */
664 if (all_fmt & LIST_SYMLINK)
665 if (S_ISLNK(dn->dstat.st_mode))
666 lpath = xmalloc_readlink_or_warn(dn->fullname);
668 if (all_fmt & LIST_INO)
669 column += printf("%7llu ", (long long) dn->dstat.st_ino);
670 if (all_fmt & LIST_BLOCKS)
671 column += printf("%4"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
672 if (all_fmt & LIST_MODEBITS)
673 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
674 if (all_fmt & LIST_NLINKS)
675 column += printf("%4lu ", (long) dn->dstat.st_nlink);
676 #if ENABLE_FEATURE_LS_USERNAME
677 if (all_fmt & LIST_ID_NAME) {
678 if (option_mask32 & OPT_g) {
679 column += printf("%-8.8s ",
680 get_cached_groupname(dn->dstat.st_gid));
682 column += printf("%-8.8s %-8.8s ",
683 get_cached_username(dn->dstat.st_uid),
684 get_cached_groupname(dn->dstat.st_gid));
688 if (all_fmt & LIST_ID_NUMERIC) {
689 if (option_mask32 & OPT_g)
690 column += printf("%-8u ", (int) dn->dstat.st_gid);
692 column += printf("%-8u %-8u ",
693 (int) dn->dstat.st_uid,
694 (int) dn->dstat.st_gid);
696 if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
697 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
698 column += printf("%4u, %3u ",
699 (int) major(dn->dstat.st_rdev),
700 (int) minor(dn->dstat.st_rdev));
702 if (all_fmt & LS_DISP_HR) {
703 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
704 /* print st_size, show one fractional, use suffixes */
705 make_human_readable_str(dn->dstat.st_size, 1, 0)
708 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
712 #if ENABLE_FEATURE_LS_TIMESTAMPS
713 if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
715 time_t ttime = dn->dstat.st_mtime;
716 if (all_fmt & TIME_ACCESS)
717 ttime = dn->dstat.st_atime;
718 if (all_fmt & TIME_CHANGE)
719 ttime = dn->dstat.st_ctime;
720 filetime = ctime(&ttime);
721 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
722 if (all_fmt & LIST_FULLTIME)
723 column += printf("%.24s ", filetime);
724 else { /* LIST_DATE_TIME */
725 /* current_time_t ~== time(NULL) */
726 time_t age = current_time_t - ttime;
727 printf("%.6s ", filetime + 4); /* "Jun 30" */
728 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
729 /* hh:mm if less than 6 months old */
730 printf("%.5s ", filetime + 11);
731 } else { /* year. buggy if year > 9999 ;) */
732 printf(" %.4s ", filetime + 20);
739 if (all_fmt & LIST_CONTEXT) {
740 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
744 if (all_fmt & LIST_FILENAME) {
745 #if ENABLE_FEATURE_LS_COLOR
747 info.st_mode = 0; /* for fgcolor() */
748 lstat(dn->fullname, &info);
749 printf("\033[%u;%um", bold(info.st_mode),
750 fgcolor(info.st_mode));
753 column += print_name(dn->name);
758 if (all_fmt & LIST_SYMLINK) {
759 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
761 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
762 #if ENABLE_FEATURE_LS_COLOR
763 info.st_mode = 0; /* for fgcolor() */
765 if (stat(dn->fullname, &info) == 0) {
766 append = append_char(info.st_mode);
769 #if ENABLE_FEATURE_LS_COLOR
771 printf("\033[%u;%um", bold(info.st_mode),
772 fgcolor(info.st_mode));
775 column += print_name(lpath) + 4;
782 #if ENABLE_FEATURE_LS_FILETYPES
783 if (all_fmt & LIST_FILETYPE) {
794 static void showfiles(struct dnode **dn, unsigned nfiles)
796 unsigned i, ncols, nrows, row, nc;
799 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
801 if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
804 /* find the longest file name, use that as the column width */
805 for (i = 0; dn[i]; i++) {
806 int len = calc_name_len(dn[i]->name);
807 if (column_width < len)
811 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
812 ((all_fmt & LIST_INO) ? 8 : 0) +
813 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
814 ncols = (int) (terminal_width / column_width);
818 nrows = nfiles / ncols;
819 if (nrows * ncols < nfiles)
820 nrows++; /* round up fractionals */
828 for (row = 0; row < nrows; row++) {
829 for (nc = 0; nc < ncols; nc++) {
830 /* reach into the array based on the column and row */
831 if (all_fmt & DISP_ROWS)
832 i = (row * ncols) + nc; /* display across row */
834 i = (nc * nrows) + row; /* display by column */
838 printf("%*s ", nexttab, "");
839 column += nexttab + 1;
841 nexttab = column + column_width;
842 column += list_single(dn[i]);
852 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
853 * If any of the -l, -n, -s options is specified, each list
854 * of files within the directory shall be preceded by a
855 * status line indicating the number of file system blocks
856 * occupied by files in the directory in 512-byte units if
857 * the -k option is not specified, or 1024-byte units if the
858 * -k option is specified, rounded up to the next integral
861 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
862 static off_t calculate_blocks(struct dnode **dn)
867 /* st_blocks is in 512 byte blocks */
868 blocks += (*dn)->dstat.st_blocks;
873 /* Even though standard says use 512 byte blocks, coreutils use 1k */
874 /* Actually, we round up by calculating (blocks + 1) / 2,
875 * "+ 1" was done when we initialized blocks to 1 */
881 static struct dnode **list_dir(const char *, unsigned *);
883 static void showdirs(struct dnode **dn, int first)
887 struct dnode **subdnp;
891 if (dn == NULL || ndirs < 1) {
897 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
901 printf("%s:\n", (*dn)->fullname);
903 subdnp = list_dir((*dn)->fullname, &nfiles);
905 if ((all_fmt & STYLE_MASK) == STYLE_LONG)
906 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
909 /* list all files at this level */
910 dnsort(subdnp, nfiles);
911 showfiles(subdnp, nfiles);
912 if (ENABLE_FEATURE_LS_RECURSIVE
913 && (all_fmt & DISP_RECURSIVE)
915 /* recursive - list the sub-dirs */
916 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
917 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
921 /* free the array of dnode pointers to the dirs */
925 /* free the dnodes and the fullname mem */
932 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
933 static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
935 struct dnode *dn, *cur, **dnp;
936 struct dirent *entry;
946 dir = warn_opendir(path);
948 exit_code = EXIT_FAILURE;
949 return NULL; /* could not open the dir */
953 while ((entry = readdir(dir)) != NULL) {
956 /* are we going to list the file- it may be . or .. or a hidden file */
957 if (entry->d_name[0] == '.') {
958 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
959 && !(all_fmt & DISP_DOT)
963 if (!(all_fmt & DISP_HIDDEN))
966 fullname = concat_path_file(path, entry->d_name);
967 cur = my_stat(fullname, bb_basename(fullname), 0);
972 cur->fname_allocated = 1;
982 /* now that we know how many files there are
983 * allocate memory for an array to hold dnode pointers
986 dnp = dnalloc(nfiles);
987 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
988 dnp[i] = dn; /* save pointer to node in array */
998 int ls_main(int argc UNUSED_PARAM, char **argv)
1010 #if ENABLE_FEATURE_LS_COLOR
1011 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1013 * # ls --color=BOGUS
1014 * ls: invalid argument 'BOGUS' for '--color'
1015 * Valid arguments are:
1016 * 'always', 'yes', 'force'
1017 * 'never', 'no', 'none'
1018 * 'auto', 'tty', 'if-tty'
1019 * (and substrings: "--color=alwa" work too)
1021 static const char ls_longopts[] ALIGN1 =
1022 "color\0" Optional_argument "\xff"; /* no short equivalent */
1023 static const char color_str[] ALIGN1 =
1024 "always\0""yes\0""force\0"
1025 "auto\0""tty\0""if-tty\0";
1026 /* need to initialize since --color has _an optional_ argument */
1027 const char *color_opt = color_str; /* "always" */
1034 all_fmt = LIST_SHORT |
1035 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
1037 #if ENABLE_FEATURE_AUTOWIDTH
1038 /* obtain the terminal width */
1039 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
1040 /* go one less... */
1044 /* process options */
1045 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1049 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1050 * in some pairs of opts, only last one takes effect:
1052 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1053 // ":H-L:L-H:" - we don't have -H
1054 // ":m-l:l-m:" - we don't have -m
1055 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1056 ":C-1:1-C" /* bycols/oneline */
1057 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1058 ":c-u:u-c" /* mtime/atime */
1060 IF_FEATURE_AUTOWIDTH(":w+");
1061 opt = getopt32(argv, ls_options
1062 IF_FEATURE_AUTOWIDTH(, NULL, &terminal_width)
1063 IF_FEATURE_LS_COLOR(, &color_opt)
1065 for (i = 0; opt_flags[i] != (1U<<31); i++) {
1066 if (opt & (1 << i)) {
1067 unsigned flags = opt_flags[i];
1069 if (flags & STYLE_MASK)
1070 all_fmt &= ~STYLE_MASK;
1071 if (flags & SORT_MASK)
1072 all_fmt &= ~SORT_MASK;
1073 if (flags & TIME_MASK)
1074 all_fmt &= ~TIME_MASK;
1076 if (flags & LIST_CONTEXT)
1077 all_fmt |= STYLE_SINGLE;
1082 #if ENABLE_FEATURE_LS_COLOR
1083 /* find color bit value - last position for short getopt */
1084 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1085 char *p = getenv("LS_COLORS");
1086 /* LS_COLORS is unset, or (not empty && not "none") ? */
1087 if (!p || (p[0] && strcmp(p, "none") != 0))
1090 if (opt & OPT_color) {
1091 if (color_opt[0] == 'n')
1093 else switch (index_in_substrings(color_str, color_opt)) {
1097 if (isatty(STDOUT_FILENO)) {
1107 /* sort out which command line options take precedence */
1108 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1109 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1110 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1111 if (all_fmt & TIME_CHANGE)
1112 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1113 if (all_fmt & TIME_ACCESS)
1114 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1116 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1117 all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1118 if (ENABLE_FEATURE_LS_USERNAME)
1119 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1120 all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
1122 /* choose a display format if one was not already specified by an option */
1123 if (!(all_fmt & STYLE_MASK))
1124 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1128 *--argv = (char*)".";
1131 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1133 /* stuff the command line file names into a dnode array */
1137 /* NB: follow links on command line unless -l, -s or -F */
1138 cur = my_stat(*argv, *argv,
1139 !((all_fmt & (STYLE_LONG|LIST_BLOCKS)) || (option_mask32 & OPT_F))
1144 cur->fname_allocated = 0;
1150 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1154 /* now that we know how many files there are
1155 * allocate memory for an array to hold dnode pointers
1157 dnp = dnalloc(nfiles);
1158 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1159 dnp[i] = dn; /* save pointer to node in array */
1165 if (all_fmt & DISP_NOLIST) {
1166 dnsort(dnp, nfiles);
1167 showfiles(dnp, nfiles);
1169 dnd = splitdnarray(dnp, SPLIT_DIR);
1170 dnf = splitdnarray(dnp, SPLIT_FILE);
1171 dndirs = count_dirs(dnp, SPLIT_DIR);
1172 dnfiles = nfiles - dndirs;
1174 dnsort(dnf, dnfiles);
1175 showfiles(dnf, dnfiles);
1176 if (ENABLE_FEATURE_CLEAN_UP)
1180 dnsort(dnd, dndirs);
1181 showdirs(dnd, dnfiles == 0);
1182 if (ENABLE_FEATURE_CLEAN_UP)
1186 if (ENABLE_FEATURE_CLEAN_UP)