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.
37 /* This is a NOEXEC applet. Be very careful! */
41 /* ftpd uses ls, and without timestamps Mozilla won't understand
44 # undef CONFIG_FEATURE_LS_TIMESTAMPS
45 # undef ENABLE_FEATURE_LS_TIMESTAMPS
46 # undef IF_FEATURE_LS_TIMESTAMPS
47 # undef IF_NOT_FEATURE_LS_TIMESTAMPS
48 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
49 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
50 # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
51 # define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
57 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
58 COLUMN_GAP = 2, /* includes the file type char */
60 /* what is the overall style of the listing */
61 STYLE_COLUMNS = 1 << 21, /* fill columns */
62 STYLE_LONG = 2 << 21, /* one record per line, extended info */
63 STYLE_SINGLE = 3 << 21, /* one record per line */
64 STYLE_MASK = STYLE_SINGLE,
66 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
67 /* what file information will be listed */
70 LIST_MODEBITS = 1 << 2,
72 LIST_ID_NAME = 1 << 4,
73 LIST_ID_NUMERIC = 1 << 5,
74 LIST_CONTEXT = 1 << 6,
76 //LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE
77 LIST_DATE_TIME = 1 << 9,
78 LIST_FULLTIME = 1 << 10,
79 LIST_FILENAME = 1 << 11,
80 LIST_SYMLINK = 1 << 12,
81 LIST_FILETYPE = 1 << 13,
83 LIST_MASK = (LIST_EXEC << 1) - 1,
85 /* what files will be displayed */
86 DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */
87 DISP_HIDDEN = 1 << 16, /* show filenames starting with . */
88 DISP_DOT = 1 << 17, /* show . and .. */
89 DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */
90 DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */
91 DISP_ROWS = 1 << 20, /* print across rows */
92 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
94 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
95 SORT_FORWARD = 0, /* sort in reverse order */
96 SORT_REVERSE = 1 << 27, /* sort in reverse order */
98 SORT_NAME = 0, /* sort by file name */
99 SORT_SIZE = 1 << 28, /* sort by file size */
100 SORT_ATIME = 2 << 28, /* sort by last access time */
101 SORT_CTIME = 3 << 28, /* sort by last change time */
102 SORT_MTIME = 4 << 28, /* sort by last modification time */
103 SORT_VERSION = 5 << 28, /* sort by version */
104 SORT_EXT = 6 << 28, /* sort by file name extension */
105 SORT_DIR = 7 << 28, /* sort by file or directory */
106 SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
108 /* which of the three times will be used */
109 TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
110 TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
111 TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
113 FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
115 LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
117 LIST_SHORT = LIST_FILENAME,
118 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
119 LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
127 /* "[-]Cadil1", POSIX mandated options, busybox always supports */
128 /* "[-]gnsx", POSIX non-mandated options, busybox always supports */
129 /* "[-]Q" GNU option? busybox always supports */
130 /* "[-]Ak" GNU options, busybox always supports */
131 /* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
132 /* "[-]p", POSIX non-mandated options, busybox optionally supports */
133 /* "[-]SXvThw", GNU options, busybox optionally supports */
134 /* "[-]K", SELinux mandated options, busybox optionally supports */
135 /* "[-]e", I think we made this one up */
136 static const char ls_options[] ALIGN1 =
137 "Cadil1gnsxQAk" /* 13 opts, total 13 */
138 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
139 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
140 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
141 IF_FEATURE_LS_FOLLOWLINKS("L") /* 1, 24 */
142 IF_FEATURE_LS_RECURSIVE("R") /* 1, 25 */
143 IF_FEATURE_HUMAN_READABLE("h") /* 1, 26 */
144 IF_SELINUX("KZ") /* 2, 28 */
145 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
162 + 4 * ENABLE_FEATURE_LS_TIMESTAMPS
163 + 4 * ENABLE_FEATURE_LS_SORTFILES
164 + 2 * ENABLE_FEATURE_LS_FILETYPES
165 + 1 * ENABLE_FEATURE_LS_FOLLOWLINKS
166 + 1 * ENABLE_FEATURE_LS_RECURSIVE
167 + 1 * ENABLE_FEATURE_HUMAN_READABLE
169 + 2 * ENABLE_FEATURE_AUTOWIDTH,
170 OPT_color = 1 << OPTBIT_color,
174 LIST_MASK_TRIGGER = 0,
175 STYLE_MASK_TRIGGER = STYLE_MASK,
176 DISP_MASK_TRIGGER = DISP_ROWS,
177 SORT_MASK_TRIGGER = SORT_MASK,
180 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
181 static const unsigned opt_flags[] = {
182 LIST_SHORT | STYLE_COLUMNS, /* C */
183 DISP_HIDDEN | DISP_DOT, /* a */
186 LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
187 LIST_SHORT | STYLE_SINGLE, /* 1 */
188 0, /* g (don't show group) - handled via OPT_g */
189 LIST_ID_NUMERIC, /* n */
192 0, /* Q (quote filename) - handled via OPT_Q */
194 ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
195 #if ENABLE_FEATURE_LS_TIMESTAMPS
196 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
197 LIST_FULLTIME, /* e */
198 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
199 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
201 #if ENABLE_FEATURE_LS_SORTFILES
204 SORT_REVERSE, /* r */
205 SORT_VERSION, /* v */
207 #if ENABLE_FEATURE_LS_FILETYPES
208 LIST_FILETYPE | LIST_EXEC, /* F */
209 LIST_FILETYPE, /* p */
211 #if ENABLE_FEATURE_LS_FOLLOWLINKS
212 FOLLOW_LINKS, /* L */
214 #if ENABLE_FEATURE_LS_RECURSIVE
215 DISP_RECURSIVE, /* R */
217 #if ENABLE_FEATURE_HUMAN_READABLE
221 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
224 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
227 /* options after Z are not processed through opt_flags:
234 * a directory entry and its stat info are stored here
236 struct dnode { /* the basic node */
237 const char *name; /* the dir entry name */
238 const char *fullname; /* the dir entry name */
240 struct stat dstat; /* the file stat info */
241 IF_SELINUX(security_context_t sid;)
242 struct dnode *next; /* point at the next node */
245 static struct dnode **list_dir(const char *);
246 static struct dnode **dnalloc(int);
247 static int list_single(const struct dnode *);
251 #if ENABLE_FEATURE_LS_COLOR
256 #if ENABLE_FEATURE_AUTOWIDTH
257 unsigned tabstops; // = COLUMN_GAP;
258 unsigned terminal_width; // = TERMINAL_WIDTH;
260 #if ENABLE_FEATURE_LS_TIMESTAMPS
261 /* Do time() just once. Saves one syscall per file for "ls -l" */
262 time_t current_time_t;
265 #define G (*(struct globals*)&bb_common_bufsiz1)
266 #if ENABLE_FEATURE_LS_COLOR
267 #define show_color (G.show_color )
269 enum { show_color = 0 };
271 #define exit_code (G.exit_code )
272 #define all_fmt (G.all_fmt )
273 #if ENABLE_FEATURE_AUTOWIDTH
274 #define tabstops (G.tabstops )
275 #define terminal_width (G.terminal_width)
278 tabstops = COLUMN_GAP,
279 terminal_width = TERMINAL_WIDTH,
282 #define current_time_t (G.current_time_t)
283 /* memset: we have to zero it out because of NOEXEC */
284 #define INIT_G() do { \
285 memset(&G, 0, sizeof(G)); \
286 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
287 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
288 IF_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \
292 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
296 IF_SELINUX(security_context_t sid = NULL;)
298 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
300 if (is_selinux_enabled()) {
301 getfilecon(fullname, &sid);
304 if (stat(fullname, &dstat)) {
305 bb_simple_perror_msg(fullname);
306 exit_code = EXIT_FAILURE;
311 if (is_selinux_enabled()) {
312 lgetfilecon(fullname, &sid);
315 if (lstat(fullname, &dstat)) {
316 bb_simple_perror_msg(fullname);
317 exit_code = EXIT_FAILURE;
322 cur = xmalloc(sizeof(struct dnode));
323 cur->fullname = fullname;
326 IF_SELINUX(cur->sid = sid;)
331 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
332 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
333 * 3/7:multiplexed char/block device)
334 * and we use 0 for unknown and 15 for executables (see below) */
335 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
336 #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
337 #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
338 /* 036 black foreground 050 black background
339 037 red foreground 051 red background
340 040 green foreground 052 green background
341 041 brown foreground 053 brown background
342 042 blue foreground 054 blue background
343 043 magenta (purple) foreground 055 magenta background
344 044 cyan (light blue) foreground 056 cyan background
345 045 gray foreground 057 white background
347 #define COLOR(mode) ( \
348 /*un fi chr dir blk file link sock exe */ \
349 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
351 /* Select normal (0) [actually "reset all"] or bold (1)
352 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
353 * let's use 7 for "impossible" types, just for fun)
354 * Note: coreutils 6.9 uses inverted red for setuid binaries.
356 #define ATTR(mode) ( \
357 /*un fi chr dir blk file link sock exe */ \
358 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
361 #if ENABLE_FEATURE_LS_COLOR
362 /* mode of zero is interpreted as "unknown" (stat failed) */
363 static char fgcolor(mode_t mode)
365 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
366 return COLOR(0xF000); /* File is executable ... */
369 static char bold(mode_t mode)
371 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
372 return ATTR(0xF000); /* File is executable ... */
377 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
378 static char append_char(mode_t mode)
380 if (!(all_fmt & LIST_FILETYPE))
384 if (!(all_fmt & LIST_EXEC))
386 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
388 return APPCHAR(mode);
393 #define countdirs(A, B) count_dirs((A), (B), 1)
394 #define countsubdirs(A, B) count_dirs((A), (B), 0)
395 static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs)
402 for (i = 0; i < nfiles; i++) {
404 if (!S_ISDIR(dn[i]->dstat.st_mode))
408 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
416 static int countfiles(struct dnode **dnp)
424 for (cur = dnp[0]; cur->next; cur = cur->next)
430 /* get memory to hold an array of pointers */
431 static struct dnode **dnalloc(int num)
436 return xzalloc(num * sizeof(struct dnode *));
439 #if ENABLE_FEATURE_LS_RECURSIVE
440 static void dfree(struct dnode **dnp, int nfiles)
447 for (i = 0; i < nfiles; i++) {
448 struct dnode *cur = dnp[i];
450 free((char*)cur->fullname); /* free the filename */
451 free(cur); /* free the dnode */
453 free(dnp); /* free the array holding the dnode pointers */
456 #define dfree(...) ((void)0)
459 static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which)
464 if (dn == NULL || nfiles < 1)
467 /* count how many dirs and regular files there are */
468 if (which == SPLIT_SUBDIR)
469 dncnt = countsubdirs(dn, nfiles);
471 dncnt = countdirs(dn, nfiles); /* assume we are looking for dirs */
472 if (which == SPLIT_FILE)
473 dncnt = nfiles - dncnt; /* looking for files */
476 /* allocate a file array and a dir array */
477 dnp = dnalloc(dncnt);
479 /* copy the entrys into the file or dir array */
480 for (d = i = 0; i < nfiles; i++) {
481 if (S_ISDIR(dn[i]->dstat.st_mode)) {
483 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
486 if ((which & SPLIT_DIR)
487 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
491 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
498 #if ENABLE_FEATURE_LS_SORTFILES
499 static int sortcmp(const void *a, const void *b)
501 struct dnode *d1 = *(struct dnode **)a;
502 struct dnode *d2 = *(struct dnode **)b;
503 unsigned sort_opts = all_fmt & SORT_MASK;
506 dif = 0; /* assume SORT_NAME */
507 // TODO: use pre-initialized function pointer
508 // instead of branch forest
509 if (sort_opts == SORT_SIZE) {
510 dif = (int) (d2->dstat.st_size - d1->dstat.st_size);
511 } else if (sort_opts == SORT_ATIME) {
512 dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime);
513 } else if (sort_opts == SORT_CTIME) {
514 dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime);
515 } else if (sort_opts == SORT_MTIME) {
516 dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime);
517 } else if (sort_opts == SORT_DIR) {
518 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
519 /* } else if (sort_opts == SORT_VERSION) { */
520 /* } else if (sort_opts == SORT_EXT) { */
524 /* sort by name - may be a tie_breaker for time or size cmp */
525 if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name);
526 else dif = strcmp(d1->name, d2->name);
529 if (all_fmt & SORT_REVERSE) {
535 static void dnsort(struct dnode **dn, int size)
537 qsort(dn, size, sizeof(*dn), sortcmp);
540 #define dnsort(dn, size) ((void)0)
544 static void showfiles(struct dnode **dn, int nfiles)
546 int i, ncols, nrows, row, nc;
549 int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */
551 if (dn == NULL || nfiles < 1)
554 if (all_fmt & STYLE_LONG) {
557 /* find the longest file name, use that as the column width */
558 for (i = 0; i < nfiles; i++) {
559 int len = bb_mbstrlen(dn[i]->name);
560 if (column_width < len)
563 column_width += tabstops +
564 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
565 ((all_fmt & LIST_INO) ? 8 : 0) +
566 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
567 ncols = (int) (terminal_width / column_width);
571 nrows = nfiles / ncols;
572 if (nrows * ncols < nfiles)
573 nrows++; /* round up fractionals */
579 for (row = 0; row < nrows; row++) {
580 for (nc = 0; nc < ncols; nc++) {
581 /* reach into the array based on the column and row */
582 i = (nc * nrows) + row; /* assume display by column */
583 if (all_fmt & DISP_ROWS)
584 i = (row * ncols) + nc; /* display across row */
588 printf("%*s", nexttab, "");
591 nexttab = column + column_width;
592 column += list_single(dn[i]);
601 static void showdirs(struct dnode **dn, int ndirs, int first)
604 struct dnode **subdnp;
608 if (dn == NULL || ndirs < 1)
611 for (i = 0; i < ndirs; i++) {
612 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
616 printf("%s:\n", dn[i]->fullname);
618 subdnp = list_dir(dn[i]->fullname);
619 nfiles = countfiles(subdnp);
621 /* list all files at this level */
622 dnsort(subdnp, nfiles);
623 showfiles(subdnp, nfiles);
624 if (ENABLE_FEATURE_LS_RECURSIVE) {
625 if (all_fmt & DISP_RECURSIVE) {
626 /* recursive- list the sub-dirs */
627 dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR);
628 dndirs = countsubdirs(subdnp, nfiles);
631 showdirs(dnd, dndirs, 0);
632 /* free the array of dnode pointers to the dirs */
636 /* free the dnodes and the fullname mem */
637 dfree(subdnp, nfiles);
644 static struct dnode **list_dir(const char *path)
646 struct dnode *dn, *cur, **dnp;
647 struct dirent *entry;
656 dir = warn_opendir(path);
658 exit_code = EXIT_FAILURE;
659 return NULL; /* could not open the dir */
661 while ((entry = readdir(dir)) != NULL) {
664 /* are we going to list the file- it may be . or .. or a hidden file */
665 if (entry->d_name[0] == '.') {
666 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
667 && !(all_fmt & DISP_DOT)
671 if (!(all_fmt & DISP_HIDDEN))
674 fullname = concat_path_file(path, entry->d_name);
675 cur = my_stat(fullname, bb_basename(fullname), 0);
687 /* now that we know how many files there are
688 * allocate memory for an array to hold dnode pointers
692 dnp = dnalloc(nfiles);
693 for (i = 0, cur = dn; i < nfiles; i++) {
694 dnp[i] = cur; /* save pointer to node in array */
702 static int print_name(const char *name)
704 if (option_mask32 & OPT_Q) {
705 #if ENABLE_FEATURE_ASSUME_UNICODE
706 int len = 2 + bb_mbstrlen(name);
717 if (!ENABLE_FEATURE_ASSUME_UNICODE)
724 #if ENABLE_FEATURE_ASSUME_UNICODE
726 return bb_mbstrlen(name);
728 return printf("%s", name);
733 static int list_single(const struct dnode *dn)
736 char *lpath = lpath; /* for compiler */
737 #if ENABLE_FEATURE_LS_TIMESTAMPS
741 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
746 if (dn->fullname == NULL)
749 #if ENABLE_FEATURE_LS_TIMESTAMPS
750 ttime = dn->dstat.st_mtime; /* the default time */
751 if (all_fmt & TIME_ACCESS)
752 ttime = dn->dstat.st_atime;
753 if (all_fmt & TIME_CHANGE)
754 ttime = dn->dstat.st_ctime;
755 filetime = ctime(&ttime);
757 #if ENABLE_FEATURE_LS_FILETYPES
758 append = append_char(dn->dstat.st_mode);
761 /* Do readlink early, so that if it fails, error message
762 * does not appear *inside* of the "ls -l" line */
763 if (all_fmt & LIST_SYMLINK)
764 if (S_ISLNK(dn->dstat.st_mode))
765 lpath = xmalloc_readlink_or_warn(dn->fullname);
767 if (all_fmt & LIST_INO)
768 column += printf("%7lu ", (long) dn->dstat.st_ino);
769 if (all_fmt & LIST_BLOCKS)
770 column += printf("%4"OFF_FMT"u ", (off_t) dn->dstat.st_blocks >> 1);
771 if (all_fmt & LIST_MODEBITS)
772 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
773 if (all_fmt & LIST_NLINKS)
774 column += printf("%4lu ", (long) dn->dstat.st_nlink);
775 #if ENABLE_FEATURE_LS_USERNAME
776 if (all_fmt & LIST_ID_NAME) {
777 if (option_mask32 & OPT_g) {
778 column += printf("%-8.8s",
779 get_cached_username(dn->dstat.st_uid));
781 column += printf("%-8.8s %-8.8s",
782 get_cached_username(dn->dstat.st_uid),
783 get_cached_groupname(dn->dstat.st_gid));
787 if (all_fmt & LIST_ID_NUMERIC) {
788 if (option_mask32 & OPT_g)
789 column += printf("%-8u", (int) dn->dstat.st_uid);
791 column += printf("%-8u %-8u",
792 (int) dn->dstat.st_uid,
793 (int) dn->dstat.st_gid);
795 if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
796 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
797 column += printf("%4u, %3u ",
798 (int) major(dn->dstat.st_rdev),
799 (int) minor(dn->dstat.st_rdev));
801 if (all_fmt & LS_DISP_HR) {
802 column += printf("%9s ",
803 make_human_readable_str(dn->dstat.st_size, 1, 0));
805 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
809 #if ENABLE_FEATURE_LS_TIMESTAMPS
810 if (all_fmt & LIST_FULLTIME)
811 column += printf("%24.24s ", filetime);
812 if (all_fmt & LIST_DATE_TIME)
813 if ((all_fmt & LIST_FULLTIME) == 0) {
814 /* current_time_t ~== time(NULL) */
815 age = current_time_t - ttime;
816 printf("%6.6s ", filetime + 4);
817 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
818 /* hh:mm if less than 6 months old */
819 printf("%5.5s ", filetime + 11);
821 printf(" %4.4s ", filetime + 20);
827 if (all_fmt & LIST_CONTEXT) {
828 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
832 if (all_fmt & LIST_FILENAME) {
833 #if ENABLE_FEATURE_LS_COLOR
835 info.st_mode = 0; /* for fgcolor() */
836 lstat(dn->fullname, &info);
837 printf("\033[%u;%um", bold(info.st_mode),
838 fgcolor(info.st_mode));
841 column += print_name(dn->name);
846 if (all_fmt & LIST_SYMLINK) {
847 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
849 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
850 #if ENABLE_FEATURE_LS_COLOR
851 info.st_mode = 0; /* for fgcolor() */
853 if (stat(dn->fullname, &info) == 0) {
854 append = append_char(info.st_mode);
857 #if ENABLE_FEATURE_LS_COLOR
859 printf("\033[%u;%um", bold(info.st_mode),
860 fgcolor(info.st_mode));
863 column += print_name(lpath) + 4;
870 #if ENABLE_FEATURE_LS_FILETYPES
871 if (all_fmt & LIST_FILETYPE) {
883 int ls_main(int argc UNUSED_PARAM, char **argv)
895 #if ENABLE_FEATURE_LS_COLOR
896 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
899 * ls: invalid argument 'BOGUS' for '--color'
900 * Valid arguments are:
901 * 'always', 'yes', 'force'
902 * 'never', 'no', 'none'
903 * 'auto', 'tty', 'if-tty'
904 * (and substrings: "--color=alwa" work too)
906 static const char ls_longopts[] ALIGN1 =
907 "color\0" Optional_argument "\xff"; /* no short equivalent */
908 static const char color_str[] ALIGN1 =
909 "always\0""yes\0""force\0"
910 "auto\0""tty\0""if-tty\0";
911 /* need to initialize since --color has _an optional_ argument */
912 const char *color_opt = color_str; /* "always" */
917 check_unicode_in_env();
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_longopts;)
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 & OPT_color) {
970 if (color_opt[0] == 'n')
972 else switch (index_in_substrings(color_str, color_opt)) {
976 if (isatty(STDOUT_FILENO)) {
986 /* sort out which command line options take precedence */
987 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
988 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
989 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
990 if (all_fmt & TIME_CHANGE)
991 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
992 if (all_fmt & TIME_ACCESS)
993 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
995 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
996 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
997 if (ENABLE_FEATURE_LS_USERNAME)
998 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
999 all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
1001 /* choose a display format */
1002 if (!(all_fmt & STYLE_MASK))
1003 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1007 *--argv = (char*)".";
1010 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1012 /* stuff the command line file names into a dnode array */
1016 /* NB: follow links on command line unless -l or -s */
1017 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1027 /* now that we know how many files there are
1028 * allocate memory for an array to hold dnode pointers
1030 dnp = dnalloc(nfiles);
1031 for (i = 0, cur = dn; i < nfiles; i++) {
1032 dnp[i] = cur; /* save pointer to node in array */
1036 if (all_fmt & DISP_NOLIST) {
1037 dnsort(dnp, nfiles);
1039 showfiles(dnp, nfiles);
1041 dnd = splitdnarray(dnp, nfiles, SPLIT_DIR);
1042 dnf = splitdnarray(dnp, nfiles, SPLIT_FILE);
1043 dndirs = countdirs(dnp, nfiles);
1044 dnfiles = nfiles - dndirs;
1046 dnsort(dnf, dnfiles);
1047 showfiles(dnf, dnfiles);
1048 if (ENABLE_FEATURE_CLEAN_UP)
1052 dnsort(dnd, dndirs);
1053 showdirs(dnd, dndirs, dnfiles == 0);
1054 if (ENABLE_FEATURE_CLEAN_UP)
1058 if (ENABLE_FEATURE_CLEAN_UP)