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