1 /* vi: set sw=4 ts=4: */
3 * tiny-ls.c version 0.1.0: A minimalist 'ls'
4 * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
9 /* [date unknown. Perhaps before year 2000]
10 * To achieve a small memory footprint, this version of 'ls' doesn't do any
11 * file sorting, and only has the most essential command line switches
12 * (i.e., the ones I couldn't live without :-) All features which involve
13 * linking in substantial chunks of libc can be disabled.
15 * Although I don't really want to add new features to this program to
16 * keep it small, I *am* interested to receive bug fixes and ways to make
20 * 1. ls -l of a directory doesn't give "total <blocks>" header
21 * 2. hidden files can make column width too large
23 * NON-OPTIMAL BEHAVIOUR:
24 * 1. autowidth reads directories twice
25 * 2. if you do a short directory listing without filetype characters
26 * appended, there's no need to stat each one
28 * 1. requires lstat (BSD) - how do you do it without?
31 * ls sorts listing now, and supports almost all options.
36 #if ENABLE_FEATURE_ASSUME_UNICODE
40 /* This is a NOEXEC applet. Be very careful! */
44 /* ftpd uses ls, and without timestamps Mozilla won't understand
47 # undef CONFIG_FEATURE_LS_TIMESTAMPS
48 # undef ENABLE_FEATURE_LS_TIMESTAMPS
49 # undef USE_FEATURE_LS_TIMESTAMPS
50 # undef SKIP_FEATURE_LS_TIMESTAMPS
51 # define CONFIG_FEATURE_LS_TIMESTAMPS 1
52 # define ENABLE_FEATURE_LS_TIMESTAMPS 1
53 # define USE_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
54 # define SKIP_FEATURE_LS_TIMESTAMPS(...)
60 TERMINAL_WIDTH
= 80, /* use 79 if terminal has linefold bug */
61 COLUMN_GAP
= 2, /* includes the file type char */
63 /* what is the overall style of the listing */
64 STYLE_COLUMNS
= 1 << 21, /* fill columns */
65 STYLE_LONG
= 2 << 21, /* one record per line, extended info */
66 STYLE_SINGLE
= 3 << 21, /* one record per line */
67 STYLE_MASK
= STYLE_SINGLE
,
69 /* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
70 /* what file information will be listed */
73 LIST_MODEBITS
= 1 << 2,
75 LIST_ID_NAME
= 1 << 4,
76 LIST_ID_NUMERIC
= 1 << 5,
77 LIST_CONTEXT
= 1 << 6,
79 //LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE
80 LIST_DATE_TIME
= 1 << 9,
81 LIST_FULLTIME
= 1 << 10,
82 LIST_FILENAME
= 1 << 11,
83 LIST_SYMLINK
= 1 << 12,
84 LIST_FILETYPE
= 1 << 13,
86 LIST_MASK
= (LIST_EXEC
<< 1) - 1,
88 /* what files will be displayed */
89 DISP_DIRNAME
= 1 << 15, /* 2 or more items? label directories */
90 DISP_HIDDEN
= 1 << 16, /* show filenames starting with . */
91 DISP_DOT
= 1 << 17, /* show . and .. */
92 DISP_NOLIST
= 1 << 18, /* show directory as itself, not contents */
93 DISP_RECURSIVE
= 1 << 19, /* show directory and everything below it */
94 DISP_ROWS
= 1 << 20, /* print across rows */
95 DISP_MASK
= ((DISP_ROWS
<< 1) - 1) & ~(DISP_DIRNAME
- 1),
97 /* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
98 SORT_FORWARD
= 0, /* sort in reverse order */
99 SORT_REVERSE
= 1 << 27, /* sort in reverse order */
101 SORT_NAME
= 0, /* sort by file name */
102 SORT_SIZE
= 1 << 28, /* sort by file size */
103 SORT_ATIME
= 2 << 28, /* sort by last access time */
104 SORT_CTIME
= 3 << 28, /* sort by last change time */
105 SORT_MTIME
= 4 << 28, /* sort by last modification time */
106 SORT_VERSION
= 5 << 28, /* sort by version */
107 SORT_EXT
= 6 << 28, /* sort by file name extension */
108 SORT_DIR
= 7 << 28, /* sort by file or directory */
109 SORT_MASK
= (7 << 28) * ENABLE_FEATURE_LS_SORTFILES
,
111 /* which of the three times will be used */
112 TIME_CHANGE
= (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS
,
113 TIME_ACCESS
= (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS
,
114 TIME_MASK
= (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS
,
116 FOLLOW_LINKS
= (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS
,
118 LS_DISP_HR
= (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE
,
120 LIST_SHORT
= LIST_FILENAME
,
121 LIST_LONG
= LIST_MODEBITS
| LIST_NLINKS
| LIST_ID_NAME
| LIST_SIZE
| \
122 LIST_DATE_TIME
| LIST_FILENAME
| LIST_SYMLINK
,
130 /* "[-]Cadil1", POSIX mandated options, busybox always supports */
131 /* "[-]gnsx", POSIX non-mandated options, busybox always supports */
132 /* "[-]Q" GNU option? busybox always supports */
133 /* "[-]Ak" GNU options, busybox always supports */
134 /* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
135 /* "[-]p", POSIX non-mandated options, busybox optionally supports */
136 /* "[-]SXvThw", GNU options, busybox optionally supports */
137 /* "[-]K", SELinux mandated options, busybox optionally supports */
138 /* "[-]e", I think we made this one up */
139 static const char ls_options
[] ALIGN1
=
140 "Cadil1gnsxQAk" /* 13 opts, total 13 */
141 USE_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
142 USE_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
143 USE_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
144 USE_FEATURE_LS_FOLLOWLINKS("L") /* 1, 24 */
145 USE_FEATURE_LS_RECURSIVE("R") /* 1, 25 */
146 USE_FEATURE_HUMAN_READABLE("h") /* 1, 26 */
147 USE_SELINUX("KZ") /* 2, 28 */
148 USE_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
165 + 4 * ENABLE_FEATURE_LS_TIMESTAMPS
166 + 4 * ENABLE_FEATURE_LS_SORTFILES
167 + 2 * ENABLE_FEATURE_LS_FILETYPES
168 + 1 * ENABLE_FEATURE_LS_FOLLOWLINKS
169 + 1 * ENABLE_FEATURE_LS_RECURSIVE
170 + 1 * ENABLE_FEATURE_HUMAN_READABLE
172 + 2 * ENABLE_FEATURE_AUTOWIDTH
,
173 OPT_color
= 1 << OPTBIT_color
,
177 LIST_MASK_TRIGGER
= 0,
178 STYLE_MASK_TRIGGER
= STYLE_MASK
,
179 DISP_MASK_TRIGGER
= DISP_ROWS
,
180 SORT_MASK_TRIGGER
= SORT_MASK
,
183 /* TODO: simple toggles may be stored as OPT_xxx bits instead */
184 static const unsigned opt_flags
[] = {
185 LIST_SHORT
| STYLE_COLUMNS
, /* C */
186 DISP_HIDDEN
| DISP_DOT
, /* a */
189 LIST_LONG
| STYLE_LONG
, /* l - remember LS_DISP_HR in mask! */
190 LIST_SHORT
| STYLE_SINGLE
, /* 1 */
191 0, /* g (don't show group) - handled via OPT_g */
192 LIST_ID_NUMERIC
, /* n */
195 0, /* Q (quote filename) - handled via OPT_Q */
197 ENABLE_SELINUX
* LIST_CONTEXT
, /* k (ignored if !SELINUX) */
198 #if ENABLE_FEATURE_LS_TIMESTAMPS
199 TIME_CHANGE
| (ENABLE_FEATURE_LS_SORTFILES
* SORT_CTIME
), /* c */
200 LIST_FULLTIME
, /* e */
201 ENABLE_FEATURE_LS_SORTFILES
* SORT_MTIME
, /* t */
202 TIME_ACCESS
| (ENABLE_FEATURE_LS_SORTFILES
* SORT_ATIME
), /* u */
204 #if ENABLE_FEATURE_LS_SORTFILES
207 SORT_REVERSE
, /* r */
208 SORT_VERSION
, /* v */
210 #if ENABLE_FEATURE_LS_FILETYPES
211 LIST_FILETYPE
| LIST_EXEC
, /* F */
212 LIST_FILETYPE
, /* p */
214 #if ENABLE_FEATURE_LS_FOLLOWLINKS
215 FOLLOW_LINKS
, /* L */
217 #if ENABLE_FEATURE_LS_RECURSIVE
218 DISP_RECURSIVE
, /* R */
220 #if ENABLE_FEATURE_HUMAN_READABLE
224 LIST_MODEBITS
|LIST_NLINKS
|LIST_CONTEXT
|LIST_SIZE
|LIST_DATE_TIME
, /* K */
227 LIST_MODEBITS
|LIST_ID_NAME
|LIST_CONTEXT
, /* Z */
230 /* options after Z are not processed through opt_flags:
237 * a directory entry and its stat info are stored here
239 struct dnode
{ /* the basic node */
240 const char *name
; /* the dir entry name */
241 const char *fullname
; /* the dir entry name */
243 struct stat dstat
; /* the file stat info */
244 USE_SELINUX(security_context_t sid
;)
245 struct dnode
*next
; /* point at the next node */
248 static struct dnode
**list_dir(const char *);
249 static struct dnode
**dnalloc(int);
250 static int list_single(const struct dnode
*);
254 #if ENABLE_FEATURE_LS_COLOR
259 #if ENABLE_FEATURE_AUTOWIDTH
260 unsigned tabstops
; // = COLUMN_GAP;
261 unsigned terminal_width
; // = TERMINAL_WIDTH;
263 #if ENABLE_FEATURE_LS_TIMESTAMPS
264 /* Do time() just once. Saves one syscall per file for "ls -l" */
265 time_t current_time_t
;
268 #define G (*(struct globals*)&bb_common_bufsiz1)
269 #if ENABLE_FEATURE_LS_COLOR
270 #define show_color (G.show_color )
272 enum { show_color
= 0 };
274 #define exit_code (G.exit_code )
275 #define all_fmt (G.all_fmt )
276 #if ENABLE_FEATURE_AUTOWIDTH
277 #define tabstops (G.tabstops )
278 #define terminal_width (G.terminal_width)
281 tabstops
= COLUMN_GAP
,
282 terminal_width
= TERMINAL_WIDTH
,
285 #define current_time_t (G.current_time_t)
286 /* memset: we have to zero it out because of NOEXEC */
287 #define INIT_G() do { \
288 memset(&G, 0, sizeof(G)); \
289 USE_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
290 USE_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
291 USE_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \
295 #if ENABLE_FEATURE_ASSUME_UNICODE
296 /* libbb candidate */
297 static size_t mbstrlen(const char *string
)
299 size_t width
= mbsrtowcs(NULL
/*dest*/, &string
,
300 MAXINT(size_t) /*len*/, NULL
/*state*/);
301 if (width
== (size_t)-1)
302 return strlen(string
);
306 #define mbstrlen(string) strlen(string)
310 static struct dnode
*my_stat(const char *fullname
, const char *name
, int force_follow
)
314 USE_SELINUX(security_context_t sid
= NULL
;)
316 if ((all_fmt
& FOLLOW_LINKS
) || force_follow
) {
318 if (is_selinux_enabled()) {
319 getfilecon(fullname
, &sid
);
322 if (stat(fullname
, &dstat
)) {
323 bb_simple_perror_msg(fullname
);
324 exit_code
= EXIT_FAILURE
;
329 if (is_selinux_enabled()) {
330 lgetfilecon(fullname
, &sid
);
333 if (lstat(fullname
, &dstat
)) {
334 bb_simple_perror_msg(fullname
);
335 exit_code
= EXIT_FAILURE
;
340 cur
= xmalloc(sizeof(struct dnode
));
341 cur
->fullname
= fullname
;
344 USE_SELINUX(cur
->sid
= sid
;)
349 /* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
350 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
351 * 3/7:multiplexed char/block device)
352 * and we use 0 for unknown and 15 for executables (see below) */
353 #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
354 #define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
355 #define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
356 /* 036 black foreground 050 black background
357 037 red foreground 051 red background
358 040 green foreground 052 green background
359 041 brown foreground 053 brown background
360 042 blue foreground 054 blue background
361 043 magenta (purple) foreground 055 magenta background
362 044 cyan (light blue) foreground 056 cyan background
363 045 gray foreground 057 white background
365 #define COLOR(mode) ( \
366 /*un fi chr dir blk file link sock exe */ \
367 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
369 /* Select normal (0) [actually "reset all"] or bold (1)
370 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
371 * let's use 7 for "impossible" types, just for fun)
372 * Note: coreutils 6.9 uses inverted red for setuid binaries.
374 #define ATTR(mode) ( \
375 /*un fi chr dir blk file link sock exe */ \
376 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
379 #if ENABLE_FEATURE_LS_COLOR
380 /* mode of zero is interpreted as "unknown" (stat failed) */
381 static char fgcolor(mode_t mode
)
383 if (S_ISREG(mode
) && (mode
& (S_IXUSR
| S_IXGRP
| S_IXOTH
)))
384 return COLOR(0xF000); /* File is executable ... */
387 static char bold(mode_t mode
)
389 if (S_ISREG(mode
) && (mode
& (S_IXUSR
| S_IXGRP
| S_IXOTH
)))
390 return ATTR(0xF000); /* File is executable ... */
395 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
396 static char append_char(mode_t mode
)
398 if (!(all_fmt
& LIST_FILETYPE
))
402 if (!(all_fmt
& LIST_EXEC
))
404 if (S_ISREG(mode
) && (mode
& (S_IXUSR
| S_IXGRP
| S_IXOTH
)))
406 return APPCHAR(mode
);
411 #define countdirs(A, B) count_dirs((A), (B), 1)
412 #define countsubdirs(A, B) count_dirs((A), (B), 0)
413 static int count_dirs(struct dnode
**dn
, int nfiles
, int notsubdirs
)
420 for (i
= 0; i
< nfiles
; i
++) {
422 if (!S_ISDIR(dn
[i
]->dstat
.st_mode
))
426 || name
[0]!='.' || (name
[1] && (name
[1]!='.' || name
[2]))
434 static int countfiles(struct dnode
**dnp
)
442 for (cur
= dnp
[0]; cur
->next
; cur
= cur
->next
)
448 /* get memory to hold an array of pointers */
449 static struct dnode
**dnalloc(int num
)
454 return xzalloc(num
* sizeof(struct dnode
*));
457 #if ENABLE_FEATURE_LS_RECURSIVE
458 static void dfree(struct dnode
**dnp
, int nfiles
)
465 for (i
= 0; i
< nfiles
; i
++) {
466 struct dnode
*cur
= dnp
[i
];
468 free((char*)cur
->fullname
); /* free the filename */
469 free(cur
); /* free the dnode */
471 free(dnp
); /* free the array holding the dnode pointers */
474 #define dfree(...) ((void)0)
477 static struct dnode
**splitdnarray(struct dnode
**dn
, int nfiles
, int which
)
482 if (dn
== NULL
|| nfiles
< 1)
485 /* count how many dirs and regular files there are */
486 if (which
== SPLIT_SUBDIR
)
487 dncnt
= countsubdirs(dn
, nfiles
);
489 dncnt
= countdirs(dn
, nfiles
); /* assume we are looking for dirs */
490 if (which
== SPLIT_FILE
)
491 dncnt
= nfiles
- dncnt
; /* looking for files */
494 /* allocate a file array and a dir array */
495 dnp
= dnalloc(dncnt
);
497 /* copy the entrys into the file or dir array */
498 for (d
= i
= 0; i
< nfiles
; i
++) {
499 if (S_ISDIR(dn
[i
]->dstat
.st_mode
)) {
501 if (!(which
& (SPLIT_DIR
|SPLIT_SUBDIR
)))
504 if ((which
& SPLIT_DIR
)
505 || name
[0]!='.' || (name
[1] && (name
[1]!='.' || name
[2]))
509 } else if (!(which
& (SPLIT_DIR
|SPLIT_SUBDIR
))) {
516 #if ENABLE_FEATURE_LS_SORTFILES
517 static int sortcmp(const void *a
, const void *b
)
519 struct dnode
*d1
= *(struct dnode
**)a
;
520 struct dnode
*d2
= *(struct dnode
**)b
;
521 unsigned sort_opts
= all_fmt
& SORT_MASK
;
524 dif
= 0; /* assume SORT_NAME */
525 // TODO: use pre-initialized function pointer
526 // instead of branch forest
527 if (sort_opts
== SORT_SIZE
) {
528 dif
= (int) (d2
->dstat
.st_size
- d1
->dstat
.st_size
);
529 } else if (sort_opts
== SORT_ATIME
) {
530 dif
= (int) (d2
->dstat
.st_atime
- d1
->dstat
.st_atime
);
531 } else if (sort_opts
== SORT_CTIME
) {
532 dif
= (int) (d2
->dstat
.st_ctime
- d1
->dstat
.st_ctime
);
533 } else if (sort_opts
== SORT_MTIME
) {
534 dif
= (int) (d2
->dstat
.st_mtime
- d1
->dstat
.st_mtime
);
535 } else if (sort_opts
== SORT_DIR
) {
536 dif
= S_ISDIR(d2
->dstat
.st_mode
) - S_ISDIR(d1
->dstat
.st_mode
);
537 /* } else if (sort_opts == SORT_VERSION) { */
538 /* } else if (sort_opts == SORT_EXT) { */
542 /* sort by name - may be a tie_breaker for time or size cmp */
543 if (ENABLE_LOCALE_SUPPORT
) dif
= strcoll(d1
->name
, d2
->name
);
544 else dif
= strcmp(d1
->name
, d2
->name
);
547 if (all_fmt
& SORT_REVERSE
) {
553 static void dnsort(struct dnode
**dn
, int size
)
555 qsort(dn
, size
, sizeof(*dn
), sortcmp
);
558 #define dnsort(dn, size) ((void)0)
562 static void showfiles(struct dnode
**dn
, int nfiles
)
564 int i
, ncols
, nrows
, row
, nc
;
567 int column_width
= 0; /* for STYLE_LONG and STYLE_SINGLE not used */
569 if (dn
== NULL
|| nfiles
< 1)
572 if (all_fmt
& STYLE_LONG
) {
575 /* find the longest file name, use that as the column width */
576 for (i
= 0; i
< nfiles
; i
++) {
577 int len
= mbstrlen(dn
[i
]->name
);
578 if (column_width
< len
)
581 column_width
+= tabstops
+
582 USE_SELINUX( ((all_fmt
& LIST_CONTEXT
) ? 33 : 0) + )
583 ((all_fmt
& LIST_INO
) ? 8 : 0) +
584 ((all_fmt
& LIST_BLOCKS
) ? 5 : 0);
585 ncols
= (int) (terminal_width
/ column_width
);
589 nrows
= nfiles
/ ncols
;
590 if (nrows
* ncols
< nfiles
)
591 nrows
++; /* round up fractionals */
597 for (row
= 0; row
< nrows
; row
++) {
598 for (nc
= 0; nc
< ncols
; nc
++) {
599 /* reach into the array based on the column and row */
600 i
= (nc
* nrows
) + row
; /* assume display by column */
601 if (all_fmt
& DISP_ROWS
)
602 i
= (row
* ncols
) + nc
; /* display across row */
606 printf("%*s", nexttab
, "");
609 nexttab
= column
+ column_width
;
610 column
+= list_single(dn
[i
]);
619 static void showdirs(struct dnode
**dn
, int ndirs
, int first
)
622 struct dnode
**subdnp
;
626 if (dn
== NULL
|| ndirs
< 1)
629 for (i
= 0; i
< ndirs
; i
++) {
630 if (all_fmt
& (DISP_DIRNAME
| DISP_RECURSIVE
)) {
634 printf("%s:\n", dn
[i
]->fullname
);
636 subdnp
= list_dir(dn
[i
]->fullname
);
637 nfiles
= countfiles(subdnp
);
639 /* list all files at this level */
640 dnsort(subdnp
, nfiles
);
641 showfiles(subdnp
, nfiles
);
642 if (ENABLE_FEATURE_LS_RECURSIVE
) {
643 if (all_fmt
& DISP_RECURSIVE
) {
644 /* recursive- list the sub-dirs */
645 dnd
= splitdnarray(subdnp
, nfiles
, SPLIT_SUBDIR
);
646 dndirs
= countsubdirs(subdnp
, nfiles
);
649 showdirs(dnd
, dndirs
, 0);
650 /* free the array of dnode pointers to the dirs */
654 /* free the dnodes and the fullname mem */
655 dfree(subdnp
, nfiles
);
662 static struct dnode
**list_dir(const char *path
)
664 struct dnode
*dn
, *cur
, **dnp
;
665 struct dirent
*entry
;
674 dir
= warn_opendir(path
);
676 exit_code
= EXIT_FAILURE
;
677 return NULL
; /* could not open the dir */
679 while ((entry
= readdir(dir
)) != NULL
) {
682 /* are we going to list the file- it may be . or .. or a hidden file */
683 if (entry
->d_name
[0] == '.') {
684 if ((!entry
->d_name
[1] || (entry
->d_name
[1] == '.' && !entry
->d_name
[2]))
685 && !(all_fmt
& DISP_DOT
)
689 if (!(all_fmt
& DISP_HIDDEN
))
692 fullname
= concat_path_file(path
, entry
->d_name
);
693 cur
= my_stat(fullname
, bb_basename(fullname
), 0);
705 /* now that we know how many files there are
706 * allocate memory for an array to hold dnode pointers
710 dnp
= dnalloc(nfiles
);
711 for (i
= 0, cur
= dn
; i
< nfiles
; i
++) {
712 dnp
[i
] = cur
; /* save pointer to node in array */
720 static int print_name(const char *name
)
722 if (option_mask32
& OPT_Q
) {
723 #if ENABLE_FEATURE_ASSUME_UNICODE
724 int len
= 2 + mbstrlen(name
);
735 if (!ENABLE_FEATURE_ASSUME_UNICODE
)
742 #if ENABLE_FEATURE_ASSUME_UNICODE
744 return mbstrlen(name
);
746 return printf("%s", name
);
751 static int list_single(const struct dnode
*dn
)
754 char *lpath
= lpath
; /* for compiler */
755 #if ENABLE_FEATURE_LS_TIMESTAMPS
759 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
764 if (dn
->fullname
== NULL
)
767 #if ENABLE_FEATURE_LS_TIMESTAMPS
768 ttime
= dn
->dstat
.st_mtime
; /* the default time */
769 if (all_fmt
& TIME_ACCESS
)
770 ttime
= dn
->dstat
.st_atime
;
771 if (all_fmt
& TIME_CHANGE
)
772 ttime
= dn
->dstat
.st_ctime
;
773 filetime
= ctime(&ttime
);
775 #if ENABLE_FEATURE_LS_FILETYPES
776 append
= append_char(dn
->dstat
.st_mode
);
779 /* Do readlink early, so that if it fails, error message
780 * does not appear *inside* of the "ls -l" line */
781 if (all_fmt
& LIST_SYMLINK
)
782 if (S_ISLNK(dn
->dstat
.st_mode
))
783 lpath
= xmalloc_readlink_or_warn(dn
->fullname
);
785 if (all_fmt
& LIST_INO
)
786 column
+= printf("%7lu ", (long) dn
->dstat
.st_ino
);
787 if (all_fmt
& LIST_BLOCKS
)
788 column
+= printf("%4"OFF_FMT
"u ", (off_t
) dn
->dstat
.st_blocks
>> 1);
789 if (all_fmt
& LIST_MODEBITS
)
790 column
+= printf("%-10s ", (char *) bb_mode_string(dn
->dstat
.st_mode
));
791 if (all_fmt
& LIST_NLINKS
)
792 column
+= printf("%4lu ", (long) dn
->dstat
.st_nlink
);
793 #if ENABLE_FEATURE_LS_USERNAME
794 if (all_fmt
& LIST_ID_NAME
) {
795 if (option_mask32
& OPT_g
) {
796 column
+= printf("%-8.8s",
797 get_cached_username(dn
->dstat
.st_uid
));
799 column
+= printf("%-8.8s %-8.8s",
800 get_cached_username(dn
->dstat
.st_uid
),
801 get_cached_groupname(dn
->dstat
.st_gid
));
805 if (all_fmt
& LIST_ID_NUMERIC
) {
806 if (option_mask32
& OPT_g
)
807 column
+= printf("%-8u", (int) dn
->dstat
.st_uid
);
809 column
+= printf("%-8u %-8u",
810 (int) dn
->dstat
.st_uid
,
811 (int) dn
->dstat
.st_gid
);
813 if (all_fmt
& (LIST_SIZE
/*|LIST_DEV*/ )) {
814 if (S_ISBLK(dn
->dstat
.st_mode
) || S_ISCHR(dn
->dstat
.st_mode
)) {
815 column
+= printf("%4u, %3u ",
816 (int) major(dn
->dstat
.st_rdev
),
817 (int) minor(dn
->dstat
.st_rdev
));
819 if (all_fmt
& LS_DISP_HR
) {
820 column
+= printf("%9s ",
821 make_human_readable_str(dn
->dstat
.st_size
, 1, 0));
823 column
+= printf("%9"OFF_FMT
"u ", (off_t
) dn
->dstat
.st_size
);
827 #if ENABLE_FEATURE_LS_TIMESTAMPS
828 if (all_fmt
& LIST_FULLTIME
)
829 column
+= printf("%24.24s ", filetime
);
830 if (all_fmt
& LIST_DATE_TIME
)
831 if ((all_fmt
& LIST_FULLTIME
) == 0) {
832 /* current_time_t ~== time(NULL) */
833 age
= current_time_t
- ttime
;
834 printf("%6.6s ", filetime
+ 4);
835 if (age
< 3600L * 24 * 365 / 2 && age
> -15 * 60) {
836 /* hh:mm if less than 6 months old */
837 printf("%5.5s ", filetime
+ 11);
839 printf(" %4.4s ", filetime
+ 20);
845 if (all_fmt
& LIST_CONTEXT
) {
846 column
+= printf("%-32s ", dn
->sid
? dn
->sid
: "unknown");
850 if (all_fmt
& LIST_FILENAME
) {
851 #if ENABLE_FEATURE_LS_COLOR
853 info
.st_mode
= 0; /* for fgcolor() */
854 lstat(dn
->fullname
, &info
);
855 printf("\033[%u;%um", bold(info
.st_mode
),
856 fgcolor(info
.st_mode
));
859 column
+= print_name(dn
->name
);
864 if (all_fmt
& LIST_SYMLINK
) {
865 if (S_ISLNK(dn
->dstat
.st_mode
) && lpath
) {
867 #if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
868 #if ENABLE_FEATURE_LS_COLOR
869 info
.st_mode
= 0; /* for fgcolor() */
871 if (stat(dn
->fullname
, &info
) == 0) {
872 append
= append_char(info
.st_mode
);
875 #if ENABLE_FEATURE_LS_COLOR
877 printf("\033[%u;%um", bold(info
.st_mode
),
878 fgcolor(info
.st_mode
));
881 column
+= print_name(lpath
) + 4;
888 #if ENABLE_FEATURE_LS_FILETYPES
889 if (all_fmt
& LIST_FILETYPE
) {
901 int ls_main(int argc UNUSED_PARAM
, char **argv
)
913 #if ENABLE_FEATURE_LS_COLOR
914 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
917 * ls: invalid argument 'BOGUS' for '--color'
918 * Valid arguments are:
919 * 'always', 'yes', 'force'
920 * 'never', 'no', 'none'
921 * 'auto', 'tty', 'if-tty'
922 * (and substrings: "--color=alwa" work too)
924 static const char ls_longopts
[] ALIGN1
=
925 "color\0" Optional_argument
"\xff"; /* no short equivalent */
926 static const char color_str
[] ALIGN1
=
927 "always\0""yes\0""force\0"
928 "auto\0""tty\0""if-tty\0";
929 /* need to initialize since --color has _an optional_ argument */
930 const char *color_opt
= color_str
; /* "always" */
935 all_fmt
= LIST_SHORT
|
936 (ENABLE_FEATURE_LS_SORTFILES
* (SORT_NAME
| SORT_FORWARD
));
938 #if ENABLE_FEATURE_AUTOWIDTH
939 /* Obtain the terminal width */
940 get_terminal_width_height(STDIN_FILENO
, &terminal_width
, NULL
);
945 /* process options */
946 USE_FEATURE_LS_COLOR(applet_long_options
= ls_longopts
;)
947 #if ENABLE_FEATURE_AUTOWIDTH
948 opt_complementary
= "T+:w+"; /* -T N, -w N */
949 opt
= getopt32(argv
, ls_options
, &tabstops
, &terminal_width
950 USE_FEATURE_LS_COLOR(, &color_opt
));
952 opt
= getopt32(argv
, ls_options
USE_FEATURE_LS_COLOR(, &color_opt
));
954 for (i
= 0; opt_flags
[i
] != (1U<<31); i
++) {
955 if (opt
& (1 << i
)) {
956 unsigned flags
= opt_flags
[i
];
958 if (flags
& LIST_MASK_TRIGGER
)
959 all_fmt
&= ~LIST_MASK
;
960 if (flags
& STYLE_MASK_TRIGGER
)
961 all_fmt
&= ~STYLE_MASK
;
962 if (flags
& SORT_MASK_TRIGGER
)
963 all_fmt
&= ~SORT_MASK
;
964 if (flags
& DISP_MASK_TRIGGER
)
965 all_fmt
&= ~DISP_MASK
;
966 if (flags
& TIME_MASK
)
967 all_fmt
&= ~TIME_MASK
;
968 if (flags
& LIST_CONTEXT
)
969 all_fmt
|= STYLE_SINGLE
;
970 /* huh?? opt cannot be 'l' */
971 //if (LS_DISP_HR && opt == 'l')
972 // all_fmt &= ~LS_DISP_HR;
977 #if ENABLE_FEATURE_LS_COLOR
978 /* find color bit value - last position for short getopt */
979 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT
&& isatty(STDOUT_FILENO
)) {
980 char *p
= getenv("LS_COLORS");
981 /* LS_COLORS is unset, or (not empty && not "none") ? */
982 if (!p
|| (p
[0] && strcmp(p
, "none") != 0))
985 if (opt
& OPT_color
) {
986 if (color_opt
[0] == 'n')
988 else switch (index_in_substrings(color_str
, color_opt
)) {
992 if (isatty(STDOUT_FILENO
)) {
1002 /* sort out which command line options take precedence */
1003 if (ENABLE_FEATURE_LS_RECURSIVE
&& (all_fmt
& DISP_NOLIST
))
1004 all_fmt
&= ~DISP_RECURSIVE
; /* no recurse if listing only dir */
1005 if (ENABLE_FEATURE_LS_TIMESTAMPS
&& ENABLE_FEATURE_LS_SORTFILES
) {
1006 if (all_fmt
& TIME_CHANGE
)
1007 all_fmt
= (all_fmt
& ~SORT_MASK
) | SORT_CTIME
;
1008 if (all_fmt
& TIME_ACCESS
)
1009 all_fmt
= (all_fmt
& ~SORT_MASK
) | SORT_ATIME
;
1011 if ((all_fmt
& STYLE_MASK
) != STYLE_LONG
) /* only for long list */
1012 all_fmt
&= ~(LIST_ID_NUMERIC
|LIST_FULLTIME
|LIST_ID_NAME
|LIST_ID_NUMERIC
);
1013 if (ENABLE_FEATURE_LS_USERNAME
)
1014 if ((all_fmt
& STYLE_MASK
) == STYLE_LONG
&& (all_fmt
& LIST_ID_NUMERIC
))
1015 all_fmt
&= ~LIST_ID_NAME
; /* don't list names if numeric uid */
1017 /* choose a display format */
1018 if (!(all_fmt
& STYLE_MASK
))
1019 all_fmt
|= (isatty(STDOUT_FILENO
) ? STYLE_COLUMNS
: STYLE_SINGLE
);
1023 *--argv
= (char*)".";
1026 all_fmt
|= DISP_DIRNAME
; /* 2 or more items? label directories */
1028 /* stuff the command line file names into a dnode array */
1032 /* NB: follow links on command line unless -l or -s */
1033 cur
= my_stat(*argv
, *argv
, !(all_fmt
& (STYLE_LONG
|LIST_BLOCKS
)));
1043 /* now that we know how many files there are
1044 * allocate memory for an array to hold dnode pointers
1046 dnp
= dnalloc(nfiles
);
1047 for (i
= 0, cur
= dn
; i
< nfiles
; i
++) {
1048 dnp
[i
] = cur
; /* save pointer to node in array */
1052 if (all_fmt
& DISP_NOLIST
) {
1053 dnsort(dnp
, nfiles
);
1055 showfiles(dnp
, nfiles
);
1057 dnd
= splitdnarray(dnp
, nfiles
, SPLIT_DIR
);
1058 dnf
= splitdnarray(dnp
, nfiles
, SPLIT_FILE
);
1059 dndirs
= countdirs(dnp
, nfiles
);
1060 dnfiles
= nfiles
- dndirs
;
1062 dnsort(dnf
, dnfiles
);
1063 showfiles(dnf
, dnfiles
);
1064 if (ENABLE_FEATURE_CLEAN_UP
)
1068 dnsort(dnd
, dndirs
);
1069 showdirs(dnd
, dndirs
, dnfiles
== 0);
1070 if (ENABLE_FEATURE_CLEAN_UP
)
1074 if (ENABLE_FEATURE_CLEAN_UP
)