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