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
236 const char *name; /* the dir entry name */
237 const char *fullname; /* the dir entry name */
238 struct dnode *next; /* point at the next node */
239 smallint fname_allocated;
240 struct stat dstat; /* the file stat info */
241 IF_SELINUX(security_context_t sid;)
244 static struct dnode **list_dir(const char *, unsigned *);
245 static unsigned list_single(const struct dnode *);
248 #if ENABLE_FEATURE_LS_COLOR
253 #if ENABLE_FEATURE_AUTOWIDTH
254 unsigned tabstops; // = COLUMN_GAP;
255 unsigned terminal_width; // = TERMINAL_WIDTH;
257 #if ENABLE_FEATURE_LS_TIMESTAMPS
258 /* Do time() just once. Saves one syscall per file for "ls -l" */
259 time_t current_time_t;
262 #define G (*(struct globals*)&bb_common_bufsiz1)
263 #if ENABLE_FEATURE_LS_COLOR
264 # define show_color (G.show_color )
266 enum { show_color = 0 };
268 #define exit_code (G.exit_code )
269 #define all_fmt (G.all_fmt )
270 #if ENABLE_FEATURE_AUTOWIDTH
271 # define tabstops (G.tabstops )
272 # define terminal_width (G.terminal_width)
275 tabstops = COLUMN_GAP,
276 terminal_width = TERMINAL_WIDTH,
279 #define current_time_t (G.current_time_t)
280 #define INIT_G() do { \
281 /* we have to zero it out because of NOEXEC */ \
282 memset(&G, 0, sizeof(G)); \
283 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
284 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
285 IF_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \
289 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
293 IF_SELINUX(security_context_t sid = NULL;)
295 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
297 if (is_selinux_enabled()) {
298 getfilecon(fullname, &sid);
301 if (stat(fullname, &dstat)) {
302 bb_simple_perror_msg(fullname);
303 exit_code = EXIT_FAILURE;
308 if (is_selinux_enabled()) {
309 lgetfilecon(fullname, &sid);
312 if (lstat(fullname, &dstat)) {
313 bb_simple_perror_msg(fullname);
314 exit_code = EXIT_FAILURE;
319 cur = xmalloc(sizeof(*cur));
320 cur->fullname = fullname;
323 IF_SELINUX(cur->sid = sid;)
327 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
328 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
329 * 3/7:multiplexed char/block device)
330 * and we use 0 for unknown and 15 for executables (see below) */
331 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
332 #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
333 #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
334 /* 036 black foreground 050 black background
335 037 red foreground 051 red background
336 040 green foreground 052 green background
337 041 brown foreground 053 brown background
338 042 blue foreground 054 blue background
339 043 magenta (purple) foreground 055 magenta background
340 044 cyan (light blue) foreground 056 cyan background
341 045 gray foreground 057 white background
343 #define COLOR(mode) ( \
344 /*un fi chr dir blk file link sock exe */ \
345 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
347 /* Select normal (0) [actually "reset all"] or bold (1)
348 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
349 * let's use 7 for "impossible" types, just for fun)
350 * Note: coreutils 6.9 uses inverted red for setuid binaries.
352 #define ATTR(mode) ( \
353 /*un fi chr dir blk file link sock exe */ \
354 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
357 #if ENABLE_FEATURE_LS_COLOR
358 /* mode of zero is interpreted as "unknown" (stat failed) */
359 static char fgcolor(mode_t mode)
361 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
362 return COLOR(0xF000); /* File is executable ... */
365 static char bold(mode_t mode)
367 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
368 return ATTR(0xF000); /* File is executable ... */
373 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
374 static char append_char(mode_t mode)
376 if (!(all_fmt & LIST_FILETYPE))
380 if (!(all_fmt & LIST_EXEC))
382 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
384 return APPCHAR(mode);
388 static unsigned count_dirs(struct dnode **dn, int which)
400 if (!S_ISDIR((*dn)->dstat.st_mode))
403 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
404 /* or if it's not . or .. */
405 || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
410 return which != SPLIT_FILE ? dirs : all - dirs;
413 /* get memory to hold an array of pointers */
414 static struct dnode **dnalloc(unsigned num)
419 num++; /* so that we have terminating NULL */
420 return xzalloc(num * sizeof(struct dnode *));
423 #if ENABLE_FEATURE_LS_RECURSIVE
424 static void dfree(struct dnode **dnp)
431 for (i = 0; dnp[i]; i++) {
432 struct dnode *cur = dnp[i];
433 if (cur->fname_allocated)
434 free((char*)cur->fullname);
440 #define dfree(...) ((void)0)
443 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
444 static struct dnode **splitdnarray(struct dnode **dn, int which)
452 /* count how many dirs or files there are */
453 dncnt = count_dirs(dn, which);
455 /* allocate a file array and a dir array */
456 dnp = dnalloc(dncnt);
458 /* copy the entrys into the file or dir array */
459 for (d = 0; *dn; dn++) {
460 if (S_ISDIR((*dn)->dstat.st_mode)) {
463 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
466 if ((which & SPLIT_DIR)
467 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
471 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
478 #if ENABLE_FEATURE_LS_SORTFILES
479 static int sortcmp(const void *a, const void *b)
481 struct dnode *d1 = *(struct dnode **)a;
482 struct dnode *d2 = *(struct dnode **)b;
483 unsigned sort_opts = all_fmt & SORT_MASK;
486 dif = 0; /* assume SORT_NAME */
487 // TODO: use pre-initialized function pointer
488 // instead of branch forest
489 if (sort_opts == SORT_SIZE) {
490 dif = (d2->dstat.st_size - d1->dstat.st_size);
491 } else if (sort_opts == SORT_ATIME) {
492 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
493 } else if (sort_opts == SORT_CTIME) {
494 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
495 } else if (sort_opts == SORT_MTIME) {
496 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
497 } else if (sort_opts == SORT_DIR) {
498 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
499 /* } else if (sort_opts == SORT_VERSION) { */
500 /* } else if (sort_opts == SORT_EXT) { */
503 /* sort by name, or tie_breaker for other sorts */
504 if (ENABLE_LOCALE_SUPPORT)
505 dif = strcoll(d1->name, d2->name);
507 dif = strcmp(d1->name, d2->name);
510 /* Make dif fit into an int */
511 if (sizeof(dif) > sizeof(int)) {
512 if (sizeof(dif) == sizeof(int)*2) {
513 /* typical on many arches */
515 dif = 1 | (int)((uoff_t)dif >> (sizeof(int)*8));
518 while ((dif & ~(off_t)INT_MAX) != 0) {
519 dif >>= (sizeof(int)*8 / 2);
524 return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
527 static void dnsort(struct dnode **dn, int size)
529 qsort(dn, size, sizeof(*dn), sortcmp);
532 #define dnsort(dn, size) ((void)0)
536 static void showfiles(struct dnode **dn, unsigned nfiles)
538 unsigned i, ncols, nrows, row, nc;
540 unsigned nexttab = 0;
541 unsigned column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */
544 if (dn == NULL || nfiles < 1)
548 if (all_fmt & STYLE_LONG) {
551 /* find the longest file name, use that as the column width */
552 for (i = 0; dn[i]; i++) {
553 int len = unicode_strlen(dn[i]->name);
554 if (column_width < len)
557 column_width += tabstops +
558 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
559 ((all_fmt & LIST_INO) ? 8 : 0) +
560 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
561 ncols = (int) (terminal_width / column_width);
565 nrows = nfiles / ncols;
566 if (nrows * ncols < nfiles)
567 nrows++; /* round up fractionals */
573 for (row = 0; row < nrows; row++) {
574 for (nc = 0; nc < ncols; nc++) {
575 /* reach into the array based on the column and row */
576 if (all_fmt & DISP_ROWS)
577 i = (row * ncols) + nc; /* display across row */
579 i = (nc * nrows) + row; /* display by column */
583 printf("%*s", nexttab, "");
586 nexttab = column + column_width;
587 column += list_single(dn[i]);
597 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
598 * If any of the -l, -n, -s options is specified, each list
599 * of files within the directory shall be preceded by a
600 * status line indicating the number of file system blocks
601 * occupied by files in the directory in 512-byte units if
602 * the -k option is not specified, or 1024-byte units if the
603 * -k option is specified, rounded up to the next integral
606 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
607 static off_t calculate_blocks(struct dnode **dn)
612 /* st_blocks is in 512 byte blocks */
613 blocks += (*dn)->dstat.st_blocks;
618 /* Even though standard says use 512 byte blocks, coreutils use 1k */
619 /* Actually, we round up by calculating (blocks + 1) / 2,
620 * "+ 1" was done when we initialized blocks to 1 */
626 static void showdirs(struct dnode **dn, int first)
630 struct dnode **subdnp;
634 if (dn == NULL || ndirs < 1) {
640 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
644 printf("%s:\n", (*dn)->fullname);
646 subdnp = list_dir((*dn)->fullname, &nfiles);
648 if ((all_fmt & STYLE_MASK) == STYLE_LONG)
649 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
652 /* list all files at this level */
653 dnsort(subdnp, nfiles);
654 showfiles(subdnp, nfiles);
655 if (ENABLE_FEATURE_LS_RECURSIVE
656 && (all_fmt & DISP_RECURSIVE)
658 /* recursive - list the sub-dirs */
659 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
660 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
664 /* free the array of dnode pointers to the dirs */
668 /* free the dnodes and the fullname mem */
675 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
676 static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
678 struct dnode *dn, *cur, **dnp;
679 struct dirent *entry;
689 dir = warn_opendir(path);
691 exit_code = EXIT_FAILURE;
692 return NULL; /* could not open the dir */
696 while ((entry = readdir(dir)) != NULL) {
699 /* are we going to list the file- it may be . or .. or a hidden file */
700 if (entry->d_name[0] == '.') {
701 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
702 && !(all_fmt & DISP_DOT)
706 if (!(all_fmt & DISP_HIDDEN))
709 fullname = concat_path_file(path, entry->d_name);
710 cur = my_stat(fullname, bb_basename(fullname), 0);
715 cur->fname_allocated = 1;
725 /* now that we know how many files there are
726 * allocate memory for an array to hold dnode pointers
729 dnp = dnalloc(nfiles);
730 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
731 dnp[i] = dn; /* save pointer to node in array */
741 static int print_name(const char *name)
743 if (option_mask32 & OPT_Q) {
744 #if ENABLE_FEATURE_ASSUME_UNICODE
745 unsigned len = 2 + unicode_strlen(name);
756 if (!ENABLE_FEATURE_ASSUME_UNICODE)
763 #if ENABLE_FEATURE_ASSUME_UNICODE
765 return unicode_strlen(name);
767 return printf("%s", name);
772 static NOINLINE unsigned list_single(const struct dnode *dn)
775 char *lpath = lpath; /* for compiler */
776 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
782 if (dn->fullname == NULL)
786 #if ENABLE_FEATURE_LS_FILETYPES
787 append = append_char(dn->dstat.st_mode);
790 /* Do readlink early, so that if it fails, error message
791 * does not appear *inside* the "ls -l" line */
792 if (all_fmt & LIST_SYMLINK)
793 if (S_ISLNK(dn->dstat.st_mode))
794 lpath = xmalloc_readlink_or_warn(dn->fullname);
796 if (all_fmt & LIST_INO)
797 column += printf("%7llu ", (long long) dn->dstat.st_ino);
798 if (all_fmt & LIST_BLOCKS)
799 column += printf("%4"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
800 if (all_fmt & LIST_MODEBITS)
801 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
802 if (all_fmt & LIST_NLINKS)
803 column += printf("%4lu ", (long) dn->dstat.st_nlink);
804 #if ENABLE_FEATURE_LS_USERNAME
805 if (all_fmt & LIST_ID_NAME) {
806 if (option_mask32 & OPT_g) {
807 column += printf("%-8.8s ",
808 get_cached_username(dn->dstat.st_uid));
810 column += printf("%-8.8s %-8.8s ",
811 get_cached_username(dn->dstat.st_uid),
812 get_cached_groupname(dn->dstat.st_gid));
816 if (all_fmt & LIST_ID_NUMERIC) {
817 if (option_mask32 & OPT_g)
818 column += printf("%-8u ", (int) dn->dstat.st_uid);
820 column += printf("%-8u %-8u ",
821 (int) dn->dstat.st_uid,
822 (int) dn->dstat.st_gid);
824 if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
825 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
826 column += printf("%4u, %3u ",
827 (int) major(dn->dstat.st_rdev),
828 (int) minor(dn->dstat.st_rdev));
830 if (all_fmt & LS_DISP_HR) {
831 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
832 /* print st_size, show one fractional, use suffixes */
833 make_human_readable_str(dn->dstat.st_size, 1, 0)
836 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
840 #if ENABLE_FEATURE_LS_TIMESTAMPS
841 if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
843 time_t ttime = dn->dstat.st_mtime;
844 if (all_fmt & TIME_ACCESS)
845 ttime = dn->dstat.st_atime;
846 if (all_fmt & TIME_CHANGE)
847 ttime = dn->dstat.st_ctime;
848 filetime = ctime(&ttime);
849 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
850 if (all_fmt & LIST_FULLTIME)
851 column += printf("%.24s ", filetime);
852 else { /* LIST_DATE_TIME */
853 /* current_time_t ~== time(NULL) */
854 time_t age = current_time_t - ttime;
855 printf("%.6s ", filetime + 4); /* "Jun 30" */
856 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
857 /* hh:mm if less than 6 months old */
858 printf("%.5s ", filetime + 11);
859 } else { /* year. buggy if year > 9999 ;) */
860 printf(" %.4s ", filetime + 20);
867 if (all_fmt & LIST_CONTEXT) {
868 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
872 if (all_fmt & LIST_FILENAME) {
873 #if ENABLE_FEATURE_LS_COLOR
875 info.st_mode = 0; /* for fgcolor() */
876 lstat(dn->fullname, &info);
877 printf("\033[%u;%um", bold(info.st_mode),
878 fgcolor(info.st_mode));
881 column += print_name(dn->name);
886 if (all_fmt & LIST_SYMLINK) {
887 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
889 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
890 #if ENABLE_FEATURE_LS_COLOR
891 info.st_mode = 0; /* for fgcolor() */
893 if (stat(dn->fullname, &info) == 0) {
894 append = append_char(info.st_mode);
897 #if ENABLE_FEATURE_LS_COLOR
899 printf("\033[%u;%um", bold(info.st_mode),
900 fgcolor(info.st_mode));
903 column += print_name(lpath) + 4;
910 #if ENABLE_FEATURE_LS_FILETYPES
911 if (all_fmt & LIST_FILETYPE) {
923 int ls_main(int argc UNUSED_PARAM, char **argv)
935 #if ENABLE_FEATURE_LS_COLOR
936 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
939 * ls: invalid argument 'BOGUS' for '--color'
940 * Valid arguments are:
941 * 'always', 'yes', 'force'
942 * 'never', 'no', 'none'
943 * 'auto', 'tty', 'if-tty'
944 * (and substrings: "--color=alwa" work too)
946 static const char ls_longopts[] ALIGN1 =
947 "color\0" Optional_argument "\xff"; /* no short equivalent */
948 static const char color_str[] ALIGN1 =
949 "always\0""yes\0""force\0"
950 "auto\0""tty\0""if-tty\0";
951 /* need to initialize since --color has _an optional_ argument */
952 const char *color_opt = color_str; /* "always" */
959 all_fmt = LIST_SHORT |
960 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
962 #if ENABLE_FEATURE_AUTOWIDTH
963 /* obtain the terminal width */
964 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
969 /* process options */
970 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
971 #if ENABLE_FEATURE_AUTOWIDTH
972 opt_complementary = "T+:w+"; /* -T N, -w N */
973 opt = getopt32(argv, ls_options, &tabstops, &terminal_width
974 IF_FEATURE_LS_COLOR(, &color_opt));
976 opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
978 for (i = 0; opt_flags[i] != (1U<<31); i++) {
979 if (opt & (1 << i)) {
980 unsigned flags = opt_flags[i];
982 if (flags & LIST_MASK_TRIGGER)
983 all_fmt &= ~LIST_MASK;
984 if (flags & STYLE_MASK_TRIGGER)
985 all_fmt &= ~STYLE_MASK;
986 if (flags & SORT_MASK_TRIGGER)
987 all_fmt &= ~SORT_MASK;
988 if (flags & DISP_MASK_TRIGGER)
989 all_fmt &= ~DISP_MASK;
990 if (flags & TIME_MASK)
991 all_fmt &= ~TIME_MASK;
992 if (flags & LIST_CONTEXT)
993 all_fmt |= STYLE_SINGLE;
994 /* huh?? opt cannot be 'l' */
995 //if (LS_DISP_HR && opt == 'l')
996 // all_fmt &= ~LS_DISP_HR;
1001 #if ENABLE_FEATURE_LS_COLOR
1002 /* find color bit value - last position for short getopt */
1003 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1004 char *p = getenv("LS_COLORS");
1005 /* LS_COLORS is unset, or (not empty && not "none") ? */
1006 if (!p || (p[0] && strcmp(p, "none") != 0))
1009 if (opt & OPT_color) {
1010 if (color_opt[0] == 'n')
1012 else switch (index_in_substrings(color_str, color_opt)) {
1016 if (isatty(STDOUT_FILENO)) {
1026 /* sort out which command line options take precedence */
1027 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1028 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1029 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1030 if (all_fmt & TIME_CHANGE)
1031 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1032 if (all_fmt & TIME_ACCESS)
1033 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1035 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1036 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
1037 if (ENABLE_FEATURE_LS_USERNAME)
1038 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1039 all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
1041 /* choose a display format */
1042 if (!(all_fmt & STYLE_MASK))
1043 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1047 *--argv = (char*)".";
1050 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1052 /* stuff the command line file names into a dnode array */
1056 /* NB: follow links on command line unless -l or -s */
1057 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1061 cur->fname_allocated = 0;
1067 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1071 /* now that we know how many files there are
1072 * allocate memory for an array to hold dnode pointers
1074 dnp = dnalloc(nfiles);
1075 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1076 dnp[i] = dn; /* save pointer to node in array */
1082 if (all_fmt & DISP_NOLIST) {
1083 dnsort(dnp, nfiles);
1084 showfiles(dnp, nfiles);
1086 dnd = splitdnarray(dnp, SPLIT_DIR);
1087 dnf = splitdnarray(dnp, SPLIT_FILE);
1088 dndirs = count_dirs(dnp, SPLIT_DIR);
1089 dnfiles = nfiles - dndirs;
1091 dnsort(dnf, dnfiles);
1092 showfiles(dnf, dnfiles);
1093 if (ENABLE_FEATURE_CLEAN_UP)
1097 dnsort(dnd, dndirs);
1098 showdirs(dnd, dnfiles == 0);
1099 if (ENABLE_FEATURE_CLEAN_UP)
1103 if (ENABLE_FEATURE_CLEAN_UP)