Magellan Linux

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

Parent Directory Parent Directory | Revision Log Revision Log


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