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 tarball for details.
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.
36 /* This is a NOEXEC applet. Be very careful! */
40 /* ftpd uses ls, and without timestamps Mozilla won't understand
43 # undef CONFIG_FEATURE_LS_TIMESTAMPS
44 # undef ENABLE_FEATURE_LS_TIMESTAMPS
45 # undef IF_FEATURE_LS_TIMESTAMPS
46 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
47 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
48 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
49 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
50 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
55 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
56 COLUMN_GAP = 2, /* includes the file type char */
58 /* what is the overall style of the listing */
59 STYLE_COLUMNS = 1 << 21, /* fill columns */
60 STYLE_LONG = 2 << 21, /* one record per line, extended info */
61 STYLE_SINGLE = 3 << 21, /* one record per line */
62 STYLE_MASK = STYLE_SINGLE,
64 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
65 /* what file information will be listed */
68 LIST_MODEBITS = 1 << 2,
70 LIST_ID_NAME = 1 << 4,
71 LIST_ID_NUMERIC = 1 << 5,
72 LIST_CONTEXT = 1 << 6,
74 //LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE
75 LIST_DATE_TIME = 1 << 9,
76 LIST_FULLTIME = 1 << 10,
77 LIST_FILENAME = 1 << 11,
78 LIST_SYMLINK = 1 << 12,
79 LIST_FILETYPE = 1 << 13,
81 LIST_MASK = (LIST_EXEC << 1) - 1,
83 /* what files will be displayed */
84 DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */
85 DISP_HIDDEN = 1 << 16, /* show filenames starting with . */
86 DISP_DOT = 1 << 17, /* show . and .. */
87 DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */
88 DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */
89 DISP_ROWS = 1 << 20, /* print across rows */
90 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
92 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
93 SORT_FORWARD = 0, /* sort in reverse order */
94 SORT_REVERSE = 1 << 27, /* sort in reverse order */
96 SORT_NAME = 0, /* sort by file name */
97 SORT_SIZE = 1 << 28, /* sort by file size */
98 SORT_ATIME = 2 << 28, /* sort by last access time */
99 SORT_CTIME = 3 << 28, /* sort by last change time */
100 SORT_MTIME = 4 << 28, /* sort by last modification time */
101 SORT_VERSION = 5 << 28, /* sort by version */
102 SORT_EXT = 6 << 28, /* sort by file name extension */
103 SORT_DIR = 7 << 28, /* sort by file or directory */
104 SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
106 /* which of the three times will be used */
107 TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
108 TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
109 TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
111 FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
113 LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
115 LIST_SHORT = LIST_FILENAME,
116 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
117 LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
124 /* "[-]Cadil1", POSIX mandated options, busybox always supports */
125 /* "[-]gnsx", POSIX non-mandated options, busybox always supports */
126 /* "[-]Q" GNU option? busybox always supports */
127 /* "[-]Ak" GNU options, busybox always supports */
128 /* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
129 /* "[-]p", POSIX non-mandated options, busybox optionally supports */
130 /* "[-]SXvThw", GNU options, busybox optionally supports */
131 /* "[-]K", SELinux mandated options, busybox optionally supports */
132 /* "[-]e", I think we made this one up */
133 static const char ls_options[] ALIGN1 =
134 "Cadil1gnsxQAk" /* 13 opts, total 13 */
135 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
136 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
137 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
138 IF_FEATURE_LS_FOLLOWLINKS("L") /* 1, 24 */
139 IF_FEATURE_LS_RECURSIVE("R") /* 1, 25 */
140 IF_FEATURE_HUMAN_READABLE("h") /* 1, 26 */
141 IF_SELINUX("KZ") /* 2, 28 */
142 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
159 + 4 * ENABLE_FEATURE_LS_TIMESTAMPS
160 + 4 * ENABLE_FEATURE_LS_SORTFILES
161 + 2 * ENABLE_FEATURE_LS_FILETYPES
162 + 1 * ENABLE_FEATURE_LS_FOLLOWLINKS
163 + 1 * ENABLE_FEATURE_LS_RECURSIVE
164 + 1 * ENABLE_FEATURE_HUMAN_READABLE
166 + 2 * ENABLE_FEATURE_AUTOWIDTH,
167 OPT_color = 1 << OPTBIT_color,
171 LIST_MASK_TRIGGER = 0,
172 STYLE_MASK_TRIGGER = STYLE_MASK,
173 DISP_MASK_TRIGGER = DISP_ROWS,
174 SORT_MASK_TRIGGER = SORT_MASK,
177 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
178 static const unsigned opt_flags[] = {
179 LIST_SHORT | STYLE_COLUMNS, /* C */
180 DISP_HIDDEN | DISP_DOT, /* a */
183 LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
184 LIST_SHORT | STYLE_SINGLE, /* 1 */
185 0, /* g (don't show group) - handled via OPT_g */
186 LIST_ID_NUMERIC, /* n */
189 0, /* Q (quote filename) - handled via OPT_Q */
191 ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
192 #if ENABLE_FEATURE_LS_TIMESTAMPS
193 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
194 LIST_FULLTIME, /* e */
195 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
196 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
198 #if ENABLE_FEATURE_LS_SORTFILES
201 SORT_REVERSE, /* r */
202 SORT_VERSION, /* v */
204 #if ENABLE_FEATURE_LS_FILETYPES
205 LIST_FILETYPE | LIST_EXEC, /* F */
206 LIST_FILETYPE, /* p */
208 #if ENABLE_FEATURE_LS_FOLLOWLINKS
209 FOLLOW_LINKS, /* L */
211 #if ENABLE_FEATURE_LS_RECURSIVE
212 DISP_RECURSIVE, /* R */
214 #if ENABLE_FEATURE_HUMAN_READABLE
218 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
221 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
224 /* options after Z are not processed through opt_flags:
231 * a directory entry and its stat info are stored here
234 const char *name; /* the dir entry name */
235 const char *fullname; /* the dir entry name */
236 struct dnode *next; /* point at the next node */
237 smallint fname_allocated;
238 struct stat dstat; /* the file stat info */
239 IF_SELINUX(security_context_t sid;)
243 #if ENABLE_FEATURE_LS_COLOR
248 #if ENABLE_FEATURE_AUTOWIDTH
249 unsigned tabstops; // = COLUMN_GAP;
250 unsigned terminal_width; // = TERMINAL_WIDTH;
252 #if ENABLE_FEATURE_LS_TIMESTAMPS
253 /* Do time() just once. Saves one syscall per file for "ls -l" */
254 time_t current_time_t;
257 #define G (*(struct globals*)&bb_common_bufsiz1)
258 #if ENABLE_FEATURE_LS_COLOR
259 # define show_color (G.show_color )
261 enum { show_color = 0 };
263 #define exit_code (G.exit_code )
264 #define all_fmt (G.all_fmt )
265 #if ENABLE_FEATURE_AUTOWIDTH
266 # define tabstops (G.tabstops )
267 # define terminal_width (G.terminal_width)
270 tabstops = COLUMN_GAP,
271 terminal_width = TERMINAL_WIDTH,
274 #define current_time_t (G.current_time_t)
275 #define INIT_G() do { \
276 /* we have to zero it out because of NOEXEC */ \
277 memset(&G, 0, sizeof(G)); \
278 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
279 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
280 IF_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \
284 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
288 IF_SELINUX(security_context_t sid = NULL;)
290 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
292 if (is_selinux_enabled()) {
293 getfilecon(fullname, &sid);
296 if (stat(fullname, &dstat)) {
297 bb_simple_perror_msg(fullname);
298 exit_code = EXIT_FAILURE;
303 if (is_selinux_enabled()) {
304 lgetfilecon(fullname, &sid);
307 if (lstat(fullname, &dstat)) {
308 bb_simple_perror_msg(fullname);
309 exit_code = EXIT_FAILURE;
314 cur = xmalloc(sizeof(*cur));
315 cur->fullname = fullname;
318 IF_SELINUX(cur->sid = sid;)
322 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
323 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
324 * 3/7:multiplexed char/block device)
325 * and we use 0 for unknown and 15 for executables (see below) */
326 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
327 #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
328 #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
329 /* 036 black foreground 050 black background
330 037 red foreground 051 red background
331 040 green foreground 052 green background
332 041 brown foreground 053 brown background
333 042 blue foreground 054 blue background
334 043 magenta (purple) foreground 055 magenta background
335 044 cyan (light blue) foreground 056 cyan background
336 045 gray foreground 057 white background
338 #define COLOR(mode) ( \
339 /*un fi chr dir blk file link sock exe */ \
340 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
342 /* Select normal (0) [actually "reset all"] or bold (1)
343 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
344 * let's use 7 for "impossible" types, just for fun)
345 * Note: coreutils 6.9 uses inverted red for setuid binaries.
347 #define ATTR(mode) ( \
348 /*un fi chr dir blk file link sock exe */ \
349 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
352 #if ENABLE_FEATURE_LS_COLOR
353 /* mode of zero is interpreted as "unknown" (stat failed) */
354 static char fgcolor(mode_t mode)
356 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
357 return COLOR(0xF000); /* File is executable ... */
360 static char bold(mode_t mode)
362 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
363 return ATTR(0xF000); /* File is executable ... */
368 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
369 static char append_char(mode_t mode)
371 if (!(all_fmt & LIST_FILETYPE))
375 if (!(all_fmt & LIST_EXEC))
377 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
379 return APPCHAR(mode);
383 static unsigned count_dirs(struct dnode **dn, int which)
395 if (!S_ISDIR((*dn)->dstat.st_mode))
398 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
399 /* or if it's not . or .. */
400 || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
405 return which != SPLIT_FILE ? dirs : all - dirs;
408 /* get memory to hold an array of pointers */
409 static struct dnode **dnalloc(unsigned num)
414 num++; /* so that we have terminating NULL */
415 return xzalloc(num * sizeof(struct dnode *));
418 #if ENABLE_FEATURE_LS_RECURSIVE
419 static void dfree(struct dnode **dnp)
426 for (i = 0; dnp[i]; i++) {
427 struct dnode *cur = dnp[i];
428 if (cur->fname_allocated)
429 free((char*)cur->fullname);
435 #define dfree(...) ((void)0)
438 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
439 static struct dnode **splitdnarray(struct dnode **dn, int which)
447 /* count how many dirs or files there are */
448 dncnt = count_dirs(dn, which);
450 /* allocate a file array and a dir array */
451 dnp = dnalloc(dncnt);
453 /* copy the entrys into the file or dir array */
454 for (d = 0; *dn; dn++) {
455 if (S_ISDIR((*dn)->dstat.st_mode)) {
458 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
461 if ((which & SPLIT_DIR)
462 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
466 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
473 #if ENABLE_FEATURE_LS_SORTFILES
474 static int sortcmp(const void *a, const void *b)
476 struct dnode *d1 = *(struct dnode **)a;
477 struct dnode *d2 = *(struct dnode **)b;
478 unsigned sort_opts = all_fmt & SORT_MASK;
481 dif = 0; /* assume SORT_NAME */
482 // TODO: use pre-initialized function pointer
483 // instead of branch forest
484 if (sort_opts == SORT_SIZE) {
485 dif = (d2->dstat.st_size - d1->dstat.st_size);
486 } else if (sort_opts == SORT_ATIME) {
487 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
488 } else if (sort_opts == SORT_CTIME) {
489 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
490 } else if (sort_opts == SORT_MTIME) {
491 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
492 } else if (sort_opts == SORT_DIR) {
493 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
494 /* } else if (sort_opts == SORT_VERSION) { */
495 /* } else if (sort_opts == SORT_EXT) { */
498 /* sort by name, or tie_breaker for other sorts */
499 if (ENABLE_LOCALE_SUPPORT)
500 dif = strcoll(d1->name, d2->name);
502 dif = strcmp(d1->name, d2->name);
505 /* Make dif fit into an int */
506 if (sizeof(dif) > sizeof(int)) {
507 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
508 /* shift leaving only "int" worth of bits */
510 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
514 return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
517 static void dnsort(struct dnode **dn, int size)
519 qsort(dn, size, sizeof(*dn), sortcmp);
522 #define dnsort(dn, size) ((void)0)
526 static unsigned calc_name_len(const char *name)
531 // TODO: quote tab as \t, etc, if -Q
532 name = printable_string(&uni_stat, name);
534 if (!(option_mask32 & OPT_Q)) {
535 return uni_stat.unicode_width;
538 len = 2 + uni_stat.unicode_width;
540 if (*name == '"' || *name == '\\') {
549 /* Return the number of used columns.
550 * Note that only STYLE_COLUMNS uses return value.
551 * STYLE_SINGLE and STYLE_LONG don't care.
552 * coreutils 7.2 also supports:
553 * ls -b (--escape) = octal escapes (although it doesn't look like working)
554 * ls -N (--literal) = not escape at all
556 static unsigned print_name(const char *name)
561 // TODO: quote tab as \t, etc, if -Q
562 name = printable_string(&uni_stat, name);
564 if (!(option_mask32 & OPT_Q)) {
566 return uni_stat.unicode_width;
569 len = 2 + uni_stat.unicode_width;
572 if (*name == '"' || *name == '\\') {
582 /* Return the number of used columns.
583 * Note that only STYLE_COLUMNS uses return value,
584 * STYLE_SINGLE and STYLE_LONG don't care.
586 static NOINLINE unsigned list_single(const struct dnode *dn)
589 char *lpath = lpath; /* for compiler */
590 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
596 if (dn->fullname == NULL)
600 #if ENABLE_FEATURE_LS_FILETYPES
601 append = append_char(dn->dstat.st_mode);
604 /* Do readlink early, so that if it fails, error message
605 * does not appear *inside* the "ls -l" line */
606 if (all_fmt & LIST_SYMLINK)
607 if (S_ISLNK(dn->dstat.st_mode))
608 lpath = xmalloc_readlink_or_warn(dn->fullname);
610 if (all_fmt & LIST_INO)
611 column += printf("%7llu ", (long long) dn->dstat.st_ino);
612 if (all_fmt & LIST_BLOCKS)
613 column += printf("%4"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
614 if (all_fmt & LIST_MODEBITS)
615 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
616 if (all_fmt & LIST_NLINKS)
617 column += printf("%4lu ", (long) dn->dstat.st_nlink);
618 #if ENABLE_FEATURE_LS_USERNAME
619 if (all_fmt & LIST_ID_NAME) {
620 if (option_mask32 & OPT_g) {
621 column += printf("%-8.8s ",
622 get_cached_username(dn->dstat.st_uid));
624 column += printf("%-8.8s %-8.8s ",
625 get_cached_username(dn->dstat.st_uid),
626 get_cached_groupname(dn->dstat.st_gid));
630 if (all_fmt & LIST_ID_NUMERIC) {
631 if (option_mask32 & OPT_g)
632 column += printf("%-8u ", (int) dn->dstat.st_uid);
634 column += printf("%-8u %-8u ",
635 (int) dn->dstat.st_uid,
636 (int) dn->dstat.st_gid);
638 if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
639 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
640 column += printf("%4u, %3u ",
641 (int) major(dn->dstat.st_rdev),
642 (int) minor(dn->dstat.st_rdev));
644 if (all_fmt & LS_DISP_HR) {
645 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
646 /* print st_size, show one fractional, use suffixes */
647 make_human_readable_str(dn->dstat.st_size, 1, 0)
650 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
654 #if ENABLE_FEATURE_LS_TIMESTAMPS
655 if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
657 time_t ttime = dn->dstat.st_mtime;
658 if (all_fmt & TIME_ACCESS)
659 ttime = dn->dstat.st_atime;
660 if (all_fmt & TIME_CHANGE)
661 ttime = dn->dstat.st_ctime;
662 filetime = ctime(&ttime);
663 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
664 if (all_fmt & LIST_FULLTIME)
665 column += printf("%.24s ", filetime);
666 else { /* LIST_DATE_TIME */
667 /* current_time_t ~== time(NULL) */
668 time_t age = current_time_t - ttime;
669 printf("%.6s ", filetime + 4); /* "Jun 30" */
670 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
671 /* hh:mm if less than 6 months old */
672 printf("%.5s ", filetime + 11);
673 } else { /* year. buggy if year > 9999 ;) */
674 printf(" %.4s ", filetime + 20);
681 if (all_fmt & LIST_CONTEXT) {
682 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
686 if (all_fmt & LIST_FILENAME) {
687 #if ENABLE_FEATURE_LS_COLOR
689 info.st_mode = 0; /* for fgcolor() */
690 lstat(dn->fullname, &info);
691 printf("\033[%u;%um", bold(info.st_mode),
692 fgcolor(info.st_mode));
695 column += print_name(dn->name);
700 if (all_fmt & LIST_SYMLINK) {
701 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
703 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
704 #if ENABLE_FEATURE_LS_COLOR
705 info.st_mode = 0; /* for fgcolor() */
707 if (stat(dn->fullname, &info) == 0) {
708 append = append_char(info.st_mode);
711 #if ENABLE_FEATURE_LS_COLOR
713 printf("\033[%u;%um", bold(info.st_mode),
714 fgcolor(info.st_mode));
717 column += print_name(lpath) + 4;
724 #if ENABLE_FEATURE_LS_FILETYPES
725 if (all_fmt & LIST_FILETYPE) {
736 static void showfiles(struct dnode **dn, unsigned nfiles)
738 unsigned i, ncols, nrows, row, nc;
740 unsigned nexttab = 0;
741 unsigned column_width = 0; /* used only by STYLE_COLUMNS */
743 if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
746 /* find the longest file name, use that as the column width */
747 for (i = 0; dn[i]; i++) {
748 int len = calc_name_len(dn[i]->name);
749 if (column_width < len)
752 column_width += tabstops +
753 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
754 ((all_fmt & LIST_INO) ? 8 : 0) +
755 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
756 ncols = (int) (terminal_width / column_width);
760 nrows = nfiles / ncols;
761 if (nrows * ncols < nfiles)
762 nrows++; /* round up fractionals */
768 for (row = 0; row < nrows; row++) {
769 for (nc = 0; nc < ncols; nc++) {
770 /* reach into the array based on the column and row */
771 if (all_fmt & DISP_ROWS)
772 i = (row * ncols) + nc; /* display across row */
774 i = (nc * nrows) + row; /* display by column */
778 printf("%*s", nexttab, "");
781 nexttab = column + column_width;
782 column += list_single(dn[i]);
792 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
793 * If any of the -l, -n, -s options is specified, each list
794 * of files within the directory shall be preceded by a
795 * status line indicating the number of file system blocks
796 * occupied by files in the directory in 512-byte units if
797 * the -k option is not specified, or 1024-byte units if the
798 * -k option is specified, rounded up to the next integral
801 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
802 static off_t calculate_blocks(struct dnode **dn)
807 /* st_blocks is in 512 byte blocks */
808 blocks += (*dn)->dstat.st_blocks;
813 /* Even though standard says use 512 byte blocks, coreutils use 1k */
814 /* Actually, we round up by calculating (blocks + 1) / 2,
815 * "+ 1" was done when we initialized blocks to 1 */
821 static struct dnode **list_dir(const char *, unsigned *);
823 static void showdirs(struct dnode **dn, int first)
827 struct dnode **subdnp;
831 if (dn == NULL || ndirs < 1) {
837 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
841 printf("%s:\n", (*dn)->fullname);
843 subdnp = list_dir((*dn)->fullname, &nfiles);
845 if ((all_fmt & STYLE_MASK) == STYLE_LONG)
846 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
849 /* list all files at this level */
850 dnsort(subdnp, nfiles);
851 showfiles(subdnp, nfiles);
852 if (ENABLE_FEATURE_LS_RECURSIVE
853 && (all_fmt & DISP_RECURSIVE)
855 /* recursive - list the sub-dirs */
856 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
857 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
861 /* free the array of dnode pointers to the dirs */
865 /* free the dnodes and the fullname mem */
872 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
873 static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
875 struct dnode *dn, *cur, **dnp;
876 struct dirent *entry;
886 dir = warn_opendir(path);
888 exit_code = EXIT_FAILURE;
889 return NULL; /* could not open the dir */
893 while ((entry = readdir(dir)) != NULL) {
896 /* are we going to list the file- it may be . or .. or a hidden file */
897 if (entry->d_name[0] == '.') {
898 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
899 && !(all_fmt & DISP_DOT)
903 if (!(all_fmt & DISP_HIDDEN))
906 fullname = concat_path_file(path, entry->d_name);
907 cur = my_stat(fullname, bb_basename(fullname), 0);
912 cur->fname_allocated = 1;
922 /* now that we know how many files there are
923 * allocate memory for an array to hold dnode pointers
926 dnp = dnalloc(nfiles);
927 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
928 dnp[i] = dn; /* save pointer to node in array */
938 int ls_main(int argc UNUSED_PARAM, char **argv)
950 #if ENABLE_FEATURE_LS_COLOR
951 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
954 * ls: invalid argument 'BOGUS' for '--color'
955 * Valid arguments are:
956 * 'always', 'yes', 'force'
957 * 'never', 'no', 'none'
958 * 'auto', 'tty', 'if-tty'
959 * (and substrings: "--color=alwa" work too)
961 static const char ls_longopts[] ALIGN1 =
962 "color\0" Optional_argument "\xff"; /* no short equivalent */
963 static const char color_str[] ALIGN1 =
964 "always\0""yes\0""force\0"
965 "auto\0""tty\0""if-tty\0";
966 /* need to initialize since --color has _an optional_ argument */
967 const char *color_opt = color_str; /* "always" */
974 all_fmt = LIST_SHORT |
975 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
977 #if ENABLE_FEATURE_AUTOWIDTH
978 /* obtain the terminal width */
979 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
984 /* process options */
985 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
986 #if ENABLE_FEATURE_AUTOWIDTH
987 opt_complementary = "T+:w+"; /* -T N, -w N */
988 opt = getopt32(argv, ls_options, &tabstops, &terminal_width
989 IF_FEATURE_LS_COLOR(, &color_opt));
991 opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
993 for (i = 0; opt_flags[i] != (1U<<31); i++) {
994 if (opt & (1 << i)) {
995 unsigned flags = opt_flags[i];
997 if (flags & LIST_MASK_TRIGGER)
998 all_fmt &= ~LIST_MASK;
999 if (flags & STYLE_MASK_TRIGGER)
1000 all_fmt &= ~STYLE_MASK;
1001 if (flags & SORT_MASK_TRIGGER)
1002 all_fmt &= ~SORT_MASK;
1003 if (flags & DISP_MASK_TRIGGER)
1004 all_fmt &= ~DISP_MASK;
1005 if (flags & TIME_MASK)
1006 all_fmt &= ~TIME_MASK;
1007 if (flags & LIST_CONTEXT)
1008 all_fmt |= STYLE_SINGLE;
1009 /* huh?? opt cannot be 'l' */
1010 //if (LS_DISP_HR && opt == 'l')
1011 // all_fmt &= ~LS_DISP_HR;
1016 #if ENABLE_FEATURE_LS_COLOR
1017 /* find color bit value - last position for short getopt */
1018 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1019 char *p = getenv("LS_COLORS");
1020 /* LS_COLORS is unset, or (not empty && not "none") ? */
1021 if (!p || (p[0] && strcmp(p, "none") != 0))
1024 if (opt & OPT_color) {
1025 if (color_opt[0] == 'n')
1027 else switch (index_in_substrings(color_str, color_opt)) {
1031 if (isatty(STDOUT_FILENO)) {
1041 /* sort out which command line options take precedence */
1042 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1043 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1044 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1045 if (all_fmt & TIME_CHANGE)
1046 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1047 if (all_fmt & TIME_ACCESS)
1048 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1050 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1051 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
1052 if (ENABLE_FEATURE_LS_USERNAME)
1053 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1054 all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
1056 /* choose a display format */
1057 if (!(all_fmt & STYLE_MASK))
1058 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1062 *--argv = (char*)".";
1065 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1067 /* stuff the command line file names into a dnode array */
1071 /* NB: follow links on command line unless -l or -s */
1072 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1076 cur->fname_allocated = 0;
1082 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1086 /* now that we know how many files there are
1087 * allocate memory for an array to hold dnode pointers
1089 dnp = dnalloc(nfiles);
1090 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1091 dnp[i] = dn; /* save pointer to node in array */
1097 if (all_fmt & DISP_NOLIST) {
1098 dnsort(dnp, nfiles);
1099 showfiles(dnp, nfiles);
1101 dnd = splitdnarray(dnp, SPLIT_DIR);
1102 dnf = splitdnarray(dnp, SPLIT_FILE);
1103 dndirs = count_dirs(dnp, SPLIT_DIR);
1104 dnfiles = nfiles - dndirs;
1106 dnsort(dnf, dnfiles);
1107 showfiles(dnf, dnfiles);
1108 if (ENABLE_FEATURE_CLEAN_UP)
1112 dnsort(dnd, dndirs);
1113 showdirs(dnd, dnfiles == 0);
1114 if (ENABLE_FEATURE_CLEAN_UP)
1118 if (ENABLE_FEATURE_CLEAN_UP)