Tomato 1.24
[tomato.git] / release / src / router / busybox / coreutils / ls.c
blob61baa9a1109ebfb398914bd7ce0589916f2683ad
1 /* vi: set sw=4 ts=4: */
2 /*
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.
7 */
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
17 * it more portable.
19 * KNOWN BUGS:
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
27 * PORTABILITY:
28 * 1. requires lstat (BSD) - how do you do it without?
30 * [2009-03]
31 * ls sorts listing now, and supports almost all options.
34 #include "libbb.h"
36 #if ENABLE_FEATURE_ASSUME_UNICODE
37 #include <wchar.h>
38 #endif
40 /* This is a NOEXEC applet. Be very careful! */
43 #if ENABLE_FTPD
44 /* ftpd uses ls, and without timestamps Mozilla won't understand
45 * ftpd's LIST output.
47 # undef CONFIG_FEATURE_LS_TIMESTAMPS
48 # undef ENABLE_FEATURE_LS_TIMESTAMPS
49 # undef USE_FEATURE_LS_TIMESTAMPS
50 # undef SKIP_FEATURE_LS_TIMESTAMPS
51 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
52 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
53 # define USE_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
54 # define SKIP_FEATURE_LS_TIMESTAMPS(...)
55 #endif
58 enum {
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 */
71 LIST_INO = 1 << 0,
72 LIST_BLOCKS = 1 << 1,
73 LIST_MODEBITS = 1 << 2,
74 LIST_NLINKS = 1 << 3,
75 LIST_ID_NAME = 1 << 4,
76 LIST_ID_NUMERIC = 1 << 5,
77 LIST_CONTEXT = 1 << 6,
78 LIST_SIZE = 1 << 7,
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,
85 LIST_EXEC = 1 << 14,
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,
124 SPLIT_DIR = 1,
125 SPLIT_FILE = 0,
126 SPLIT_SUBDIR = 2,
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 USE_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
142 USE_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
143 USE_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
144 USE_FEATURE_LS_FOLLOWLINKS("L") /* 1, 24 */
145 USE_FEATURE_LS_RECURSIVE("R") /* 1, 25 */
146 USE_FEATURE_HUMAN_READABLE("h") /* 1, 26 */
147 USE_SELINUX("K") /* 1, 27 */
148 USE_SELINUX("Z") /* 1, 28 */
149 USE_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
151 enum {
152 //OPT_C = (1 << 0),
153 //OPT_a = (1 << 1),
154 //OPT_d = (1 << 2),
155 //OPT_i = (1 << 3),
156 //OPT_l = (1 << 4),
157 //OPT_1 = (1 << 5),
158 OPT_g = (1 << 6),
159 //OPT_n = (1 << 7),
160 //OPT_s = (1 << 8),
161 //OPT_x = (1 << 9),
162 OPT_Q = (1 << 10),
163 //OPT_A = (1 << 11),
164 //OPT_k = (1 << 12),
167 enum {
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 */
178 DISP_NOLIST, /* d */
179 LIST_INO, /* i */
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 */
184 LIST_BLOCKS, /* s */
185 DISP_ROWS, /* x */
186 0, /* Q (quote filename) - handled via OPT_Q */
187 DISP_HIDDEN, /* A */
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 */
194 #endif
195 #if ENABLE_FEATURE_LS_SORTFILES
196 SORT_SIZE, /* S */
197 SORT_EXT, /* X */
198 SORT_REVERSE, /* r */
199 SORT_VERSION, /* v */
200 #endif
201 #if ENABLE_FEATURE_LS_FILETYPES
202 LIST_FILETYPE | LIST_EXEC, /* F */
203 LIST_FILETYPE, /* p */
204 #endif
205 #if ENABLE_FEATURE_LS_FOLLOWLINKS
206 FOLLOW_LINKS, /* L */
207 #endif
208 #if ENABLE_FEATURE_LS_RECURSIVE
209 DISP_RECURSIVE, /* R */
210 #endif
211 #if ENABLE_FEATURE_HUMAN_READABLE
212 LS_DISP_HR, /* h */
213 #endif
214 #if ENABLE_SELINUX
215 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
216 #endif
217 #if ENABLE_SELINUX
218 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
219 #endif
220 (1U<<31)
221 /* options after Z are not processed through opt_flags:
222 * T, w - ignored
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 */
233 int allocated;
234 struct stat dstat; /* the file stat info */
235 USE_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 *);
244 struct globals {
245 #if ENABLE_FEATURE_LS_COLOR
246 smallint show_color;
247 #endif
248 smallint exit_code;
249 unsigned all_fmt;
250 #if ENABLE_FEATURE_AUTOWIDTH
251 unsigned tabstops; // = COLUMN_GAP;
252 unsigned terminal_width; // = TERMINAL_WIDTH;
253 #endif
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;
257 #endif
259 #define G (*(struct globals*)&bb_common_bufsiz1)
260 #if ENABLE_FEATURE_LS_COLOR
261 #define show_color (G.show_color )
262 #else
263 enum { show_color = 0 };
264 #endif
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)
270 #else
271 enum {
272 tabstops = COLUMN_GAP,
273 terminal_width = TERMINAL_WIDTH,
275 #endif
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 USE_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
281 USE_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
282 USE_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
283 } while (0)
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);
294 return width;
296 #else
297 #define mbstrlen(string) strlen(string)
298 #endif
301 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
303 struct stat dstat;
304 struct dnode *cur;
305 USE_SELINUX(security_context_t sid = NULL;)
307 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
308 #if ENABLE_SELINUX
309 if (is_selinux_enabled()) {
310 getfilecon(fullname, &sid);
312 #endif
313 if (stat(fullname, &dstat)) {
314 bb_simple_perror_msg(fullname);
315 exit_code = EXIT_FAILURE;
316 return 0;
318 } else {
319 #if ENABLE_SELINUX
320 if (is_selinux_enabled()) {
321 lgetfilecon(fullname, &sid);
323 #endif
324 if (lstat(fullname, &dstat)) {
325 bb_simple_perror_msg(fullname);
326 exit_code = EXIT_FAILURE;
327 return 0;
331 cur = xmalloc(sizeof(struct dnode));
332 cur->fullname = fullname;
333 cur->name = name;
334 cur->dstat = dstat;
335 USE_SELINUX(cur->sid = sid;)
336 return cur;
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" \
359 [TYPEINDEX(mode)])
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" \
368 [TYPEINDEX(mode)])
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 ... */
376 return COLOR(mode);
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 ... */
382 return ATTR(mode);
384 #endif
386 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
387 static char append_char(mode_t mode)
389 if (!(all_fmt & LIST_FILETYPE))
390 return '\0';
391 if (S_ISDIR(mode))
392 return '/';
393 if (!(all_fmt & LIST_EXEC))
394 return '\0';
395 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
396 return '*';
397 return APPCHAR(mode);
399 #endif
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)
406 int i, dirs;
408 if (!dn)
409 return 0;
410 dirs = 0;
411 for (i = 0; i < nfiles; i++) {
412 const char *name;
413 if (!S_ISDIR(dn[i]->dstat.st_mode))
414 continue;
415 name = dn[i]->name;
416 if (notsubdirs
417 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
419 dirs++;
422 return dirs;
425 static int countfiles(struct dnode **dnp)
427 int nfiles;
428 struct dnode *cur;
430 if (dnp == NULL)
431 return 0;
432 nfiles = 0;
433 for (cur = dnp[0]; cur->next; cur = cur->next)
434 nfiles++;
435 nfiles++;
436 return nfiles;
439 /* get memory to hold an array of pointers */
440 static struct dnode **dnalloc(int num)
442 if (num < 1)
443 return NULL;
445 return xzalloc(num * sizeof(struct dnode *));
448 #if ENABLE_FEATURE_LS_RECURSIVE
449 static void dfree(struct dnode **dnp, int nfiles)
451 int i;
453 if (dnp == NULL)
454 return;
456 for (i = 0; i < nfiles; i++) {
457 struct dnode *cur = dnp[i];
458 if (cur->allocated)
459 free((char*)cur->fullname); /* free the filename */
460 free(cur); /* free the dnode */
462 free(dnp); /* free the array holding the dnode pointers */
464 #else
465 #define dfree(...) ((void)0)
466 #endif
468 static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which)
470 int dncnt, i, d;
471 struct dnode **dnp;
473 if (dn == NULL || nfiles < 1)
474 return NULL;
476 /* count how many dirs and regular files there are */
477 if (which == SPLIT_SUBDIR)
478 dncnt = countsubdirs(dn, nfiles);
479 else {
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)) {
491 const char *name;
492 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
493 continue;
494 name = dn[i]->name;
495 if ((which & SPLIT_DIR)
496 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
498 dnp[d++] = dn[i];
500 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
501 dnp[d++] = dn[i];
504 return dnp;
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;
513 int dif;
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) { */
532 if (dif == 0) {
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) {
539 dif = -dif;
541 return dif;
544 static void dnsort(struct dnode **dn, int size)
546 qsort(dn, size, sizeof(*dn), sortcmp);
548 #else
549 #define dnsort(dn, size) ((void)0)
550 #endif
553 static void showfiles(struct dnode **dn, int nfiles)
555 int i, ncols, nrows, row, nc;
556 int column = 0;
557 int nexttab = 0;
558 int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */
560 if (dn == NULL || nfiles < 1)
561 return;
563 if (all_fmt & STYLE_LONG) {
564 ncols = 1;
565 } else {
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)
570 column_width = len;
572 column_width += tabstops +
573 USE_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);
579 if (ncols > 1) {
580 nrows = nfiles / ncols;
581 if (nrows * ncols < nfiles)
582 nrows++; /* round up fractionals */
583 } else {
584 nrows = nfiles;
585 ncols = 1;
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 */
594 if (i < nfiles) {
595 if (column > 0) {
596 nexttab -= column;
597 printf("%*s", nexttab, "");
598 column += nexttab;
600 nexttab = column + column_width;
601 column += list_single(dn[i]);
604 putchar('\n');
605 column = 0;
610 static void showdirs(struct dnode **dn, int ndirs, int first)
612 int i, nfiles;
613 struct dnode **subdnp;
614 int dndirs;
615 struct dnode **dnd;
617 if (dn == NULL || ndirs < 1)
618 return;
620 for (i = 0; i < ndirs; i++) {
621 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
622 if (!first)
623 bb_putchar('\n');
624 first = 0;
625 printf("%s:\n", dn[i]->fullname);
627 subdnp = list_dir(dn[i]->fullname);
628 nfiles = countfiles(subdnp);
629 if (nfiles > 0) {
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);
638 if (dndirs > 0) {
639 dnsort(dnd, dndirs);
640 showdirs(dnd, dndirs, 0);
641 /* free the array of dnode pointers to the dirs */
642 free(dnd);
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;
657 DIR *dir;
658 int i, nfiles;
660 if (path == NULL)
661 return NULL;
663 dn = NULL;
664 nfiles = 0;
665 dir = warn_opendir(path);
666 if (dir == NULL) {
667 exit_code = EXIT_FAILURE;
668 return NULL; /* could not open the dir */
670 while ((entry = readdir(dir)) != NULL) {
671 char *fullname;
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)
678 continue;
680 if (!(all_fmt & DISP_HIDDEN))
681 continue;
683 fullname = concat_path_file(path, entry->d_name);
684 cur = my_stat(fullname, bb_basename(fullname), 0);
685 if (!cur) {
686 free(fullname);
687 continue;
689 cur->allocated = 1;
690 cur->next = dn;
691 dn = cur;
692 nfiles++;
694 closedir(dir);
696 /* now that we know how many files there are
697 * allocate memory for an array to hold dnode pointers
699 if (dn == NULL)
700 return NULL;
701 dnp = dnalloc(nfiles);
702 for (i = 0, cur = dn; i < nfiles; i++) {
703 dnp[i] = cur; /* save pointer to node in array */
704 cur = cur->next;
707 return dnp;
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);
716 #else
717 int len = 2;
718 #endif
719 putchar('"');
720 while (*name) {
721 if (*name == '"') {
722 putchar('\\');
723 len++;
725 putchar(*name++);
726 if (!ENABLE_FEATURE_ASSUME_UNICODE)
727 len++;
729 putchar('"');
730 return len;
732 /* No -Q: */
733 #if ENABLE_FEATURE_ASSUME_UNICODE
734 fputs(name, stdout);
735 return mbstrlen(name);
736 #else
737 return printf("%s", name);
738 #endif
742 static int list_single(const struct dnode *dn)
744 int column = 0;
745 char *lpath = lpath; /* for compiler */
746 #if ENABLE_FEATURE_LS_TIMESTAMPS
747 char *filetime;
748 time_t ttime, age;
749 #endif
750 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
751 struct stat info;
752 char append;
753 #endif
755 if (dn->fullname == NULL)
756 return 0;
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);
765 #endif
766 #if ENABLE_FEATURE_LS_FILETYPES
767 append = append_char(dn->dstat.st_mode);
768 #endif
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));
789 } else {
790 column += printf("%-8.8s %-8.8s",
791 get_cached_username(dn->dstat.st_uid),
792 get_cached_groupname(dn->dstat.st_gid));
795 #endif
796 if (all_fmt & LIST_ID_NUMERIC) {
797 if (option_mask32 & OPT_g)
798 column += printf("%-8u", (int) dn->dstat.st_uid);
799 else
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));
809 } else {
810 if (all_fmt & LS_DISP_HR) {
811 column += printf("%9s ",
812 make_human_readable_str(dn->dstat.st_size, 1, 0));
813 } else {
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);
829 } else {
830 printf(" %4.4s ", filetime + 20);
832 column += 13;
834 #endif
835 #if ENABLE_SELINUX
836 if (all_fmt & LIST_CONTEXT) {
837 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
838 freecon(dn->sid);
840 #endif
841 if (all_fmt & LIST_FILENAME) {
842 #if ENABLE_FEATURE_LS_COLOR
843 if (show_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));
849 #endif
850 column += print_name(dn->name);
851 if (show_color) {
852 printf("\033[0m");
855 if (all_fmt & LIST_SYMLINK) {
856 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
857 printf(" -> ");
858 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
859 #if ENABLE_FEATURE_LS_COLOR
860 info.st_mode = 0; /* for fgcolor() */
861 #endif
862 if (stat(dn->fullname, &info) == 0) {
863 append = append_char(info.st_mode);
865 #endif
866 #if ENABLE_FEATURE_LS_COLOR
867 if (show_color) {
868 printf("\033[%u;%um", bold(info.st_mode),
869 fgcolor(info.st_mode));
871 #endif
872 column += print_name(lpath) + 4;
873 if (show_color) {
874 printf("\033[0m");
876 free(lpath);
879 #if ENABLE_FEATURE_LS_FILETYPES
880 if (all_fmt & LIST_FILETYPE) {
881 if (append) {
882 putchar(append);
883 column++;
886 #endif
888 return column;
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
895 * equivalent */
896 static const char ls_color_opt[] ALIGN1 =
897 "color\0" Optional_argument "\xff" /* no short equivalent */
899 #endif
902 int ls_main(int argc UNUSED_PARAM, char **argv)
904 struct dnode **dnd;
905 struct dnode **dnf;
906 struct dnode **dnp;
907 struct dnode *dn;
908 struct dnode *cur;
909 unsigned opt;
910 int nfiles;
911 int dnfiles;
912 int dndirs;
913 int i;
914 /* need to initialize since --color has _an optional_ argument */
915 USE_FEATURE_LS_COLOR(const char *color_opt = "always";)
917 INIT_G();
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);
925 /* Go one less... */
926 terminal_width--;
927 #endif
929 /* process options */
930 USE_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 USE_FEATURE_LS_COLOR(, &color_opt));
935 #else
936 opt = getopt32(argv, ls_options USE_FEATURE_LS_COLOR(, &color_opt));
937 #endif
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;
957 all_fmt |= flags;
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))
967 show_color = 1;
969 if (opt & (1 << i)) { /* next flag after short options */
970 if (strcmp("always", color_opt) == 0)
971 show_color = 1;
972 else if (strcmp("never", color_opt) == 0)
973 show_color = 0;
974 else if (strcmp("auto", color_opt) == 0 && isatty(STDOUT_FILENO))
975 show_color = 1;
977 #endif
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);
998 argv += optind;
999 if (!argv[0])
1000 *--argv = (char*)".";
1002 if (argv[1])
1003 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1005 /* stuff the command line file names into a dnode array */
1006 dn = NULL;
1007 nfiles = 0;
1008 do {
1009 /* NB: follow links on command line unless -l or -s */
1010 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1011 argv++;
1012 if (!cur)
1013 continue;
1014 cur->allocated = 0;
1015 cur->next = dn;
1016 dn = cur;
1017 nfiles++;
1018 } while (*argv);
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 */
1026 cur = cur->next;
1029 if (all_fmt & DISP_NOLIST) {
1030 dnsort(dnp, nfiles);
1031 if (nfiles > 0)
1032 showfiles(dnp, nfiles);
1033 } else {
1034 dnd = splitdnarray(dnp, nfiles, SPLIT_DIR);
1035 dnf = splitdnarray(dnp, nfiles, SPLIT_FILE);
1036 dndirs = countdirs(dnp, nfiles);
1037 dnfiles = nfiles - dndirs;
1038 if (dnfiles > 0) {
1039 dnsort(dnf, dnfiles);
1040 showfiles(dnf, dnfiles);
1041 if (ENABLE_FEATURE_CLEAN_UP)
1042 free(dnf);
1044 if (dndirs > 0) {
1045 dnsort(dnd, dndirs);
1046 showdirs(dnd, dndirs, dnfiles == 0);
1047 if (ENABLE_FEATURE_CLEAN_UP)
1048 free(dnd);
1051 if (ENABLE_FEATURE_CLEAN_UP)
1052 dfree(dnp, nfiles);
1053 return exit_code;