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