Magellan Linux

Annotation of /trunk/mkinitrd-magellan/busybox/coreutils/ls.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1179 - (hide annotations) (download)
Wed Dec 15 21:33:41 2010 UTC (13 years, 5 months ago) by niro
File MIME type: text/plain
File size: 30977 byte(s)
-updated to busybox-1.17.4
1 niro 532 /* 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>
5     *
6     * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
7     */
8    
9 niro 984 /* [date unknown. Perhaps before year 2000]
10 niro 532 * 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.
14     *
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.
18     *
19     * KNOWN BUGS:
20 niro 984 * 1. hidden files can make column width too large
21 niro 532 *
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
26     * PORTABILITY:
27     * 1. requires lstat (BSD) - how do you do it without?
28 niro 984 *
29     * [2009-03]
30     * ls sorts listing now, and supports almost all options.
31 niro 532 */
32 niro 816 #include "libbb.h"
33 niro 984 #include "unicode.h"
34 niro 532
35 niro 816
36     /* This is a NOEXEC applet. Be very careful! */
37    
38    
39 niro 984 #if ENABLE_FTPD
40     /* ftpd uses ls, and without timestamps Mozilla won't understand
41     * ftpd's LIST output.
42     */
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(...)
51     #endif
52    
53    
54 niro 532 enum {
55    
56     TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
57     COLUMN_GAP = 2, /* includes the file type char */
58    
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,
64    
65     /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
66     /* what file information will be listed */
67     LIST_INO = 1 << 0,
68     LIST_BLOCKS = 1 << 1,
69     LIST_MODEBITS = 1 << 2,
70     LIST_NLINKS = 1 << 3,
71     LIST_ID_NAME = 1 << 4,
72     LIST_ID_NUMERIC = 1 << 5,
73     LIST_CONTEXT = 1 << 6,
74     LIST_SIZE = 1 << 7,
75 niro 984 //LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE
76 niro 532 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,
81     LIST_EXEC = 1 << 14,
82     LIST_MASK = (LIST_EXEC << 1) - 1,
83    
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),
92    
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 */
96    
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,
106    
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,
111    
112     FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
113    
114     LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
115    
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,
119    
120     SPLIT_DIR = 1,
121     SPLIT_FILE = 0,
122     SPLIT_SUBDIR = 2,
123    
124     };
125    
126 niro 984 /* "[-]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 */
145     ;
146     enum {
147     //OPT_C = (1 << 0),
148     //OPT_a = (1 << 1),
149     //OPT_d = (1 << 2),
150     //OPT_i = (1 << 3),
151     //OPT_l = (1 << 4),
152     //OPT_1 = (1 << 5),
153     OPT_g = (1 << 6),
154     //OPT_n = (1 << 7),
155     //OPT_s = (1 << 8),
156     //OPT_x = (1 << 9),
157     OPT_Q = (1 << 10),
158     //OPT_A = (1 << 11),
159     //OPT_k = (1 << 12),
160     OPTBIT_color = 13
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
167     + 2 * ENABLE_SELINUX
168     + 2 * ENABLE_FEATURE_AUTOWIDTH,
169     OPT_color = 1 << OPTBIT_color,
170     };
171 niro 532
172 niro 984 enum {
173     LIST_MASK_TRIGGER = 0,
174     STYLE_MASK_TRIGGER = STYLE_MASK,
175     DISP_MASK_TRIGGER = DISP_ROWS,
176     SORT_MASK_TRIGGER = SORT_MASK,
177     };
178    
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 */
183     DISP_NOLIST, /* d */
184     LIST_INO, /* i */
185     LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
186     LIST_SHORT | STYLE_SINGLE, /* 1 */
187 niro 1179 0, /* g (don't show owner) - handled via OPT_g */
188 niro 984 LIST_ID_NUMERIC, /* n */
189     LIST_BLOCKS, /* s */
190     DISP_ROWS, /* x */
191     0, /* Q (quote filename) - handled via OPT_Q */
192     DISP_HIDDEN, /* A */
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 */
199     #endif
200     #if ENABLE_FEATURE_LS_SORTFILES
201     SORT_SIZE, /* S */
202     SORT_EXT, /* X */
203     SORT_REVERSE, /* r */
204     SORT_VERSION, /* v */
205     #endif
206     #if ENABLE_FEATURE_LS_FILETYPES
207     LIST_FILETYPE | LIST_EXEC, /* F */
208     LIST_FILETYPE, /* p */
209     #endif
210     #if ENABLE_FEATURE_LS_FOLLOWLINKS
211     FOLLOW_LINKS, /* L */
212     #endif
213     #if ENABLE_FEATURE_LS_RECURSIVE
214     DISP_RECURSIVE, /* R */
215     #endif
216     #if ENABLE_FEATURE_HUMAN_READABLE
217     LS_DISP_HR, /* h */
218     #endif
219     #if ENABLE_SELINUX
220     LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
221     #endif
222     #if ENABLE_SELINUX
223     LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
224     #endif
225     (1U<<31)
226     /* options after Z are not processed through opt_flags:
227     * T, w - ignored
228     */
229     };
230    
231    
232 niro 532 /*
233     * a directory entry and its stat info are stored here
234     */
235 niro 984 struct dnode {
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 niro 532 struct stat dstat; /* the file stat info */
241 niro 984 IF_SELINUX(security_context_t sid;)
242 niro 532 };
243    
244 niro 816 struct globals {
245     #if ENABLE_FEATURE_LS_COLOR
246     smallint show_color;
247     #endif
248     smallint exit_code;
249     unsigned all_fmt;
250 niro 532 #if ENABLE_FEATURE_AUTOWIDTH
251 niro 816 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
258 niro 1123 } FIX_ALIASING;
259 niro 816 #define G (*(struct globals*)&bb_common_bufsiz1)
260     #if ENABLE_FEATURE_LS_COLOR
261 niro 984 # define show_color (G.show_color )
262 niro 532 #else
263 niro 816 enum { show_color = 0 };
264     #endif
265 niro 984 #define exit_code (G.exit_code )
266     #define all_fmt (G.all_fmt )
267 niro 816 #if ENABLE_FEATURE_AUTOWIDTH
268 niro 984 # define tabstops (G.tabstops )
269     # define terminal_width (G.terminal_width)
270 niro 816 #else
271 niro 532 enum {
272     tabstops = COLUMN_GAP,
273     terminal_width = TERMINAL_WIDTH,
274     };
275     #endif
276 niro 816 #define current_time_t (G.current_time_t)
277     #define INIT_G() do { \
278 niro 984 /* we have to zero it out because of NOEXEC */ \
279 niro 816 memset(&G, 0, sizeof(G)); \
280 niro 984 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
281     IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
282     IF_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
283 niro 816 } while (0)
284 niro 532
285    
286 niro 816 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
287     {
288 niro 532 struct stat dstat;
289     struct dnode *cur;
290 niro 984 IF_SELINUX(security_context_t sid = NULL;)
291 niro 532
292 niro 816 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
293 niro 532 #if ENABLE_SELINUX
294     if (is_selinux_enabled()) {
295     getfilecon(fullname, &sid);
296     }
297     #endif
298     if (stat(fullname, &dstat)) {
299 niro 816 bb_simple_perror_msg(fullname);
300     exit_code = EXIT_FAILURE;
301 niro 532 return 0;
302     }
303     } else {
304     #if ENABLE_SELINUX
305     if (is_selinux_enabled()) {
306 niro 816 lgetfilecon(fullname, &sid);
307 niro 532 }
308     #endif
309     if (lstat(fullname, &dstat)) {
310 niro 816 bb_simple_perror_msg(fullname);
311     exit_code = EXIT_FAILURE;
312 niro 532 return 0;
313     }
314     }
315    
316 niro 984 cur = xmalloc(sizeof(*cur));
317 niro 532 cur->fullname = fullname;
318     cur->name = name;
319     cur->dstat = dstat;
320 niro 984 IF_SELINUX(cur->sid = sid;)
321 niro 532 return cur;
322     }
323    
324 niro 984 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
325     * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
326     * 3/7:multiplexed char/block device)
327     * and we use 0 for unknown and 15 for executables (see below) */
328     #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
329     #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
330     #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
331     /* 036 black foreground 050 black background
332     037 red foreground 051 red background
333     040 green foreground 052 green background
334     041 brown foreground 053 brown background
335     042 blue foreground 054 blue background
336     043 magenta (purple) foreground 055 magenta background
337     044 cyan (light blue) foreground 056 cyan background
338     045 gray foreground 057 white background
339     */
340     #define COLOR(mode) ( \
341     /*un fi chr dir blk file link sock exe */ \
342     "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
343     [TYPEINDEX(mode)])
344     /* Select normal (0) [actually "reset all"] or bold (1)
345     * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
346     * let's use 7 for "impossible" types, just for fun)
347     * Note: coreutils 6.9 uses inverted red for setuid binaries.
348     */
349     #define ATTR(mode) ( \
350     /*un fi chr dir blk file link sock exe */ \
351     "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
352     [TYPEINDEX(mode)])
353    
354 niro 532 #if ENABLE_FEATURE_LS_COLOR
355 niro 984 /* mode of zero is interpreted as "unknown" (stat failed) */
356 niro 532 static char fgcolor(mode_t mode)
357     {
358     if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
359     return COLOR(0xF000); /* File is executable ... */
360     return COLOR(mode);
361     }
362 niro 984 static char bold(mode_t mode)
363 niro 532 {
364     if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
365     return ATTR(0xF000); /* File is executable ... */
366     return ATTR(mode);
367     }
368     #endif
369    
370     #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
371     static char append_char(mode_t mode)
372     {
373     if (!(all_fmt & LIST_FILETYPE))
374     return '\0';
375     if (S_ISDIR(mode))
376     return '/';
377     if (!(all_fmt & LIST_EXEC))
378     return '\0';
379     if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
380     return '*';
381     return APPCHAR(mode);
382     }
383     #endif
384    
385 niro 984 static unsigned count_dirs(struct dnode **dn, int which)
386 niro 532 {
387 niro 984 unsigned dirs, all;
388 niro 532
389     if (!dn)
390     return 0;
391 niro 984
392     dirs = all = 0;
393     for (; *dn; dn++) {
394 niro 816 const char *name;
395 niro 984
396     all++;
397     if (!S_ISDIR((*dn)->dstat.st_mode))
398 niro 532 continue;
399 niro 984 name = (*dn)->name;
400     if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
401     /* or if it's not . or .. */
402     || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
403 niro 532 ) {
404     dirs++;
405     }
406     }
407 niro 984 return which != SPLIT_FILE ? dirs : all - dirs;
408 niro 532 }
409    
410     /* get memory to hold an array of pointers */
411 niro 984 static struct dnode **dnalloc(unsigned num)
412 niro 532 {
413     if (num < 1)
414     return NULL;
415    
416 niro 984 num++; /* so that we have terminating NULL */
417 niro 532 return xzalloc(num * sizeof(struct dnode *));
418     }
419    
420     #if ENABLE_FEATURE_LS_RECURSIVE
421 niro 984 static void dfree(struct dnode **dnp)
422 niro 532 {
423 niro 984 unsigned i;
424 niro 532
425     if (dnp == NULL)
426     return;
427    
428 niro 984 for (i = 0; dnp[i]; i++) {
429 niro 532 struct dnode *cur = dnp[i];
430 niro 984 if (cur->fname_allocated)
431     free((char*)cur->fullname);
432     free(cur);
433 niro 532 }
434 niro 984 free(dnp);
435 niro 532 }
436     #else
437     #define dfree(...) ((void)0)
438     #endif
439    
440 niro 984 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
441     static struct dnode **splitdnarray(struct dnode **dn, int which)
442 niro 532 {
443 niro 984 unsigned dncnt, d;
444 niro 532 struct dnode **dnp;
445    
446 niro 984 if (dn == NULL)
447 niro 532 return NULL;
448    
449 niro 984 /* count how many dirs or files there are */
450     dncnt = count_dirs(dn, which);
451 niro 532
452     /* allocate a file array and a dir array */
453     dnp = dnalloc(dncnt);
454    
455     /* copy the entrys into the file or dir array */
456 niro 984 for (d = 0; *dn; dn++) {
457     if (S_ISDIR((*dn)->dstat.st_mode)) {
458 niro 816 const char *name;
459 niro 984
460 niro 532 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
461     continue;
462 niro 984 name = (*dn)->name;
463 niro 532 if ((which & SPLIT_DIR)
464     || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
465     ) {
466 niro 984 dnp[d++] = *dn;
467 niro 532 }
468     } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
469 niro 984 dnp[d++] = *dn;
470 niro 532 }
471     }
472     return dnp;
473     }
474    
475     #if ENABLE_FEATURE_LS_SORTFILES
476     static int sortcmp(const void *a, const void *b)
477     {
478     struct dnode *d1 = *(struct dnode **)a;
479     struct dnode *d2 = *(struct dnode **)b;
480     unsigned sort_opts = all_fmt & SORT_MASK;
481 niro 984 off_t dif;
482 niro 532
483     dif = 0; /* assume SORT_NAME */
484     // TODO: use pre-initialized function pointer
485     // instead of branch forest
486     if (sort_opts == SORT_SIZE) {
487 niro 984 dif = (d2->dstat.st_size - d1->dstat.st_size);
488 niro 532 } else if (sort_opts == SORT_ATIME) {
489 niro 984 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
490 niro 532 } else if (sort_opts == SORT_CTIME) {
491 niro 984 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
492 niro 532 } else if (sort_opts == SORT_MTIME) {
493 niro 984 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
494 niro 532 } else if (sort_opts == SORT_DIR) {
495     dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
496     /* } else if (sort_opts == SORT_VERSION) { */
497     /* } else if (sort_opts == SORT_EXT) { */
498     }
499     if (dif == 0) {
500 niro 984 /* sort by name, or tie_breaker for other sorts */
501     if (ENABLE_LOCALE_SUPPORT)
502     dif = strcoll(d1->name, d2->name);
503     else
504     dif = strcmp(d1->name, d2->name);
505 niro 532 }
506    
507 niro 984 /* Make dif fit into an int */
508     if (sizeof(dif) > sizeof(int)) {
509     enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
510     /* shift leaving only "int" worth of bits */
511     if (dif != 0) {
512     dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
513     }
514 niro 532 }
515 niro 984
516     return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
517 niro 532 }
518    
519     static void dnsort(struct dnode **dn, int size)
520     {
521     qsort(dn, size, sizeof(*dn), sortcmp);
522     }
523     #else
524     #define dnsort(dn, size) ((void)0)
525     #endif
526    
527    
528 niro 1123 static unsigned calc_name_len(const char *name)
529     {
530     unsigned len;
531     uni_stat_t uni_stat;
532    
533     // TODO: quote tab as \t, etc, if -Q
534     name = printable_string(&uni_stat, name);
535    
536     if (!(option_mask32 & OPT_Q)) {
537     return uni_stat.unicode_width;
538     }
539    
540     len = 2 + uni_stat.unicode_width;
541     while (*name) {
542     if (*name == '"' || *name == '\\') {
543     len++;
544     }
545     name++;
546     }
547     return len;
548     }
549    
550    
551     /* Return the number of used columns.
552     * Note that only STYLE_COLUMNS uses return value.
553     * STYLE_SINGLE and STYLE_LONG don't care.
554     * coreutils 7.2 also supports:
555     * ls -b (--escape) = octal escapes (although it doesn't look like working)
556     * ls -N (--literal) = not escape at all
557     */
558     static unsigned print_name(const char *name)
559     {
560     unsigned len;
561     uni_stat_t uni_stat;
562    
563     // TODO: quote tab as \t, etc, if -Q
564     name = printable_string(&uni_stat, name);
565    
566     if (!(option_mask32 & OPT_Q)) {
567     fputs(name, stdout);
568     return uni_stat.unicode_width;
569     }
570    
571     len = 2 + uni_stat.unicode_width;
572     putchar('"');
573     while (*name) {
574     if (*name == '"' || *name == '\\') {
575     putchar('\\');
576     len++;
577     }
578     putchar(*name++);
579     }
580     putchar('"');
581     return len;
582     }
583    
584     /* Return the number of used columns.
585     * Note that only STYLE_COLUMNS uses return value,
586     * STYLE_SINGLE and STYLE_LONG don't care.
587     */
588     static NOINLINE unsigned list_single(const struct dnode *dn)
589     {
590     unsigned column = 0;
591     char *lpath = lpath; /* for compiler */
592     #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
593     struct stat info;
594     char append;
595     #endif
596    
597     /* Never happens:
598     if (dn->fullname == NULL)
599     return 0;
600     */
601    
602     #if ENABLE_FEATURE_LS_FILETYPES
603     append = append_char(dn->dstat.st_mode);
604     #endif
605    
606     /* Do readlink early, so that if it fails, error message
607     * does not appear *inside* the "ls -l" line */
608     if (all_fmt & LIST_SYMLINK)
609     if (S_ISLNK(dn->dstat.st_mode))
610     lpath = xmalloc_readlink_or_warn(dn->fullname);
611    
612     if (all_fmt & LIST_INO)
613     column += printf("%7llu ", (long long) dn->dstat.st_ino);
614     if (all_fmt & LIST_BLOCKS)
615     column += printf("%4"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
616     if (all_fmt & LIST_MODEBITS)
617     column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
618     if (all_fmt & LIST_NLINKS)
619     column += printf("%4lu ", (long) dn->dstat.st_nlink);
620     #if ENABLE_FEATURE_LS_USERNAME
621     if (all_fmt & LIST_ID_NAME) {
622     if (option_mask32 & OPT_g) {
623     column += printf("%-8.8s ",
624 niro 1179 get_cached_groupname(dn->dstat.st_gid));
625 niro 1123 } else {
626     column += printf("%-8.8s %-8.8s ",
627     get_cached_username(dn->dstat.st_uid),
628     get_cached_groupname(dn->dstat.st_gid));
629     }
630     }
631     #endif
632     if (all_fmt & LIST_ID_NUMERIC) {
633     if (option_mask32 & OPT_g)
634 niro 1179 column += printf("%-8u ", (int) dn->dstat.st_gid);
635 niro 1123 else
636     column += printf("%-8u %-8u ",
637     (int) dn->dstat.st_uid,
638     (int) dn->dstat.st_gid);
639     }
640     if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
641     if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
642     column += printf("%4u, %3u ",
643     (int) major(dn->dstat.st_rdev),
644     (int) minor(dn->dstat.st_rdev));
645     } else {
646     if (all_fmt & LS_DISP_HR) {
647     column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
648     /* print st_size, show one fractional, use suffixes */
649     make_human_readable_str(dn->dstat.st_size, 1, 0)
650     );
651     } else {
652     column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
653     }
654     }
655     }
656     #if ENABLE_FEATURE_LS_TIMESTAMPS
657     if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
658     char *filetime;
659     time_t ttime = dn->dstat.st_mtime;
660     if (all_fmt & TIME_ACCESS)
661     ttime = dn->dstat.st_atime;
662     if (all_fmt & TIME_CHANGE)
663     ttime = dn->dstat.st_ctime;
664     filetime = ctime(&ttime);
665     /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
666     if (all_fmt & LIST_FULLTIME)
667     column += printf("%.24s ", filetime);
668     else { /* LIST_DATE_TIME */
669     /* current_time_t ~== time(NULL) */
670     time_t age = current_time_t - ttime;
671     printf("%.6s ", filetime + 4); /* "Jun 30" */
672     if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
673     /* hh:mm if less than 6 months old */
674     printf("%.5s ", filetime + 11);
675     } else { /* year. buggy if year > 9999 ;) */
676     printf(" %.4s ", filetime + 20);
677     }
678     column += 13;
679     }
680     }
681     #endif
682     #if ENABLE_SELINUX
683     if (all_fmt & LIST_CONTEXT) {
684     column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
685     freecon(dn->sid);
686     }
687     #endif
688     if (all_fmt & LIST_FILENAME) {
689     #if ENABLE_FEATURE_LS_COLOR
690     if (show_color) {
691     info.st_mode = 0; /* for fgcolor() */
692     lstat(dn->fullname, &info);
693     printf("\033[%u;%um", bold(info.st_mode),
694     fgcolor(info.st_mode));
695     }
696     #endif
697     column += print_name(dn->name);
698     if (show_color) {
699     printf("\033[0m");
700     }
701     }
702     if (all_fmt & LIST_SYMLINK) {
703     if (S_ISLNK(dn->dstat.st_mode) && lpath) {
704     printf(" -> ");
705     #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
706     #if ENABLE_FEATURE_LS_COLOR
707     info.st_mode = 0; /* for fgcolor() */
708     #endif
709     if (stat(dn->fullname, &info) == 0) {
710     append = append_char(info.st_mode);
711     }
712     #endif
713     #if ENABLE_FEATURE_LS_COLOR
714     if (show_color) {
715     printf("\033[%u;%um", bold(info.st_mode),
716     fgcolor(info.st_mode));
717     }
718     #endif
719     column += print_name(lpath) + 4;
720     if (show_color) {
721     printf("\033[0m");
722     }
723     free(lpath);
724     }
725     }
726     #if ENABLE_FEATURE_LS_FILETYPES
727     if (all_fmt & LIST_FILETYPE) {
728     if (append) {
729     putchar(append);
730     column++;
731     }
732     }
733     #endif
734    
735     return column;
736     }
737    
738 niro 984 static void showfiles(struct dnode **dn, unsigned nfiles)
739 niro 532 {
740 niro 984 unsigned i, ncols, nrows, row, nc;
741     unsigned column = 0;
742     unsigned nexttab = 0;
743 niro 1123 unsigned column_width = 0; /* used only by STYLE_COLUMNS */
744 niro 532
745 niro 1123 if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
746 niro 532 ncols = 1;
747     } else {
748     /* find the longest file name, use that as the column width */
749 niro 984 for (i = 0; dn[i]; i++) {
750 niro 1123 int len = calc_name_len(dn[i]->name);
751 niro 532 if (column_width < len)
752     column_width = len;
753     }
754     column_width += tabstops +
755 niro 984 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
756 niro 1123 ((all_fmt & LIST_INO) ? 8 : 0) +
757     ((all_fmt & LIST_BLOCKS) ? 5 : 0);
758 niro 532 ncols = (int) (terminal_width / column_width);
759     }
760    
761     if (ncols > 1) {
762     nrows = nfiles / ncols;
763     if (nrows * ncols < nfiles)
764     nrows++; /* round up fractionals */
765     } else {
766     nrows = nfiles;
767     ncols = 1;
768     }
769    
770     for (row = 0; row < nrows; row++) {
771     for (nc = 0; nc < ncols; nc++) {
772     /* reach into the array based on the column and row */
773     if (all_fmt & DISP_ROWS)
774     i = (row * ncols) + nc; /* display across row */
775 niro 984 else
776     i = (nc * nrows) + row; /* display by column */
777 niro 532 if (i < nfiles) {
778     if (column > 0) {
779     nexttab -= column;
780     printf("%*s", nexttab, "");
781     column += nexttab;
782     }
783     nexttab = column + column_width;
784     column += list_single(dn[i]);
785     }
786     }
787     putchar('\n');
788     column = 0;
789     }
790     }
791    
792    
793 niro 984 #if ENABLE_DESKTOP
794     /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
795     * If any of the -l, -n, -s options is specified, each list
796     * of files within the directory shall be preceded by a
797     * status line indicating the number of file system blocks
798     * occupied by files in the directory in 512-byte units if
799     * the -k option is not specified, or 1024-byte units if the
800     * -k option is specified, rounded up to the next integral
801     * number of units.
802     */
803     /* by Jorgen Overgaard (jorgen AT antistaten.se) */
804     static off_t calculate_blocks(struct dnode **dn)
805 niro 532 {
806 niro 984 uoff_t blocks = 1;
807     if (dn) {
808     while (*dn) {
809     /* st_blocks is in 512 byte blocks */
810     blocks += (*dn)->dstat.st_blocks;
811     dn++;
812     }
813     }
814    
815     /* Even though standard says use 512 byte blocks, coreutils use 1k */
816     /* Actually, we round up by calculating (blocks + 1) / 2,
817     * "+ 1" was done when we initialized blocks to 1 */
818     return blocks >> 1;
819     }
820     #endif
821    
822    
823 niro 1123 static struct dnode **list_dir(const char *, unsigned *);
824    
825 niro 984 static void showdirs(struct dnode **dn, int first)
826     {
827     unsigned nfiles;
828     unsigned dndirs;
829 niro 532 struct dnode **subdnp;
830     struct dnode **dnd;
831    
832 niro 984 /* Never happens:
833     if (dn == NULL || ndirs < 1) {
834 niro 532 return;
835 niro 984 }
836     */
837 niro 532
838 niro 984 for (; *dn; dn++) {
839 niro 532 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
840     if (!first)
841 niro 816 bb_putchar('\n');
842 niro 532 first = 0;
843 niro 984 printf("%s:\n", (*dn)->fullname);
844 niro 532 }
845 niro 984 subdnp = list_dir((*dn)->fullname, &nfiles);
846     #if ENABLE_DESKTOP
847     if ((all_fmt & STYLE_MASK) == STYLE_LONG)
848     printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
849     #endif
850 niro 532 if (nfiles > 0) {
851     /* list all files at this level */
852     dnsort(subdnp, nfiles);
853     showfiles(subdnp, nfiles);
854 niro 984 if (ENABLE_FEATURE_LS_RECURSIVE
855     && (all_fmt & DISP_RECURSIVE)
856     ) {
857     /* recursive - list the sub-dirs */
858     dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
859     dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
860     if (dndirs > 0) {
861     dnsort(dnd, dndirs);
862     showdirs(dnd, 0);
863     /* free the array of dnode pointers to the dirs */
864     free(dnd);
865 niro 532 }
866     }
867 niro 984 /* free the dnodes and the fullname mem */
868     dfree(subdnp);
869 niro 532 }
870     }
871     }
872    
873    
874 niro 984 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
875     static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
876 niro 532 {
877     struct dnode *dn, *cur, **dnp;
878     struct dirent *entry;
879     DIR *dir;
880 niro 984 unsigned i, nfiles;
881 niro 532
882 niro 984 /* Never happens:
883 niro 532 if (path == NULL)
884     return NULL;
885 niro 984 */
886 niro 532
887 niro 984 *nfiles_p = 0;
888 niro 532 dir = warn_opendir(path);
889     if (dir == NULL) {
890 niro 816 exit_code = EXIT_FAILURE;
891 niro 532 return NULL; /* could not open the dir */
892     }
893 niro 984 dn = NULL;
894     nfiles = 0;
895 niro 532 while ((entry = readdir(dir)) != NULL) {
896     char *fullname;
897    
898     /* are we going to list the file- it may be . or .. or a hidden file */
899     if (entry->d_name[0] == '.') {
900 niro 816 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
901     && !(all_fmt & DISP_DOT)
902     ) {
903 niro 532 continue;
904 niro 816 }
905 niro 532 if (!(all_fmt & DISP_HIDDEN))
906     continue;
907     }
908     fullname = concat_path_file(path, entry->d_name);
909 niro 816 cur = my_stat(fullname, bb_basename(fullname), 0);
910 niro 532 if (!cur) {
911     free(fullname);
912     continue;
913     }
914 niro 984 cur->fname_allocated = 1;
915 niro 532 cur->next = dn;
916     dn = cur;
917     nfiles++;
918     }
919     closedir(dir);
920    
921 niro 984 if (dn == NULL)
922     return NULL;
923    
924 niro 532 /* now that we know how many files there are
925     * allocate memory for an array to hold dnode pointers
926     */
927 niro 984 *nfiles_p = nfiles;
928 niro 532 dnp = dnalloc(nfiles);
929 niro 984 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
930     dnp[i] = dn; /* save pointer to node in array */
931     dn = dn->next;
932     if (!dn)
933     break;
934 niro 532 }
935    
936     return dnp;
937     }
938    
939    
940 niro 816 int ls_main(int argc UNUSED_PARAM, char **argv)
941 niro 532 {
942     struct dnode **dnd;
943     struct dnode **dnf;
944     struct dnode **dnp;
945     struct dnode *dn;
946     struct dnode *cur;
947     unsigned opt;
948 niro 984 unsigned nfiles;
949     unsigned dnfiles;
950     unsigned dndirs;
951     unsigned i;
952     #if ENABLE_FEATURE_LS_COLOR
953     /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
954     /* coreutils 6.10:
955     * # ls --color=BOGUS
956     * ls: invalid argument 'BOGUS' for '--color'
957     * Valid arguments are:
958     * 'always', 'yes', 'force'
959     * 'never', 'no', 'none'
960     * 'auto', 'tty', 'if-tty'
961     * (and substrings: "--color=alwa" work too)
962     */
963     static const char ls_longopts[] ALIGN1 =
964     "color\0" Optional_argument "\xff"; /* no short equivalent */
965     static const char color_str[] ALIGN1 =
966     "always\0""yes\0""force\0"
967     "auto\0""tty\0""if-tty\0";
968 niro 816 /* need to initialize since --color has _an optional_ argument */
969 niro 984 const char *color_opt = color_str; /* "always" */
970     #endif
971 niro 532
972 niro 816 INIT_G();
973 niro 532
974 niro 984 init_unicode();
975    
976 niro 532 all_fmt = LIST_SHORT |
977     (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
978    
979     #if ENABLE_FEATURE_AUTOWIDTH
980 niro 984 /* obtain the terminal width */
981 niro 816 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
982 niro 984 /* go one less... */
983 niro 532 terminal_width--;
984     #endif
985    
986     /* process options */
987 niro 984 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
988 niro 532 #if ENABLE_FEATURE_AUTOWIDTH
989 niro 816 opt_complementary = "T+:w+"; /* -T N, -w N */
990     opt = getopt32(argv, ls_options, &tabstops, &terminal_width
991 niro 984 IF_FEATURE_LS_COLOR(, &color_opt));
992 niro 532 #else
993 niro 984 opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
994 niro 532 #endif
995     for (i = 0; opt_flags[i] != (1U<<31); i++) {
996     if (opt & (1 << i)) {
997     unsigned flags = opt_flags[i];
998    
999     if (flags & LIST_MASK_TRIGGER)
1000     all_fmt &= ~LIST_MASK;
1001     if (flags & STYLE_MASK_TRIGGER)
1002     all_fmt &= ~STYLE_MASK;
1003     if (flags & SORT_MASK_TRIGGER)
1004     all_fmt &= ~SORT_MASK;
1005     if (flags & DISP_MASK_TRIGGER)
1006     all_fmt &= ~DISP_MASK;
1007     if (flags & TIME_MASK)
1008     all_fmt &= ~TIME_MASK;
1009     if (flags & LIST_CONTEXT)
1010     all_fmt |= STYLE_SINGLE;
1011 niro 816 /* huh?? opt cannot be 'l' */
1012     //if (LS_DISP_HR && opt == 'l')
1013     // all_fmt &= ~LS_DISP_HR;
1014 niro 532 all_fmt |= flags;
1015     }
1016     }
1017    
1018     #if ENABLE_FEATURE_LS_COLOR
1019     /* find color bit value - last position for short getopt */
1020     if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1021     char *p = getenv("LS_COLORS");
1022     /* LS_COLORS is unset, or (not empty && not "none") ? */
1023 niro 816 if (!p || (p[0] && strcmp(p, "none") != 0))
1024 niro 532 show_color = 1;
1025     }
1026 niro 984 if (opt & OPT_color) {
1027     if (color_opt[0] == 'n')
1028 niro 532 show_color = 0;
1029 niro 984 else switch (index_in_substrings(color_str, color_opt)) {
1030     case 3:
1031     case 4:
1032     case 5:
1033     if (isatty(STDOUT_FILENO)) {
1034     case 0:
1035     case 1:
1036     case 2:
1037     show_color = 1;
1038     }
1039     }
1040 niro 532 }
1041     #endif
1042    
1043     /* sort out which command line options take precedence */
1044     if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
1045     all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1046     if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1047     if (all_fmt & TIME_CHANGE)
1048     all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1049     if (all_fmt & TIME_ACCESS)
1050     all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1051     }
1052     if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1053     all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
1054     if (ENABLE_FEATURE_LS_USERNAME)
1055     if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1056     all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
1057    
1058     /* choose a display format */
1059     if (!(all_fmt & STYLE_MASK))
1060     all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
1061    
1062 niro 816 argv += optind;
1063     if (!argv[0])
1064     *--argv = (char*)".";
1065 niro 532
1066 niro 816 if (argv[1])
1067     all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1068 niro 532
1069     /* stuff the command line file names into a dnode array */
1070     dn = NULL;
1071 niro 816 nfiles = 0;
1072     do {
1073 niro 984 /* NB: follow links on command line unless -l or -s */
1074     cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1075 niro 816 argv++;
1076 niro 532 if (!cur)
1077     continue;
1078 niro 984 cur->fname_allocated = 0;
1079 niro 532 cur->next = dn;
1080     dn = cur;
1081     nfiles++;
1082 niro 816 } while (*argv);
1083 niro 532
1084 niro 984 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1085     if (nfiles == 0)
1086     return exit_code;
1087    
1088 niro 532 /* now that we know how many files there are
1089     * allocate memory for an array to hold dnode pointers
1090     */
1091     dnp = dnalloc(nfiles);
1092 niro 984 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1093     dnp[i] = dn; /* save pointer to node in array */
1094     dn = dn->next;
1095     if (!dn)
1096     break;
1097 niro 532 }
1098    
1099     if (all_fmt & DISP_NOLIST) {
1100     dnsort(dnp, nfiles);
1101 niro 984 showfiles(dnp, nfiles);
1102 niro 532 } else {
1103 niro 984 dnd = splitdnarray(dnp, SPLIT_DIR);
1104     dnf = splitdnarray(dnp, SPLIT_FILE);
1105     dndirs = count_dirs(dnp, SPLIT_DIR);
1106 niro 532 dnfiles = nfiles - dndirs;
1107     if (dnfiles > 0) {
1108     dnsort(dnf, dnfiles);
1109     showfiles(dnf, dnfiles);
1110     if (ENABLE_FEATURE_CLEAN_UP)
1111     free(dnf);
1112     }
1113     if (dndirs > 0) {
1114     dnsort(dnd, dndirs);
1115 niro 984 showdirs(dnd, dnfiles == 0);
1116 niro 532 if (ENABLE_FEATURE_CLEAN_UP)
1117     free(dnd);
1118     }
1119     }
1120     if (ENABLE_FEATURE_CLEAN_UP)
1121 niro 984 dfree(dnp);
1122 niro 816 return exit_code;
1123 niro 532 }