Magellan Linux

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1179 - (show annotations) (download)
Wed Dec 15 21:33:41 2010 UTC (13 years, 4 months ago) by niro
File MIME type: text/plain
File size: 30977 byte(s)
-updated to busybox-1.17.4
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>
5 *
6 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
7 */
8
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.
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 * 1. hidden files can make column width too large
21 *
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 *
29 * [2009-03]
30 * ls sorts listing now, and supports almost all options.
31 */
32 #include "libbb.h"
33 #include "unicode.h"
34
35
36 /* This is a NOEXEC applet. Be very careful! */
37
38
39 #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 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 //LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE
76 LIST_DATE_TIME = 1 << 9,
77 LIST_FULLTIME = 1 << 10,
78 LIST_FILENAME = 1 << 11,
79 LIST_SYMLINK = 1 << 12,
80 LIST_FILETYPE = 1 << 13,
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 /* "[-]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
172 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 owner) - 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 /*
233 * a directory entry and its stat info are stored here
234 */
235 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 struct stat dstat; /* the file stat info */
241 IF_SELINUX(security_context_t sid;)
242 };
243
244 struct globals {
245 #if ENABLE_FEATURE_LS_COLOR
246 smallint show_color;
247 #endif
248 smallint exit_code;
249 unsigned all_fmt;
250 #if ENABLE_FEATURE_AUTOWIDTH
251 unsigned tabstops; // = COLUMN_GAP;
252 unsigned terminal_width; // = TERMINAL_WIDTH;
253 #endif
254 #if ENABLE_FEATURE_LS_TIMESTAMPS
255 /* Do time() just once. Saves one syscall per file for "ls -l" */
256 time_t current_time_t;
257 #endif
258 } FIX_ALIASING;
259 #define G (*(struct globals*)&bb_common_bufsiz1)
260 #if ENABLE_FEATURE_LS_COLOR
261 # define show_color (G.show_color )
262 #else
263 enum { show_color = 0 };
264 #endif
265 #define exit_code (G.exit_code )
266 #define all_fmt (G.all_fmt )
267 #if ENABLE_FEATURE_AUTOWIDTH
268 # define tabstops (G.tabstops )
269 # define terminal_width (G.terminal_width)
270 #else
271 enum {
272 tabstops = COLUMN_GAP,
273 terminal_width = TERMINAL_WIDTH,
274 };
275 #endif
276 #define current_time_t (G.current_time_t)
277 #define INIT_G() do { \
278 /* we have to zero it out because of NOEXEC */ \
279 memset(&G, 0, sizeof(G)); \
280 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
281 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
282 IF_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
283 } while (0)
284
285
286 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
287 {
288 struct stat dstat;
289 struct dnode *cur;
290 IF_SELINUX(security_context_t sid = NULL;)
291
292 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
293 #if ENABLE_SELINUX
294 if (is_selinux_enabled()) {
295 getfilecon(fullname, &sid);
296 }
297 #endif
298 if (stat(fullname, &dstat)) {
299 bb_simple_perror_msg(fullname);
300 exit_code = EXIT_FAILURE;
301 return 0;
302 }
303 } else {
304 #if ENABLE_SELINUX
305 if (is_selinux_enabled()) {
306 lgetfilecon(fullname, &sid);
307 }
308 #endif
309 if (lstat(fullname, &dstat)) {
310 bb_simple_perror_msg(fullname);
311 exit_code = EXIT_FAILURE;
312 return 0;
313 }
314 }
315
316 cur = xmalloc(sizeof(*cur));
317 cur->fullname = fullname;
318 cur->name = name;
319 cur->dstat = dstat;
320 IF_SELINUX(cur->sid = sid;)
321 return cur;
322 }
323
324 /* 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 #if ENABLE_FEATURE_LS_COLOR
355 /* mode of zero is interpreted as "unknown" (stat failed) */
356 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 static char bold(mode_t mode)
363 {
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 static unsigned count_dirs(struct dnode **dn, int which)
386 {
387 unsigned dirs, all;
388
389 if (!dn)
390 return 0;
391
392 dirs = all = 0;
393 for (; *dn; dn++) {
394 const char *name;
395
396 all++;
397 if (!S_ISDIR((*dn)->dstat.st_mode))
398 continue;
399 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 ) {
404 dirs++;
405 }
406 }
407 return which != SPLIT_FILE ? dirs : all - dirs;
408 }
409
410 /* get memory to hold an array of pointers */
411 static struct dnode **dnalloc(unsigned num)
412 {
413 if (num < 1)
414 return NULL;
415
416 num++; /* so that we have terminating NULL */
417 return xzalloc(num * sizeof(struct dnode *));
418 }
419
420 #if ENABLE_FEATURE_LS_RECURSIVE
421 static void dfree(struct dnode **dnp)
422 {
423 unsigned i;
424
425 if (dnp == NULL)
426 return;
427
428 for (i = 0; dnp[i]; i++) {
429 struct dnode *cur = dnp[i];
430 if (cur->fname_allocated)
431 free((char*)cur->fullname);
432 free(cur);
433 }
434 free(dnp);
435 }
436 #else
437 #define dfree(...) ((void)0)
438 #endif
439
440 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
441 static struct dnode **splitdnarray(struct dnode **dn, int which)
442 {
443 unsigned dncnt, d;
444 struct dnode **dnp;
445
446 if (dn == NULL)
447 return NULL;
448
449 /* count how many dirs or files there are */
450 dncnt = count_dirs(dn, which);
451
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 for (d = 0; *dn; dn++) {
457 if (S_ISDIR((*dn)->dstat.st_mode)) {
458 const char *name;
459
460 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
461 continue;
462 name = (*dn)->name;
463 if ((which & SPLIT_DIR)
464 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
465 ) {
466 dnp[d++] = *dn;
467 }
468 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
469 dnp[d++] = *dn;
470 }
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 off_t dif;
482
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 dif = (d2->dstat.st_size - d1->dstat.st_size);
488 } else if (sort_opts == SORT_ATIME) {
489 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
490 } else if (sort_opts == SORT_CTIME) {
491 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
492 } else if (sort_opts == SORT_MTIME) {
493 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
494 } 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 /* 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 }
506
507 /* 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 }
515
516 return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
517 }
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 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 get_cached_groupname(dn->dstat.st_gid));
625 } 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 column += printf("%-8u ", (int) dn->dstat.st_gid);
635 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 static void showfiles(struct dnode **dn, unsigned nfiles)
739 {
740 unsigned i, ncols, nrows, row, nc;
741 unsigned column = 0;
742 unsigned nexttab = 0;
743 unsigned column_width = 0; /* used only by STYLE_COLUMNS */
744
745 if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
746 ncols = 1;
747 } else {
748 /* find the longest file name, use that as the column width */
749 for (i = 0; dn[i]; i++) {
750 int len = calc_name_len(dn[i]->name);
751 if (column_width < len)
752 column_width = len;
753 }
754 column_width += tabstops +
755 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
756 ((all_fmt & LIST_INO) ? 8 : 0) +
757 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
758 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 else
776 i = (nc * nrows) + row; /* display by column */
777 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 #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 {
806 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 static struct dnode **list_dir(const char *, unsigned *);
824
825 static void showdirs(struct dnode **dn, int first)
826 {
827 unsigned nfiles;
828 unsigned dndirs;
829 struct dnode **subdnp;
830 struct dnode **dnd;
831
832 /* Never happens:
833 if (dn == NULL || ndirs < 1) {
834 return;
835 }
836 */
837
838 for (; *dn; dn++) {
839 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
840 if (!first)
841 bb_putchar('\n');
842 first = 0;
843 printf("%s:\n", (*dn)->fullname);
844 }
845 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 if (nfiles > 0) {
851 /* list all files at this level */
852 dnsort(subdnp, nfiles);
853 showfiles(subdnp, nfiles);
854 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 }
866 }
867 /* free the dnodes and the fullname mem */
868 dfree(subdnp);
869 }
870 }
871 }
872
873
874 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
875 static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
876 {
877 struct dnode *dn, *cur, **dnp;
878 struct dirent *entry;
879 DIR *dir;
880 unsigned i, nfiles;
881
882 /* Never happens:
883 if (path == NULL)
884 return NULL;
885 */
886
887 *nfiles_p = 0;
888 dir = warn_opendir(path);
889 if (dir == NULL) {
890 exit_code = EXIT_FAILURE;
891 return NULL; /* could not open the dir */
892 }
893 dn = NULL;
894 nfiles = 0;
895 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 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
901 && !(all_fmt & DISP_DOT)
902 ) {
903 continue;
904 }
905 if (!(all_fmt & DISP_HIDDEN))
906 continue;
907 }
908 fullname = concat_path_file(path, entry->d_name);
909 cur = my_stat(fullname, bb_basename(fullname), 0);
910 if (!cur) {
911 free(fullname);
912 continue;
913 }
914 cur->fname_allocated = 1;
915 cur->next = dn;
916 dn = cur;
917 nfiles++;
918 }
919 closedir(dir);
920
921 if (dn == NULL)
922 return NULL;
923
924 /* now that we know how many files there are
925 * allocate memory for an array to hold dnode pointers
926 */
927 *nfiles_p = nfiles;
928 dnp = dnalloc(nfiles);
929 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 }
935
936 return dnp;
937 }
938
939
940 int ls_main(int argc UNUSED_PARAM, char **argv)
941 {
942 struct dnode **dnd;
943 struct dnode **dnf;
944 struct dnode **dnp;
945 struct dnode *dn;
946 struct dnode *cur;
947 unsigned opt;
948 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 /* need to initialize since --color has _an optional_ argument */
969 const char *color_opt = color_str; /* "always" */
970 #endif
971
972 INIT_G();
973
974 init_unicode();
975
976 all_fmt = LIST_SHORT |
977 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
978
979 #if ENABLE_FEATURE_AUTOWIDTH
980 /* obtain the terminal width */
981 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
982 /* go one less... */
983 terminal_width--;
984 #endif
985
986 /* process options */
987 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
988 #if ENABLE_FEATURE_AUTOWIDTH
989 opt_complementary = "T+:w+"; /* -T N, -w N */
990 opt = getopt32(argv, ls_options, &tabstops, &terminal_width
991 IF_FEATURE_LS_COLOR(, &color_opt));
992 #else
993 opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
994 #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 /* huh?? opt cannot be 'l' */
1012 //if (LS_DISP_HR && opt == 'l')
1013 // all_fmt &= ~LS_DISP_HR;
1014 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 if (!p || (p[0] && strcmp(p, "none") != 0))
1024 show_color = 1;
1025 }
1026 if (opt & OPT_color) {
1027 if (color_opt[0] == 'n')
1028 show_color = 0;
1029 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 }
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 argv += optind;
1063 if (!argv[0])
1064 *--argv = (char*)".";
1065
1066 if (argv[1])
1067 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1068
1069 /* stuff the command line file names into a dnode array */
1070 dn = NULL;
1071 nfiles = 0;
1072 do {
1073 /* NB: follow links on command line unless -l or -s */
1074 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1075 argv++;
1076 if (!cur)
1077 continue;
1078 cur->fname_allocated = 0;
1079 cur->next = dn;
1080 dn = cur;
1081 nfiles++;
1082 } while (*argv);
1083
1084 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1085 if (nfiles == 0)
1086 return exit_code;
1087
1088 /* 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 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 }
1098
1099 if (all_fmt & DISP_NOLIST) {
1100 dnsort(dnp, nfiles);
1101 showfiles(dnp, nfiles);
1102 } else {
1103 dnd = splitdnarray(dnp, SPLIT_DIR);
1104 dnf = splitdnarray(dnp, SPLIT_FILE);
1105 dndirs = count_dirs(dnp, SPLIT_DIR);
1106 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 showdirs(dnd, dnfiles == 0);
1116 if (ENABLE_FEATURE_CLEAN_UP)
1117 free(dnd);
1118 }
1119 }
1120 if (ENABLE_FEATURE_CLEAN_UP)
1121 dfree(dnp);
1122 return exit_code;
1123 }