Tomato 1.26 beta(1766)
[tomato.git] / release / src / router / busybox / coreutils / ls.c
blob38957e93d814b2791a702db4e89e72b14aa2436c
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("KZ") /* 2, 28 */
148 USE_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
150 enum {
151 //OPT_C = (1 << 0),
152 //OPT_a = (1 << 1),
153 //OPT_d = (1 << 2),
154 //OPT_i = (1 << 3),
155 //OPT_l = (1 << 4),
156 //OPT_1 = (1 << 5),
157 OPT_g = (1 << 6),
158 //OPT_n = (1 << 7),
159 //OPT_s = (1 << 8),
160 //OPT_x = (1 << 9),
161 OPT_Q = (1 << 10),
162 //OPT_A = (1 << 11),
163 //OPT_k = (1 << 12),
164 OPTBIT_color = 13
165 + 4 * ENABLE_FEATURE_LS_TIMESTAMPS
166 + 4 * ENABLE_FEATURE_LS_SORTFILES
167 + 2 * ENABLE_FEATURE_LS_FILETYPES
168 + 1 * ENABLE_FEATURE_LS_FOLLOWLINKS
169 + 1 * ENABLE_FEATURE_LS_RECURSIVE
170 + 1 * ENABLE_FEATURE_HUMAN_READABLE
171 + 2 * ENABLE_SELINUX
172 + 2 * ENABLE_FEATURE_AUTOWIDTH,
173 OPT_color = 1 << OPTBIT_color,
176 enum {
177 LIST_MASK_TRIGGER = 0,
178 STYLE_MASK_TRIGGER = STYLE_MASK,
179 DISP_MASK_TRIGGER = DISP_ROWS,
180 SORT_MASK_TRIGGER = SORT_MASK,
183 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
184 static const unsigned opt_flags[] = {
185 LIST_SHORT | STYLE_COLUMNS, /* C */
186 DISP_HIDDEN | DISP_DOT, /* a */
187 DISP_NOLIST, /* d */
188 LIST_INO, /* i */
189 LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
190 LIST_SHORT | STYLE_SINGLE, /* 1 */
191 0, /* g (don't show group) - handled via OPT_g */
192 LIST_ID_NUMERIC, /* n */
193 LIST_BLOCKS, /* s */
194 DISP_ROWS, /* x */
195 0, /* Q (quote filename) - handled via OPT_Q */
196 DISP_HIDDEN, /* A */
197 ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
198 #if ENABLE_FEATURE_LS_TIMESTAMPS
199 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
200 LIST_FULLTIME, /* e */
201 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
202 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
203 #endif
204 #if ENABLE_FEATURE_LS_SORTFILES
205 SORT_SIZE, /* S */
206 SORT_EXT, /* X */
207 SORT_REVERSE, /* r */
208 SORT_VERSION, /* v */
209 #endif
210 #if ENABLE_FEATURE_LS_FILETYPES
211 LIST_FILETYPE | LIST_EXEC, /* F */
212 LIST_FILETYPE, /* p */
213 #endif
214 #if ENABLE_FEATURE_LS_FOLLOWLINKS
215 FOLLOW_LINKS, /* L */
216 #endif
217 #if ENABLE_FEATURE_LS_RECURSIVE
218 DISP_RECURSIVE, /* R */
219 #endif
220 #if ENABLE_FEATURE_HUMAN_READABLE
221 LS_DISP_HR, /* h */
222 #endif
223 #if ENABLE_SELINUX
224 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
225 #endif
226 #if ENABLE_SELINUX
227 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
228 #endif
229 (1U<<31)
230 /* options after Z are not processed through opt_flags:
231 * T, w - ignored
237 * a directory entry and its stat info are stored here
239 struct dnode { /* the basic node */
240 const char *name; /* the dir entry name */
241 const char *fullname; /* the dir entry name */
242 int allocated;
243 struct stat dstat; /* the file stat info */
244 USE_SELINUX(security_context_t sid;)
245 struct dnode *next; /* point at the next node */
248 static struct dnode **list_dir(const char *);
249 static struct dnode **dnalloc(int);
250 static int list_single(const struct dnode *);
253 struct globals {
254 #if ENABLE_FEATURE_LS_COLOR
255 smallint show_color;
256 #endif
257 smallint exit_code;
258 unsigned all_fmt;
259 #if ENABLE_FEATURE_AUTOWIDTH
260 unsigned tabstops; // = COLUMN_GAP;
261 unsigned terminal_width; // = TERMINAL_WIDTH;
262 #endif
263 #if ENABLE_FEATURE_LS_TIMESTAMPS
264 /* Do time() just once. Saves one syscall per file for "ls -l" */
265 time_t current_time_t;
266 #endif
268 #define G (*(struct globals*)&bb_common_bufsiz1)
269 #if ENABLE_FEATURE_LS_COLOR
270 #define show_color (G.show_color )
271 #else
272 enum { show_color = 0 };
273 #endif
274 #define exit_code (G.exit_code )
275 #define all_fmt (G.all_fmt )
276 #if ENABLE_FEATURE_AUTOWIDTH
277 #define tabstops (G.tabstops )
278 #define terminal_width (G.terminal_width)
279 #else
280 enum {
281 tabstops = COLUMN_GAP,
282 terminal_width = TERMINAL_WIDTH,
284 #endif
285 #define current_time_t (G.current_time_t)
286 /* memset: we have to zero it out because of NOEXEC */
287 #define INIT_G() do { \
288 memset(&G, 0, sizeof(G)); \
289 USE_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
290 USE_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
291 USE_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
292 } while (0)
295 #if ENABLE_FEATURE_ASSUME_UNICODE
296 /* libbb candidate */
297 static size_t mbstrlen(const char *string)
299 size_t width = mbsrtowcs(NULL /*dest*/, &string,
300 MAXINT(size_t) /*len*/, NULL /*state*/);
301 if (width == (size_t)-1)
302 return strlen(string);
303 return width;
305 #else
306 #define mbstrlen(string) strlen(string)
307 #endif
310 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
312 struct stat dstat;
313 struct dnode *cur;
314 USE_SELINUX(security_context_t sid = NULL;)
316 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
317 #if ENABLE_SELINUX
318 if (is_selinux_enabled()) {
319 getfilecon(fullname, &sid);
321 #endif
322 if (stat(fullname, &dstat)) {
323 bb_simple_perror_msg(fullname);
324 exit_code = EXIT_FAILURE;
325 return 0;
327 } else {
328 #if ENABLE_SELINUX
329 if (is_selinux_enabled()) {
330 lgetfilecon(fullname, &sid);
332 #endif
333 if (lstat(fullname, &dstat)) {
334 bb_simple_perror_msg(fullname);
335 exit_code = EXIT_FAILURE;
336 return 0;
340 cur = xmalloc(sizeof(struct dnode));
341 cur->fullname = fullname;
342 cur->name = name;
343 cur->dstat = dstat;
344 USE_SELINUX(cur->sid = sid;)
345 return cur;
349 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
350 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
351 * 3/7:multiplexed char/block device)
352 * and we use 0 for unknown and 15 for executables (see below) */
353 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
354 #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
355 #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
356 /* 036 black foreground 050 black background
357 037 red foreground 051 red background
358 040 green foreground 052 green background
359 041 brown foreground 053 brown background
360 042 blue foreground 054 blue background
361 043 magenta (purple) foreground 055 magenta background
362 044 cyan (light blue) foreground 056 cyan background
363 045 gray foreground 057 white background
365 #define COLOR(mode) ( \
366 /*un fi chr dir blk file link sock exe */ \
367 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
368 [TYPEINDEX(mode)])
369 /* Select normal (0) [actually "reset all"] or bold (1)
370 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
371 * let's use 7 for "impossible" types, just for fun)
372 * Note: coreutils 6.9 uses inverted red for setuid binaries.
374 #define ATTR(mode) ( \
375 /*un fi chr dir blk file link sock exe */ \
376 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
377 [TYPEINDEX(mode)])
379 #if ENABLE_FEATURE_LS_COLOR
380 /* mode of zero is interpreted as "unknown" (stat failed) */
381 static char fgcolor(mode_t mode)
383 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
384 return COLOR(0xF000); /* File is executable ... */
385 return COLOR(mode);
387 static char bold(mode_t mode)
389 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
390 return ATTR(0xF000); /* File is executable ... */
391 return ATTR(mode);
393 #endif
395 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
396 static char append_char(mode_t mode)
398 if (!(all_fmt & LIST_FILETYPE))
399 return '\0';
400 if (S_ISDIR(mode))
401 return '/';
402 if (!(all_fmt & LIST_EXEC))
403 return '\0';
404 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
405 return '*';
406 return APPCHAR(mode);
408 #endif
411 #define countdirs(A, B) count_dirs((A), (B), 1)
412 #define countsubdirs(A, B) count_dirs((A), (B), 0)
413 static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs)
415 int i, dirs;
417 if (!dn)
418 return 0;
419 dirs = 0;
420 for (i = 0; i < nfiles; i++) {
421 const char *name;
422 if (!S_ISDIR(dn[i]->dstat.st_mode))
423 continue;
424 name = dn[i]->name;
425 if (notsubdirs
426 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
428 dirs++;
431 return dirs;
434 static int countfiles(struct dnode **dnp)
436 int nfiles;
437 struct dnode *cur;
439 if (dnp == NULL)
440 return 0;
441 nfiles = 0;
442 for (cur = dnp[0]; cur->next; cur = cur->next)
443 nfiles++;
444 nfiles++;
445 return nfiles;
448 /* get memory to hold an array of pointers */
449 static struct dnode **dnalloc(int num)
451 if (num < 1)
452 return NULL;
454 return xzalloc(num * sizeof(struct dnode *));
457 #if ENABLE_FEATURE_LS_RECURSIVE
458 static void dfree(struct dnode **dnp, int nfiles)
460 int i;
462 if (dnp == NULL)
463 return;
465 for (i = 0; i < nfiles; i++) {
466 struct dnode *cur = dnp[i];
467 if (cur->allocated)
468 free((char*)cur->fullname); /* free the filename */
469 free(cur); /* free the dnode */
471 free(dnp); /* free the array holding the dnode pointers */
473 #else
474 #define dfree(...) ((void)0)
475 #endif
477 static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which)
479 int dncnt, i, d;
480 struct dnode **dnp;
482 if (dn == NULL || nfiles < 1)
483 return NULL;
485 /* count how many dirs and regular files there are */
486 if (which == SPLIT_SUBDIR)
487 dncnt = countsubdirs(dn, nfiles);
488 else {
489 dncnt = countdirs(dn, nfiles); /* assume we are looking for dirs */
490 if (which == SPLIT_FILE)
491 dncnt = nfiles - dncnt; /* looking for files */
494 /* allocate a file array and a dir array */
495 dnp = dnalloc(dncnt);
497 /* copy the entrys into the file or dir array */
498 for (d = i = 0; i < nfiles; i++) {
499 if (S_ISDIR(dn[i]->dstat.st_mode)) {
500 const char *name;
501 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
502 continue;
503 name = dn[i]->name;
504 if ((which & SPLIT_DIR)
505 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
507 dnp[d++] = dn[i];
509 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
510 dnp[d++] = dn[i];
513 return dnp;
516 #if ENABLE_FEATURE_LS_SORTFILES
517 static int sortcmp(const void *a, const void *b)
519 struct dnode *d1 = *(struct dnode **)a;
520 struct dnode *d2 = *(struct dnode **)b;
521 unsigned sort_opts = all_fmt & SORT_MASK;
522 int dif;
524 dif = 0; /* assume SORT_NAME */
525 // TODO: use pre-initialized function pointer
526 // instead of branch forest
527 if (sort_opts == SORT_SIZE) {
528 dif = (int) (d2->dstat.st_size - d1->dstat.st_size);
529 } else if (sort_opts == SORT_ATIME) {
530 dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime);
531 } else if (sort_opts == SORT_CTIME) {
532 dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime);
533 } else if (sort_opts == SORT_MTIME) {
534 dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime);
535 } else if (sort_opts == SORT_DIR) {
536 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
537 /* } else if (sort_opts == SORT_VERSION) { */
538 /* } else if (sort_opts == SORT_EXT) { */
541 if (dif == 0) {
542 /* sort by name - may be a tie_breaker for time or size cmp */
543 if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name);
544 else dif = strcmp(d1->name, d2->name);
547 if (all_fmt & SORT_REVERSE) {
548 dif = -dif;
550 return dif;
553 static void dnsort(struct dnode **dn, int size)
555 qsort(dn, size, sizeof(*dn), sortcmp);
557 #else
558 #define dnsort(dn, size) ((void)0)
559 #endif
562 static void showfiles(struct dnode **dn, int nfiles)
564 int i, ncols, nrows, row, nc;
565 int column = 0;
566 int nexttab = 0;
567 int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */
569 if (dn == NULL || nfiles < 1)
570 return;
572 if (all_fmt & STYLE_LONG) {
573 ncols = 1;
574 } else {
575 /* find the longest file name, use that as the column width */
576 for (i = 0; i < nfiles; i++) {
577 int len = mbstrlen(dn[i]->name);
578 if (column_width < len)
579 column_width = len;
581 column_width += tabstops +
582 USE_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
583 ((all_fmt & LIST_INO) ? 8 : 0) +
584 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
585 ncols = (int) (terminal_width / column_width);
588 if (ncols > 1) {
589 nrows = nfiles / ncols;
590 if (nrows * ncols < nfiles)
591 nrows++; /* round up fractionals */
592 } else {
593 nrows = nfiles;
594 ncols = 1;
597 for (row = 0; row < nrows; row++) {
598 for (nc = 0; nc < ncols; nc++) {
599 /* reach into the array based on the column and row */
600 i = (nc * nrows) + row; /* assume display by column */
601 if (all_fmt & DISP_ROWS)
602 i = (row * ncols) + nc; /* display across row */
603 if (i < nfiles) {
604 if (column > 0) {
605 nexttab -= column;
606 printf("%*s", nexttab, "");
607 column += nexttab;
609 nexttab = column + column_width;
610 column += list_single(dn[i]);
613 putchar('\n');
614 column = 0;
619 static void showdirs(struct dnode **dn, int ndirs, int first)
621 int i, nfiles;
622 struct dnode **subdnp;
623 int dndirs;
624 struct dnode **dnd;
626 if (dn == NULL || ndirs < 1)
627 return;
629 for (i = 0; i < ndirs; i++) {
630 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
631 if (!first)
632 bb_putchar('\n');
633 first = 0;
634 printf("%s:\n", dn[i]->fullname);
636 subdnp = list_dir(dn[i]->fullname);
637 nfiles = countfiles(subdnp);
638 if (nfiles > 0) {
639 /* list all files at this level */
640 dnsort(subdnp, nfiles);
641 showfiles(subdnp, nfiles);
642 if (ENABLE_FEATURE_LS_RECURSIVE) {
643 if (all_fmt & DISP_RECURSIVE) {
644 /* recursive- list the sub-dirs */
645 dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR);
646 dndirs = countsubdirs(subdnp, nfiles);
647 if (dndirs > 0) {
648 dnsort(dnd, dndirs);
649 showdirs(dnd, dndirs, 0);
650 /* free the array of dnode pointers to the dirs */
651 free(dnd);
654 /* free the dnodes and the fullname mem */
655 dfree(subdnp, nfiles);
662 static struct dnode **list_dir(const char *path)
664 struct dnode *dn, *cur, **dnp;
665 struct dirent *entry;
666 DIR *dir;
667 int i, nfiles;
669 if (path == NULL)
670 return NULL;
672 dn = NULL;
673 nfiles = 0;
674 dir = warn_opendir(path);
675 if (dir == NULL) {
676 exit_code = EXIT_FAILURE;
677 return NULL; /* could not open the dir */
679 while ((entry = readdir(dir)) != NULL) {
680 char *fullname;
682 /* are we going to list the file- it may be . or .. or a hidden file */
683 if (entry->d_name[0] == '.') {
684 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
685 && !(all_fmt & DISP_DOT)
687 continue;
689 if (!(all_fmt & DISP_HIDDEN))
690 continue;
692 fullname = concat_path_file(path, entry->d_name);
693 cur = my_stat(fullname, bb_basename(fullname), 0);
694 if (!cur) {
695 free(fullname);
696 continue;
698 cur->allocated = 1;
699 cur->next = dn;
700 dn = cur;
701 nfiles++;
703 closedir(dir);
705 /* now that we know how many files there are
706 * allocate memory for an array to hold dnode pointers
708 if (dn == NULL)
709 return NULL;
710 dnp = dnalloc(nfiles);
711 for (i = 0, cur = dn; i < nfiles; i++) {
712 dnp[i] = cur; /* save pointer to node in array */
713 cur = cur->next;
716 return dnp;
720 static int print_name(const char *name)
722 if (option_mask32 & OPT_Q) {
723 #if ENABLE_FEATURE_ASSUME_UNICODE
724 int len = 2 + mbstrlen(name);
725 #else
726 int len = 2;
727 #endif
728 putchar('"');
729 while (*name) {
730 if (*name == '"') {
731 putchar('\\');
732 len++;
734 putchar(*name++);
735 if (!ENABLE_FEATURE_ASSUME_UNICODE)
736 len++;
738 putchar('"');
739 return len;
741 /* No -Q: */
742 #if ENABLE_FEATURE_ASSUME_UNICODE
743 fputs(name, stdout);
744 return mbstrlen(name);
745 #else
746 return printf("%s", name);
747 #endif
751 static int list_single(const struct dnode *dn)
753 int column = 0;
754 char *lpath = lpath; /* for compiler */
755 #if ENABLE_FEATURE_LS_TIMESTAMPS
756 char *filetime;
757 time_t ttime, age;
758 #endif
759 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
760 struct stat info;
761 char append;
762 #endif
764 if (dn->fullname == NULL)
765 return 0;
767 #if ENABLE_FEATURE_LS_TIMESTAMPS
768 ttime = dn->dstat.st_mtime; /* the default time */
769 if (all_fmt & TIME_ACCESS)
770 ttime = dn->dstat.st_atime;
771 if (all_fmt & TIME_CHANGE)
772 ttime = dn->dstat.st_ctime;
773 filetime = ctime(&ttime);
774 #endif
775 #if ENABLE_FEATURE_LS_FILETYPES
776 append = append_char(dn->dstat.st_mode);
777 #endif
779 /* Do readlink early, so that if it fails, error message
780 * does not appear *inside* of the "ls -l" line */
781 if (all_fmt & LIST_SYMLINK)
782 if (S_ISLNK(dn->dstat.st_mode))
783 lpath = xmalloc_readlink_or_warn(dn->fullname);
785 if (all_fmt & LIST_INO)
786 column += printf("%7lu ", (long) dn->dstat.st_ino);
787 if (all_fmt & LIST_BLOCKS)
788 column += printf("%4"OFF_FMT"u ", (off_t) dn->dstat.st_blocks >> 1);
789 if (all_fmt & LIST_MODEBITS)
790 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
791 if (all_fmt & LIST_NLINKS)
792 column += printf("%4lu ", (long) dn->dstat.st_nlink);
793 #if ENABLE_FEATURE_LS_USERNAME
794 if (all_fmt & LIST_ID_NAME) {
795 if (option_mask32 & OPT_g) {
796 column += printf("%-8.8s",
797 get_cached_username(dn->dstat.st_uid));
798 } else {
799 column += printf("%-8.8s %-8.8s",
800 get_cached_username(dn->dstat.st_uid),
801 get_cached_groupname(dn->dstat.st_gid));
804 #endif
805 if (all_fmt & LIST_ID_NUMERIC) {
806 if (option_mask32 & OPT_g)
807 column += printf("%-8u", (int) dn->dstat.st_uid);
808 else
809 column += printf("%-8u %-8u",
810 (int) dn->dstat.st_uid,
811 (int) dn->dstat.st_gid);
813 if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
814 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
815 column += printf("%4u, %3u ",
816 (int) major(dn->dstat.st_rdev),
817 (int) minor(dn->dstat.st_rdev));
818 } else {
819 if (all_fmt & LS_DISP_HR) {
820 column += printf("%9s ",
821 make_human_readable_str(dn->dstat.st_size, 1, 0));
822 } else {
823 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
827 #if ENABLE_FEATURE_LS_TIMESTAMPS
828 if (all_fmt & LIST_FULLTIME)
829 column += printf("%24.24s ", filetime);
830 if (all_fmt & LIST_DATE_TIME)
831 if ((all_fmt & LIST_FULLTIME) == 0) {
832 /* current_time_t ~== time(NULL) */
833 age = current_time_t - ttime;
834 printf("%6.6s ", filetime + 4);
835 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
836 /* hh:mm if less than 6 months old */
837 printf("%5.5s ", filetime + 11);
838 } else {
839 printf(" %4.4s ", filetime + 20);
841 column += 13;
843 #endif
844 #if ENABLE_SELINUX
845 if (all_fmt & LIST_CONTEXT) {
846 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
847 freecon(dn->sid);
849 #endif
850 if (all_fmt & LIST_FILENAME) {
851 #if ENABLE_FEATURE_LS_COLOR
852 if (show_color) {
853 info.st_mode = 0; /* for fgcolor() */
854 lstat(dn->fullname, &info);
855 printf("\033[%u;%um", bold(info.st_mode),
856 fgcolor(info.st_mode));
858 #endif
859 column += print_name(dn->name);
860 if (show_color) {
861 printf("\033[0m");
864 if (all_fmt & LIST_SYMLINK) {
865 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
866 printf(" -> ");
867 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
868 #if ENABLE_FEATURE_LS_COLOR
869 info.st_mode = 0; /* for fgcolor() */
870 #endif
871 if (stat(dn->fullname, &info) == 0) {
872 append = append_char(info.st_mode);
874 #endif
875 #if ENABLE_FEATURE_LS_COLOR
876 if (show_color) {
877 printf("\033[%u;%um", bold(info.st_mode),
878 fgcolor(info.st_mode));
880 #endif
881 column += print_name(lpath) + 4;
882 if (show_color) {
883 printf("\033[0m");
885 free(lpath);
888 #if ENABLE_FEATURE_LS_FILETYPES
889 if (all_fmt & LIST_FILETYPE) {
890 if (append) {
891 putchar(append);
892 column++;
895 #endif
897 return column;
901 int ls_main(int argc UNUSED_PARAM, char **argv)
903 struct dnode **dnd;
904 struct dnode **dnf;
905 struct dnode **dnp;
906 struct dnode *dn;
907 struct dnode *cur;
908 unsigned opt;
909 int nfiles;
910 int dnfiles;
911 int dndirs;
912 int i;
913 #if ENABLE_FEATURE_LS_COLOR
914 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
915 /* coreutils 6.10:
916 * # ls --color=BOGUS
917 * ls: invalid argument 'BOGUS' for '--color'
918 * Valid arguments are:
919 * 'always', 'yes', 'force'
920 * 'never', 'no', 'none'
921 * 'auto', 'tty', 'if-tty'
922 * (and substrings: "--color=alwa" work too)
924 static const char ls_longopts[] ALIGN1 =
925 "color\0" Optional_argument "\xff"; /* no short equivalent */
926 static const char color_str[] ALIGN1 =
927 "always\0""yes\0""force\0"
928 "auto\0""tty\0""if-tty\0";
929 /* need to initialize since --color has _an optional_ argument */
930 const char *color_opt = color_str; /* "always" */
931 #endif
933 INIT_G();
935 all_fmt = LIST_SHORT |
936 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
938 #if ENABLE_FEATURE_AUTOWIDTH
939 /* Obtain the terminal width */
940 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
941 /* Go one less... */
942 terminal_width--;
943 #endif
945 /* process options */
946 USE_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
947 #if ENABLE_FEATURE_AUTOWIDTH
948 opt_complementary = "T+:w+"; /* -T N, -w N */
949 opt = getopt32(argv, ls_options, &tabstops, &terminal_width
950 USE_FEATURE_LS_COLOR(, &color_opt));
951 #else
952 opt = getopt32(argv, ls_options USE_FEATURE_LS_COLOR(, &color_opt));
953 #endif
954 for (i = 0; opt_flags[i] != (1U<<31); i++) {
955 if (opt & (1 << i)) {
956 unsigned flags = opt_flags[i];
958 if (flags & LIST_MASK_TRIGGER)
959 all_fmt &= ~LIST_MASK;
960 if (flags & STYLE_MASK_TRIGGER)
961 all_fmt &= ~STYLE_MASK;
962 if (flags & SORT_MASK_TRIGGER)
963 all_fmt &= ~SORT_MASK;
964 if (flags & DISP_MASK_TRIGGER)
965 all_fmt &= ~DISP_MASK;
966 if (flags & TIME_MASK)
967 all_fmt &= ~TIME_MASK;
968 if (flags & LIST_CONTEXT)
969 all_fmt |= STYLE_SINGLE;
970 /* huh?? opt cannot be 'l' */
971 //if (LS_DISP_HR && opt == 'l')
972 // all_fmt &= ~LS_DISP_HR;
973 all_fmt |= flags;
977 #if ENABLE_FEATURE_LS_COLOR
978 /* find color bit value - last position for short getopt */
979 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
980 char *p = getenv("LS_COLORS");
981 /* LS_COLORS is unset, or (not empty && not "none") ? */
982 if (!p || (p[0] && strcmp(p, "none") != 0))
983 show_color = 1;
985 if (opt & OPT_color) {
986 if (color_opt[0] == 'n')
987 show_color = 0;
988 else switch (index_in_substrings(color_str, color_opt)) {
989 case 3:
990 case 4:
991 case 5:
992 if (isatty(STDOUT_FILENO)) {
993 case 0:
994 case 1:
995 case 2:
996 show_color = 1;
1000 #endif
1002 /* sort out which command line options take precedence */
1003 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1004 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1005 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1006 if (all_fmt & TIME_CHANGE)
1007 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1008 if (all_fmt & TIME_ACCESS)
1009 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1011 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1012 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
1013 if (ENABLE_FEATURE_LS_USERNAME)
1014 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1015 all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
1017 /* choose a display format */
1018 if (!(all_fmt & STYLE_MASK))
1019 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1021 argv += optind;
1022 if (!argv[0])
1023 *--argv = (char*)".";
1025 if (argv[1])
1026 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1028 /* stuff the command line file names into a dnode array */
1029 dn = NULL;
1030 nfiles = 0;
1031 do {
1032 /* NB: follow links on command line unless -l or -s */
1033 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1034 argv++;
1035 if (!cur)
1036 continue;
1037 cur->allocated = 0;
1038 cur->next = dn;
1039 dn = cur;
1040 nfiles++;
1041 } while (*argv);
1043 /* now that we know how many files there are
1044 * allocate memory for an array to hold dnode pointers
1046 dnp = dnalloc(nfiles);
1047 for (i = 0, cur = dn; i < nfiles; i++) {
1048 dnp[i] = cur; /* save pointer to node in array */
1049 cur = cur->next;
1052 if (all_fmt & DISP_NOLIST) {
1053 dnsort(dnp, nfiles);
1054 if (nfiles > 0)
1055 showfiles(dnp, nfiles);
1056 } else {
1057 dnd = splitdnarray(dnp, nfiles, SPLIT_DIR);
1058 dnf = splitdnarray(dnp, nfiles, SPLIT_FILE);
1059 dndirs = countdirs(dnp, nfiles);
1060 dnfiles = nfiles - dndirs;
1061 if (dnfiles > 0) {
1062 dnsort(dnf, dnfiles);
1063 showfiles(dnf, dnfiles);
1064 if (ENABLE_FEATURE_CLEAN_UP)
1065 free(dnf);
1067 if (dndirs > 0) {
1068 dnsort(dnd, dndirs);
1069 showdirs(dnd, dndirs, dnfiles == 0);
1070 if (ENABLE_FEATURE_CLEAN_UP)
1071 free(dnd);
1074 if (ENABLE_FEATURE_CLEAN_UP)
1075 dfree(dnp, nfiles);
1076 return exit_code;