source: MondoRescue/branches/3.0/mindi-busybox/coreutils/ls.c@ 3085

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