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) */
fgcolor(mode_t mode)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 }
bold(mode_t mode)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
append_char(mode_t mode)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
calc_name_len(const char * name)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 */
print_name(const char * name)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 */
display_single(const struct dnode * dn)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 if (option_mask32 & OPT_g) {
611 column += printf("%-8.8s ",
612 get_cached_groupname(dn->dn_gid));
613 } else {
614 column += printf("%-8.8s %-8.8s ",
615 get_cached_username(dn->dn_uid),
616 get_cached_groupname(dn->dn_gid));
617 }
618 }
619 #endif
620 if (G.all_fmt & LIST_SIZE) {
621 if (S_ISBLK(dn->dn_mode) || S_ISCHR(dn->dn_mode)) {
622 column += printf("%4u, %3u ",
623 dn->dn_rdev_maj,
624 dn->dn_rdev_min);
625 } else {
626 if (option_mask32 & OPT_h) {
627 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
628 /* print size, show one fractional, use suffixes */
629 make_human_readable_str(dn->dn_size, 1, 0)
630 );
631 } else {
632 column += printf("%9"OFF_FMT"u ", dn->dn_size);
633 }
634 }
635 }
636 #if ENABLE_FEATURE_LS_TIMESTAMPS
637 if (G.all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
638 char *filetime;
639 const time_t *ttime = &dn->dn_mtime;
640 if (G.all_fmt & TIME_ACCESS)
641 ttime = &dn->dn_atime;
642 if (G.all_fmt & TIME_CHANGE)
643 ttime = &dn->dn_ctime;
644 filetime = ctime(ttime);
645 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
646 if (G.all_fmt & LIST_FULLTIME) { /* -e */
647 /* Note: coreutils 8.4 ls --full-time prints:
648 * 2009-07-13 17:49:27.000000000 +0200
649 */
650 column += printf("%.24s ", filetime);
651 } else { /* LIST_DATE_TIME */
652 /* G.current_time_t ~== time(NULL) */
653 time_t age = G.current_time_t - *ttime;
654 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
655 /* less than 6 months old */
656 /* "mmm dd hh:mm " */
657 printf("%.12s ", filetime + 4);
658 } else {
659 /* "mmm dd yyyy " */
660 /* "mmm dd yyyyy " after year 9999 :) */
661 strchr(filetime + 20, '\n')[0] = ' ';
662 printf("%.7s%6s", filetime + 4, filetime + 20);
663 }
664 column += 13;
665 }
666 }
667 #endif
668 #if ENABLE_SELINUX
669 if (G.all_fmt & LIST_CONTEXT) {
670 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
671 freecon(dn->sid);
672 }
673 #endif
674
675 #if ENABLE_FEATURE_LS_COLOR
676 if (G_show_color) {
677 mode_t mode = dn->dn_mode_lstat;
678 if (!mode)
679 if (lstat(dn->fullname, &statbuf) == 0)
680 mode = statbuf.st_mode;
681 printf("\033[%u;%um", bold(mode), fgcolor(mode));
682 }
683 #endif
684 column += print_name(dn->name);
685 if (G_show_color) {
686 printf("\033[0m");
687 }
688
689 if (lpath) {
690 printf(" -> ");
691 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
692 if ((G.all_fmt & LIST_FILETYPE) || G_show_color) {
693 mode_t mode = dn->dn_mode_stat;
694 if (!mode)
695 if (stat(dn->fullname, &statbuf) == 0)
696 mode = statbuf.st_mode;
697 # if ENABLE_FEATURE_LS_FILETYPES
698 append = append_char(mode);
699 # endif
700 # if ENABLE_FEATURE_LS_COLOR
701 if (G_show_color) {
702 printf("\033[%u;%um", bold(mode), fgcolor(mode));
703 }
704 # endif
705 }
706 #endif
707 column += print_name(lpath) + 4;
708 free(lpath);
709 if (G_show_color) {
710 printf("\033[0m");
711 }
712 }
713 #if ENABLE_FEATURE_LS_FILETYPES
714 if (G.all_fmt & LIST_FILETYPE) {
715 if (append) {
716 putchar(append);
717 column++;
718 }
719 }
720 #endif
721
722 return column;
723 }
724
display_files(struct dnode ** dn,unsigned nfiles)725 static void display_files(struct dnode **dn, unsigned nfiles)
726 {
727 unsigned i, ncols, nrows, row, nc;
728 unsigned column;
729 unsigned nexttab;
730 unsigned column_width = 0; /* used only by STYLE_COLUMNAR */
731
732 if (G.all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
733 ncols = 1;
734 } else {
735 /* find the longest file name, use that as the column width */
736 for (i = 0; dn[i]; i++) {
737 int len = calc_name_len(dn[i]->name);
738 if (column_width < len)
739 column_width = len;
740 }
741 column_width += 2 +
742 IF_SELINUX( ((G.all_fmt & LIST_CONTEXT) ? 33 : 0) + )
743 ((G.all_fmt & LIST_INO) ? 8 : 0) +
744 ((G.all_fmt & LIST_BLOCKS) ? 5 : 0);
745 ncols = (unsigned)G_terminal_width / column_width;
746 }
747
748 if (ncols > 1) {
749 nrows = nfiles / ncols;
750 if (nrows * ncols < nfiles)
751 nrows++; /* round up fractionals */
752 } else {
753 nrows = nfiles;
754 ncols = 1;
755 }
756
757 column = 0;
758 nexttab = 0;
759 for (row = 0; row < nrows; row++) {
760 for (nc = 0; nc < ncols; nc++) {
761 /* reach into the array based on the column and row */
762 if (G.all_fmt & DISP_ROWS)
763 i = (row * ncols) + nc; /* display across row */
764 else
765 i = (nc * nrows) + row; /* display by column */
766 if (i < nfiles) {
767 if (column > 0) {
768 nexttab -= column;
769 printf("%*s", nexttab, "");
770 column += nexttab;
771 }
772 nexttab = column + column_width;
773 column += display_single(dn[i]);
774 }
775 }
776 putchar('\n');
777 column = 0;
778 }
779 }
780
781
782 /*** Dir scanning code ***/
783
my_stat(const char * fullname,const char * name,int force_follow)784 static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
785 {
786 struct stat statbuf;
787 struct dnode *cur;
788
789 cur = xzalloc(sizeof(*cur));
790 cur->fullname = fullname;
791 cur->name = name;
792
793 if ((option_mask32 & OPT_L) || force_follow) {
794 #if ENABLE_SELINUX
795 if (is_selinux_enabled()) {
796 getfilecon(fullname, &cur->sid);
797 }
798 #endif
799 if (stat(fullname, &statbuf)) {
800 bb_simple_perror_msg(fullname);
801 G.exit_code = EXIT_FAILURE;
802 free(cur);
803 return NULL;
804 }
805 cur->dn_mode_stat = statbuf.st_mode;
806 } else {
807 #if ENABLE_SELINUX
808 if (is_selinux_enabled()) {
809 lgetfilecon(fullname, &cur->sid);
810 }
811 #endif
812 if (lstat(fullname, &statbuf)) {
813 bb_simple_perror_msg(fullname);
814 G.exit_code = EXIT_FAILURE;
815 free(cur);
816 return NULL;
817 }
818 cur->dn_mode_lstat = statbuf.st_mode;
819 }
820
821 /* cur->dstat = statbuf: */
822 cur->dn_mode = statbuf.st_mode ;
823 cur->dn_size = statbuf.st_size ;
824 #if ENABLE_FEATURE_LS_TIMESTAMPS || ENABLE_FEATURE_LS_SORTFILES
825 cur->dn_atime = statbuf.st_atime ;
826 cur->dn_mtime = statbuf.st_mtime ;
827 cur->dn_ctime = statbuf.st_ctime ;
828 #endif
829 cur->dn_ino = statbuf.st_ino ;
830 cur->dn_blocks = statbuf.st_blocks;
831 cur->dn_nlink = statbuf.st_nlink ;
832 cur->dn_uid = statbuf.st_uid ;
833 cur->dn_gid = statbuf.st_gid ;
834 cur->dn_rdev_maj = major(statbuf.st_rdev);
835 cur->dn_rdev_min = minor(statbuf.st_rdev);
836
837 return cur;
838 }
839
count_dirs(struct dnode ** dn,int which)840 static unsigned count_dirs(struct dnode **dn, int which)
841 {
842 unsigned dirs, all;
843
844 if (!dn)
845 return 0;
846
847 dirs = all = 0;
848 for (; *dn; dn++) {
849 const char *name;
850
851 all++;
852 if (!S_ISDIR((*dn)->dn_mode))
853 continue;
854
855 name = (*dn)->name;
856 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
857 /* or if it's not . or .. */
858 || name[0] != '.'
859 || (name[1] && (name[1] != '.' || name[2]))
860 ) {
861 dirs++;
862 }
863 }
864 return which != SPLIT_FILE ? dirs : all - dirs;
865 }
866
867 /* get memory to hold an array of pointers */
dnalloc(unsigned num)868 static struct dnode **dnalloc(unsigned num)
869 {
870 if (num < 1)
871 return NULL;
872
873 num++; /* so that we have terminating NULL */
874 return xzalloc(num * sizeof(struct dnode *));
875 }
876
877 #if ENABLE_FEATURE_LS_RECURSIVE
dfree(struct dnode ** dnp)878 static void dfree(struct dnode **dnp)
879 {
880 unsigned i;
881
882 if (dnp == NULL)
883 return;
884
885 for (i = 0; dnp[i]; i++) {
886 struct dnode *cur = dnp[i];
887 if (cur->fname_allocated)
888 free((char*)cur->fullname);
889 free(cur);
890 }
891 free(dnp);
892 }
893 #else
894 #define dfree(...) ((void)0)
895 #endif
896
897 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
splitdnarray(struct dnode ** dn,int which)898 static struct dnode **splitdnarray(struct dnode **dn, int which)
899 {
900 unsigned dncnt, d;
901 struct dnode **dnp;
902
903 if (dn == NULL)
904 return NULL;
905
906 /* count how many dirs or files there are */
907 dncnt = count_dirs(dn, which);
908
909 /* allocate a file array and a dir array */
910 dnp = dnalloc(dncnt);
911
912 /* copy the entrys into the file or dir array */
913 for (d = 0; *dn; dn++) {
914 if (S_ISDIR((*dn)->dn_mode)) {
915 const char *name;
916
917 if (which == SPLIT_FILE)
918 continue;
919
920 name = (*dn)->name;
921 if ((which & SPLIT_DIR) /* any dir... */
922 /* ... or not . or .. */
923 || name[0] != '.'
924 || (name[1] && (name[1] != '.' || name[2]))
925 ) {
926 dnp[d++] = *dn;
927 }
928 } else
929 if (which == SPLIT_FILE) {
930 dnp[d++] = *dn;
931 }
932 }
933 return dnp;
934 }
935
936 #if ENABLE_FEATURE_LS_SORTFILES
sortcmp(const void * a,const void * b)937 static int sortcmp(const void *a, const void *b)
938 {
939 struct dnode *d1 = *(struct dnode **)a;
940 struct dnode *d2 = *(struct dnode **)b;
941 unsigned sort_opts = G.all_fmt & SORT_MASK;
942 off_t dif;
943
944 dif = 0; /* assume SORT_NAME */
945 // TODO: use pre-initialized function pointer
946 // instead of branch forest
947 if (sort_opts == SORT_SIZE) {
948 dif = (d2->dn_size - d1->dn_size);
949 } else
950 if (sort_opts == SORT_ATIME) {
951 dif = (d2->dn_atime - d1->dn_atime);
952 } else
953 if (sort_opts == SORT_CTIME) {
954 dif = (d2->dn_ctime - d1->dn_ctime);
955 } else
956 if (sort_opts == SORT_MTIME) {
957 dif = (d2->dn_mtime - d1->dn_mtime);
958 } else
959 if (sort_opts == SORT_DIR) {
960 dif = S_ISDIR(d2->dn_mode) - S_ISDIR(d1->dn_mode);
961 } else
962 #if defined(HAVE_STRVERSCMP) && HAVE_STRVERSCMP == 1
963 if (sort_opts == SORT_VERSION) {
964 dif = strverscmp(d1->name, d2->name);
965 } else
966 #endif
967 if (sort_opts == SORT_EXT) {
968 dif = strcmp(strchrnul(d1->name, '.'), strchrnul(d2->name, '.'));
969 }
970 if (dif == 0) {
971 /* sort by name, use as tie breaker for other sorts */
972 if (ENABLE_LOCALE_SUPPORT)
973 dif = strcoll(d1->name, d2->name);
974 else
975 dif = strcmp(d1->name, d2->name);
976 }
977
978 /* Make dif fit into an int */
979 if (sizeof(dif) > sizeof(int)) {
980 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
981 /* shift leaving only "int" worth of bits */
982 if (dif != 0) {
983 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
984 }
985 }
986
987 return (G.all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
988 }
989
dnsort(struct dnode ** dn,int size)990 static void dnsort(struct dnode **dn, int size)
991 {
992 qsort(dn, size, sizeof(*dn), sortcmp);
993 }
994
sort_and_display_files(struct dnode ** dn,unsigned nfiles)995 static void sort_and_display_files(struct dnode **dn, unsigned nfiles)
996 {
997 dnsort(dn, nfiles);
998 display_files(dn, nfiles);
999 }
1000 #else
1001 # define dnsort(dn, size) ((void)0)
1002 # define sort_and_display_files(dn, nfiles) display_files(dn, nfiles)
1003 #endif
1004
1005 /* Returns NULL-terminated malloced vector of pointers (or NULL) */
scan_one_dir(const char * path,unsigned * nfiles_p)1006 static struct dnode **scan_one_dir(const char *path, unsigned *nfiles_p)
1007 {
1008 struct dnode *dn, *cur, **dnp;
1009 struct dirent *entry;
1010 DIR *dir;
1011 unsigned i, nfiles;
1012
1013 *nfiles_p = 0;
1014 dir = warn_opendir(path);
1015 if (dir == NULL) {
1016 G.exit_code = EXIT_FAILURE;
1017 return NULL; /* could not open the dir */
1018 }
1019 dn = NULL;
1020 nfiles = 0;
1021 while ((entry = readdir(dir)) != NULL) {
1022 char *fullname;
1023
1024 /* are we going to list the file- it may be . or .. or a hidden file */
1025 if (entry->d_name[0] == '.') {
1026 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
1027 && !(G.all_fmt & DISP_DOT)
1028 ) {
1029 continue;
1030 }
1031 if (!(G.all_fmt & DISP_HIDDEN))
1032 continue;
1033 }
1034 fullname = concat_path_file(path, entry->d_name);
1035 cur = my_stat(fullname, bb_basename(fullname), 0);
1036 if (!cur) {
1037 free(fullname);
1038 continue;
1039 }
1040 cur->fname_allocated = 1;
1041 cur->dn_next = dn;
1042 dn = cur;
1043 nfiles++;
1044 }
1045 closedir(dir);
1046
1047 if (dn == NULL)
1048 return NULL;
1049
1050 /* now that we know how many files there are
1051 * allocate memory for an array to hold dnode pointers
1052 */
1053 *nfiles_p = nfiles;
1054 dnp = dnalloc(nfiles);
1055 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1056 dnp[i] = dn; /* save pointer to node in array */
1057 dn = dn->dn_next;
1058 if (!dn)
1059 break;
1060 }
1061
1062 return dnp;
1063 }
1064
1065 #if ENABLE_DESKTOP
1066 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
1067 * If any of the -l, -n, -s options is specified, each list
1068 * of files within the directory shall be preceded by a
1069 * status line indicating the number of file system blocks
1070 * occupied by files in the directory in 512-byte units if
1071 * the -k option is not specified, or 1024-byte units if the
1072 * -k option is specified, rounded up to the next integral
1073 * number of units.
1074 */
1075 /* by Jorgen Overgaard (jorgen AT antistaten.se) */
calculate_blocks(struct dnode ** dn)1076 static off_t calculate_blocks(struct dnode **dn)
1077 {
1078 uoff_t blocks = 1;
1079 if (dn) {
1080 while (*dn) {
1081 /* st_blocks is in 512 byte blocks */
1082 blocks += (*dn)->dn_blocks;
1083 dn++;
1084 }
1085 }
1086
1087 /* Even though standard says use 512 byte blocks, coreutils use 1k */
1088 /* Actually, we round up by calculating (blocks + 1) / 2,
1089 * "+ 1" was done when we initialized blocks to 1 */
1090 return blocks >> 1;
1091 }
1092 #endif
1093
scan_and_display_dirs_recur(struct dnode ** dn,int first)1094 static void scan_and_display_dirs_recur(struct dnode **dn, int first)
1095 {
1096 unsigned nfiles;
1097 struct dnode **subdnp;
1098
1099 for (; *dn; dn++) {
1100 if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
1101 if (!first)
1102 bb_putchar('\n');
1103 first = 0;
1104 printf("%s:\n", (*dn)->fullname);
1105 }
1106 subdnp = scan_one_dir((*dn)->fullname, &nfiles);
1107 #if ENABLE_DESKTOP
1108 if ((G.all_fmt & STYLE_MASK) == STYLE_LONG || (G.all_fmt & LIST_BLOCKS))
1109 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
1110 #endif
1111 if (nfiles > 0) {
1112 /* list all files at this level */
1113 sort_and_display_files(subdnp, nfiles);
1114
1115 if (ENABLE_FEATURE_LS_RECURSIVE
1116 && (G.all_fmt & DISP_RECURSIVE)
1117 ) {
1118 struct dnode **dnd;
1119 unsigned dndirs;
1120 /* recursive - list the sub-dirs */
1121 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
1122 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
1123 if (dndirs > 0) {
1124 dnsort(dnd, dndirs);
1125 scan_and_display_dirs_recur(dnd, 0);
1126 /* free the array of dnode pointers to the dirs */
1127 free(dnd);
1128 }
1129 }
1130 /* free the dnodes and the fullname mem */
1131 dfree(subdnp);
1132 }
1133 }
1134 }
1135
1136
ls_main(int argc UNUSED_PARAM,char ** argv)1137 int ls_main(int argc UNUSED_PARAM, char **argv)
1138 {
1139 struct dnode **dnd;
1140 struct dnode **dnf;
1141 struct dnode **dnp;
1142 struct dnode *dn;
1143 struct dnode *cur;
1144 unsigned opt;
1145 unsigned nfiles;
1146 unsigned dnfiles;
1147 unsigned dndirs;
1148 unsigned i;
1149 #if ENABLE_FEATURE_LS_COLOR
1150 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
1151 /* coreutils 6.10:
1152 * # ls --color=BOGUS
1153 * ls: invalid argument 'BOGUS' for '--color'
1154 * Valid arguments are:
1155 * 'always', 'yes', 'force'
1156 * 'never', 'no', 'none'
1157 * 'auto', 'tty', 'if-tty'
1158 * (and substrings: "--color=alwa" work too)
1159 */
1160 static const char ls_longopts[] ALIGN1 =
1161 "color\0" Optional_argument "\xff"; /* no short equivalent */
1162 static const char color_str[] ALIGN1 =
1163 "always\0""yes\0""force\0"
1164 "auto\0""tty\0""if-tty\0";
1165 /* need to initialize since --color has _an optional_ argument */
1166 const char *color_opt = color_str; /* "always" */
1167 #endif
1168
1169 INIT_G();
1170
1171 init_unicode();
1172
1173 if (ENABLE_FEATURE_LS_SORTFILES)
1174 G.all_fmt = SORT_NAME;
1175
1176 #if ENABLE_FEATURE_AUTOWIDTH
1177 /* obtain the terminal width */
1178 G_terminal_width = get_terminal_width(STDIN_FILENO);
1179 /* go one less... */
1180 G_terminal_width--;
1181 #endif
1182
1183 /* process options */
1184 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
1185 opt_complementary =
1186 /* -e implies -l */
1187 IF_FEATURE_LS_TIMESTAMPS("el")
1188 /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html:
1189 * in some pairs of opts, only last one takes effect:
1190 */
1191 IF_FEATURE_LS_TIMESTAMPS(IF_FEATURE_LS_SORTFILES(":t-S:S-t")) /* time/size */
1192 // ":m-l:l-m" - we don't have -m
1193 IF_FEATURE_LS_FOLLOWLINKS(":H-L:L-H")
1194 ":C-xl:x-Cl:l-xC" /* bycols/bylines/long */
1195 ":C-1:1-C" /* bycols/oneline */
1196 ":x-1:1-x" /* bylines/oneline (not in SuS, but in GNU coreutils 8.4) */
1197 IF_FEATURE_LS_TIMESTAMPS(":c-u:u-c") /* mtime/atime */
1198 /* -w NUM: */
1199 IF_FEATURE_AUTOWIDTH(":w+");
1200 opt = getopt32(argv, ls_options
1201 IF_FEATURE_AUTOWIDTH(, NULL, &G_terminal_width)
1202 IF_FEATURE_LS_COLOR(, &color_opt)
1203 );
1204 for (i = 0; opt_flags[i] != (1U << 31); i++) {
1205 if (opt & (1 << i)) {
1206 uint32_t flags = opt_flags[i];
1207
1208 if (flags & STYLE_MASK)
1209 G.all_fmt &= ~STYLE_MASK;
1210 if (flags & SORT_MASK)
1211 G.all_fmt &= ~SORT_MASK;
1212 if (flags & TIME_MASK)
1213 G.all_fmt &= ~TIME_MASK;
1214
1215 G.all_fmt |= flags;
1216 }
1217 }
1218
1219 #if ENABLE_FEATURE_LS_COLOR
1220 /* set G_show_color = 1/0 */
1221 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1222 char *p = getenv("LS_COLORS");
1223 /* LS_COLORS is unset, or (not empty && not "none") ? */
1224 if (!p || (p[0] && strcmp(p, "none") != 0))
1225 G_show_color = 1;
1226 }
1227 if (opt & OPT_color) {
1228 if (color_opt[0] == 'n')
1229 G_show_color = 0;
1230 else switch (index_in_substrings(color_str, color_opt)) {
1231 case 3:
1232 case 4:
1233 case 5:
1234 if (isatty(STDOUT_FILENO)) {
1235 case 0:
1236 case 1:
1237 case 2:
1238 G_show_color = 1;
1239 }
1240 }
1241 }
1242 #endif
1243
1244 /* sort out which command line options take precedence */
1245 if (ENABLE_FEATURE_LS_RECURSIVE && (G.all_fmt & DISP_NOLIST))
1246 G.all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
1247 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1248 if (G.all_fmt & TIME_CHANGE)
1249 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_CTIME;
1250 if (G.all_fmt & TIME_ACCESS)
1251 G.all_fmt = (G.all_fmt & ~SORT_MASK) | SORT_ATIME;
1252 }
1253 if ((G.all_fmt & STYLE_MASK) != STYLE_LONG) /* not -l? */
1254 G.all_fmt &= ~(LIST_ID_NUMERIC|LIST_ID_NAME|LIST_FULLTIME);
1255
1256 /* choose a display format if one was not already specified by an option */
1257 if (!(G.all_fmt & STYLE_MASK))
1258 G.all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNAR : STYLE_SINGLE);
1259
1260 argv += optind;
1261 if (!argv[0])
1262 *--argv = (char*)".";
1263
1264 if (argv[1])
1265 G.all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
1266
1267 /* stuff the command line file names into a dnode array */
1268 dn = NULL;
1269 nfiles = 0;
1270 do {
1271 cur = my_stat(*argv, *argv,
1272 /* follow links on command line unless -l, -s or -F: */
1273 !((G.all_fmt & STYLE_MASK) == STYLE_LONG
1274 || (G.all_fmt & LIST_BLOCKS)
1275 || (option_mask32 & OPT_F)
1276 )
1277 /* ... or if -H: */
1278 || (option_mask32 & OPT_H)
1279 /* ... or if -L, but my_stat always follows links if -L */
1280 );
1281 argv++;
1282 if (!cur)
1283 continue;
1284 /*cur->fname_allocated = 0; - already is */
1285 cur->dn_next = dn;
1286 dn = cur;
1287 nfiles++;
1288 } while (*argv);
1289
1290 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1291 if (nfiles == 0)
1292 return G.exit_code;
1293
1294 /* now that we know how many files there are
1295 * allocate memory for an array to hold dnode pointers
1296 */
1297 dnp = dnalloc(nfiles);
1298 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1299 dnp[i] = dn; /* save pointer to node in array */
1300 dn = dn->dn_next;
1301 if (!dn)
1302 break;
1303 }
1304
1305 if (G.all_fmt & DISP_NOLIST) {
1306 sort_and_display_files(dnp, nfiles);
1307 } else {
1308 dnd = splitdnarray(dnp, SPLIT_DIR);
1309 dnf = splitdnarray(dnp, SPLIT_FILE);
1310 dndirs = count_dirs(dnp, SPLIT_DIR);
1311 dnfiles = nfiles - dndirs;
1312 if (dnfiles > 0) {
1313 sort_and_display_files(dnf, dnfiles);
1314 if (ENABLE_FEATURE_CLEAN_UP)
1315 free(dnf);
1316 }
1317 if (dndirs > 0) {
1318 dnsort(dnd, dndirs);
1319 scan_and_display_dirs_recur(dnd, dnfiles == 0);
1320 if (ENABLE_FEATURE_CLEAN_UP)
1321 free(dnd);
1322 }
1323 }
1324
1325 if (ENABLE_FEATURE_CLEAN_UP)
1326 dfree(dnp);
1327 return G.exit_code;
1328 }
1329