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