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. ls -l of a directory doesn't give "total <blocks>" header
21 * 2. hidden files can make column width too large
23 * NON-OPTIMAL BEHAVIOUR:
24 * 1. autowidth reads directories twice
25 * 2. if you do a short directory listing without filetype characters
26 * appended, there's no need to stat each one
28 * 1. requires lstat (BSD) - how do you do it without?
31 * ls sorts listing now, and supports almost all options.
36 #if ENABLE_FEATURE_ASSUME_UNICODE
40 /* This is a NOEXEC applet. Be very careful! */
44 /* ftpd uses ls, and without timestamps Mozilla won't understand
47 # undef CONFIG_FEATURE_LS_TIMESTAMPS
48 # undef ENABLE_FEATURE_LS_TIMESTAMPS
49 # undef IF_FEATURE_LS_TIMESTAMPS
50 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
51 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
52 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
53 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
54 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
60 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
61 COLUMN_GAP = 2, /* includes the file type char */
63 /* what is the overall style of the listing */
64 STYLE_COLUMNS = 1 << 21, /* fill columns */
65 STYLE_LONG = 2 << 21, /* one record per line, extended info */
66 STYLE_SINGLE = 3 << 21, /* one record per line */
67 STYLE_MASK = STYLE_SINGLE,
69 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
70 /* what file information will be listed */
73 LIST_MODEBITS = 1 << 2,
75 LIST_ID_NAME = 1 << 4,
76 LIST_ID_NUMERIC = 1 << 5,
77 LIST_CONTEXT = 1 << 6,
79 //LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE
80 LIST_DATE_TIME = 1 << 9,
81 LIST_FULLTIME = 1 << 10,
82 LIST_FILENAME = 1 << 11,
83 LIST_SYMLINK = 1 << 12,
84 LIST_FILETYPE = 1 << 13,
86 LIST_MASK = (LIST_EXEC << 1) - 1,
88 /* what files will be displayed */
89 DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */
90 DISP_HIDDEN = 1 << 16, /* show filenames starting with . */
91 DISP_DOT = 1 << 17, /* show . and .. */
92 DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */
93 DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */
94 DISP_ROWS = 1 << 20, /* print across rows */
95 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
97 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
98 SORT_FORWARD = 0, /* sort in reverse order */
99 SORT_REVERSE = 1 << 27, /* sort in reverse order */
101 SORT_NAME = 0, /* sort by file name */
102 SORT_SIZE = 1 << 28, /* sort by file size */
103 SORT_ATIME = 2 << 28, /* sort by last access time */
104 SORT_CTIME = 3 << 28, /* sort by last change time */
105 SORT_MTIME = 4 << 28, /* sort by last modification time */
106 SORT_VERSION = 5 << 28, /* sort by version */
107 SORT_EXT = 6 << 28, /* sort by file name extension */
108 SORT_DIR = 7 << 28, /* sort by file or directory */
109 SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
111 /* which of the three times will be used */
112 TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
113 TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
114 TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
116 FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
118 LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
120 LIST_SHORT = LIST_FILENAME,
121 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
122 LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
130 /* "[-]Cadil1", POSIX mandated options, busybox always supports */
131 /* "[-]gnsx", POSIX non-mandated options, busybox always supports */
132 /* "[-]Q" GNU option? busybox always supports */
133 /* "[-]Ak" GNU options, busybox always supports */
134 /* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
135 /* "[-]p", POSIX non-mandated options, busybox optionally supports */
136 /* "[-]SXvThw", GNU options, busybox optionally supports */
137 /* "[-]K", SELinux mandated options, busybox optionally supports */
138 /* "[-]e", I think we made this one up */
139 static const char ls_options[] ALIGN1 =
140 "Cadil1gnsxQAk" /* 13 opts, total 13 */
141 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
142 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
143 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
144 IF_FEATURE_LS_FOLLOWLINKS("L") /* 1, 24 */
145 IF_FEATURE_LS_RECURSIVE("R") /* 1, 25 */
146 IF_FEATURE_HUMAN_READABLE("h") /* 1, 26 */
147 IF_SELINUX("K") /* 1, 27 */
148 IF_SELINUX("Z") /* 1, 28 */
149 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
168 LIST_MASK_TRIGGER = 0,
169 STYLE_MASK_TRIGGER = STYLE_MASK,
170 DISP_MASK_TRIGGER = DISP_ROWS,
171 SORT_MASK_TRIGGER = SORT_MASK,
174 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
175 static const unsigned opt_flags[] = {
176 LIST_SHORT | STYLE_COLUMNS, /* C */
177 DISP_HIDDEN | DISP_DOT, /* a */
180 LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
181 LIST_SHORT | STYLE_SINGLE, /* 1 */
182 0, /* g (don't show group) - handled via OPT_g */
183 LIST_ID_NUMERIC, /* n */
186 0, /* Q (quote filename) - handled via OPT_Q */
188 ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
189 #if ENABLE_FEATURE_LS_TIMESTAMPS
190 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
191 LIST_FULLTIME, /* e */
192 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
193 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
195 #if ENABLE_FEATURE_LS_SORTFILES
198 SORT_REVERSE, /* r */
199 SORT_VERSION, /* v */
201 #if ENABLE_FEATURE_LS_FILETYPES
202 LIST_FILETYPE | LIST_EXEC, /* F */
203 LIST_FILETYPE, /* p */
205 #if ENABLE_FEATURE_LS_FOLLOWLINKS
206 FOLLOW_LINKS, /* L */
208 #if ENABLE_FEATURE_LS_RECURSIVE
209 DISP_RECURSIVE, /* R */
211 #if ENABLE_FEATURE_HUMAN_READABLE
215 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
218 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
221 /* options after Z are not processed through opt_flags:
228 * a directory entry and its stat info are stored here
230 struct dnode { /* the basic node */
231 const char *name; /* the dir entry name */
232 const char *fullname; /* the dir entry name */
234 struct stat dstat; /* the file stat info */
235 IF_SELINUX(security_context_t sid;)
236 struct dnode *next; /* point at the next node */
239 static struct dnode **list_dir(const char *);
240 static struct dnode **dnalloc(int);
241 static int list_single(const struct dnode *);
245 #if ENABLE_FEATURE_LS_COLOR
250 #if ENABLE_FEATURE_AUTOWIDTH
251 unsigned tabstops; // = COLUMN_GAP;
252 unsigned terminal_width; // = TERMINAL_WIDTH;
254 #if ENABLE_FEATURE_LS_TIMESTAMPS
255 /* Do time() just once. Saves one syscall per file for "ls -l" */
256 time_t current_time_t;
259 #define G (*(struct globals*)&bb_common_bufsiz1)
260 #if ENABLE_FEATURE_LS_COLOR
261 #define show_color (G.show_color )
263 enum { show_color = 0 };
265 #define exit_code (G.exit_code )
266 #define all_fmt (G.all_fmt )
267 #if ENABLE_FEATURE_AUTOWIDTH
268 #define tabstops (G.tabstops )
269 #define terminal_width (G.terminal_width)
272 tabstops = COLUMN_GAP,
273 terminal_width = TERMINAL_WIDTH,
276 #define current_time_t (G.current_time_t)
277 /* memset: we have to zero it out because of NOEXEC */
278 #define INIT_G() do { \
279 memset(&G, 0, sizeof(G)); \
280 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
281 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
282 IF_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \
286 #if ENABLE_FEATURE_ASSUME_UNICODE
287 /* libbb candidate */
288 static size_t mbstrlen(const char *string)
290 size_t width = mbsrtowcs(NULL /*dest*/, &string,
291 MAXINT(size_t) /*len*/, NULL /*state*/);
292 if (width == (size_t)-1)
293 return strlen(string);
297 #define mbstrlen(string) strlen(string)
301 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
305 IF_SELINUX(security_context_t sid = NULL;)
307 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
309 if (is_selinux_enabled()) {
310 getfilecon(fullname, &sid);
313 if (stat(fullname, &dstat)) {
314 bb_simple_perror_msg(fullname);
315 exit_code = EXIT_FAILURE;
320 if (is_selinux_enabled()) {
321 lgetfilecon(fullname, &sid);
324 if (lstat(fullname, &dstat)) {
325 bb_simple_perror_msg(fullname);
326 exit_code = EXIT_FAILURE;
331 cur = xmalloc(sizeof(struct dnode));
332 cur->fullname = fullname;
335 IF_SELINUX(cur->sid = sid;)
340 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
341 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
342 * 3/7:multiplexed char/block device)
343 * and we use 0 for unknown and 15 for executables (see below) */
344 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
345 #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
346 #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
347 /* 036 black foreground 050 black background
348 037 red foreground 051 red background
349 040 green foreground 052 green background
350 041 brown foreground 053 brown background
351 042 blue foreground 054 blue background
352 043 magenta (purple) foreground 055 magenta background
353 044 cyan (light blue) foreground 056 cyan background
354 045 gray foreground 057 white background
356 #define COLOR(mode) ( \
357 /*un fi chr dir blk file link sock exe */ \
358 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
360 /* Select normal (0) [actually "reset all"] or bold (1)
361 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
362 * let's use 7 for "impossible" types, just for fun)
363 * Note: coreutils 6.9 uses inverted red for setuid binaries.
365 #define ATTR(mode) ( \
366 /*un fi chr dir blk file link sock exe */ \
367 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
370 #if ENABLE_FEATURE_LS_COLOR
371 /* mode of zero is interpreted as "unknown" (stat failed) */
372 static char fgcolor(mode_t mode)
374 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
375 return COLOR(0xF000); /* File is executable ... */
378 static char bold(mode_t mode)
380 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
381 return ATTR(0xF000); /* File is executable ... */
386 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
387 static char append_char(mode_t mode)
389 if (!(all_fmt & LIST_FILETYPE))
393 if (!(all_fmt & LIST_EXEC))
395 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
397 return APPCHAR(mode);
402 #define countdirs(A, B) count_dirs((A), (B), 1)
403 #define countsubdirs(A, B) count_dirs((A), (B), 0)
404 static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs)
411 for (i = 0; i < nfiles; i++) {
413 if (!S_ISDIR(dn[i]->dstat.st_mode))
417 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
425 static int countfiles(struct dnode **dnp)
433 for (cur = dnp[0]; cur->next; cur = cur->next)
439 /* get memory to hold an array of pointers */
440 static struct dnode **dnalloc(int num)
445 return xzalloc(num * sizeof(struct dnode *));
448 #if ENABLE_FEATURE_LS_RECURSIVE
449 static void dfree(struct dnode **dnp, int nfiles)
456 for (i = 0; i < nfiles; i++) {
457 struct dnode *cur = dnp[i];
459 free((char*)cur->fullname); /* free the filename */
460 free(cur); /* free the dnode */
462 free(dnp); /* free the array holding the dnode pointers */
465 #define dfree(...) ((void)0)
468 static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which)
473 if (dn == NULL || nfiles < 1)
476 /* count how many dirs and regular files there are */
477 if (which == SPLIT_SUBDIR)
478 dncnt = countsubdirs(dn, nfiles);
480 dncnt = countdirs(dn, nfiles); /* assume we are looking for dirs */
481 if (which == SPLIT_FILE)
482 dncnt = nfiles - dncnt; /* looking for files */
485 /* allocate a file array and a dir array */
486 dnp = dnalloc(dncnt);
488 /* copy the entrys into the file or dir array */
489 for (d = i = 0; i < nfiles; i++) {
490 if (S_ISDIR(dn[i]->dstat.st_mode)) {
492 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
495 if ((which & SPLIT_DIR)
496 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
500 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
507 #if ENABLE_FEATURE_LS_SORTFILES
508 static int sortcmp(const void *a, const void *b)
510 struct dnode *d1 = *(struct dnode **)a;
511 struct dnode *d2 = *(struct dnode **)b;
512 unsigned sort_opts = all_fmt & SORT_MASK;
515 dif = 0; /* assume SORT_NAME */
516 // TODO: use pre-initialized function pointer
517 // instead of branch forest
518 if (sort_opts == SORT_SIZE) {
519 dif = (int) (d2->dstat.st_size - d1->dstat.st_size);
520 } else if (sort_opts == SORT_ATIME) {
521 dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime);
522 } else if (sort_opts == SORT_CTIME) {
523 dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime);
524 } else if (sort_opts == SORT_MTIME) {
525 dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime);
526 } else if (sort_opts == SORT_DIR) {
527 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
528 /* } else if (sort_opts == SORT_VERSION) { */
529 /* } else if (sort_opts == SORT_EXT) { */
533 /* sort by name - may be a tie_breaker for time or size cmp */
534 if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name);
535 else dif = strcmp(d1->name, d2->name);
538 if (all_fmt & SORT_REVERSE) {
544 static void dnsort(struct dnode **dn, int size)
546 qsort(dn, size, sizeof(*dn), sortcmp);
549 #define dnsort(dn, size) ((void)0)
553 static void showfiles(struct dnode **dn, int nfiles)
555 int i, ncols, nrows, row, nc;
558 int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */
560 if (dn == NULL || nfiles < 1)
563 if (all_fmt & STYLE_LONG) {
566 /* find the longest file name, use that as the column width */
567 for (i = 0; i < nfiles; i++) {
568 int len = mbstrlen(dn[i]->name);
569 if (column_width < len)
572 column_width += tabstops +
573 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
574 ((all_fmt & LIST_INO) ? 8 : 0) +
575 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
576 ncols = (int) (terminal_width / column_width);
580 nrows = nfiles / ncols;
581 if (nrows * ncols < nfiles)
582 nrows++; /* round up fractionals */
588 for (row = 0; row < nrows; row++) {
589 for (nc = 0; nc < ncols; nc++) {
590 /* reach into the array based on the column and row */
591 i = (nc * nrows) + row; /* assume display by column */
592 if (all_fmt & DISP_ROWS)
593 i = (row * ncols) + nc; /* display across row */
597 printf("%*s", nexttab, "");
600 nexttab = column + column_width;
601 column += list_single(dn[i]);
610 static void showdirs(struct dnode **dn, int ndirs, int first)
613 struct dnode **subdnp;
617 if (dn == NULL || ndirs < 1)
620 for (i = 0; i < ndirs; i++) {
621 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
625 printf("%s:\n", dn[i]->fullname);
627 subdnp = list_dir(dn[i]->fullname);
628 nfiles = countfiles(subdnp);
630 /* list all files at this level */
631 dnsort(subdnp, nfiles);
632 showfiles(subdnp, nfiles);
633 if (ENABLE_FEATURE_LS_RECURSIVE) {
634 if (all_fmt & DISP_RECURSIVE) {
635 /* recursive- list the sub-dirs */
636 dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR);
637 dndirs = countsubdirs(subdnp, nfiles);
640 showdirs(dnd, dndirs, 0);
641 /* free the array of dnode pointers to the dirs */
645 /* free the dnodes and the fullname mem */
646 dfree(subdnp, nfiles);
653 static struct dnode **list_dir(const char *path)
655 struct dnode *dn, *cur, **dnp;
656 struct dirent *entry;
665 dir = warn_opendir(path);
667 exit_code = EXIT_FAILURE;
668 return NULL; /* could not open the dir */
670 while ((entry = readdir(dir)) != NULL) {
673 /* are we going to list the file- it may be . or .. or a hidden file */
674 if (entry->d_name[0] == '.') {
675 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
676 && !(all_fmt & DISP_DOT)
680 if (!(all_fmt & DISP_HIDDEN))
683 fullname = concat_path_file(path, entry->d_name);
684 cur = my_stat(fullname, bb_basename(fullname), 0);
696 /* now that we know how many files there are
697 * allocate memory for an array to hold dnode pointers
701 dnp = dnalloc(nfiles);
702 for (i = 0, cur = dn; i < nfiles; i++) {
703 dnp[i] = cur; /* save pointer to node in array */
711 static int print_name(const char *name)
713 if (option_mask32 & OPT_Q) {
714 #if ENABLE_FEATURE_ASSUME_UNICODE
715 int len = 2 + mbstrlen(name);
726 if (!ENABLE_FEATURE_ASSUME_UNICODE)
733 #if ENABLE_FEATURE_ASSUME_UNICODE
735 return mbstrlen(name);
737 return printf("%s", name);
742 static int list_single(const struct dnode *dn)
745 char *lpath = lpath; /* for compiler */
746 #if ENABLE_FEATURE_LS_TIMESTAMPS
750 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
755 if (dn->fullname == NULL)
758 #if ENABLE_FEATURE_LS_TIMESTAMPS
759 ttime = dn->dstat.st_mtime; /* the default time */
760 if (all_fmt & TIME_ACCESS)
761 ttime = dn->dstat.st_atime;
762 if (all_fmt & TIME_CHANGE)
763 ttime = dn->dstat.st_ctime;
764 filetime = ctime(&ttime);
766 #if ENABLE_FEATURE_LS_FILETYPES
767 append = append_char(dn->dstat.st_mode);
770 /* Do readlink early, so that if it fails, error message
771 * does not appear *inside* of the "ls -l" line */
772 if (all_fmt & LIST_SYMLINK)
773 if (S_ISLNK(dn->dstat.st_mode))
774 lpath = xmalloc_readlink_or_warn(dn->fullname);
776 if (all_fmt & LIST_INO)
777 column += printf("%7lu ", (long) dn->dstat.st_ino);
778 if (all_fmt & LIST_BLOCKS)
779 column += printf("%4"OFF_FMT"u ", (off_t) dn->dstat.st_blocks >> 1);
780 if (all_fmt & LIST_MODEBITS)
781 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
782 if (all_fmt & LIST_NLINKS)
783 column += printf("%4lu ", (long) dn->dstat.st_nlink);
784 #if ENABLE_FEATURE_LS_USERNAME
785 if (all_fmt & LIST_ID_NAME) {
786 if (option_mask32 & OPT_g) {
787 column += printf("%-8.8s",
788 get_cached_username(dn->dstat.st_uid));
790 column += printf("%-8.8s %-8.8s",
791 get_cached_username(dn->dstat.st_uid),
792 get_cached_groupname(dn->dstat.st_gid));
796 if (all_fmt & LIST_ID_NUMERIC) {
797 if (option_mask32 & OPT_g)
798 column += printf("%-8u", (int) dn->dstat.st_uid);
800 column += printf("%-8u %-8u",
801 (int) dn->dstat.st_uid,
802 (int) dn->dstat.st_gid);
804 if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
805 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
806 column += printf("%4u, %3u ",
807 (int) major(dn->dstat.st_rdev),
808 (int) minor(dn->dstat.st_rdev));
810 if (all_fmt & LS_DISP_HR) {
811 column += printf("%9s ",
812 make_human_readable_str(dn->dstat.st_size, 1, 0));
814 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
818 #if ENABLE_FEATURE_LS_TIMESTAMPS
819 if (all_fmt & LIST_FULLTIME)
820 column += printf("%24.24s ", filetime);
821 if (all_fmt & LIST_DATE_TIME)
822 if ((all_fmt & LIST_FULLTIME) == 0) {
823 /* current_time_t ~== time(NULL) */
824 age = current_time_t - ttime;
825 printf("%6.6s ", filetime + 4);
826 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
827 /* hh:mm if less than 6 months old */
828 printf("%5.5s ", filetime + 11);
830 printf(" %4.4s ", filetime + 20);
836 if (all_fmt & LIST_CONTEXT) {
837 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
841 if (all_fmt & LIST_FILENAME) {
842 #if ENABLE_FEATURE_LS_COLOR
844 info.st_mode = 0; /* for fgcolor() */
845 lstat(dn->fullname, &info);
846 printf("\033[%u;%um", bold(info.st_mode),
847 fgcolor(info.st_mode));
850 column += print_name(dn->name);
855 if (all_fmt & LIST_SYMLINK) {
856 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
858 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
859 #if ENABLE_FEATURE_LS_COLOR
860 info.st_mode = 0; /* for fgcolor() */
862 if (stat(dn->fullname, &info) == 0) {
863 append = append_char(info.st_mode);
866 #if ENABLE_FEATURE_LS_COLOR
868 printf("\033[%u;%um", bold(info.st_mode),
869 fgcolor(info.st_mode));
872 column += print_name(lpath) + 4;
879 #if ENABLE_FEATURE_LS_FILETYPES
880 if (all_fmt & LIST_FILETYPE) {
892 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
893 #if ENABLE_FEATURE_LS_COLOR
894 /* long option entry used only for --color, which has no short option
896 static const char ls_color_opt[] ALIGN1 =
897 "color\0" Optional_argument "\xff" /* no short equivalent */
902 int ls_main(int argc UNUSED_PARAM, char **argv)
914 /* need to initialize since --color has _an optional_ argument */
915 IF_FEATURE_LS_COLOR(const char *color_opt = "always";)
919 all_fmt = LIST_SHORT |
920 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
922 #if ENABLE_FEATURE_AUTOWIDTH
923 /* Obtain the terminal width */
924 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
929 /* process options */
930 IF_FEATURE_LS_COLOR(applet_long_options = ls_color_opt;)
931 #if ENABLE_FEATURE_AUTOWIDTH
932 opt_complementary = "T+:w+"; /* -T N, -w N */
933 opt = getopt32(argv, ls_options, &tabstops, &terminal_width
934 IF_FEATURE_LS_COLOR(, &color_opt));
936 opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
938 for (i = 0; opt_flags[i] != (1U<<31); i++) {
939 if (opt & (1 << i)) {
940 unsigned flags = opt_flags[i];
942 if (flags & LIST_MASK_TRIGGER)
943 all_fmt &= ~LIST_MASK;
944 if (flags & STYLE_MASK_TRIGGER)
945 all_fmt &= ~STYLE_MASK;
946 if (flags & SORT_MASK_TRIGGER)
947 all_fmt &= ~SORT_MASK;
948 if (flags & DISP_MASK_TRIGGER)
949 all_fmt &= ~DISP_MASK;
950 if (flags & TIME_MASK)
951 all_fmt &= ~TIME_MASK;
952 if (flags & LIST_CONTEXT)
953 all_fmt |= STYLE_SINGLE;
954 /* huh?? opt cannot be 'l' */
955 //if (LS_DISP_HR && opt == 'l')
956 // all_fmt &= ~LS_DISP_HR;
961 #if ENABLE_FEATURE_LS_COLOR
962 /* find color bit value - last position for short getopt */
963 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
964 char *p = getenv("LS_COLORS");
965 /* LS_COLORS is unset, or (not empty && not "none") ? */
966 if (!p || (p[0] && strcmp(p, "none") != 0))
969 if (opt & (1 << i)) { /* next flag after short options */
970 if (strcmp("always", color_opt) == 0)
972 else if (strcmp("never", color_opt) == 0)
974 else if (strcmp("auto", color_opt) == 0 && isatty(STDOUT_FILENO))
979 /* sort out which command line options take precedence */
980 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
981 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
982 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
983 if (all_fmt & TIME_CHANGE)
984 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
985 if (all_fmt & TIME_ACCESS)
986 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
988 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
989 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
990 if (ENABLE_FEATURE_LS_USERNAME)
991 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
992 all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
994 /* choose a display format */
995 if (!(all_fmt & STYLE_MASK))
996 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1000 *--argv = (char*)".";
1003 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1005 /* stuff the command line file names into a dnode array */
1009 /* NB: follow links on command line unless -l or -s */
1010 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1020 /* now that we know how many files there are
1021 * allocate memory for an array to hold dnode pointers
1023 dnp = dnalloc(nfiles);
1024 for (i = 0, cur = dn; i < nfiles; i++) {
1025 dnp[i] = cur; /* save pointer to node in array */
1029 if (all_fmt & DISP_NOLIST) {
1030 dnsort(dnp, nfiles);
1032 showfiles(dnp, nfiles);
1034 dnd = splitdnarray(dnp, nfiles, SPLIT_DIR);
1035 dnf = splitdnarray(dnp, nfiles, SPLIT_FILE);
1036 dndirs = countdirs(dnp, nfiles);
1037 dnfiles = nfiles - dndirs;
1039 dnsort(dnf, dnfiles);
1040 showfiles(dnf, dnfiles);
1041 if (ENABLE_FEATURE_CLEAN_UP)
1045 dnsort(dnd, dndirs);
1046 showdirs(dnd, dndirs, dnfiles == 0);
1047 if (ENABLE_FEATURE_CLEAN_UP)
1051 if (ENABLE_FEATURE_CLEAN_UP)