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(...)
56 TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
57 COLUMN_GAP = 2, /* includes the file type char */
59 /* what is the overall style of the listing */
60 STYLE_COLUMNS = 1 << 21, /* fill columns */
61 STYLE_LONG = 2 << 21, /* one record per line, extended info */
62 STYLE_SINGLE = 3 << 21, /* one record per line */
63 STYLE_MASK = STYLE_SINGLE,
65 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
66 /* what file information will be listed */
69 LIST_MODEBITS = 1 << 2,
71 LIST_ID_NAME = 1 << 4,
72 LIST_ID_NUMERIC = 1 << 5,
73 LIST_CONTEXT = 1 << 6,
75 //LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE
76 LIST_DATE_TIME = 1 << 9,
77 LIST_FULLTIME = 1 << 10,
78 LIST_FILENAME = 1 << 11,
79 LIST_SYMLINK = 1 << 12,
80 LIST_FILETYPE = 1 << 13,
82 LIST_MASK = (LIST_EXEC << 1) - 1,
84 /* what files will be displayed */
85 DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */
86 DISP_HIDDEN = 1 << 16, /* show filenames starting with . */
87 DISP_DOT = 1 << 17, /* show . and .. */
88 DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */
89 DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */
90 DISP_ROWS = 1 << 20, /* print across rows */
91 DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
93 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
94 SORT_FORWARD = 0, /* sort in reverse order */
95 SORT_REVERSE = 1 << 27, /* sort in reverse order */
97 SORT_NAME = 0, /* sort by file name */
98 SORT_SIZE = 1 << 28, /* sort by file size */
99 SORT_ATIME = 2 << 28, /* sort by last access time */
100 SORT_CTIME = 3 << 28, /* sort by last change time */
101 SORT_MTIME = 4 << 28, /* sort by last modification time */
102 SORT_VERSION = 5 << 28, /* sort by version */
103 SORT_EXT = 6 << 28, /* sort by file name extension */
104 SORT_DIR = 7 << 28, /* sort by file or directory */
105 SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
107 /* which of the three times will be used */
108 TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
109 TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
110 TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
112 FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
114 LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
116 LIST_SHORT = LIST_FILENAME,
117 LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
118 LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
126 /* "[-]Cadil1", POSIX mandated options, busybox always supports */
127 /* "[-]gnsx", POSIX non-mandated options, busybox always supports */
128 /* "[-]Q" GNU option? busybox always supports */
129 /* "[-]Ak" GNU options, busybox always supports */
130 /* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
131 /* "[-]p", POSIX non-mandated options, busybox optionally supports */
132 /* "[-]SXvThw", GNU options, busybox optionally supports */
133 /* "[-]K", SELinux mandated options, busybox optionally supports */
134 /* "[-]e", I think we made this one up */
135 static const char ls_options[] ALIGN1 =
136 "Cadil1gnsxQAk" /* 13 opts, total 13 */
137 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
138 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
139 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
140 IF_FEATURE_LS_FOLLOWLINKS("L") /* 1, 24 */
141 IF_FEATURE_LS_RECURSIVE("R") /* 1, 25 */
142 IF_FEATURE_HUMAN_READABLE("h") /* 1, 26 */
143 IF_SELINUX("KZ") /* 2, 28 */
144 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
161 + 4 * ENABLE_FEATURE_LS_TIMESTAMPS
162 + 4 * ENABLE_FEATURE_LS_SORTFILES
163 + 2 * ENABLE_FEATURE_LS_FILETYPES
164 + 1 * ENABLE_FEATURE_LS_FOLLOWLINKS
165 + 1 * ENABLE_FEATURE_LS_RECURSIVE
166 + 1 * ENABLE_FEATURE_HUMAN_READABLE
168 + 2 * ENABLE_FEATURE_AUTOWIDTH,
169 OPT_color = 1 << OPTBIT_color,
173 LIST_MASK_TRIGGER = 0,
174 STYLE_MASK_TRIGGER = STYLE_MASK,
175 DISP_MASK_TRIGGER = DISP_ROWS,
176 SORT_MASK_TRIGGER = SORT_MASK,
179 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
180 static const unsigned opt_flags[] = {
181 LIST_SHORT | STYLE_COLUMNS, /* C */
182 DISP_HIDDEN | DISP_DOT, /* a */
185 LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
186 LIST_SHORT | STYLE_SINGLE, /* 1 */
187 0, /* g (don't show group) - handled via OPT_g */
188 LIST_ID_NUMERIC, /* n */
191 0, /* Q (quote filename) - handled via OPT_Q */
193 ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
194 #if ENABLE_FEATURE_LS_TIMESTAMPS
195 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
196 LIST_FULLTIME, /* e */
197 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
198 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
200 #if ENABLE_FEATURE_LS_SORTFILES
203 SORT_REVERSE, /* r */
204 SORT_VERSION, /* v */
206 #if ENABLE_FEATURE_LS_FILETYPES
207 LIST_FILETYPE | LIST_EXEC, /* F */
208 LIST_FILETYPE, /* p */
210 #if ENABLE_FEATURE_LS_FOLLOWLINKS
211 FOLLOW_LINKS, /* L */
213 #if ENABLE_FEATURE_LS_RECURSIVE
214 DISP_RECURSIVE, /* R */
216 #if ENABLE_FEATURE_HUMAN_READABLE
220 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
223 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
226 /* options after Z are not processed through opt_flags:
233 * a directory entry and its stat info are stored here
235 struct dnode { /* the basic node */
236 const char *name; /* the dir entry name */
237 const char *fullname; /* the dir entry name */
239 struct stat dstat; /* the file stat info */
240 IF_SELINUX(security_context_t sid;)
241 struct dnode *next; /* point at the next node */
244 static struct dnode **list_dir(const char *);
245 static struct dnode **dnalloc(int);
246 static int list_single(const struct dnode *);
250 #if ENABLE_FEATURE_LS_COLOR
255 #if ENABLE_FEATURE_AUTOWIDTH
256 unsigned tabstops; // = COLUMN_GAP;
257 unsigned terminal_width; // = TERMINAL_WIDTH;
259 #if ENABLE_FEATURE_LS_TIMESTAMPS
260 /* Do time() just once. Saves one syscall per file for "ls -l" */
261 time_t current_time_t;
264 #define G (*(struct globals*)&bb_common_bufsiz1)
265 #if ENABLE_FEATURE_LS_COLOR
266 # define show_color (G.show_color )
268 enum { show_color = 0 };
270 #define exit_code (G.exit_code )
271 #define all_fmt (G.all_fmt )
272 #if ENABLE_FEATURE_AUTOWIDTH
273 # define tabstops (G.tabstops )
274 # define terminal_width (G.terminal_width)
277 tabstops = COLUMN_GAP,
278 terminal_width = TERMINAL_WIDTH,
281 #define current_time_t (G.current_time_t)
282 #define INIT_G() do { \
283 /* we have to zero it out because of NOEXEC */ \
284 memset(&G, 0, sizeof(G)); \
285 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
286 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
287 IF_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \
291 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
295 IF_SELINUX(security_context_t sid = NULL;)
297 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
299 if (is_selinux_enabled()) {
300 getfilecon(fullname, &sid);
303 if (stat(fullname, &dstat)) {
304 bb_simple_perror_msg(fullname);
305 exit_code = EXIT_FAILURE;
310 if (is_selinux_enabled()) {
311 lgetfilecon(fullname, &sid);
314 if (lstat(fullname, &dstat)) {
315 bb_simple_perror_msg(fullname);
316 exit_code = EXIT_FAILURE;
321 cur = xmalloc(sizeof(struct dnode));
322 cur->fullname = fullname;
325 IF_SELINUX(cur->sid = sid;)
330 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
331 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
332 * 3/7:multiplexed char/block device)
333 * and we use 0 for unknown and 15 for executables (see below) */
334 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
335 #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
336 #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
337 /* 036 black foreground 050 black background
338 037 red foreground 051 red background
339 040 green foreground 052 green background
340 041 brown foreground 053 brown background
341 042 blue foreground 054 blue background
342 043 magenta (purple) foreground 055 magenta background
343 044 cyan (light blue) foreground 056 cyan background
344 045 gray foreground 057 white background
346 #define COLOR(mode) ( \
347 /*un fi chr dir blk file link sock exe */ \
348 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
350 /* Select normal (0) [actually "reset all"] or bold (1)
351 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
352 * let's use 7 for "impossible" types, just for fun)
353 * Note: coreutils 6.9 uses inverted red for setuid binaries.
355 #define ATTR(mode) ( \
356 /*un fi chr dir blk file link sock exe */ \
357 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
360 #if ENABLE_FEATURE_LS_COLOR
361 /* mode of zero is interpreted as "unknown" (stat failed) */
362 static char fgcolor(mode_t mode)
364 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
365 return COLOR(0xF000); /* File is executable ... */
368 static char bold(mode_t mode)
370 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
371 return ATTR(0xF000); /* File is executable ... */
376 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
377 static char append_char(mode_t mode)
379 if (!(all_fmt & LIST_FILETYPE))
383 if (!(all_fmt & LIST_EXEC))
385 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
387 return APPCHAR(mode);
392 #define countdirs(A, B) count_dirs((A), (B), 1)
393 #define countsubdirs(A, B) count_dirs((A), (B), 0)
394 static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs)
401 for (i = 0; i < nfiles; i++) {
403 if (!S_ISDIR(dn[i]->dstat.st_mode))
407 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
415 static int countfiles(struct dnode **dnp)
423 for (cur = dnp[0]; cur->next; cur = cur->next)
429 /* get memory to hold an array of pointers */
430 static struct dnode **dnalloc(int num)
435 return xzalloc(num * sizeof(struct dnode *));
438 #if ENABLE_FEATURE_LS_RECURSIVE
439 static void dfree(struct dnode **dnp, int nfiles)
446 for (i = 0; i < nfiles; i++) {
447 struct dnode *cur = dnp[i];
449 free((char*)cur->fullname); /* free the filename */
450 free(cur); /* free the dnode */
452 free(dnp); /* free the array holding the dnode pointers */
455 #define dfree(...) ((void)0)
458 static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which)
463 if (dn == NULL || nfiles < 1)
466 /* count how many dirs and regular files there are */
467 if (which == SPLIT_SUBDIR)
468 dncnt = countsubdirs(dn, nfiles);
470 dncnt = countdirs(dn, nfiles); /* assume we are looking for dirs */
471 if (which == SPLIT_FILE)
472 dncnt = nfiles - dncnt; /* looking for files */
475 /* allocate a file array and a dir array */
476 dnp = dnalloc(dncnt);
478 /* copy the entrys into the file or dir array */
479 for (d = i = 0; i < nfiles; i++) {
480 if (S_ISDIR(dn[i]->dstat.st_mode)) {
482 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
485 if ((which & SPLIT_DIR)
486 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
490 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
497 #if ENABLE_FEATURE_LS_SORTFILES
498 static int sortcmp(const void *a, const void *b)
500 struct dnode *d1 = *(struct dnode **)a;
501 struct dnode *d2 = *(struct dnode **)b;
502 unsigned sort_opts = all_fmt & SORT_MASK;
505 dif = 0; /* assume SORT_NAME */
506 // TODO: use pre-initialized function pointer
507 // instead of branch forest
508 if (sort_opts == SORT_SIZE) {
509 dif = (int) (d2->dstat.st_size - d1->dstat.st_size);
510 } else if (sort_opts == SORT_ATIME) {
511 dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime);
512 } else if (sort_opts == SORT_CTIME) {
513 dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime);
514 } else if (sort_opts == SORT_MTIME) {
515 dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime);
516 } else if (sort_opts == SORT_DIR) {
517 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
518 /* } else if (sort_opts == SORT_VERSION) { */
519 /* } else if (sort_opts == SORT_EXT) { */
523 /* sort by name - may be a tie_breaker for time or size cmp */
524 if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name);
525 else dif = strcmp(d1->name, d2->name);
528 if (all_fmt & SORT_REVERSE) {
534 static void dnsort(struct dnode **dn, int size)
536 qsort(dn, size, sizeof(*dn), sortcmp);
539 #define dnsort(dn, size) ((void)0)
543 static void showfiles(struct dnode **dn, int nfiles)
545 int i, ncols, nrows, row, nc;
548 int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */
550 if (dn == NULL || nfiles < 1)
553 if (all_fmt & STYLE_LONG) {
556 /* find the longest file name, use that as the column width */
557 for (i = 0; i < nfiles; i++) {
558 int len = bb_mbstrlen(dn[i]->name);
559 if (column_width < len)
562 column_width += tabstops +
563 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
564 ((all_fmt & LIST_INO) ? 8 : 0) +
565 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
566 ncols = (int) (terminal_width / column_width);
570 nrows = nfiles / ncols;
571 if (nrows * ncols < nfiles)
572 nrows++; /* round up fractionals */
578 for (row = 0; row < nrows; row++) {
579 for (nc = 0; nc < ncols; nc++) {
580 /* reach into the array based on the column and row */
581 i = (nc * nrows) + row; /* assume display by column */
582 if (all_fmt & DISP_ROWS)
583 i = (row * ncols) + nc; /* display across row */
587 printf("%*s", nexttab, "");
590 nexttab = column + column_width;
591 column += list_single(dn[i]);
601 static off_t calculate_blocks(struct dnode **dn, int nfiles)
605 blocks += (*dn)->dstat.st_blocks; /* in 512 byte blocks */
610 /* Even though POSIX says use 512 byte blocks, coreutils use 1k */
611 /* Actually, we round up by calculating (blocks + 1) / 2,
612 * "+ 1" was done when we initialized blocks to 1 */
618 static void showdirs(struct dnode **dn, int ndirs, int first)
621 struct dnode **subdnp;
625 if (dn == NULL || ndirs < 1)
628 for (i = 0; i < ndirs; i++) {
629 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
633 printf("%s:\n", dn[i]->fullname);
635 subdnp = list_dir(dn[i]->fullname);
636 nfiles = countfiles(subdnp);
638 if (all_fmt & STYLE_LONG)
639 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp, nfiles));
642 /* list all files at this level */
643 dnsort(subdnp, nfiles);
644 showfiles(subdnp, nfiles);
645 if (ENABLE_FEATURE_LS_RECURSIVE) {
646 if (all_fmt & DISP_RECURSIVE) {
647 /* recursive- list the sub-dirs */
648 dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR);
649 dndirs = countsubdirs(subdnp, nfiles);
652 showdirs(dnd, dndirs, 0);
653 /* free the array of dnode pointers to the dirs */
657 /* free the dnodes and the fullname mem */
658 dfree(subdnp, nfiles);
665 static struct dnode **list_dir(const char *path)
667 struct dnode *dn, *cur, **dnp;
668 struct dirent *entry;
677 dir = warn_opendir(path);
679 exit_code = EXIT_FAILURE;
680 return NULL; /* could not open the dir */
682 while ((entry = readdir(dir)) != NULL) {
685 /* are we going to list the file- it may be . or .. or a hidden file */
686 if (entry->d_name[0] == '.') {
687 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
688 && !(all_fmt & DISP_DOT)
692 if (!(all_fmt & DISP_HIDDEN))
695 fullname = concat_path_file(path, entry->d_name);
696 cur = my_stat(fullname, bb_basename(fullname), 0);
708 /* now that we know how many files there are
709 * allocate memory for an array to hold dnode pointers
713 dnp = dnalloc(nfiles);
714 for (i = 0, cur = dn; i < nfiles; i++) {
715 dnp[i] = cur; /* save pointer to node in array */
723 static int print_name(const char *name)
725 if (option_mask32 & OPT_Q) {
726 #if ENABLE_FEATURE_ASSUME_UNICODE
727 int len = 2 + bb_mbstrlen(name);
738 if (!ENABLE_FEATURE_ASSUME_UNICODE)
745 #if ENABLE_FEATURE_ASSUME_UNICODE
747 return bb_mbstrlen(name);
749 return printf("%s", name);
754 static int list_single(const struct dnode *dn)
757 char *lpath = lpath; /* for compiler */
758 #if ENABLE_FEATURE_LS_TIMESTAMPS
762 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
767 if (dn->fullname == NULL)
770 #if ENABLE_FEATURE_LS_TIMESTAMPS
771 ttime = dn->dstat.st_mtime; /* the default time */
772 if (all_fmt & TIME_ACCESS)
773 ttime = dn->dstat.st_atime;
774 if (all_fmt & TIME_CHANGE)
775 ttime = dn->dstat.st_ctime;
776 filetime = ctime(&ttime);
778 #if ENABLE_FEATURE_LS_FILETYPES
779 append = append_char(dn->dstat.st_mode);
782 /* Do readlink early, so that if it fails, error message
783 * does not appear *inside* of the "ls -l" line */
784 if (all_fmt & LIST_SYMLINK)
785 if (S_ISLNK(dn->dstat.st_mode))
786 lpath = xmalloc_readlink_or_warn(dn->fullname);
788 if (all_fmt & LIST_INO)
789 column += printf("%7lu ", (long) dn->dstat.st_ino);
790 if (all_fmt & LIST_BLOCKS)
791 column += printf("%4"OFF_FMT"u ", (off_t) dn->dstat.st_blocks >> 1);
792 if (all_fmt & LIST_MODEBITS)
793 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
794 if (all_fmt & LIST_NLINKS)
795 column += printf("%4lu ", (long) dn->dstat.st_nlink);
796 #if ENABLE_FEATURE_LS_USERNAME
797 if (all_fmt & LIST_ID_NAME) {
798 if (option_mask32 & OPT_g) {
799 column += printf("%-8.8s",
800 get_cached_username(dn->dstat.st_uid));
802 column += printf("%-8.8s %-8.8s",
803 get_cached_username(dn->dstat.st_uid),
804 get_cached_groupname(dn->dstat.st_gid));
808 if (all_fmt & LIST_ID_NUMERIC) {
809 if (option_mask32 & OPT_g)
810 column += printf("%-8u", (int) dn->dstat.st_uid);
812 column += printf("%-8u %-8u",
813 (int) dn->dstat.st_uid,
814 (int) dn->dstat.st_gid);
816 if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
817 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
818 column += printf("%4u, %3u ",
819 (int) major(dn->dstat.st_rdev),
820 (int) minor(dn->dstat.st_rdev));
822 if (all_fmt & LS_DISP_HR) {
823 column += printf("%9s ",
824 make_human_readable_str(dn->dstat.st_size, 1, 0));
826 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
830 #if ENABLE_FEATURE_LS_TIMESTAMPS
831 if (all_fmt & LIST_FULLTIME)
832 column += printf("%24.24s ", filetime);
833 if (all_fmt & LIST_DATE_TIME)
834 if ((all_fmt & LIST_FULLTIME) == 0) {
835 /* current_time_t ~== time(NULL) */
836 age = current_time_t - ttime;
837 printf("%6.6s ", filetime + 4);
838 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
839 /* hh:mm if less than 6 months old */
840 printf("%5.5s ", filetime + 11);
842 printf(" %4.4s ", filetime + 20);
848 if (all_fmt & LIST_CONTEXT) {
849 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
853 if (all_fmt & LIST_FILENAME) {
854 #if ENABLE_FEATURE_LS_COLOR
856 info.st_mode = 0; /* for fgcolor() */
857 lstat(dn->fullname, &info);
858 printf("\033[%u;%um", bold(info.st_mode),
859 fgcolor(info.st_mode));
862 column += print_name(dn->name);
867 if (all_fmt & LIST_SYMLINK) {
868 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
870 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
871 #if ENABLE_FEATURE_LS_COLOR
872 info.st_mode = 0; /* for fgcolor() */
874 if (stat(dn->fullname, &info) == 0) {
875 append = append_char(info.st_mode);
878 #if ENABLE_FEATURE_LS_COLOR
880 printf("\033[%u;%um", bold(info.st_mode),
881 fgcolor(info.st_mode));
884 column += print_name(lpath) + 4;
891 #if ENABLE_FEATURE_LS_FILETYPES
892 if (all_fmt & LIST_FILETYPE) {
904 int ls_main(int argc UNUSED_PARAM, char **argv)
916 #if ENABLE_FEATURE_LS_COLOR
917 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
920 * ls: invalid argument 'BOGUS' for '--color'
921 * Valid arguments are:
922 * 'always', 'yes', 'force'
923 * 'never', 'no', 'none'
924 * 'auto', 'tty', 'if-tty'
925 * (and substrings: "--color=alwa" work too)
927 static const char ls_longopts[] ALIGN1 =
928 "color\0" Optional_argument "\xff"; /* no short equivalent */
929 static const char color_str[] ALIGN1 =
930 "always\0""yes\0""force\0"
931 "auto\0""tty\0""if-tty\0";
932 /* need to initialize since --color has _an optional_ argument */
933 const char *color_opt = color_str; /* "always" */
938 check_unicode_in_env();
940 all_fmt = LIST_SHORT |
941 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
943 #if ENABLE_FEATURE_AUTOWIDTH
944 /* obtain the terminal width */
945 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
950 /* process options */
951 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
952 #if ENABLE_FEATURE_AUTOWIDTH
953 opt_complementary = "T+:w+"; /* -T N, -w N */
954 opt = getopt32(argv, ls_options, &tabstops, &terminal_width
955 IF_FEATURE_LS_COLOR(, &color_opt));
957 opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
959 for (i = 0; opt_flags[i] != (1U<<31); i++) {
960 if (opt & (1 << i)) {
961 unsigned flags = opt_flags[i];
963 if (flags & LIST_MASK_TRIGGER)
964 all_fmt &= ~LIST_MASK;
965 if (flags & STYLE_MASK_TRIGGER)
966 all_fmt &= ~STYLE_MASK;
967 if (flags & SORT_MASK_TRIGGER)
968 all_fmt &= ~SORT_MASK;
969 if (flags & DISP_MASK_TRIGGER)
970 all_fmt &= ~DISP_MASK;
971 if (flags & TIME_MASK)
972 all_fmt &= ~TIME_MASK;
973 if (flags & LIST_CONTEXT)
974 all_fmt |= STYLE_SINGLE;
975 /* huh?? opt cannot be 'l' */
976 //if (LS_DISP_HR && opt == 'l')
977 // all_fmt &= ~LS_DISP_HR;
982 #if ENABLE_FEATURE_LS_COLOR
983 /* find color bit value - last position for short getopt */
984 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
985 char *p = getenv("LS_COLORS");
986 /* LS_COLORS is unset, or (not empty && not "none") ? */
987 if (!p || (p[0] && strcmp(p, "none") != 0))
990 if (opt & OPT_color) {
991 if (color_opt[0] == 'n')
993 else switch (index_in_substrings(color_str, color_opt)) {
997 if (isatty(STDOUT_FILENO)) {
1007 /* sort out which command line options take precedence */
1008 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1009 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1010 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1011 if (all_fmt & TIME_CHANGE)
1012 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1013 if (all_fmt & TIME_ACCESS)
1014 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1016 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1017 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
1018 if (ENABLE_FEATURE_LS_USERNAME)
1019 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1020 all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
1022 /* choose a display format */
1023 if (!(all_fmt & STYLE_MASK))
1024 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1028 *--argv = (char*)".";
1031 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1033 /* stuff the command line file names into a dnode array */
1037 /* NB: follow links on command line unless -l or -s */
1038 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1048 /* now that we know how many files there are
1049 * allocate memory for an array to hold dnode pointers
1051 dnp = dnalloc(nfiles);
1052 for (i = 0, cur = dn; i < nfiles; i++) {
1053 dnp[i] = cur; /* save pointer to node in array */
1057 if (all_fmt & DISP_NOLIST) {
1058 dnsort(dnp, nfiles);
1060 showfiles(dnp, nfiles);
1062 dnd = splitdnarray(dnp, nfiles, SPLIT_DIR);
1063 dnf = splitdnarray(dnp, nfiles, SPLIT_FILE);
1064 dndirs = countdirs(dnp, nfiles);
1065 dnfiles = nfiles - dndirs;
1067 dnsort(dnf, dnfiles);
1068 showfiles(dnf, dnfiles);
1069 if (ENABLE_FEATURE_CLEAN_UP)
1073 dnsort(dnd, dndirs);
1074 showdirs(dnd, dndirs, dnfiles == 0);
1075 if (ENABLE_FEATURE_CLEAN_UP)
1079 if (ENABLE_FEATURE_CLEAN_UP)