blob: e7490ac29386785cd9e38c1632d589be2a687bc8
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com> |
4 | * |
5 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
6 | */ |
7 | /* [date unknown. Perhaps before year 2000] |
8 | * To achieve a small memory footprint, this version of 'ls' doesn't do any |
9 | * file sorting, and only has the most essential command line switches |
10 | * (i.e., the ones I couldn't live without :-) All features which involve |
11 | * linking in substantial chunks of libc can be disabled. |
12 | * |
13 | * Although I don't really want to add new features to this program to |
14 | * keep it small, I *am* interested to receive bug fixes and ways to make |
15 | * it more portable. |
16 | * |
17 | * KNOWN BUGS: |
18 | * 1. hidden files can make column width too large |
19 | * |
20 | * NON-OPTIMAL BEHAVIOUR: |
21 | * 1. autowidth reads directories twice |
22 | * 2. if you do a short directory listing without filetype characters |
23 | * appended, there's no need to stat each one |
24 | * PORTABILITY: |
25 | * 1. requires lstat (BSD) - how do you do it without? |
26 | * |
27 | * [2009-03] |
28 | * ls sorts listing now, and supports almost all options. |
29 | */ |
30 | //config:config LS |
31 | //config: bool "ls" |
32 | //config: default y |
33 | //config: help |
34 | //config: ls is used to list the contents of directories. |
35 | //config: |
36 | //config:config FEATURE_LS_FILETYPES |
37 | //config: bool "Enable filetyping options (-p and -F)" |
38 | //config: default y |
39 | //config: depends on LS |
40 | //config: help |
41 | //config: Enable the ls options (-p and -F). |
42 | //config: |
43 | //config:config FEATURE_LS_FOLLOWLINKS |
44 | //config: bool "Enable symlinks dereferencing (-L)" |
45 | //config: default y |
46 | //config: depends on LS |
47 | //config: help |
48 | //config: Enable the ls option (-L). |
49 | //config: |
50 | //config:config FEATURE_LS_RECURSIVE |
51 | //config: bool "Enable recursion (-R)" |
52 | //config: default y |
53 | //config: depends on LS |
54 | //config: help |
55 | //config: Enable the ls option (-R). |
56 | //config: |
57 | //config:config FEATURE_LS_SORTFILES |
58 | //config: bool "Sort the file names" |
59 | //config: default y |
60 | //config: depends on LS |
61 | //config: help |
62 | //config: Allow ls to sort file names alphabetically. |
63 | //config: |
64 | //config:config FEATURE_LS_TIMESTAMPS |
65 | //config: bool "Show file timestamps" |
66 | //config: default y |
67 | //config: depends on LS |
68 | //config: help |
69 | //config: Allow ls to display timestamps for files. |
70 | //config: |
71 | //config:config FEATURE_LS_USERNAME |
72 | //config: bool "Show username/groupnames" |
73 | //config: default y |
74 | //config: depends on LS |
75 | //config: help |
76 | //config: Allow ls to display username/groupname for files. |
77 | //config: |
78 | //config:config FEATURE_LS_COLOR |
79 | //config: bool "Allow use of color to identify file types" |
80 | //config: default y |
81 | //config: depends on LS && LONG_OPTS |
82 | //config: help |
83 | //config: This enables the --color option to ls. |
84 | //config: |
85 | //config:config FEATURE_LS_COLOR_IS_DEFAULT |
86 | //config: bool "Produce colored ls output by default" |
87 | //config: default y |
88 | //config: depends on FEATURE_LS_COLOR |
89 | //config: help |
90 | //config: Saying yes here will turn coloring on by default, |
91 | //config: even if no "--color" option is given to the ls command. |
92 | //config: This is not recommended, since the colors are not |
93 | //config: configurable, and the output may not be legible on |
94 | //config: many output screens. |
95 | |
96 | //applet:IF_LS(APPLET_NOEXEC(ls, ls, BB_DIR_BIN, BB_SUID_DROP, ls)) |
97 | |
98 | //kbuild:lib-$(CONFIG_LS) += ls.o |
99 | |
100 | //usage:#define ls_trivial_usage |
101 | //usage: "[-1AaCxd" |
102 | //usage: IF_FEATURE_LS_FOLLOWLINKS("LH") |
103 | //usage: IF_FEATURE_LS_RECURSIVE("R") |
104 | //usage: IF_FEATURE_LS_FILETYPES("Fp") "lins" |
105 | //usage: IF_FEATURE_LS_TIMESTAMPS("e") |
106 | //usage: IF_FEATURE_HUMAN_READABLE("h") |
107 | //usage: IF_FEATURE_LS_SORTFILES("rSXv") |
108 | //usage: IF_FEATURE_LS_TIMESTAMPS("ctu") |
109 | //usage: IF_SELINUX("kKZ") "]" |
110 | //usage: IF_FEATURE_AUTOWIDTH(" [-w WIDTH]") " [FILE]..." |
111 | //usage:#define ls_full_usage "\n\n" |
112 | //usage: "List directory contents\n" |
113 | //usage: "\n -1 One column output" |
114 | //usage: "\n -a Include entries which start with ." |
115 | //usage: "\n -A Like -a, but exclude . and .." |
116 | //usage: "\n -C List by columns" |
117 | //usage: "\n -x List by lines" |
118 | //usage: "\n -d List directory entries instead of contents" |
119 | //usage: IF_FEATURE_LS_FOLLOWLINKS( |
120 | //usage: "\n -L Follow symlinks" |
121 | //usage: "\n -H Follow symlinks on command line" |
122 | //usage: ) |
123 | //usage: IF_FEATURE_LS_RECURSIVE( |
124 | //usage: "\n -R Recurse" |
125 | //usage: ) |
126 | //usage: IF_FEATURE_LS_FILETYPES( |
127 | //usage: "\n -p Append / to dir entries" |
128 | //usage: "\n -F Append indicator (one of */=@|) to entries" |
129 | //usage: ) |
130 | //usage: "\n -l Long listing format" |
131 | //usage: "\n -i List inode numbers" |
132 | //usage: "\n -n List numeric UIDs and GIDs instead of names" |
133 | //usage: "\n -s List allocated blocks" |
134 | //usage: IF_FEATURE_LS_TIMESTAMPS( |
135 | //usage: "\n -e List full date and time" |
136 | //usage: ) |
137 | //usage: IF_FEATURE_HUMAN_READABLE( |
138 | //usage: "\n -h List sizes in human readable format (1K 243M 2G)" |
139 | //usage: ) |
140 | //usage: IF_FEATURE_LS_SORTFILES( |
141 | //usage: "\n -r Sort in reverse order" |
142 | //usage: "\n -S Sort by size" |
143 | //usage: "\n -X Sort by extension" |
144 | //usage: "\n -v Sort by version" |
145 | //usage: ) |
146 | //usage: IF_FEATURE_LS_TIMESTAMPS( |
147 | //usage: "\n -c With -l: sort by ctime" |
148 | //usage: "\n -t With -l: sort by mtime" |
149 | //usage: "\n -u With -l: sort by atime" |
150 | //usage: ) |
151 | //usage: IF_SELINUX( |
152 | //usage: "\n -k List security context" |
153 | //usage: "\n -K List security context in long format" |
154 | //usage: "\n -Z List security context and permission" |
155 | //usage: ) |
156 | //usage: IF_FEATURE_AUTOWIDTH( |
157 | //usage: "\n -w N Assume the terminal is N columns wide" |
158 | //usage: ) |
159 | //usage: IF_FEATURE_LS_COLOR( |
160 | //usage: "\n --color[={always,never,auto}] Control coloring" |
161 | //usage: ) |
162 | |
163 | #include "libbb.h" |
164 | #include "common_bufsiz.h" |
165 | #include "unicode.h" |
166 | |
167 | |
168 | /* This is a NOEXEC applet. Be very careful! */ |
169 | |
170 | |
171 | #if ENABLE_FTPD |
172 | /* ftpd uses ls, and without timestamps Mozilla won't understand |
173 | * ftpd's LIST output. |
174 | */ |
175 | # undef CONFIG_FEATURE_LS_TIMESTAMPS |
176 | # undef ENABLE_FEATURE_LS_TIMESTAMPS |
177 | # undef IF_FEATURE_LS_TIMESTAMPS |
178 | # undef IF_NOT_FEATURE_LS_TIMESTAMPS |
179 | # define CONFIG_FEATURE_LS_TIMESTAMPS 1 |
180 | # define ENABLE_FEATURE_LS_TIMESTAMPS 1 |
181 | # define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__ |
182 | # define IF_NOT_FEATURE_LS_TIMESTAMPS(...) |
183 | #endif |
184 | |
185 | |
186 | enum { |
187 | TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */ |
188 | |
189 | SPLIT_FILE = 0, |
190 | SPLIT_DIR = 1, |
191 | SPLIT_SUBDIR = 2, |
192 | |
193 | /* Bits in G.all_fmt: */ |
194 | |
195 | /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */ |
196 | /* what file information will be listed */ |
197 | LIST_INO = 1 << 0, |
198 | LIST_BLOCKS = 1 << 1, |
199 | LIST_MODEBITS = 1 << 2, |
200 | LIST_NLINKS = 1 << 3, |
201 | LIST_ID_NAME = 1 << 4, |
202 | LIST_ID_NUMERIC = 1 << 5, |
203 | LIST_CONTEXT = 1 << 6, |
204 | LIST_SIZE = 1 << 7, |
205 | LIST_DATE_TIME = 1 << 8, |
206 | LIST_FULLTIME = 1 << 9, |
207 | LIST_SYMLINK = 1 << 10, |
208 | LIST_FILETYPE = 1 << 11, /* show / suffix for dirs */ |
209 | LIST_CLASSIFY = 1 << 12, /* requires LIST_FILETYPE, also show *,|,@,= suffixes */ |
210 | LIST_MASK = (LIST_CLASSIFY << 1) - 1, |
211 | |
212 | /* what files will be displayed */ |
213 | DISP_DIRNAME = 1 << 13, /* 2 or more items? label directories */ |
214 | DISP_HIDDEN = 1 << 14, /* show filenames starting with . */ |
215 | DISP_DOT = 1 << 15, /* show . and .. */ |
216 | DISP_NOLIST = 1 << 16, /* show directory as itself, not contents */ |
217 | DISP_RECURSIVE = 1 << 17, /* show directory and everything below it */ |
218 | DISP_ROWS = 1 << 18, /* print across rows */ |
219 | DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1), |
220 | |
221 | /* what is the overall style of the listing */ |
222 | STYLE_COLUMNAR = 1 << 19, /* many records per line */ |
223 | STYLE_LONG = 2 << 19, /* one record per line, extended info */ |
224 | STYLE_SINGLE = 3 << 19, /* one record per line */ |
225 | STYLE_MASK = STYLE_SINGLE, |
226 | |
227 | /* which of the three times will be used */ |
228 | TIME_CHANGE = (1 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS, |
229 | TIME_ACCESS = (2 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS, |
230 | TIME_MASK = (3 << 21) * ENABLE_FEATURE_LS_TIMESTAMPS, |
231 | |
232 | /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */ |
233 | SORT_REVERSE = 1 << 23, |
234 | |
235 | SORT_NAME = 0, /* sort by file name */ |
236 | SORT_SIZE = 1 << 24, /* sort by file size */ |
237 | SORT_ATIME = 2 << 24, /* sort by last access time */ |
238 | SORT_CTIME = 3 << 24, /* sort by last change time */ |
239 | SORT_MTIME = 4 << 24, /* sort by last modification time */ |
240 | SORT_VERSION = 5 << 24, /* sort by version */ |
241 | SORT_EXT = 6 << 24, /* sort by file name extension */ |
242 | SORT_DIR = 7 << 24, /* sort by file or directory */ |
243 | SORT_MASK = (7 << 24) * ENABLE_FEATURE_LS_SORTFILES, |
244 | |
245 | LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \ |
246 | LIST_DATE_TIME | LIST_SYMLINK, |
247 | }; |
248 | |
249 | /* -Cadil1 Std options, busybox always supports */ |
250 | /* -gnsxA Std options, busybox always supports */ |
251 | /* -Q GNU option, busybox always supports */ |
252 | /* -k SELinux option, busybox always supports (ignores if !SELinux) */ |
253 | /* Std has -k which means "show sizes in kbytes" */ |
254 | /* -LHRctur Std options, busybox optionally supports */ |
255 | /* -Fp Std options, busybox optionally supports */ |
256 | /* -SXvhTw GNU options, busybox optionally supports */ |
257 | /* -T WIDTH Ignored (we don't use tabs on output) */ |
258 | /* -KZ SELinux mandated options, busybox optionally supports */ |
259 | /* (coreutils 8.4 has no -K, remove it?) */ |
260 | /* -e I think we made this one up (looks similar to GNU --full-time) */ |
261 | /* We already used up all 32 bits, if we need to add more, candidates for removal: */ |
262 | /* -K, -T, -e (add --full-time instead) */ |
263 | static const char ls_options[] ALIGN1 = |
264 | "Cadil1gnsxQAk" /* 13 opts, total 13 */ |
265 | IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */ |
266 | IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */ |
267 | IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */ |
268 | IF_FEATURE_LS_RECURSIVE("R") /* 1, 24 */ |
269 | IF_SELINUX("KZ") /* 2, 26 */ |
270 | IF_FEATURE_LS_FOLLOWLINKS("LH") /* 2, 28 */ |
271 | IF_FEATURE_HUMAN_READABLE("h") /* 1, 29 */ |
272 | IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 31 */ |
273 | /* with --color, we use all 32 bits */; |
274 | enum { |
275 | //OPT_C = (1 << 0), |
276 | //OPT_a = (1 << 1), |
277 | //OPT_d = (1 << 2), |
278 | //OPT_i = (1 << 3), |
279 | //OPT_l = (1 << 4), |
280 | //OPT_1 = (1 << 5), |
281 | OPT_g = (1 << 6), |
282 | //OPT_n = (1 << 7), |
283 | //OPT_s = (1 << 8), |
284 | //OPT_x = (1 << 9), |
285 | OPT_Q = (1 << 10), |
286 | //OPT_A = (1 << 11), |
287 | //OPT_k = (1 << 12), |
288 | |
289 | OPTBIT_c = 13, |
290 | OPTBIT_e, |
291 | OPTBIT_t, |
292 | OPTBIT_u, |
293 | OPTBIT_S = OPTBIT_c + 4 * ENABLE_FEATURE_LS_TIMESTAMPS, |
294 | OPTBIT_X, /* 18 */ |
295 | OPTBIT_r, |
296 | OPTBIT_v, |
297 | OPTBIT_F = OPTBIT_S + 4 * ENABLE_FEATURE_LS_SORTFILES, |
298 | OPTBIT_p, /* 22 */ |
299 | OPTBIT_R = OPTBIT_F + 2 * ENABLE_FEATURE_LS_FILETYPES, |
300 | OPTBIT_K = OPTBIT_R + 1 * ENABLE_FEATURE_LS_RECURSIVE, |
301 | OPTBIT_Z, /* 25 */ |
302 | OPTBIT_L = OPTBIT_K + 2 * ENABLE_SELINUX, |
303 | OPTBIT_H, /* 27 */ |
304 | OPTBIT_h = OPTBIT_L + 2 * ENABLE_FEATURE_LS_FOLLOWLINKS, |
305 | OPTBIT_T = OPTBIT_h + 1 * ENABLE_FEATURE_HUMAN_READABLE, |
306 | OPTBIT_w, /* 30 */ |
307 | OPTBIT_color = OPTBIT_T + 2 * ENABLE_FEATURE_AUTOWIDTH, |
308 | |
309 | OPT_c = (1 << OPTBIT_c) * ENABLE_FEATURE_LS_TIMESTAMPS, |
310 | OPT_e = (1 << OPTBIT_e) * ENABLE_FEATURE_LS_TIMESTAMPS, |
311 | OPT_t = (1 << OPTBIT_t) * ENABLE_FEATURE_LS_TIMESTAMPS, |
312 | OPT_u = (1 << OPTBIT_u) * ENABLE_FEATURE_LS_TIMESTAMPS, |
313 | OPT_S = (1 << OPTBIT_S) * ENABLE_FEATURE_LS_SORTFILES, |
314 | OPT_X = (1 << OPTBIT_X) * ENABLE_FEATURE_LS_SORTFILES, |
315 | OPT_r = (1 << OPTBIT_r) * ENABLE_FEATURE_LS_SORTFILES, |
316 | OPT_v = (1 << OPTBIT_v) * ENABLE_FEATURE_LS_SORTFILES, |
317 | OPT_F = (1 << OPTBIT_F) * ENABLE_FEATURE_LS_FILETYPES, |
318 | OPT_p = (1 << OPTBIT_p) * ENABLE_FEATURE_LS_FILETYPES, |
319 | OPT_R = (1 << OPTBIT_R) * ENABLE_FEATURE_LS_RECURSIVE, |
320 | OPT_K = (1 << OPTBIT_K) * ENABLE_SELINUX, |
321 | OPT_Z = (1 << OPTBIT_Z) * ENABLE_SELINUX, |
322 | OPT_L = (1 << OPTBIT_L) * ENABLE_FEATURE_LS_FOLLOWLINKS, |
323 | OPT_H = (1 << OPTBIT_H) * ENABLE_FEATURE_LS_FOLLOWLINKS, |
324 | OPT_h = (1 << OPTBIT_h) * ENABLE_FEATURE_HUMAN_READABLE, |
325 | OPT_T = (1 << OPTBIT_T) * ENABLE_FEATURE_AUTOWIDTH, |
326 | OPT_w = (1 << OPTBIT_w) * ENABLE_FEATURE_AUTOWIDTH, |
327 | OPT_color = (1 << OPTBIT_color) * ENABLE_FEATURE_LS_COLOR, |
328 | }; |
329 | |
330 | /* TODO: simple toggles may be stored as OPT_xxx bits instead */ |
331 | static const uint32_t opt_flags[] = { |
332 | STYLE_COLUMNAR, /* C */ |
333 | DISP_HIDDEN | DISP_DOT, /* a */ |
334 | DISP_NOLIST, /* d */ |
335 | LIST_INO, /* i */ |
336 | LIST_LONG | STYLE_LONG, /* l */ |
337 | STYLE_SINGLE, /* 1 */ |
338 | LIST_LONG | STYLE_LONG, /* g (don't show owner) - handled via OPT_g. assumes l */ |
339 | LIST_ID_NUMERIC | LIST_LONG | STYLE_LONG, /* n (assumes l) */ |
340 | LIST_BLOCKS, /* s */ |
341 | DISP_ROWS | STYLE_COLUMNAR, /* x */ |
342 | 0, /* Q (quote filename) - handled via OPT_Q */ |
343 | DISP_HIDDEN, /* A */ |
344 | ENABLE_SELINUX * (LIST_CONTEXT|STYLE_SINGLE), /* k (ignored if !SELINUX) */ |
345 | #if ENABLE_FEATURE_LS_TIMESTAMPS |
346 | TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */ |
347 | LIST_FULLTIME, /* e */ |
348 | ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */ |
349 | TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */ |
350 | #endif |
351 | #if ENABLE_FEATURE_LS_SORTFILES |
352 | SORT_SIZE, /* S */ |
353 | SORT_EXT, /* X */ |
354 | SORT_REVERSE, /* r */ |
355 | SORT_VERSION, /* v */ |
356 | #endif |
357 | #if ENABLE_FEATURE_LS_FILETYPES |
358 | LIST_FILETYPE | LIST_CLASSIFY, /* F */ |
359 | LIST_FILETYPE, /* p */ |
360 | #endif |
361 | #if ENABLE_FEATURE_LS_RECURSIVE |
362 | DISP_RECURSIVE, /* R */ |
363 | #endif |
364 | #if ENABLE_SELINUX |
365 | LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME|STYLE_SINGLE, /* K */ |
366 | LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT|STYLE_SINGLE, /* Z */ |
367 | #endif |
368 | (1U << 31) |
369 | /* options after Z are not processed through opt_flags */ |
370 | }; |
371 | |
372 | |
373 | /* |
374 | * a directory entry and its stat info |
375 | */ |
376 | struct dnode { |
377 | const char *name; /* usually basename, but think "ls -l dir/file" */ |
378 | const char *fullname; /* full name (usable for stat etc) */ |
379 | struct dnode *dn_next; /* for linked list */ |
380 | IF_SELINUX(security_context_t sid;) |
381 | smallint fname_allocated; |
382 | |
383 | /* Used to avoid re-doing [l]stat at printout stage |
384 | * if we already collected needed data in scan stage: |
385 | */ |
386 | mode_t dn_mode_lstat; /* obtained with lstat, or 0 */ |
387 | mode_t dn_mode_stat; /* obtained with stat, or 0 */ |
388 | |
389 | // struct stat dstat; |
390 | // struct stat is huge. We don't need it in full. |
391 | // At least we don't need st_dev and st_blksize, |
392 | // but there are invisible fields as well |
393 | // (such as nanosecond-resolution timespamps) |
394 | // and padding, which we also don't want to store. |
395 | // We also can pre-parse dev_t dn_rdev (in glibc, it's huge). |
396 | // On 32-bit uclibc: dnode size went from 112 to 84 bytes. |
397 | // |
398 | /* Same names as in struct stat, but with dn_ instead of st_ pfx: */ |
399 | mode_t dn_mode; /* obtained with lstat OR stat, depending on -L etc */ |
400 | off_t dn_size; |
401 | #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES |
402 | time_t dn_atime; |
403 | time_t dn_mtime; |
404 | time_t dn_ctime; |
405 | #endif |
406 | ino_t dn_ino; |
407 | blkcnt_t dn_blocks; |
408 | nlink_t dn_nlink; |
409 | uid_t dn_uid; |
410 | gid_t dn_gid; |
411 | int dn_rdev_maj; |
412 | int dn_rdev_min; |
413 | // dev_t dn_dev; |
414 | // blksize_t dn_blksize; |
415 | }; |
416 | |
417 | struct globals { |
418 | #if ENABLE_FEATURE_LS_COLOR |
419 | smallint show_color; |
420 | # define G_show_color (G.show_color) |
421 | #else |
422 | # define G_show_color 0 |
423 | #endif |
424 | smallint exit_code; |
425 | unsigned all_fmt; |
426 | #if ENABLE_FEATURE_AUTOWIDTH |
427 | unsigned terminal_width; |
428 | # define G_terminal_width (G.terminal_width) |
429 | #else |
430 | # define G_terminal_width TERMINAL_WIDTH |
431 | #endif |
432 | #if ENABLE_FEATURE_LS_TIMESTAMPS |
433 | /* Do time() just once. Saves one syscall per file for "ls -l" */ |
434 | time_t current_time_t; |
435 | #endif |
436 | } FIX_ALIASING; |
437 | #define G (*(struct globals*)bb_common_bufsiz1) |
438 | #define INIT_G() do { \ |
439 | setup_common_bufsiz(); \ |
440 | /* we have to zero it out because of NOEXEC */ \ |
441 | memset(&G, 0, sizeof(G)); \ |
442 | IF_FEATURE_AUTOWIDTH(G_terminal_width = TERMINAL_WIDTH;) \ |
443 | IF_FEATURE_LS_TIMESTAMPS(time(&G.current_time_t);) \ |
444 | } while (0) |
445 | |
446 | |
447 | /*** Output code ***/ |
448 | |
449 | |
450 | /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket |
451 | * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file |
452 | * 3/7:multiplexed char/block device) |
453 | * and we use 0 for unknown and 15 for executables (see below) */ |
454 | #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f) |
455 | /* un fi chr - dir - blk - file - link - sock - - exe */ |
456 | #define APPCHAR(mode) ("\0""|""\0""\0""/""\0""\0""\0""\0""\0""@""\0""=""\0""\0""\0" [TYPEINDEX(mode)]) |
457 | /* 036 black foreground 050 black background |
458 | 037 red foreground 051 red background |
459 | 040 green foreground 052 green background |
460 | 041 brown foreground 053 brown background |
461 | 042 blue foreground 054 blue background |
462 | 043 magenta (purple) foreground 055 magenta background |
463 | 044 cyan (light blue) foreground 056 cyan background |
464 | 045 gray foreground 057 white background |
465 | */ |
466 | #define COLOR(mode) ( \ |
467 | /*un fi chr - dir - blk - file - link - sock - - exe */ \ |
468 | "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \ |
469 | [TYPEINDEX(mode)]) |
470 | /* Select normal (0) [actually "reset all"] or bold (1) |
471 | * (other attributes are 2:dim 4:underline 5:blink 7:reverse, |
472 | * let's use 7 for "impossible" types, just for fun) |
473 | * Note: coreutils 6.9 uses inverted red for setuid binaries. |
474 | */ |
475 | #define ATTR(mode) ( \ |
476 | /*un fi chr - dir - blk - file- link- sock- - exe */ \ |
477 | "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \ |
478 | [TYPEINDEX(mode)]) |
479 | |
480 | #if ENABLE_FEATURE_LS_COLOR |
481 | /* mode of zero is interpreted as "unknown" (stat failed) */ |
482 | static char fgcolor(mode_t mode) |
483 | { |
484 | if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) |
485 | return COLOR(0xF000); /* File is executable ... */ |
486 | return COLOR(mode); |
487 | } |
488 | static char bold(mode_t mode) |
489 | { |
490 | if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) |
491 | return ATTR(0xF000); /* File is executable ... */ |
492 | return ATTR(mode); |
493 | } |
494 | #endif |
495 | |
496 | #if ENABLE_FEATURE_LS_FILETYPES |
497 | static char append_char(mode_t mode) |
498 | { |
499 | if (!(G.all_fmt & LIST_FILETYPE)) |
500 | return '\0'; |
501 | if (S_ISDIR(mode)) |
502 | return '/'; |
503 | if (!(G.all_fmt & LIST_CLASSIFY)) |
504 | return '\0'; |
505 | if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) |
506 | return '*'; |
507 | return APPCHAR(mode); |
508 | } |
509 | #endif |
510 | |
511 | static unsigned calc_name_len(const char *name) |
512 | { |
513 | unsigned len; |
514 | uni_stat_t uni_stat; |
515 | |
516 | // TODO: quote tab as \t, etc, if -Q |
517 | name = printable_string(&uni_stat, name); |
518 | |
519 | if (!(option_mask32 & OPT_Q)) { |
520 | return uni_stat.unicode_width; |
521 | } |
522 | |
523 | len = 2 + uni_stat.unicode_width; |
524 | while (*name) { |
525 | if (*name == '"' || *name == '\\') { |
526 | len++; |
527 | } |
528 | name++; |
529 | } |
530 | return len; |
531 | } |
532 | |
533 | /* Return the number of used columns. |
534 | * Note that only STYLE_COLUMNAR uses return value. |
535 | * STYLE_SINGLE and STYLE_LONG don't care. |
536 | * coreutils 7.2 also supports: |
537 | * ls -b (--escape) = octal escapes (although it doesn't look like working) |
538 | * ls -N (--literal) = not escape at all |
539 | */ |
540 | static unsigned print_name(const char *name) |
541 | { |
542 | unsigned len; |
543 | uni_stat_t uni_stat; |
544 | |
545 | // TODO: quote tab as \t, etc, if -Q |
546 | name = printable_string(&uni_stat, name); |
547 | |
548 | if (!(option_mask32 & OPT_Q)) { |
549 | fputs(name, stdout); |
550 | return uni_stat.unicode_width; |
551 | } |
552 | |
553 | len = 2 + uni_stat.unicode_width; |
554 | putchar('"'); |
555 | while (*name) { |
556 | if (*name == '"' || *name == '\\') { |
557 | putchar('\\'); |
558 | len++; |
559 | } |
560 | putchar(*name); |
561 | name++; |
562 | } |
563 | putchar('"'); |
564 | return len; |
565 | } |
566 | |
567 | /* Return the number of used columns. |
568 | * Note that only STYLE_COLUMNAR uses return value, |
569 | * STYLE_SINGLE and STYLE_LONG don't care. |
570 | */ |
571 | static NOINLINE unsigned display_single(const struct dnode *dn) |
572 | { |
573 | unsigned column = 0; |
574 | char *lpath; |
575 | #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR |
576 | struct stat statbuf; |
577 | char append; |
578 | #endif |
579 | |
580 | #if ENABLE_FEATURE_LS_FILETYPES |
581 | append = append_char(dn->dn_mode); |
582 | #endif |
583 | |
584 | /* Do readlink early, so that if it fails, error message |
585 | * does not appear *inside* the "ls -l" line */ |
586 | lpath = NULL; |
587 | if (G.all_fmt & LIST_SYMLINK) |
588 | if (S_ISLNK(dn->dn_mode)) |
589 | lpath = xmalloc_readlink_or_warn(dn->fullname); |
590 | |
591 | if (G.all_fmt & LIST_INO) |
592 | column += printf("%7llu ", (long long) dn->dn_ino); |
593 | //TODO: -h should affect -s too: |
594 | if (G.all_fmt & LIST_BLOCKS) |
595 | column += printf("%6"OFF_FMT"u ", (off_t) (dn->dn_blocks >> 1)); |
596 | if (G.all_fmt & LIST_MODEBITS) |
597 | column += printf("%-10s ", (char *) bb_mode_string(dn->dn_mode)); |
598 | if (G.all_fmt & LIST_NLINKS) |
599 | column += printf("%4lu ", (long) dn->dn_nlink); |
600 | if (G.all_fmt & LIST_ID_NUMERIC) { |
601 | if (option_mask32 & OPT_g) |
602 | column += printf("%-8u ", (int) dn->dn_gid); |
603 | else |
604 | column += printf("%-8u %-8u ", |
605 | (int) dn->dn_uid, |
606 | (int) dn->dn_gid); |
607 | } |
608 | #if ENABLE_FEATURE_LS_USERNAME |
609 | else if (G.all_fmt & LIST_ID_NAME) { |
610 | //extend user/group names to 12 char. |
611 | //if terminal has more than 88 cols (or -w 88 is set) |
612 | if (G_terminal_width >= 88) { |
613 | #define UGLONG_FMT "%-12.12s " |
614 | if (option_mask32 & OPT_g) { |
615 | column += printf(UGLONG_FMT, |
616 | get_cached_groupname(dn->dn_gid)); |
617 | } else { |
618 | column += printf(UGLONG_FMT UGLONG_FMT, |
619 | get_cached_username(dn->dn_uid), |
620 | get_cached_groupname(dn->dn_gid)); |
621 | } |
622 | } else { |
623 | #define UGDEF_FMT "%-8.8s " |
624 | if (option_mask32 & OPT_g) { |
625 | column += printf(UGDEF_FMT, |
626 | get_cached_groupname(dn->dn_gid)); |
627 | } else { |
628 | column += printf(UGDEF_FMT UGDEF_FMT, |
629 | get_cached_username(dn->dn_uid), |
630 | get_cached_groupname(dn->dn_gid)); |
631 | } |
632 | } |
633 | } |
634 | #endif |
635 | if (G.all_fmt & LIST_SIZE) { |
636 | if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) { |
637 | column += printf("%4u, %3u ", |
638 | dn->dn_rdev_maj, |
639 | dn->dn_rdev_min); |
640 | } else { |
641 | if (option_mask32 & OPT_h) { |
642 | column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ", |
643 | /* print size, show one fractional, use suffixes */ |
644 | make_human_readable_str(dn->dn_size, 1, 0) |
645 | ); |
646 | } else { |
647 | column += printf("%9"OFF_FMT"u ", dn->dn_size); |
648 | } |
649 | } |
650 | } |
651 | #if ENABLE_FEATURE_LS_TIMESTAMPS |
652 | if (G.all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) { |
653 | char *filetime; |
654 | const time_t *ttime = &dn->dn_mtime; |
655 | if (G.all_fmt & TIME_ACCESS) |
656 | ttime = &dn->dn_atime; |
657 | if (G.all_fmt & TIME_CHANGE) |
658 | ttime = &dn->dn_ctime; |
659 | filetime = ctime(ttime); |
660 | /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */ |
661 | if (G.all_fmt & LIST_FULLTIME) { /* -e */ |
662 | /* Note: coreutils 8.4 ls --full-time prints: |
663 | * 2009-07-13 17:49:27.000000000 +0200 |
664 | */ |
665 | column += printf("%.24s ", filetime); |
666 | } else { /* LIST_DATE_TIME */ |
667 | /* G.current_time_t ~== time(NULL) */ |
668 | time_t age = G.current_time_t - *ttime; |
669 | if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) { |
670 | /* less than 6 months old */ |
671 | /* "mmm dd hh:mm " */ |
672 | printf("%.12s ", filetime + 4); |
673 | } else { |
674 | /* "mmm dd yyyy " */ |
675 | /* "mmm dd yyyyy " after year 9999 :) */ |
676 | strchr(filetime + 20, '\n')[0] = ' '; |
677 | printf("%.7s%6s", filetime + 4, filetime + 20); |
678 | } |
679 | column += 13; |
680 | } |
681 | } |
682 | #endif |
683 | #if ENABLE_SELINUX |
684 | if (G.all_fmt & LIST_CONTEXT) { |
685 | column += printf("%-32s ", dn->sid ? dn->sid : "unknown"); |
686 | freecon(dn->sid); |
687 | } |
688 | #endif |
689 | |
690 | #if ENABLE_FEATURE_LS_COLOR |
691 | if (G_show_color) { |
692 | mode_t mode = dn->dn_mode_lstat; |
693 | if (!mode) |
694 | if (lstat(dn->fullname, &statbuf) == 0) |
695 | mode = statbuf.st_mode; |
696 | printf("\033[%u;%um", bold(mode), fgcolor(mode)); |
697 | } |
698 | #endif |
699 | column += print_name(dn->name); |
700 | if (G_show_color) { |
701 | printf("\033[0m"); |
702 | } |
703 | |
704 | if (lpath) { |
705 | printf(" -> "); |
706 | #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR |
707 | if ((G.all_fmt & LIST_FILETYPE) || G_show_color) { |
708 | mode_t mode = dn->dn_mode_stat; |
709 | if (!mode) |
710 | if (stat(dn->fullname, &statbuf) == 0) |
711 | mode = statbuf.st_mode; |
712 | # if ENABLE_FEATURE_LS_FILETYPES |
713 | append = append_char(mode); |
714 | # endif |
715 | # if ENABLE_FEATURE_LS_COLOR |
716 | if (G_show_color) { |
717 | printf("\033[%u;%um", bold(mode), fgcolor(mode)); |
718 | } |
719 | # endif |
720 | } |
721 | #endif |
722 | column += print_name(lpath) + 4; |
723 | free(lpath); |
724 | if (G_show_color) { |
725 | printf("\033[0m"); |
726 | } |
727 | } |
728 | #if ENABLE_FEATURE_LS_FILETYPES |
729 | if (G.all_fmt & LIST_FILETYPE) { |
730 | if (append) { |
731 | putchar(append); |
732 | column++; |
733 | } |
734 | } |
735 | #endif |
736 | |
737 | return column; |
738 | } |
739 | |
740 | static void display_files(struct dnode **dn, unsigned nfiles) |
741 | { |
742 | unsigned i, ncols, nrows, row, nc; |
743 | unsigned column; |
744 | unsigned nexttab; |
745 | unsigned column_width = 0; /* used only by STYLE_COLUMNAR */ |
746 | |
747 | if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */ |
748 | ncols = 1; |
749 | } else { |
750 | /* find the longest file name, use that as the column width */ |
751 | for (i = 0; dn[i]; i++) { |
752 | int len = calc_name_len(dn[i]->name); |
753 | if ((int)column_width < len) |
754 | column_width = len; |
755 | } |
756 | column_width += 2 + |
757 | IF_SELINUX( ((G.all_fmt & LIST_CONTEXT) ? 33 : 0) + ) |
758 | ((G.all_fmt & LIST_INO) ? 8 : 0) + |
759 | ((G.all_fmt & LIST_BLOCKS) ? 5 : 0); |
760 | ncols = (unsigned)G_terminal_width / column_width; |
761 | } |
762 | |
763 | if (ncols > 1) { |
764 | nrows = nfiles / ncols; |
765 | if (nrows * ncols < nfiles) |
766 | nrows++; /* round up fractionals */ |
767 | } else { |
768 | nrows = nfiles; |
769 | ncols = 1; |
770 | } |
771 | |
772 | column = 0; |
773 | nexttab = 0; |
774 | for (row = 0; row < nrows; row++) { |
775 | for (nc = 0; nc < ncols; nc++) { |
776 | /* reach into the array based on the column and row */ |
777 | if (G.all_fmt & DISP_ROWS) |
778 | i = (row * ncols) + nc; /* display across row */ |
779 | else |
780 | i = (nc * nrows) + row; /* display by column */ |
781 | if (i < nfiles) { |
782 | if (column > 0) { |
783 | nexttab -= column; |
784 | printf("%*s", nexttab, ""); |
785 | column += nexttab; |
786 | } |
787 | nexttab = column + column_width; |
788 | column += display_single(dn[i]); |
789 | } |
790 | } |
791 | putchar('\n'); |
792 | column = 0; |
793 | } |
794 | } |
795 | |
796 | |
797 | /*** Dir scanning code ***/ |
798 | |
799 | static struct dnode *my_stat(const char *fullname, const char *name, int force_follow) |
800 | { |
801 | struct stat statbuf; |
802 | struct dnode *cur; |
803 | |
804 | cur = xzalloc(sizeof(*cur)); |
805 | cur->fullname = fullname; |
806 | cur->name = name; |
807 | |
808 | if ((option_mask32 & OPT_L) || force_follow) { |
809 | #if ENABLE_SELINUX |
810 | if (is_selinux_enabled()) { |
811 | getfilecon(fullname, &cur->sid); |
812 | } |
813 | #endif |
814 | if (stat(fullname, &statbuf)) { |
815 | bb_simple_perror_msg(fullname); |
816 | G.exit_code = EXIT_FAILURE; |
817 | free(cur); |
818 | return NULL; |
819 | } |
820 | cur->dn_mode_stat = statbuf.st_mode; |
821 | } else { |
822 | #if ENABLE_SELINUX |
823 | if (is_selinux_enabled()) { |
824 | lgetfilecon(fullname, &cur->sid); |
825 | } |
826 | #endif |
827 | if (lstat(fullname, &statbuf)) { |
828 | bb_simple_perror_msg(fullname); |
829 | G.exit_code = EXIT_FAILURE; |
830 | free(cur); |
831 | return NULL; |
832 | } |
833 | cur->dn_mode_lstat = statbuf.st_mode; |
834 | } |
835 | |
836 | /* cur->dstat = statbuf: */ |
837 | cur->dn_mode = statbuf.st_mode ; |
838 | cur->dn_size = statbuf.st_size ; |
839 | #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES |
840 | cur->dn_atime = statbuf.st_atime ; |
841 | cur->dn_mtime = statbuf.st_mtime ; |
842 | cur->dn_ctime = statbuf.st_ctime ; |
843 | #endif |
844 | cur->dn_ino = statbuf.st_ino ; |
845 | cur->dn_blocks = statbuf.st_blocks; |
846 | cur->dn_nlink = statbuf.st_nlink ; |
847 | cur->dn_uid = statbuf.st_uid ; |
848 | cur->dn_gid = statbuf.st_gid ; |
849 | cur->dn_rdev_maj = major(statbuf.st_rdev); |
850 | cur->dn_rdev_min = minor(statbuf.st_rdev); |
851 | |
852 | return cur; |
853 | } |
854 | |
855 | static unsigned count_dirs(struct dnode **dn, int which) |
856 | { |
857 | unsigned dirs, all; |
858 | |
859 | if (!dn) |
860 | return 0; |
861 | |
862 | dirs = all = 0; |
863 | for (; *dn; dn++) { |
864 | const char *name; |
865 | |
866 | all++; |
867 | if (!S_ISDIR((*dn)->dn_mode)) |
868 | continue; |
869 | |
870 | name = (*dn)->name; |
871 | if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */ |
872 | /* or if it's not . or .. */ |
873 | || name[0] != '.' |
874 | || (name[1] && (name[1] != '.' || name[2])) |
875 | ) { |
876 | dirs++; |
877 | } |
878 | } |
879 | return which != SPLIT_FILE ? dirs : all - dirs; |
880 | } |
881 | |
882 | /* get memory to hold an array of pointers */ |
883 | static struct dnode **dnalloc(unsigned num) |
884 | { |
885 | if (num < 1) |
886 | return NULL; |
887 | |
888 | num++; /* so that we have terminating NULL */ |
889 | return xzalloc(num * sizeof(struct dnode *)); |
890 | } |
891 | |
892 | #if ENABLE_FEATURE_LS_RECURSIVE |
893 | static void dfree(struct dnode **dnp) |
894 | { |
895 | unsigned i; |
896 | |
897 | if (dnp == NULL) |
898 | return; |
899 | |
900 | for (i = 0; dnp[i]; i++) { |
901 | struct dnode *cur = dnp[i]; |
902 | if (cur->fname_allocated) |
903 | free((char*)cur->fullname); |
904 | free(cur); |
905 | } |
906 | free(dnp); |
907 | } |
908 | #else |
909 | #define dfree(...) ((void)0) |
910 | #endif |
911 | |
912 | /* Returns NULL-terminated malloced vector of pointers (or NULL) */ |
913 | static struct dnode **splitdnarray(struct dnode **dn, int which) |
914 | { |
915 | unsigned dncnt, d; |
916 | struct dnode **dnp; |
917 | |
918 | if (dn == NULL) |
919 | return NULL; |
920 | |
921 | /* count how many dirs or files there are */ |
922 | dncnt = count_dirs(dn, which); |
923 | |
924 | /* allocate a file array and a dir array */ |
925 | dnp = dnalloc(dncnt); |
926 | |
927 | /* copy the entrys into the file or dir array */ |
928 | for (d = 0; *dn; dn++) { |
929 | if (S_ISDIR((*dn)->dn_mode)) { |
930 | const char *name; |
931 | |
932 | if (which == SPLIT_FILE) |
933 | continue; |
934 | |
935 | name = (*dn)->name; |
936 | if ((which & SPLIT_DIR) /* any dir... */ |
937 | /* ... or not . or .. */ |
938 | || name[0] != '.' |
939 | || (name[1] && (name[1] != '.' || name[2])) |
940 | ) { |
941 | dnp[d++] = *dn; |
942 | } |
943 | } else |
944 | if (which == SPLIT_FILE) { |
945 | dnp[d++] = *dn; |
946 | } |
947 | } |
948 | return dnp; |
949 | } |
950 | |
951 | #if ENABLE_FEATURE_LS_SORTFILES |
952 | static int sortcmp(const void *a, const void *b) |
953 | { |
954 | struct dnode *d1 = *(struct dnode **)a; |
955 | struct dnode *d2 = *(struct dnode **)b; |
956 | unsigned sort_opts = G.all_fmt & SORT_MASK; |
957 | off_t dif; |
958 | |
959 | dif = 0; /* assume SORT_NAME */ |
960 | // TODO: use pre-initialized function pointer |
961 | // instead of branch forest |
962 | if (sort_opts == SORT_SIZE) { |
963 | dif = (d2->dn_size - d1->dn_size); |
964 | } else |
965 | if (sort_opts == SORT_ATIME) { |
966 | dif = (d2->dn_atime - d1->dn_atime); |
967 | } else |
968 | if (sort_opts == SORT_CTIME) { |
969 | dif = (d2->dn_ctime - d1->dn_ctime); |
970 | } else |
971 | if (sort_opts == SORT_MTIME) { |
972 | dif = (d2->dn_mtime - d1->dn_mtime); |
973 | } else |
974 | if (sort_opts == SORT_DIR) { |
975 | dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode); |
976 | } else |
977 | #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1 |
978 | if (sort_opts == SORT_VERSION) { |
979 | dif = strverscmp(d1->name, d2->name); |
980 | } else |
981 | #endif |
982 | if (sort_opts == SORT_EXT) { |
983 | dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.')); |
984 | } |
985 | if (dif == 0) { |
986 | /* sort by name, use as tie breaker for other sorts */ |
987 | if (ENABLE_LOCALE_SUPPORT) |
988 | dif = strcoll(d1->name, d2->name); |
989 | else |
990 | dif = strcmp(d1->name, d2->name); |
991 | } |
992 | |
993 | /* Make dif fit into an int */ |
994 | if (sizeof(dif) > sizeof(int)) { |
995 | enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) }; |
996 | /* shift leaving only "int" worth of bits */ |
997 | if (dif != 0) { |
998 | dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT); |
999 | } |
1000 | } |
1001 | |
1002 | return (G.all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif; |
1003 | } |
1004 | |
1005 | static void dnsort(struct dnode **dn, int size) |
1006 | { |
1007 | qsort(dn, size, sizeof(*dn), sortcmp); |
1008 | } |
1009 | |
1010 | static void sort_and_display_files(struct dnode **dn, unsigned nfiles) |
1011 | { |
1012 | dnsort(dn, nfiles); |
1013 | display_files(dn, nfiles); |
1014 | } |
1015 | #else |
1016 | # define dnsort(dn, size) ((void)0) |
1017 | # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles) |
1018 | #endif |
1019 | |
1020 | /* Returns NULL-terminated malloced vector of pointers (or NULL) */ |
1021 | static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p) |
1022 | { |
1023 | struct dnode *dn, *cur, **dnp; |
1024 | struct dirent *entry; |
1025 | DIR *dir; |
1026 | unsigned i, nfiles; |
1027 | |
1028 | *nfiles_p = 0; |
1029 | dir = warn_opendir(path); |
1030 | if (dir == NULL) { |
1031 | G.exit_code = EXIT_FAILURE; |
1032 | return NULL; /* could not open the dir */ |
1033 | } |
1034 | dn = NULL; |
1035 | nfiles = 0; |
1036 | while ((entry = readdir(dir)) != NULL) { |
1037 | char *fullname; |
1038 | |
1039 | /* are we going to list the file- it may be . or .. or a hidden file */ |
1040 | if (entry->d_name[0] == '.') { |
1041 | if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2])) |
1042 | && !(G.all_fmt & DISP_DOT) |
1043 | ) { |
1044 | continue; |
1045 | } |
1046 | if (!(G.all_fmt & DISP_HIDDEN)) |
1047 | continue; |
1048 | } |
1049 | fullname = concat_path_file(path, entry->d_name); |
1050 | cur = my_stat(fullname, bb_basename(fullname), 0); |
1051 | if (!cur) { |
1052 | free(fullname); |
1053 | continue; |
1054 | } |
1055 | cur->fname_allocated = 1; |
1056 | cur->dn_next = dn; |
1057 | dn = cur; |
1058 | nfiles++; |
1059 | } |
1060 | closedir(dir); |
1061 | |
1062 | if (dn == NULL) |
1063 | return NULL; |
1064 | |
1065 | /* now that we know how many files there are |
1066 | * allocate memory for an array to hold dnode pointers |
1067 | */ |
1068 | *nfiles_p = nfiles; |
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->dn_next; |
1073 | if (!dn) |
1074 | break; |
1075 | } |
1076 | |
1077 | return dnp; |
1078 | } |
1079 | |
1080 | #if ENABLE_DESKTOP |
1081 | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html |
1082 | * If any of the -l, -n, -s options is specified, each list |
1083 | * of files within the directory shall be preceded by a |
1084 | * status line indicating the number of file system blocks |
1085 | * occupied by files in the directory in 512-byte units if |
1086 | * the -k option is not specified, or 1024-byte units if the |
1087 | * -k option is specified, rounded up to the next integral |
1088 | * number of units. |
1089 | */ |
1090 | /* by Jorgen Overgaard (jorgen AT antistaten.se) */ |
1091 | static off_t calculate_blocks(struct dnode **dn) |
1092 | { |
1093 | uoff_t blocks = 1; |
1094 | if (dn) { |
1095 | while (*dn) { |
1096 | /* st_blocks is in 512 byte blocks */ |
1097 | blocks += (*dn)->dn_blocks; |
1098 | dn++; |
1099 | } |
1100 | } |
1101 | |
1102 | /* Even though standard says use 512 byte blocks, coreutils use 1k */ |
1103 | /* Actually, we round up by calculating (blocks + 1) / 2, |
1104 | * "+ 1" was done when we initialized blocks to 1 */ |
1105 | return blocks >> 1; |
1106 | } |
1107 | #endif |
1108 | |
1109 | static void scan_and_display_dirs_recur(struct dnode **dn, int first) |
1110 | { |
1111 | unsigned nfiles; |
1112 | struct dnode **subdnp; |
1113 | |
1114 | for (; *dn; dn++) { |
1115 | if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) { |
1116 | if (!first) |
1117 | bb_putchar('\n'); |
1118 | first = 0; |
1119 | printf("%s:\n", (*dn)->fullname); |
1120 | } |
1121 | subdnp = scan_one_dir((*dn)->fullname, &nfiles); |
1122 | #if ENABLE_DESKTOP |
1123 | if ((G.all_fmt & STYLE_MASK) == STYLE_LONG || (G.all_fmt & LIST_BLOCKS)) |
1124 | printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp)); |
1125 | #endif |
1126 | if (nfiles > 0) { |
1127 | /* list all files at this level */ |
1128 | sort_and_display_files(subdnp, nfiles); |
1129 | |
1130 | if (ENABLE_FEATURE_LS_RECURSIVE |
1131 | && (G.all_fmt & DISP_RECURSIVE) |
1132 | ) { |
1133 | struct dnode **dnd; |
1134 | unsigned dndirs; |
1135 | /* recursive - list the sub-dirs */ |
1136 | dnd = splitdnarray(subdnp, SPLIT_SUBDIR); |
1137 | dndirs = count_dirs(subdnp, SPLIT_SUBDIR); |
1138 | if (dndirs > 0) { |
1139 | dnsort(dnd, dndirs); |
1140 | scan_and_display_dirs_recur(dnd, 0); |
1141 | /* free the array of dnode pointers to the dirs */ |
1142 | free(dnd); |
1143 | } |
1144 | } |
1145 | /* free the dnodes and the fullname mem */ |
1146 | dfree(subdnp); |
1147 | } |
1148 | } |
1149 | } |
1150 | |
1151 | |
1152 | int ls_main(int argc UNUSED_PARAM, char **argv) |
1153 | { |
1154 | struct dnode **dnd; |
1155 | struct dnode **dnf; |
1156 | struct dnode **dnp; |
1157 | struct dnode *dn; |
1158 | struct dnode *cur; |
1159 | unsigned opt; |
1160 | unsigned nfiles; |
1161 | unsigned dnfiles; |
1162 | unsigned dndirs; |
1163 | unsigned i; |
1164 | #if ENABLE_FEATURE_LS_COLOR |
1165 | /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */ |
1166 | /* coreutils 6.10: |
1167 | * # ls --color=BOGUS |
1168 | * ls: invalid argument 'BOGUS' for '--color' |
1169 | * Valid arguments are: |
1170 | * 'always', 'yes', 'force' |
1171 | * 'never', 'no', 'none' |
1172 | * 'auto', 'tty', 'if-tty' |
1173 | * (and substrings: "--color=alwa" work too) |
1174 | */ |
1175 | static const char ls_longopts[] ALIGN1 = |
1176 | "color\0" Optional_argument "\xff"; /* no short equivalent */ |
1177 | static const char color_str[] ALIGN1 = |
1178 | "always\0""yes\0""force\0" |
1179 | "auto\0""tty\0""if-tty\0"; |
1180 | /* need to initialize since --color has _an optional_ argument */ |
1181 | const char *color_opt = color_str; /* "always" */ |
1182 | #endif |
1183 | |
1184 | INIT_G(); |
1185 | |
1186 | init_unicode(); |
1187 | |
1188 | if (ENABLE_FEATURE_LS_SORTFILES) |
1189 | G.all_fmt = SORT_NAME; |
1190 | |
1191 | #if ENABLE_FEATURE_AUTOWIDTH |
1192 | /* obtain the terminal width */ |
1193 | G_terminal_width = get_terminal_width(STDIN_FILENO); |
1194 | /* go one less... */ |
1195 | G_terminal_width--; |
1196 | #endif |
1197 | |
1198 | /* process options */ |
1199 | IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;) |
1200 | opt_complementary = |
1201 | /* -e implies -l */ |
1202 | IF_FEATURE_LS_TIMESTAMPS("el") |
1203 | /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html: |
1204 | * in some pairs of opts, only last one takes effect: |
1205 | */ |
1206 | IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */ |
1207 | // ":m-l:l-m" - we don't have -m |
1208 | IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H") |
1209 | ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */ |
1210 | ":C-1:1-C" /* bycols/oneline */ |
1211 | ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */ |
1212 | IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */ |
1213 | /* -w NUM: */ |
1214 | IF_FEATURE_AUTOWIDTH(":w+"); |
1215 | opt = getopt32(argv, ls_options |
1216 | IF_FEATURE_AUTOWIDTH(, NULL, &G_terminal_width) |
1217 | IF_FEATURE_LS_COLOR(, &color_opt) |
1218 | ); |
1219 | for (i = 0; opt_flags[i] != (1U << 31); i++) { |
1220 | if (opt & (1 << i)) { |
1221 | uint32_t flags = opt_flags[i]; |
1222 | |
1223 | if (flags & STYLE_MASK) |
1224 | G.all_fmt &= ~STYLE_MASK; |
1225 | if (flags & SORT_MASK) |
1226 | G.all_fmt &= ~SORT_MASK; |
1227 | if (flags & TIME_MASK) |
1228 | G.all_fmt &= ~TIME_MASK; |
1229 | |
1230 | G.all_fmt |= flags; |
1231 | } |
1232 | } |
1233 | |
1234 | #if ENABLE_FEATURE_LS_COLOR |
1235 | /* set G_show_color = 1/0 */ |
1236 | if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) { |
1237 | char *p = getenv("LS_COLORS"); |
1238 | /* LS_COLORS is unset, or (not empty && not "none") ? */ |
1239 | if (!p || (p[0] && strcmp(p, "none") != 0)) |
1240 | G_show_color = 1; |
1241 | } |
1242 | if (opt & OPT_color) { |
1243 | if (color_opt[0] == 'n') |
1244 | G_show_color = 0; |
1245 | else switch (index_in_substrings(color_str, color_opt)) { |
1246 | case 3: |
1247 | case 4: |
1248 | case 5: |
1249 | if (isatty(STDOUT_FILENO)) { |
1250 | case 0: |
1251 | case 1: |
1252 | case 2: |
1253 | G_show_color = 1; |
1254 | } |
1255 | } |
1256 | } |
1257 | #endif |
1258 | |
1259 | /* sort out which command line options take precedence */ |
1260 | if (ENABLE_FEATURE_LS_RECURSIVE && (G.all_fmt & DISP_NOLIST)) |
1261 | G.all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */ |
1262 | if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) { |
1263 | if (G.all_fmt & TIME_CHANGE) |
1264 | G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_CTIME; |
1265 | if (G.all_fmt & TIME_ACCESS) |
1266 | G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_ATIME; |
1267 | } |
1268 | if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */ |
1269 | G.all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME); |
1270 | |
1271 | /* choose a display format if one was not already specified by an option */ |
1272 | if (!(G.all_fmt & STYLE_MASK)) |
1273 | G.all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE); |
1274 | |
1275 | argv += optind; |
1276 | if (!argv[0]) |
1277 | *--argv = (char*)"."; |
1278 | |
1279 | if (argv[1]) |
1280 | G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */ |
1281 | |
1282 | /* stuff the command line file names into a dnode array */ |
1283 | dn = NULL; |
1284 | nfiles = 0; |
1285 | do { |
1286 | cur = my_stat(*argv, *argv, |
1287 | /* follow links on command line unless -l, -s or -F: */ |
1288 | !((G.all_fmt & STYLE_MASK) == STYLE_LONG |
1289 | || (G.all_fmt & LIST_BLOCKS) |
1290 | || (option_mask32 & OPT_F) |
1291 | ) |
1292 | /* ... or if -H: */ |
1293 | || (option_mask32 & OPT_H) |
1294 | /* ... or if -L, but my_stat always follows links if -L */ |
1295 | ); |
1296 | argv++; |
1297 | if (!cur) |
1298 | continue; |
1299 | /*cur->fname_allocated = 0; - already is */ |
1300 | cur->dn_next = dn; |
1301 | dn = cur; |
1302 | nfiles++; |
1303 | } while (*argv); |
1304 | |
1305 | /* nfiles _may_ be 0 here - try "ls doesnt_exist" */ |
1306 | if (nfiles == 0) |
1307 | return G.exit_code; |
1308 | |
1309 | /* now that we know how many files there are |
1310 | * allocate memory for an array to hold dnode pointers |
1311 | */ |
1312 | dnp = dnalloc(nfiles); |
1313 | for (i = 0; /* i < nfiles - detected via !dn below */; i++) { |
1314 | dnp[i] = dn; /* save pointer to node in array */ |
1315 | dn = dn->dn_next; |
1316 | if (!dn) |
1317 | break; |
1318 | } |
1319 | |
1320 | if (G.all_fmt & DISP_NOLIST) { |
1321 | sort_and_display_files(dnp, nfiles); |
1322 | } else { |
1323 | dnd = splitdnarray(dnp, SPLIT_DIR); |
1324 | dnf = splitdnarray(dnp, SPLIT_FILE); |
1325 | dndirs = count_dirs(dnp, SPLIT_DIR); |
1326 | dnfiles = nfiles - dndirs; |
1327 | if (dnfiles > 0) { |
1328 | sort_and_display_files(dnf, dnfiles); |
1329 | if (ENABLE_FEATURE_CLEAN_UP) |
1330 | free(dnf); |
1331 | } |
1332 | if (dndirs > 0) { |
1333 | dnsort(dnd, dndirs); |
1334 | scan_and_display_dirs_recur(dnd, dnfiles == 0); |
1335 | if (ENABLE_FEATURE_CLEAN_UP) |
1336 | free(dnd); |
1337 | } |
1338 | } |
1339 | |
1340 | if (ENABLE_FEATURE_CLEAN_UP) |
1341 | dfree(dnp); |
1342 | return G.exit_code; |
1343 | } |
1344 |