Magellan Linux

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 984 - (show 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 /* 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 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 /*
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 static struct dnode **list_dir(const char *, unsigned *);
245 static unsigned list_single(const struct dnode *);
246
247 struct globals {
248 #if ENABLE_FEATURE_LS_COLOR
249 smallint show_color;
250 #endif
251 smallint exit_code;
252 unsigned all_fmt;
253 #if ENABLE_FEATURE_AUTOWIDTH
254 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 # define show_color (G.show_color )
265 #else
266 enum { show_color = 0 };
267 #endif
268 #define exit_code (G.exit_code )
269 #define all_fmt (G.all_fmt )
270 #if ENABLE_FEATURE_AUTOWIDTH
271 # define tabstops (G.tabstops )
272 # define terminal_width (G.terminal_width)
273 #else
274 enum {
275 tabstops = COLUMN_GAP,
276 terminal_width = TERMINAL_WIDTH,
277 };
278 #endif
279 #define current_time_t (G.current_time_t)
280 #define INIT_G() do { \
281 /* we have to zero it out because of NOEXEC */ \
282 memset(&G, 0, sizeof(G)); \
283 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
284 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
285 IF_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
286 } while (0)
287
288
289 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
290 {
291 struct stat dstat;
292 struct dnode *cur;
293 IF_SELINUX(security_context_t sid = NULL;)
294
295 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
296 #if ENABLE_SELINUX
297 if (is_selinux_enabled()) {
298 getfilecon(fullname, &sid);
299 }
300 #endif
301 if (stat(fullname, &dstat)) {
302 bb_simple_perror_msg(fullname);
303 exit_code = EXIT_FAILURE;
304 return 0;
305 }
306 } else {
307 #if ENABLE_SELINUX
308 if (is_selinux_enabled()) {
309 lgetfilecon(fullname, &sid);
310 }
311 #endif
312 if (lstat(fullname, &dstat)) {
313 bb_simple_perror_msg(fullname);
314 exit_code = EXIT_FAILURE;
315 return 0;
316 }
317 }
318
319 cur = xmalloc(sizeof(*cur));
320 cur->fullname = fullname;
321 cur->name = name;
322 cur->dstat = dstat;
323 IF_SELINUX(cur->sid = sid;)
324 return cur;
325 }
326
327 /* 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 #if ENABLE_FEATURE_LS_COLOR
358 /* mode of zero is interpreted as "unknown" (stat failed) */
359 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 static char bold(mode_t mode)
366 {
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 static unsigned count_dirs(struct dnode **dn, int which)
389 {
390 unsigned dirs, all;
391
392 if (!dn)
393 return 0;
394
395 dirs = all = 0;
396 for (; *dn; dn++) {
397 const char *name;
398
399 all++;
400 if (!S_ISDIR((*dn)->dstat.st_mode))
401 continue;
402 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 ) {
407 dirs++;
408 }
409 }
410 return which != SPLIT_FILE ? dirs : all - dirs;
411 }
412
413 /* get memory to hold an array of pointers */
414 static struct dnode **dnalloc(unsigned num)
415 {
416 if (num < 1)
417 return NULL;
418
419 num++; /* so that we have terminating NULL */
420 return xzalloc(num * sizeof(struct dnode *));
421 }
422
423 #if ENABLE_FEATURE_LS_RECURSIVE
424 static void dfree(struct dnode **dnp)
425 {
426 unsigned i;
427
428 if (dnp == NULL)
429 return;
430
431 for (i = 0; dnp[i]; i++) {
432 struct dnode *cur = dnp[i];
433 if (cur->fname_allocated)
434 free((char*)cur->fullname);
435 free(cur);
436 }
437 free(dnp);
438 }
439 #else
440 #define dfree(...) ((void)0)
441 #endif
442
443 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
444 static struct dnode **splitdnarray(struct dnode **dn, int which)
445 {
446 unsigned dncnt, d;
447 struct dnode **dnp;
448
449 if (dn == NULL)
450 return NULL;
451
452 /* count how many dirs or files there are */
453 dncnt = count_dirs(dn, which);
454
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 for (d = 0; *dn; dn++) {
460 if (S_ISDIR((*dn)->dstat.st_mode)) {
461 const char *name;
462
463 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
464 continue;
465 name = (*dn)->name;
466 if ((which & SPLIT_DIR)
467 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
468 ) {
469 dnp[d++] = *dn;
470 }
471 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
472 dnp[d++] = *dn;
473 }
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 off_t dif;
485
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 dif = (d2->dstat.st_size - d1->dstat.st_size);
491 } else if (sort_opts == SORT_ATIME) {
492 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
493 } else if (sort_opts == SORT_CTIME) {
494 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
495 } else if (sort_opts == SORT_MTIME) {
496 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
497 } 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 /* 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 }
509
510 /* 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 }
518
519 return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
520 }
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 static void showfiles(struct dnode **dn, unsigned nfiles)
532 {
533 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
538 /* Never happens:
539 if (dn == NULL || nfiles < 1)
540 return;
541 */
542
543 if (all_fmt & STYLE_LONG) {
544 ncols = 1;
545 } else {
546 /* find the longest file name, use that as the column width */
547 for (i = 0; dn[i]; i++) {
548 int len = unicode_strlen(dn[i]->name);
549 if (column_width < len)
550 column_width = len;
551 }
552 column_width += tabstops +
553 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
554 ((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 else
574 i = (nc * nrows) + row; /* display by column */
575 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 #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 {
604 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 struct dnode **subdnp;
626 struct dnode **dnd;
627
628 /* Never happens:
629 if (dn == NULL || ndirs < 1) {
630 return;
631 }
632 */
633
634 for (; *dn; dn++) {
635 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
636 if (!first)
637 bb_putchar('\n');
638 first = 0;
639 printf("%s:\n", (*dn)->fullname);
640 }
641 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 if (nfiles > 0) {
647 /* list all files at this level */
648 dnsort(subdnp, nfiles);
649 showfiles(subdnp, nfiles);
650 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 }
662 }
663 /* free the dnodes and the fullname mem */
664 dfree(subdnp);
665 }
666 }
667 }
668
669
670 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
671 static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
672 {
673 struct dnode *dn, *cur, **dnp;
674 struct dirent *entry;
675 DIR *dir;
676 unsigned i, nfiles;
677
678 /* Never happens:
679 if (path == NULL)
680 return NULL;
681 */
682
683 *nfiles_p = 0;
684 dir = warn_opendir(path);
685 if (dir == NULL) {
686 exit_code = EXIT_FAILURE;
687 return NULL; /* could not open the dir */
688 }
689 dn = NULL;
690 nfiles = 0;
691 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 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
697 && !(all_fmt & DISP_DOT)
698 ) {
699 continue;
700 }
701 if (!(all_fmt & DISP_HIDDEN))
702 continue;
703 }
704 fullname = concat_path_file(path, entry->d_name);
705 cur = my_stat(fullname, bb_basename(fullname), 0);
706 if (!cur) {
707 free(fullname);
708 continue;
709 }
710 cur->fname_allocated = 1;
711 cur->next = dn;
712 dn = cur;
713 nfiles++;
714 }
715 closedir(dir);
716
717 if (dn == NULL)
718 return NULL;
719
720 /* now that we know how many files there are
721 * allocate memory for an array to hold dnode pointers
722 */
723 *nfiles_p = nfiles;
724 dnp = dnalloc(nfiles);
725 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 }
731
732 return dnp;
733 }
734
735
736 static int print_name(const char *name)
737 {
738 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
766
767 static NOINLINE unsigned list_single(const struct dnode *dn)
768 {
769 unsigned column = 0;
770 char *lpath = lpath; /* for compiler */
771 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
772 struct stat info;
773 char append;
774 #endif
775
776 /* Never happens:
777 if (dn->fullname == NULL)
778 return 0;
779 */
780
781 #if ENABLE_FEATURE_LS_FILETYPES
782 append = append_char(dn->dstat.st_mode);
783 #endif
784
785 /* 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 #if ENABLE_FEATURE_LS_USERNAME
800 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 get_cached_username(dn->dstat.st_uid),
807 get_cached_groupname(dn->dstat.st_gid));
808 }
809 }
810 #endif
811 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 } else {
831 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
832 }
833 }
834 }
835 #if ENABLE_FEATURE_LS_TIMESTAMPS
836 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 }
857 column += 13;
858 }
859 }
860 #endif
861 #if ENABLE_SELINUX
862 if (all_fmt & LIST_CONTEXT) {
863 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
864 freecon(dn->sid);
865 }
866 #endif
867 if (all_fmt & LIST_FILENAME) {
868 #if ENABLE_FEATURE_LS_COLOR
869 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 #endif
876 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 #endif
888 if (stat(dn->fullname, &info) == 0) {
889 append = append_char(info.st_mode);
890 }
891 #endif
892 #if ENABLE_FEATURE_LS_COLOR
893 if (show_color) {
894 printf("\033[%u;%um", bold(info.st_mode),
895 fgcolor(info.st_mode));
896 }
897 #endif
898 column += print_name(lpath) + 4;
899 if (show_color) {
900 printf("\033[0m");
901 }
902 free(lpath);
903 }
904 }
905 #if ENABLE_FEATURE_LS_FILETYPES
906 if (all_fmt & LIST_FILETYPE) {
907 if (append) {
908 putchar(append);
909 column++;
910 }
911 }
912 #endif
913
914 return column;
915 }
916
917
918 int ls_main(int argc UNUSED_PARAM, char **argv)
919 {
920 struct dnode **dnd;
921 struct dnode **dnf;
922 struct dnode **dnp;
923 struct dnode *dn;
924 struct dnode *cur;
925 unsigned opt;
926 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 /* need to initialize since --color has _an optional_ argument */
947 const char *color_opt = color_str; /* "always" */
948 #endif
949
950 INIT_G();
951
952 init_unicode();
953
954 all_fmt = LIST_SHORT |
955 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
956
957 #if ENABLE_FEATURE_AUTOWIDTH
958 /* obtain the terminal width */
959 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
960 /* go one less... */
961 terminal_width--;
962 #endif
963
964 /* process options */
965 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
966 #if ENABLE_FEATURE_AUTOWIDTH
967 opt_complementary = "T+:w+"; /* -T N, -w N */
968 opt = getopt32(argv, ls_options, &tabstops, &terminal_width
969 IF_FEATURE_LS_COLOR(, &color_opt));
970 #else
971 opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
972 #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 /* huh?? opt cannot be 'l' */
990 //if (LS_DISP_HR && opt == 'l')
991 // all_fmt &= ~LS_DISP_HR;
992 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 if (!p || (p[0] && strcmp(p, "none") != 0))
1002 show_color = 1;
1003 }
1004 if (opt & OPT_color) {
1005 if (color_opt[0] == 'n')
1006 show_color = 0;
1007 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 }
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 argv += optind;
1041 if (!argv[0])
1042 *--argv = (char*)".";
1043
1044 if (argv[1])
1045 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1046
1047 /* stuff the command line file names into a dnode array */
1048 dn = NULL;
1049 nfiles = 0;
1050 do {
1051 /* NB: follow links on command line unless -l or -s */
1052 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
1053 argv++;
1054 if (!cur)
1055 continue;
1056 cur->fname_allocated = 0;
1057 cur->next = dn;
1058 dn = cur;
1059 nfiles++;
1060 } while (*argv);
1061
1062 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1063 if (nfiles == 0)
1064 return exit_code;
1065
1066 /* 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 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 }
1076
1077 if (all_fmt & DISP_NOLIST) {
1078 dnsort(dnp, nfiles);
1079 showfiles(dnp, nfiles);
1080 } else {
1081 dnd = splitdnarray(dnp, SPLIT_DIR);
1082 dnf = splitdnarray(dnp, SPLIT_FILE);
1083 dndirs = count_dirs(dnp, SPLIT_DIR);
1084 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 showdirs(dnd, dnfiles == 0);
1094 if (ENABLE_FEATURE_CLEAN_UP)
1095 free(dnd);
1096 }
1097 }
1098 if (ENABLE_FEATURE_CLEAN_UP)
1099 dfree(dnp);
1100 return exit_code;
1101 }