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.
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_COLUMNAR = 1 << 21, /* many records per line */
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 OPTBIT_color = OPTBIT_F
162 + 2 * ENABLE_FEATURE_LS_FILETYPES
163 + 1 * ENABLE_FEATURE_LS_FOLLOWLINKS
164 + 1 * ENABLE_FEATURE_LS_RECURSIVE
165 + 1 * ENABLE_FEATURE_HUMAN_READABLE
167 + 2 * ENABLE_FEATURE_AUTOWIDTH,
168 OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES,
169 OPT_color = 1 << OPTBIT_color,
172 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
173 static const unsigned opt_flags[] = {
174 LIST_SHORT | STYLE_COLUMNAR, /* C */
175 DISP_HIDDEN | DISP_DOT, /* a */
178 LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
179 LIST_SHORT | STYLE_SINGLE, /* 1 */
180 0, /* g (don't show owner) - handled via OPT_g */
181 LIST_ID_NUMERIC, /* n */
183 LIST_SHORT | DISP_ROWS | STYLE_COLUMNAR, /* x */
184 0, /* Q (quote filename) - handled via OPT_Q */
186 ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
187 #if ENABLE_FEATURE_LS_TIMESTAMPS
188 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
189 LIST_FULLTIME, /* e */
190 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
191 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
193 #if ENABLE_FEATURE_LS_SORTFILES
196 SORT_REVERSE, /* r */
197 SORT_VERSION, /* v */
199 #if ENABLE_FEATURE_LS_FILETYPES
200 LIST_FILETYPE | LIST_EXEC, /* F */
201 LIST_FILETYPE, /* p */
203 #if ENABLE_FEATURE_LS_FOLLOWLINKS
204 FOLLOW_LINKS, /* L */
206 #if ENABLE_FEATURE_LS_RECURSIVE
207 DISP_RECURSIVE, /* R */
209 #if ENABLE_FEATURE_HUMAN_READABLE
213 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
216 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
219 /* options after Z are not processed through opt_flags:
226 * a directory entry and its stat info are stored here
229 const char *name; /* the dir entry name */
230 const char *fullname; /* the dir entry name */
231 struct dnode *next; /* point at the next node */
232 smallint fname_allocated;
233 struct stat dstat; /* the file stat info */
234 IF_SELINUX(security_context_t sid;)
238 #if ENABLE_FEATURE_LS_COLOR
243 #if ENABLE_FEATURE_AUTOWIDTH
244 unsigned tabstops; // = COLUMN_GAP;
245 unsigned terminal_width; // = TERMINAL_WIDTH;
247 #if ENABLE_FEATURE_LS_TIMESTAMPS
248 /* Do time() just once. Saves one syscall per file for "ls -l" */
249 time_t current_time_t;
252 #define G (*(struct globals*)&bb_common_bufsiz1)
253 #if ENABLE_FEATURE_LS_COLOR
254 # define show_color (G.show_color )
256 enum { show_color = 0 };
258 #define exit_code (G.exit_code )
259 #define all_fmt (G.all_fmt )
260 #if ENABLE_FEATURE_AUTOWIDTH
261 # define tabstops (G.tabstops )
262 # define terminal_width (G.terminal_width)
265 tabstops = COLUMN_GAP,
266 terminal_width = TERMINAL_WIDTH,
269 #define current_time_t (G.current_time_t)
270 #define INIT_G() do { \
271 /* we have to zero it out because of NOEXEC */ \
272 memset(&G, 0, sizeof(G)); \
273 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
274 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
275 IF_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \
279 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
283 IF_SELINUX(security_context_t sid = NULL;)
285 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
287 if (is_selinux_enabled()) {
288 getfilecon(fullname, &sid);
291 if (stat(fullname, &dstat)) {
292 bb_simple_perror_msg(fullname);
293 exit_code = EXIT_FAILURE;
298 if (is_selinux_enabled()) {
299 lgetfilecon(fullname, &sid);
302 if (lstat(fullname, &dstat)) {
303 bb_simple_perror_msg(fullname);
304 exit_code = EXIT_FAILURE;
309 cur = xmalloc(sizeof(*cur));
310 cur->fullname = fullname;
313 IF_SELINUX(cur->sid = sid;)
317 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
318 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
319 * 3/7:multiplexed char/block device)
320 * and we use 0 for unknown and 15 for executables (see below) */
321 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
322 #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
323 #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
324 /* 036 black foreground 050 black background
325 037 red foreground 051 red background
326 040 green foreground 052 green background
327 041 brown foreground 053 brown background
328 042 blue foreground 054 blue background
329 043 magenta (purple) foreground 055 magenta background
330 044 cyan (light blue) foreground 056 cyan background
331 045 gray foreground 057 white background
333 #define COLOR(mode) ( \
334 /*un fi chr dir blk file link sock exe */ \
335 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
337 /* Select normal (0) [actually "reset all"] or bold (1)
338 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
339 * let's use 7 for "impossible" types, just for fun)
340 * Note: coreutils 6.9 uses inverted red for setuid binaries.
342 #define ATTR(mode) ( \
343 /*un fi chr dir blk file link sock exe */ \
344 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
347 #if ENABLE_FEATURE_LS_COLOR
348 /* mode of zero is interpreted as "unknown" (stat failed) */
349 static char fgcolor(mode_t mode)
351 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
352 return COLOR(0xF000); /* File is executable ... */
355 static char bold(mode_t mode)
357 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
358 return ATTR(0xF000); /* File is executable ... */
363 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
364 static char append_char(mode_t mode)
366 if (!(all_fmt & LIST_FILETYPE))
370 if (!(all_fmt & LIST_EXEC))
372 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
374 return APPCHAR(mode);
378 static unsigned count_dirs(struct dnode **dn, int which)
390 if (!S_ISDIR((*dn)->dstat.st_mode))
393 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
394 /* or if it's not . or .. */
395 || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
400 return which != SPLIT_FILE ? dirs : all - dirs;
403 /* get memory to hold an array of pointers */
404 static struct dnode **dnalloc(unsigned num)
409 num++; /* so that we have terminating NULL */
410 return xzalloc(num * sizeof(struct dnode *));
413 #if ENABLE_FEATURE_LS_RECURSIVE
414 static void dfree(struct dnode **dnp)
421 for (i = 0; dnp[i]; i++) {
422 struct dnode *cur = dnp[i];
423 if (cur->fname_allocated)
424 free((char*)cur->fullname);
430 #define dfree(...) ((void)0)
433 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
434 static struct dnode **splitdnarray(struct dnode **dn, int which)
442 /* count how many dirs or files there are */
443 dncnt = count_dirs(dn, which);
445 /* allocate a file array and a dir array */
446 dnp = dnalloc(dncnt);
448 /* copy the entrys into the file or dir array */
449 for (d = 0; *dn; dn++) {
450 if (S_ISDIR((*dn)->dstat.st_mode)) {
453 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
456 if ((which & SPLIT_DIR)
457 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
461 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
468 #if ENABLE_FEATURE_LS_SORTFILES
469 static int sortcmp(const void *a, const void *b)
471 struct dnode *d1 = *(struct dnode **)a;
472 struct dnode *d2 = *(struct dnode **)b;
473 unsigned sort_opts = all_fmt & SORT_MASK;
476 dif = 0; /* assume SORT_NAME */
477 // TODO: use pre-initialized function pointer
478 // instead of branch forest
479 if (sort_opts == SORT_SIZE) {
480 dif = (d2->dstat.st_size - d1->dstat.st_size);
481 } else if (sort_opts == SORT_ATIME) {
482 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
483 } else if (sort_opts == SORT_CTIME) {
484 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
485 } else if (sort_opts == SORT_MTIME) {
486 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
487 } else if (sort_opts == SORT_DIR) {
488 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
489 /* } else if (sort_opts == SORT_VERSION) { */
490 /* } else if (sort_opts == SORT_EXT) { */
493 /* sort by name, or tie_breaker for other sorts */
494 if (ENABLE_LOCALE_SUPPORT)
495 dif = strcoll(d1->name, d2->name);
497 dif = strcmp(d1->name, d2->name);
500 /* Make dif fit into an int */
501 if (sizeof(dif) > sizeof(int)) {
502 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
503 /* shift leaving only "int" worth of bits */
505 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
509 return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
512 static void dnsort(struct dnode **dn, int size)
514 qsort(dn, size, sizeof(*dn), sortcmp);
517 #define dnsort(dn, size) ((void)0)
521 static unsigned calc_name_len(const char *name)
526 // TODO: quote tab as \t, etc, if -Q
527 name = printable_string(&uni_stat, name);
529 if (!(option_mask32 & OPT_Q)) {
530 return uni_stat.unicode_width;
533 len = 2 + uni_stat.unicode_width;
535 if (*name == '"' || *name == '\\') {
544 /* Return the number of used columns.
545 * Note that only STYLE_COLUMNAR uses return value.
546 * STYLE_SINGLE and STYLE_LONG don't care.
547 * coreutils 7.2 also supports:
548 * ls -b (--escape) = octal escapes (although it doesn't look like working)
549 * ls -N (--literal) = not escape at all
551 static unsigned print_name(const char *name)
556 // TODO: quote tab as \t, etc, if -Q
557 name = printable_string(&uni_stat, name);
559 if (!(option_mask32 & OPT_Q)) {
561 return uni_stat.unicode_width;
564 len = 2 + uni_stat.unicode_width;
567 if (*name == '"' || *name == '\\') {
578 /* Return the number of used columns.
579 * Note that only STYLE_COLUMNAR uses return value,
580 * STYLE_SINGLE and STYLE_LONG don't care.
582 static NOINLINE unsigned list_single(const struct dnode *dn)
585 char *lpath = lpath; /* for compiler */
586 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
592 if (dn->fullname == NULL)
596 #if ENABLE_FEATURE_LS_FILETYPES
597 append = append_char(dn->dstat.st_mode);
600 /* Do readlink early, so that if it fails, error message
601 * does not appear *inside* the "ls -l" line */
602 if (all_fmt & LIST_SYMLINK)
603 if (S_ISLNK(dn->dstat.st_mode))
604 lpath = xmalloc_readlink_or_warn(dn->fullname);
606 if (all_fmt & LIST_INO)
607 column += printf("%7llu ", (long long) dn->dstat.st_ino);
608 if (all_fmt & LIST_BLOCKS)
609 column += printf("%4"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
610 if (all_fmt & LIST_MODEBITS)
611 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
612 if (all_fmt & LIST_NLINKS)
613 column += printf("%4lu ", (long) dn->dstat.st_nlink);
614 #if ENABLE_FEATURE_LS_USERNAME
615 if (all_fmt & LIST_ID_NAME) {
616 if (option_mask32 & OPT_g) {
617 column += printf("%-8.8s ",
618 get_cached_groupname(dn->dstat.st_gid));
620 column += printf("%-8.8s %-8.8s ",
621 get_cached_username(dn->dstat.st_uid),
622 get_cached_groupname(dn->dstat.st_gid));
626 if (all_fmt & LIST_ID_NUMERIC) {
627 if (option_mask32 & OPT_g)
628 column += printf("%-8u ", (int) dn->dstat.st_gid);
630 column += printf("%-8u %-8u ",
631 (int) dn->dstat.st_uid,
632 (int) dn->dstat.st_gid);
634 if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
635 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
636 column += printf("%4u, %3u ",
637 (int) major(dn->dstat.st_rdev),
638 (int) minor(dn->dstat.st_rdev));
640 if (all_fmt & LS_DISP_HR) {
641 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
642 /* print st_size, show one fractional, use suffixes */
643 make_human_readable_str(dn->dstat.st_size, 1, 0)
646 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
650 #if ENABLE_FEATURE_LS_TIMESTAMPS
651 if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
653 time_t ttime = dn->dstat.st_mtime;
654 if (all_fmt & TIME_ACCESS)
655 ttime = dn->dstat.st_atime;
656 if (all_fmt & TIME_CHANGE)
657 ttime = dn->dstat.st_ctime;
658 filetime = ctime(&ttime);
659 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
660 if (all_fmt & LIST_FULLTIME)
661 column += printf("%.24s ", filetime);
662 else { /* LIST_DATE_TIME */
663 /* current_time_t ~== time(NULL) */
664 time_t age = current_time_t - ttime;
665 printf("%.6s ", filetime + 4); /* "Jun 30" */
666 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
667 /* hh:mm if less than 6 months old */
668 printf("%.5s ", filetime + 11);
669 } else { /* year. buggy if year > 9999 ;) */
670 printf(" %.4s ", filetime + 20);
677 if (all_fmt & LIST_CONTEXT) {
678 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
682 if (all_fmt & LIST_FILENAME) {
683 #if ENABLE_FEATURE_LS_COLOR
685 info.st_mode = 0; /* for fgcolor() */
686 lstat(dn->fullname, &info);
687 printf("\033[%u;%um", bold(info.st_mode),
688 fgcolor(info.st_mode));
691 column += print_name(dn->name);
696 if (all_fmt & LIST_SYMLINK) {
697 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
699 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
700 #if ENABLE_FEATURE_LS_COLOR
701 info.st_mode = 0; /* for fgcolor() */
703 if (stat(dn->fullname, &info) == 0) {
704 append = append_char(info.st_mode);
707 #if ENABLE_FEATURE_LS_COLOR
709 printf("\033[%u;%um", bold(info.st_mode),
710 fgcolor(info.st_mode));
713 column += print_name(lpath) + 4;
720 #if ENABLE_FEATURE_LS_FILETYPES
721 if (all_fmt & LIST_FILETYPE) {
732 static void showfiles(struct dnode **dn, unsigned nfiles)
734 unsigned i, ncols, nrows, row, nc;
736 unsigned nexttab = 0;
737 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
739 if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
742 /* find the longest file name, use that as the column width */
743 for (i = 0; dn[i]; i++) {
744 int len = calc_name_len(dn[i]->name);
745 if (column_width < len)
748 column_width += tabstops +
749 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
750 ((all_fmt & LIST_INO) ? 8 : 0) +
751 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
752 ncols = (int) (terminal_width / column_width);
756 nrows = nfiles / ncols;
757 if (nrows * ncols < nfiles)
758 nrows++; /* round up fractionals */
764 for (row = 0; row < nrows; row++) {
765 for (nc = 0; nc < ncols; nc++) {
766 /* reach into the array based on the column and row */
767 if (all_fmt & DISP_ROWS)
768 i = (row * ncols) + nc; /* display across row */
770 i = (nc * nrows) + row; /* display by column */
774 printf("%*s", nexttab, "");
777 nexttab = column + column_width;
778 column += list_single(dn[i]);
788 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
789 * If any of the -l, -n, -s options is specified, each list
790 * of files within the directory shall be preceded by a
791 * status line indicating the number of file system blocks
792 * occupied by files in the directory in 512-byte units if
793 * the -k option is not specified, or 1024-byte units if the
794 * -k option is specified, rounded up to the next integral
797 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
798 static off_t calculate_blocks(struct dnode **dn)
803 /* st_blocks is in 512 byte blocks */
804 blocks += (*dn)->dstat.st_blocks;
809 /* Even though standard says use 512 byte blocks, coreutils use 1k */
810 /* Actually, we round up by calculating (blocks + 1) / 2,
811 * "+ 1" was done when we initialized blocks to 1 */
817 static struct dnode **list_dir(const char *, unsigned *);
819 static void showdirs(struct dnode **dn, int first)
823 struct dnode **subdnp;
827 if (dn == NULL || ndirs < 1) {
833 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
837 printf("%s:\n", (*dn)->fullname);
839 subdnp = list_dir((*dn)->fullname, &nfiles);
841 if ((all_fmt & STYLE_MASK) == STYLE_LONG)
842 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
845 /* list all files at this level */
846 dnsort(subdnp, nfiles);
847 showfiles(subdnp, nfiles);
848 if (ENABLE_FEATURE_LS_RECURSIVE
849 && (all_fmt & DISP_RECURSIVE)
851 /* recursive - list the sub-dirs */
852 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
853 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
857 /* free the array of dnode pointers to the dirs */
861 /* free the dnodes and the fullname mem */
868 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
869 static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
871 struct dnode *dn, *cur, **dnp;
872 struct dirent *entry;
882 dir = warn_opendir(path);
884 exit_code = EXIT_FAILURE;
885 return NULL; /* could not open the dir */
889 while ((entry = readdir(dir)) != NULL) {
892 /* are we going to list the file- it may be . or .. or a hidden file */
893 if (entry->d_name[0] == '.') {
894 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
895 && !(all_fmt & DISP_DOT)
899 if (!(all_fmt & DISP_HIDDEN))
902 fullname = concat_path_file(path, entry->d_name);
903 cur = my_stat(fullname, bb_basename(fullname), 0);
908 cur->fname_allocated = 1;
918 /* now that we know how many files there are
919 * allocate memory for an array to hold dnode pointers
922 dnp = dnalloc(nfiles);
923 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
924 dnp[i] = dn; /* save pointer to node in array */
934 int ls_main(int argc UNUSED_PARAM, char **argv)
946 #if ENABLE_FEATURE_LS_COLOR
947 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
950 * ls: invalid argument 'BOGUS' for '--color'
951 * Valid arguments are:
952 * 'always', 'yes', 'force'
953 * 'never', 'no', 'none'
954 * 'auto', 'tty', 'if-tty'
955 * (and substrings: "--color=alwa" work too)
957 static const char ls_longopts[] ALIGN1 =
958 "color\0" Optional_argument "\xff"; /* no short equivalent */
959 static const char color_str[] ALIGN1 =
960 "always\0""yes\0""force\0"
961 "auto\0""tty\0""if-tty\0";
962 /* need to initialize since --color has _an optional_ argument */
963 const char *color_opt = color_str; /* "always" */
970 all_fmt = LIST_SHORT |
971 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
973 #if ENABLE_FEATURE_AUTOWIDTH
974 /* obtain the terminal width */
975 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
980 /* process options */
981 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
983 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
984 * in some pairs of opts, only last one takes effect:
986 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES("t-S:S-t")) /* time/size */
987 // ":H-L:L-H:" - we don't have -H
988 // ":m-l:l-m:" - we don't have -m
989 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
990 ":C-1:1-C" /* bycols/oneline */
991 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
992 ":c-u:u-c" /* mtime/atime */
993 /* -T NUM, -w NUM: */
994 IF_FEATURE_AUTOWIDTH(":T+:w+");
995 opt = getopt32(argv, ls_options
996 IF_FEATURE_AUTOWIDTH(, &tabstops, &terminal_width)
997 IF_FEATURE_LS_COLOR(, &color_opt)
999 for (i = 0; opt_flags[i] != (1U<<31); i++) {
1000 if (opt & (1 << i)) {
1001 unsigned flags = opt_flags[i];
1003 if (flags & STYLE_MASK)
1004 all_fmt &= ~STYLE_MASK;
1005 if (flags & SORT_MASK)
1006 all_fmt &= ~SORT_MASK;
1007 if (flags & TIME_MASK)
1008 all_fmt &= ~TIME_MASK;
1010 if (flags & LIST_CONTEXT)
1011 all_fmt |= STYLE_SINGLE;
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 if one was not already specified by an option */
1057 if (!(all_fmt & STYLE_MASK))
1058 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : 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, -s or -F */
1072 cur = my_stat(*argv, *argv,
1073 !((all_fmt & (STYLE_LONG|LIST_BLOCKS)) || (option_mask32 & OPT_F))
1078 cur->fname_allocated = 0;
1084 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1088 /* now that we know how many files there are
1089 * allocate memory for an array to hold dnode pointers
1091 dnp = dnalloc(nfiles);
1092 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1093 dnp[i] = dn; /* save pointer to node in array */
1099 if (all_fmt & DISP_NOLIST) {
1100 dnsort(dnp, nfiles);
1101 showfiles(dnp, nfiles);
1103 dnd = splitdnarray(dnp, SPLIT_DIR);
1104 dnf = splitdnarray(dnp, SPLIT_FILE);
1105 dndirs = count_dirs(dnp, SPLIT_DIR);
1106 dnfiles = nfiles - dndirs;
1108 dnsort(dnf, dnfiles);
1109 showfiles(dnf, dnfiles);
1110 if (ENABLE_FEATURE_CLEAN_UP)
1114 dnsort(dnd, dndirs);
1115 showdirs(dnd, dnfiles == 0);
1116 if (ENABLE_FEATURE_CLEAN_UP)
1120 if (ENABLE_FEATURE_CLEAN_UP)