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