6 |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
7 |
*/ |
*/ |
8 |
|
|
9 |
/* |
/* [date unknown. Perhaps before year 2000] |
10 |
* To achieve a small memory footprint, this version of 'ls' doesn't do any |
* 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 |
* 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 |
* (i.e., the ones I couldn't live without :-) All features which involve |
17 |
* it more portable. |
* it more portable. |
18 |
* |
* |
19 |
* KNOWN BUGS: |
* KNOWN BUGS: |
20 |
* 1. ls -l of a directory doesn't give "total <blocks>" header |
* 1. hidden files can make column width too large |
|
* 2. ls of a symlink to a directory doesn't list directory contents |
|
|
* 3. hidden files can make column width too large |
|
21 |
* |
* |
22 |
* NON-OPTIMAL BEHAVIOUR: |
* NON-OPTIMAL BEHAVIOUR: |
23 |
* 1. autowidth reads directories twice |
* 1. autowidth reads directories twice |
25 |
* appended, there's no need to stat each one |
* appended, there's no need to stat each one |
26 |
* PORTABILITY: |
* PORTABILITY: |
27 |
* 1. requires lstat (BSD) - how do you do it without? |
* 1. requires lstat (BSD) - how do you do it without? |
28 |
|
* |
29 |
|
* [2009-03] |
30 |
|
* ls sorts listing now, and supports almost all options. |
31 |
*/ |
*/ |
|
|
|
32 |
#include "libbb.h" |
#include "libbb.h" |
33 |
|
#include "unicode.h" |
34 |
|
|
|
#if ENABLE_FEATURE_ASSUME_UNICODE |
|
|
#include <wchar.h> |
|
|
#endif |
|
35 |
|
|
36 |
/* This is a NOEXEC applet. Be very careful! */ |
/* This is a NOEXEC applet. Be very careful! */ |
37 |
|
|
38 |
|
|
39 |
|
#if ENABLE_FTPD |
40 |
|
/* ftpd uses ls, and without timestamps Mozilla won't understand |
41 |
|
* ftpd's LIST output. |
42 |
|
*/ |
43 |
|
# undef CONFIG_FEATURE_LS_TIMESTAMPS |
44 |
|
# undef ENABLE_FEATURE_LS_TIMESTAMPS |
45 |
|
# undef IF_FEATURE_LS_TIMESTAMPS |
46 |
|
# undef IF_NOT_FEATURE_LS_TIMESTAMPS |
47 |
|
# define CONFIG_FEATURE_LS_TIMESTAMPS 1 |
48 |
|
# define ENABLE_FEATURE_LS_TIMESTAMPS 1 |
49 |
|
# define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__ |
50 |
|
# define IF_NOT_FEATURE_LS_TIMESTAMPS(...) |
51 |
|
#endif |
52 |
|
|
53 |
|
|
54 |
enum { |
enum { |
55 |
|
|
56 |
TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */ |
TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */ |
72 |
LIST_ID_NUMERIC = 1 << 5, |
LIST_ID_NUMERIC = 1 << 5, |
73 |
LIST_CONTEXT = 1 << 6, |
LIST_CONTEXT = 1 << 6, |
74 |
LIST_SIZE = 1 << 7, |
LIST_SIZE = 1 << 7, |
75 |
LIST_DEV = 1 << 8, |
//LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE |
76 |
LIST_DATE_TIME = 1 << 9, |
LIST_DATE_TIME = 1 << 9, |
77 |
LIST_FULLTIME = 1 << 10, |
LIST_FULLTIME = 1 << 10, |
78 |
LIST_FILENAME = 1 << 11, |
LIST_FILENAME = 1 << 11, |
123 |
|
|
124 |
}; |
}; |
125 |
|
|
126 |
#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f) |
/* "[-]Cadil1", POSIX mandated options, busybox always supports */ |
127 |
#define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)]) |
/* "[-]gnsx", POSIX non-mandated options, busybox always supports */ |
128 |
#define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)]) |
/* "[-]Q" GNU option? busybox always supports */ |
129 |
#define COLOR(mode) ("\000\043\043\043\042\000\043\043"\ |
/* "[-]Ak" GNU options, busybox always supports */ |
130 |
"\000\000\044\000\043\000\000\040" [TYPEINDEX(mode)]) |
/* "[-]FLRctur", POSIX mandated options, busybox optionally supports */ |
131 |
#define ATTR(mode) ("\00\00\01\00\01\00\01\00"\ |
/* "[-]p", POSIX non-mandated options, busybox optionally supports */ |
132 |
"\00\00\01\00\01\00\00\01" [TYPEINDEX(mode)]) |
/* "[-]SXvThw", GNU options, busybox optionally supports */ |
133 |
|
/* "[-]K", SELinux mandated options, busybox optionally supports */ |
134 |
|
/* "[-]e", I think we made this one up */ |
135 |
|
static const char ls_options[] ALIGN1 = |
136 |
|
"Cadil1gnsxQAk" /* 13 opts, total 13 */ |
137 |
|
IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */ |
138 |
|
IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */ |
139 |
|
IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */ |
140 |
|
IF_FEATURE_LS_FOLLOWLINKS("L") /* 1, 24 */ |
141 |
|
IF_FEATURE_LS_RECURSIVE("R") /* 1, 25 */ |
142 |
|
IF_FEATURE_HUMAN_READABLE("h") /* 1, 26 */ |
143 |
|
IF_SELINUX("KZ") /* 2, 28 */ |
144 |
|
IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */ |
145 |
|
; |
146 |
|
enum { |
147 |
|
//OPT_C = (1 << 0), |
148 |
|
//OPT_a = (1 << 1), |
149 |
|
//OPT_d = (1 << 2), |
150 |
|
//OPT_i = (1 << 3), |
151 |
|
//OPT_l = (1 << 4), |
152 |
|
//OPT_1 = (1 << 5), |
153 |
|
OPT_g = (1 << 6), |
154 |
|
//OPT_n = (1 << 7), |
155 |
|
//OPT_s = (1 << 8), |
156 |
|
//OPT_x = (1 << 9), |
157 |
|
OPT_Q = (1 << 10), |
158 |
|
//OPT_A = (1 << 11), |
159 |
|
//OPT_k = (1 << 12), |
160 |
|
OPTBIT_color = 13 |
161 |
|
+ 4 * ENABLE_FEATURE_LS_TIMESTAMPS |
162 |
|
+ 4 * ENABLE_FEATURE_LS_SORTFILES |
163 |
|
+ 2 * ENABLE_FEATURE_LS_FILETYPES |
164 |
|
+ 1 * ENABLE_FEATURE_LS_FOLLOWLINKS |
165 |
|
+ 1 * ENABLE_FEATURE_LS_RECURSIVE |
166 |
|
+ 1 * ENABLE_FEATURE_HUMAN_READABLE |
167 |
|
+ 2 * ENABLE_SELINUX |
168 |
|
+ 2 * ENABLE_FEATURE_AUTOWIDTH, |
169 |
|
OPT_color = 1 << OPTBIT_color, |
170 |
|
}; |
171 |
|
|
172 |
|
enum { |
173 |
|
LIST_MASK_TRIGGER = 0, |
174 |
|
STYLE_MASK_TRIGGER = STYLE_MASK, |
175 |
|
DISP_MASK_TRIGGER = DISP_ROWS, |
176 |
|
SORT_MASK_TRIGGER = SORT_MASK, |
177 |
|
}; |
178 |
|
|
179 |
|
/* TODO: simple toggles may be stored as OPT_xxx bits instead */ |
180 |
|
static const unsigned opt_flags[] = { |
181 |
|
LIST_SHORT | STYLE_COLUMNS, /* C */ |
182 |
|
DISP_HIDDEN | DISP_DOT, /* a */ |
183 |
|
DISP_NOLIST, /* d */ |
184 |
|
LIST_INO, /* i */ |
185 |
|
LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */ |
186 |
|
LIST_SHORT | STYLE_SINGLE, /* 1 */ |
187 |
|
0, /* g (don't show group) - handled via OPT_g */ |
188 |
|
LIST_ID_NUMERIC, /* n */ |
189 |
|
LIST_BLOCKS, /* s */ |
190 |
|
DISP_ROWS, /* x */ |
191 |
|
0, /* Q (quote filename) - handled via OPT_Q */ |
192 |
|
DISP_HIDDEN, /* A */ |
193 |
|
ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */ |
194 |
|
#if ENABLE_FEATURE_LS_TIMESTAMPS |
195 |
|
TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */ |
196 |
|
LIST_FULLTIME, /* e */ |
197 |
|
ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */ |
198 |
|
TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */ |
199 |
|
#endif |
200 |
|
#if ENABLE_FEATURE_LS_SORTFILES |
201 |
|
SORT_SIZE, /* S */ |
202 |
|
SORT_EXT, /* X */ |
203 |
|
SORT_REVERSE, /* r */ |
204 |
|
SORT_VERSION, /* v */ |
205 |
|
#endif |
206 |
|
#if ENABLE_FEATURE_LS_FILETYPES |
207 |
|
LIST_FILETYPE | LIST_EXEC, /* F */ |
208 |
|
LIST_FILETYPE, /* p */ |
209 |
|
#endif |
210 |
|
#if ENABLE_FEATURE_LS_FOLLOWLINKS |
211 |
|
FOLLOW_LINKS, /* L */ |
212 |
|
#endif |
213 |
|
#if ENABLE_FEATURE_LS_RECURSIVE |
214 |
|
DISP_RECURSIVE, /* R */ |
215 |
|
#endif |
216 |
|
#if ENABLE_FEATURE_HUMAN_READABLE |
217 |
|
LS_DISP_HR, /* h */ |
218 |
|
#endif |
219 |
|
#if ENABLE_SELINUX |
220 |
|
LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */ |
221 |
|
#endif |
222 |
|
#if ENABLE_SELINUX |
223 |
|
LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */ |
224 |
|
#endif |
225 |
|
(1U<<31) |
226 |
|
/* options after Z are not processed through opt_flags: |
227 |
|
* T, w - ignored |
228 |
|
*/ |
229 |
|
}; |
230 |
|
|
231 |
|
|
232 |
/* |
/* |
233 |
* a directory entry and its stat info are stored here |
* a directory entry and its stat info are stored here |
234 |
*/ |
*/ |
235 |
struct dnode { /* the basic node */ |
struct dnode { |
236 |
const char *name; /* the dir entry name */ |
const char *name; /* the dir entry name */ |
237 |
const char *fullname; /* the dir entry name */ |
const char *fullname; /* the dir entry name */ |
|
int allocated; |
|
|
struct stat dstat; /* the file stat info */ |
|
|
USE_SELINUX(security_context_t sid;) |
|
238 |
struct dnode *next; /* point at the next node */ |
struct dnode *next; /* point at the next node */ |
239 |
|
smallint fname_allocated; |
240 |
|
struct stat dstat; /* the file stat info */ |
241 |
|
IF_SELINUX(security_context_t sid;) |
242 |
}; |
}; |
243 |
|
|
244 |
static struct dnode **list_dir(const char *); |
static struct dnode **list_dir(const char *, unsigned *); |
245 |
static struct dnode **dnalloc(int); |
static unsigned list_single(const struct dnode *); |
|
static int list_single(const struct dnode *); |
|
|
|
|
246 |
|
|
247 |
struct globals { |
struct globals { |
248 |
#if ENABLE_FEATURE_LS_COLOR |
#if ENABLE_FEATURE_LS_COLOR |
261 |
}; |
}; |
262 |
#define G (*(struct globals*)&bb_common_bufsiz1) |
#define G (*(struct globals*)&bb_common_bufsiz1) |
263 |
#if ENABLE_FEATURE_LS_COLOR |
#if ENABLE_FEATURE_LS_COLOR |
264 |
#define show_color (G.show_color ) |
# define show_color (G.show_color ) |
265 |
#else |
#else |
266 |
enum { show_color = 0 }; |
enum { show_color = 0 }; |
267 |
#endif |
#endif |
268 |
#define exit_code (G.exit_code ) |
#define exit_code (G.exit_code ) |
269 |
#define all_fmt (G.all_fmt ) |
#define all_fmt (G.all_fmt ) |
270 |
#if ENABLE_FEATURE_AUTOWIDTH |
#if ENABLE_FEATURE_AUTOWIDTH |
271 |
#define tabstops (G.tabstops ) |
# define tabstops (G.tabstops ) |
272 |
#define terminal_width (G.terminal_width) |
# define terminal_width (G.terminal_width) |
273 |
#else |
#else |
274 |
enum { |
enum { |
275 |
tabstops = COLUMN_GAP, |
tabstops = COLUMN_GAP, |
277 |
}; |
}; |
278 |
#endif |
#endif |
279 |
#define current_time_t (G.current_time_t) |
#define current_time_t (G.current_time_t) |
|
/* memset: we have to zero it out because of NOEXEC */ |
|
280 |
#define INIT_G() do { \ |
#define INIT_G() do { \ |
281 |
|
/* we have to zero it out because of NOEXEC */ \ |
282 |
memset(&G, 0, sizeof(G)); \ |
memset(&G, 0, sizeof(G)); \ |
283 |
USE_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \ |
IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \ |
284 |
USE_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \ |
IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \ |
285 |
USE_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \ |
IF_FEATURE_LS_TIMESTAMPS(time(¤t_time_t);) \ |
286 |
} while (0) |
} while (0) |
287 |
|
|
288 |
|
|
|
#if ENABLE_FEATURE_ASSUME_UNICODE |
|
|
/* libbb candidate */ |
|
|
static size_t mbstrlen(const char *string) |
|
|
{ |
|
|
size_t width = mbsrtowcs(NULL /*dest*/, &string, |
|
|
MAXINT(size_t) /*len*/, NULL /*state*/); |
|
|
if (width == (size_t)-1) |
|
|
return strlen(string); |
|
|
return width; |
|
|
} |
|
|
#else |
|
|
#define mbstrlen(string) strlen(string) |
|
|
#endif |
|
|
|
|
|
|
|
289 |
static struct dnode *my_stat(const char *fullname, const char *name, int force_follow) |
static struct dnode *my_stat(const char *fullname, const char *name, int force_follow) |
290 |
{ |
{ |
291 |
struct stat dstat; |
struct stat dstat; |
292 |
struct dnode *cur; |
struct dnode *cur; |
293 |
USE_SELINUX(security_context_t sid = NULL;) |
IF_SELINUX(security_context_t sid = NULL;) |
294 |
|
|
295 |
if ((all_fmt & FOLLOW_LINKS) || force_follow) { |
if ((all_fmt & FOLLOW_LINKS) || force_follow) { |
296 |
#if ENABLE_SELINUX |
#if ENABLE_SELINUX |
316 |
} |
} |
317 |
} |
} |
318 |
|
|
319 |
cur = xmalloc(sizeof(struct dnode)); |
cur = xmalloc(sizeof(*cur)); |
320 |
cur->fullname = fullname; |
cur->fullname = fullname; |
321 |
cur->name = name; |
cur->name = name; |
322 |
cur->dstat = dstat; |
cur->dstat = dstat; |
323 |
USE_SELINUX(cur->sid = sid;) |
IF_SELINUX(cur->sid = sid;) |
324 |
return cur; |
return cur; |
325 |
} |
} |
326 |
|
|
327 |
|
/* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket |
328 |
|
* (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file |
329 |
|
* 3/7:multiplexed char/block device) |
330 |
|
* and we use 0 for unknown and 15 for executables (see below) */ |
331 |
|
#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f) |
332 |
|
#define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)]) |
333 |
|
#define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)]) |
334 |
|
/* 036 black foreground 050 black background |
335 |
|
037 red foreground 051 red background |
336 |
|
040 green foreground 052 green background |
337 |
|
041 brown foreground 053 brown background |
338 |
|
042 blue foreground 054 blue background |
339 |
|
043 magenta (purple) foreground 055 magenta background |
340 |
|
044 cyan (light blue) foreground 056 cyan background |
341 |
|
045 gray foreground 057 white background |
342 |
|
*/ |
343 |
|
#define COLOR(mode) ( \ |
344 |
|
/*un fi chr dir blk file link sock exe */ \ |
345 |
|
"\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \ |
346 |
|
[TYPEINDEX(mode)]) |
347 |
|
/* Select normal (0) [actually "reset all"] or bold (1) |
348 |
|
* (other attributes are 2:dim 4:underline 5:blink 7:reverse, |
349 |
|
* let's use 7 for "impossible" types, just for fun) |
350 |
|
* Note: coreutils 6.9 uses inverted red for setuid binaries. |
351 |
|
*/ |
352 |
|
#define ATTR(mode) ( \ |
353 |
|
/*un fi chr dir blk file link sock exe */ \ |
354 |
|
"\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \ |
355 |
|
[TYPEINDEX(mode)]) |
356 |
|
|
357 |
#if ENABLE_FEATURE_LS_COLOR |
#if ENABLE_FEATURE_LS_COLOR |
358 |
|
/* mode of zero is interpreted as "unknown" (stat failed) */ |
359 |
static char fgcolor(mode_t mode) |
static char fgcolor(mode_t mode) |
360 |
{ |
{ |
|
/* Check wheter the file is existing (if so, color it red!) */ |
|
|
if (errno == ENOENT) |
|
|
return '\037'; |
|
361 |
if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) |
if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) |
362 |
return COLOR(0xF000); /* File is executable ... */ |
return COLOR(0xF000); /* File is executable ... */ |
363 |
return COLOR(mode); |
return COLOR(mode); |
364 |
} |
} |
365 |
|
static char bold(mode_t mode) |
|
static char bgcolor(mode_t mode) |
|
366 |
{ |
{ |
367 |
if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) |
if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) |
368 |
return ATTR(0xF000); /* File is executable ... */ |
return ATTR(0xF000); /* File is executable ... */ |
385 |
} |
} |
386 |
#endif |
#endif |
387 |
|
|
388 |
#define countdirs(A, B) count_dirs((A), (B), 1) |
static unsigned count_dirs(struct dnode **dn, int which) |
|
#define countsubdirs(A, B) count_dirs((A), (B), 0) |
|
|
static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs) |
|
389 |
{ |
{ |
390 |
int i, dirs; |
unsigned dirs, all; |
391 |
|
|
392 |
if (!dn) |
if (!dn) |
393 |
return 0; |
return 0; |
394 |
dirs = 0; |
|
395 |
for (i = 0; i < nfiles; i++) { |
dirs = all = 0; |
396 |
|
for (; *dn; dn++) { |
397 |
const char *name; |
const char *name; |
398 |
if (!S_ISDIR(dn[i]->dstat.st_mode)) |
|
399 |
|
all++; |
400 |
|
if (!S_ISDIR((*dn)->dstat.st_mode)) |
401 |
continue; |
continue; |
402 |
name = dn[i]->name; |
name = (*dn)->name; |
403 |
if (notsubdirs |
if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */ |
404 |
|| name[0]!='.' || (name[1] && (name[1]!='.' || name[2])) |
/* or if it's not . or .. */ |
405 |
|
|| name[0] != '.' || (name[1] && (name[1] != '.' || name[2])) |
406 |
) { |
) { |
407 |
dirs++; |
dirs++; |
408 |
} |
} |
409 |
} |
} |
410 |
return dirs; |
return which != SPLIT_FILE ? dirs : all - dirs; |
|
} |
|
|
|
|
|
static int countfiles(struct dnode **dnp) |
|
|
{ |
|
|
int nfiles; |
|
|
struct dnode *cur; |
|
|
|
|
|
if (dnp == NULL) |
|
|
return 0; |
|
|
nfiles = 0; |
|
|
for (cur = dnp[0]; cur->next; cur = cur->next) |
|
|
nfiles++; |
|
|
nfiles++; |
|
|
return nfiles; |
|
411 |
} |
} |
412 |
|
|
413 |
/* get memory to hold an array of pointers */ |
/* get memory to hold an array of pointers */ |
414 |
static struct dnode **dnalloc(int num) |
static struct dnode **dnalloc(unsigned num) |
415 |
{ |
{ |
416 |
if (num < 1) |
if (num < 1) |
417 |
return NULL; |
return NULL; |
418 |
|
|
419 |
|
num++; /* so that we have terminating NULL */ |
420 |
return xzalloc(num * sizeof(struct dnode *)); |
return xzalloc(num * sizeof(struct dnode *)); |
421 |
} |
} |
422 |
|
|
423 |
#if ENABLE_FEATURE_LS_RECURSIVE |
#if ENABLE_FEATURE_LS_RECURSIVE |
424 |
static void dfree(struct dnode **dnp, int nfiles) |
static void dfree(struct dnode **dnp) |
425 |
{ |
{ |
426 |
int i; |
unsigned i; |
427 |
|
|
428 |
if (dnp == NULL) |
if (dnp == NULL) |
429 |
return; |
return; |
430 |
|
|
431 |
for (i = 0; i < nfiles; i++) { |
for (i = 0; dnp[i]; i++) { |
432 |
struct dnode *cur = dnp[i]; |
struct dnode *cur = dnp[i]; |
433 |
if (cur->allocated) |
if (cur->fname_allocated) |
434 |
free((char*)cur->fullname); /* free the filename */ |
free((char*)cur->fullname); |
435 |
free(cur); /* free the dnode */ |
free(cur); |
436 |
} |
} |
437 |
free(dnp); /* free the array holding the dnode pointers */ |
free(dnp); |
438 |
} |
} |
439 |
#else |
#else |
440 |
#define dfree(...) ((void)0) |
#define dfree(...) ((void)0) |
441 |
#endif |
#endif |
442 |
|
|
443 |
static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which) |
/* Returns NULL-terminated malloced vector of pointers (or NULL) */ |
444 |
|
static struct dnode **splitdnarray(struct dnode **dn, int which) |
445 |
{ |
{ |
446 |
int dncnt, i, d; |
unsigned dncnt, d; |
447 |
struct dnode **dnp; |
struct dnode **dnp; |
448 |
|
|
449 |
if (dn == NULL || nfiles < 1) |
if (dn == NULL) |
450 |
return NULL; |
return NULL; |
451 |
|
|
452 |
/* count how many dirs and regular files there are */ |
/* count how many dirs or files there are */ |
453 |
if (which == SPLIT_SUBDIR) |
dncnt = count_dirs(dn, which); |
|
dncnt = countsubdirs(dn, nfiles); |
|
|
else { |
|
|
dncnt = countdirs(dn, nfiles); /* assume we are looking for dirs */ |
|
|
if (which == SPLIT_FILE) |
|
|
dncnt = nfiles - dncnt; /* looking for files */ |
|
|
} |
|
454 |
|
|
455 |
/* allocate a file array and a dir array */ |
/* allocate a file array and a dir array */ |
456 |
dnp = dnalloc(dncnt); |
dnp = dnalloc(dncnt); |
457 |
|
|
458 |
/* copy the entrys into the file or dir array */ |
/* copy the entrys into the file or dir array */ |
459 |
for (d = i = 0; i < nfiles; i++) { |
for (d = 0; *dn; dn++) { |
460 |
if (S_ISDIR(dn[i]->dstat.st_mode)) { |
if (S_ISDIR((*dn)->dstat.st_mode)) { |
461 |
const char *name; |
const char *name; |
462 |
|
|
463 |
if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) |
if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) |
464 |
continue; |
continue; |
465 |
name = dn[i]->name; |
name = (*dn)->name; |
466 |
if ((which & SPLIT_DIR) |
if ((which & SPLIT_DIR) |
467 |
|| name[0]!='.' || (name[1] && (name[1]!='.' || name[2])) |
|| name[0]!='.' || (name[1] && (name[1]!='.' || name[2])) |
468 |
) { |
) { |
469 |
dnp[d++] = dn[i]; |
dnp[d++] = *dn; |
470 |
} |
} |
471 |
} else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) { |
} else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) { |
472 |
dnp[d++] = dn[i]; |
dnp[d++] = *dn; |
473 |
} |
} |
474 |
} |
} |
475 |
return dnp; |
return dnp; |
481 |
struct dnode *d1 = *(struct dnode **)a; |
struct dnode *d1 = *(struct dnode **)a; |
482 |
struct dnode *d2 = *(struct dnode **)b; |
struct dnode *d2 = *(struct dnode **)b; |
483 |
unsigned sort_opts = all_fmt & SORT_MASK; |
unsigned sort_opts = all_fmt & SORT_MASK; |
484 |
int dif; |
off_t dif; |
485 |
|
|
486 |
dif = 0; /* assume SORT_NAME */ |
dif = 0; /* assume SORT_NAME */ |
487 |
// TODO: use pre-initialized function pointer |
// TODO: use pre-initialized function pointer |
488 |
// instead of branch forest |
// instead of branch forest |
489 |
if (sort_opts == SORT_SIZE) { |
if (sort_opts == SORT_SIZE) { |
490 |
dif = (int) (d2->dstat.st_size - d1->dstat.st_size); |
dif = (d2->dstat.st_size - d1->dstat.st_size); |
491 |
} else if (sort_opts == SORT_ATIME) { |
} else if (sort_opts == SORT_ATIME) { |
492 |
dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime); |
dif = (d2->dstat.st_atime - d1->dstat.st_atime); |
493 |
} else if (sort_opts == SORT_CTIME) { |
} else if (sort_opts == SORT_CTIME) { |
494 |
dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime); |
dif = (d2->dstat.st_ctime - d1->dstat.st_ctime); |
495 |
} else if (sort_opts == SORT_MTIME) { |
} else if (sort_opts == SORT_MTIME) { |
496 |
dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime); |
dif = (d2->dstat.st_mtime - d1->dstat.st_mtime); |
497 |
} else if (sort_opts == SORT_DIR) { |
} else if (sort_opts == SORT_DIR) { |
498 |
dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode); |
dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode); |
499 |
/* } else if (sort_opts == SORT_VERSION) { */ |
/* } else if (sort_opts == SORT_VERSION) { */ |
500 |
/* } else if (sort_opts == SORT_EXT) { */ |
/* } else if (sort_opts == SORT_EXT) { */ |
501 |
} |
} |
|
|
|
502 |
if (dif == 0) { |
if (dif == 0) { |
503 |
/* sort by name - may be a tie_breaker for time or size cmp */ |
/* sort by name, or tie_breaker for other sorts */ |
504 |
if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name); |
if (ENABLE_LOCALE_SUPPORT) |
505 |
else dif = strcmp(d1->name, d2->name); |
dif = strcoll(d1->name, d2->name); |
506 |
|
else |
507 |
|
dif = strcmp(d1->name, d2->name); |
508 |
} |
} |
509 |
|
|
510 |
if (all_fmt & SORT_REVERSE) { |
/* Make dif fit into an int */ |
511 |
dif = -dif; |
if (sizeof(dif) > sizeof(int)) { |
512 |
|
enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) }; |
513 |
|
/* shift leaving only "int" worth of bits */ |
514 |
|
if (dif != 0) { |
515 |
|
dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT); |
516 |
|
} |
517 |
} |
} |
518 |
return dif; |
|
519 |
|
return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif; |
520 |
} |
} |
521 |
|
|
522 |
static void dnsort(struct dnode **dn, int size) |
static void dnsort(struct dnode **dn, int size) |
528 |
#endif |
#endif |
529 |
|
|
530 |
|
|
531 |
static void showfiles(struct dnode **dn, int nfiles) |
static void showfiles(struct dnode **dn, unsigned nfiles) |
532 |
{ |
{ |
533 |
int i, ncols, nrows, row, nc; |
unsigned i, ncols, nrows, row, nc; |
534 |
int column = 0; |
unsigned column = 0; |
535 |
int nexttab = 0; |
unsigned nexttab = 0; |
536 |
int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */ |
unsigned column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */ |
537 |
|
|
538 |
|
/* Never happens: |
539 |
if (dn == NULL || nfiles < 1) |
if (dn == NULL || nfiles < 1) |
540 |
return; |
return; |
541 |
|
*/ |
542 |
|
|
543 |
if (all_fmt & STYLE_LONG) { |
if (all_fmt & STYLE_LONG) { |
544 |
ncols = 1; |
ncols = 1; |
545 |
} else { |
} else { |
546 |
/* find the longest file name, use that as the column width */ |
/* find the longest file name, use that as the column width */ |
547 |
for (i = 0; i < nfiles; i++) { |
for (i = 0; dn[i]; i++) { |
548 |
int len = mbstrlen(dn[i]->name); |
int len = unicode_strlen(dn[i]->name); |
549 |
if (column_width < len) |
if (column_width < len) |
550 |
column_width = len; |
column_width = len; |
551 |
} |
} |
552 |
column_width += tabstops + |
column_width += tabstops + |
553 |
USE_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + ) |
IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + ) |
554 |
((all_fmt & LIST_INO) ? 8 : 0) + |
((all_fmt & LIST_INO) ? 8 : 0) + |
555 |
((all_fmt & LIST_BLOCKS) ? 5 : 0); |
((all_fmt & LIST_BLOCKS) ? 5 : 0); |
556 |
ncols = (int) (terminal_width / column_width); |
ncols = (int) (terminal_width / column_width); |
568 |
for (row = 0; row < nrows; row++) { |
for (row = 0; row < nrows; row++) { |
569 |
for (nc = 0; nc < ncols; nc++) { |
for (nc = 0; nc < ncols; nc++) { |
570 |
/* reach into the array based on the column and row */ |
/* reach into the array based on the column and row */ |
|
i = (nc * nrows) + row; /* assume display by column */ |
|
571 |
if (all_fmt & DISP_ROWS) |
if (all_fmt & DISP_ROWS) |
572 |
i = (row * ncols) + nc; /* display across row */ |
i = (row * ncols) + nc; /* display across row */ |
573 |
|
else |
574 |
|
i = (nc * nrows) + row; /* display by column */ |
575 |
if (i < nfiles) { |
if (i < nfiles) { |
576 |
if (column > 0) { |
if (column > 0) { |
577 |
nexttab -= column; |
nexttab -= column; |
588 |
} |
} |
589 |
|
|
590 |
|
|
591 |
static void showdirs(struct dnode **dn, int ndirs, int first) |
#if ENABLE_DESKTOP |
592 |
|
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html |
593 |
|
* If any of the -l, -n, -s options is specified, each list |
594 |
|
* of files within the directory shall be preceded by a |
595 |
|
* status line indicating the number of file system blocks |
596 |
|
* occupied by files in the directory in 512-byte units if |
597 |
|
* the -k option is not specified, or 1024-byte units if the |
598 |
|
* -k option is specified, rounded up to the next integral |
599 |
|
* number of units. |
600 |
|
*/ |
601 |
|
/* by Jorgen Overgaard (jorgen AT antistaten.se) */ |
602 |
|
static off_t calculate_blocks(struct dnode **dn) |
603 |
|
{ |
604 |
|
uoff_t blocks = 1; |
605 |
|
if (dn) { |
606 |
|
while (*dn) { |
607 |
|
/* st_blocks is in 512 byte blocks */ |
608 |
|
blocks += (*dn)->dstat.st_blocks; |
609 |
|
dn++; |
610 |
|
} |
611 |
|
} |
612 |
|
|
613 |
|
/* Even though standard says use 512 byte blocks, coreutils use 1k */ |
614 |
|
/* Actually, we round up by calculating (blocks + 1) / 2, |
615 |
|
* "+ 1" was done when we initialized blocks to 1 */ |
616 |
|
return blocks >> 1; |
617 |
|
} |
618 |
|
#endif |
619 |
|
|
620 |
|
|
621 |
|
static void showdirs(struct dnode **dn, int first) |
622 |
{ |
{ |
623 |
int i, nfiles; |
unsigned nfiles; |
624 |
|
unsigned dndirs; |
625 |
struct dnode **subdnp; |
struct dnode **subdnp; |
|
int dndirs; |
|
626 |
struct dnode **dnd; |
struct dnode **dnd; |
627 |
|
|
628 |
if (dn == NULL || ndirs < 1) |
/* Never happens: |
629 |
|
if (dn == NULL || ndirs < 1) { |
630 |
return; |
return; |
631 |
|
} |
632 |
|
*/ |
633 |
|
|
634 |
for (i = 0; i < ndirs; i++) { |
for (; *dn; dn++) { |
635 |
if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) { |
if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) { |
636 |
if (!first) |
if (!first) |
637 |
bb_putchar('\n'); |
bb_putchar('\n'); |
638 |
first = 0; |
first = 0; |
639 |
printf("%s:\n", dn[i]->fullname); |
printf("%s:\n", (*dn)->fullname); |
640 |
} |
} |
641 |
subdnp = list_dir(dn[i]->fullname); |
subdnp = list_dir((*dn)->fullname, &nfiles); |
642 |
nfiles = countfiles(subdnp); |
#if ENABLE_DESKTOP |
643 |
|
if ((all_fmt & STYLE_MASK) == STYLE_LONG) |
644 |
|
printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp)); |
645 |
|
#endif |
646 |
if (nfiles > 0) { |
if (nfiles > 0) { |
647 |
/* list all files at this level */ |
/* list all files at this level */ |
648 |
dnsort(subdnp, nfiles); |
dnsort(subdnp, nfiles); |
649 |
showfiles(subdnp, nfiles); |
showfiles(subdnp, nfiles); |
650 |
if (ENABLE_FEATURE_LS_RECURSIVE) { |
if (ENABLE_FEATURE_LS_RECURSIVE |
651 |
if (all_fmt & DISP_RECURSIVE) { |
&& (all_fmt & DISP_RECURSIVE) |
652 |
/* recursive- list the sub-dirs */ |
) { |
653 |
dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR); |
/* recursive - list the sub-dirs */ |
654 |
dndirs = countsubdirs(subdnp, nfiles); |
dnd = splitdnarray(subdnp, SPLIT_SUBDIR); |
655 |
if (dndirs > 0) { |
dndirs = count_dirs(subdnp, SPLIT_SUBDIR); |
656 |
dnsort(dnd, dndirs); |
if (dndirs > 0) { |
657 |
showdirs(dnd, dndirs, 0); |
dnsort(dnd, dndirs); |
658 |
/* free the array of dnode pointers to the dirs */ |
showdirs(dnd, 0); |
659 |
free(dnd); |
/* free the array of dnode pointers to the dirs */ |
660 |
} |
free(dnd); |
661 |
} |
} |
|
/* free the dnodes and the fullname mem */ |
|
|
dfree(subdnp, nfiles); |
|
662 |
} |
} |
663 |
|
/* free the dnodes and the fullname mem */ |
664 |
|
dfree(subdnp); |
665 |
} |
} |
666 |
} |
} |
667 |
} |
} |
668 |
|
|
669 |
|
|
670 |
static struct dnode **list_dir(const char *path) |
/* Returns NULL-terminated malloced vector of pointers (or NULL) */ |
671 |
|
static struct dnode **list_dir(const char *path, unsigned *nfiles_p) |
672 |
{ |
{ |
673 |
struct dnode *dn, *cur, **dnp; |
struct dnode *dn, *cur, **dnp; |
674 |
struct dirent *entry; |
struct dirent *entry; |
675 |
DIR *dir; |
DIR *dir; |
676 |
int i, nfiles; |
unsigned i, nfiles; |
677 |
|
|
678 |
|
/* Never happens: |
679 |
if (path == NULL) |
if (path == NULL) |
680 |
return NULL; |
return NULL; |
681 |
|
*/ |
682 |
|
|
683 |
dn = NULL; |
*nfiles_p = 0; |
|
nfiles = 0; |
|
684 |
dir = warn_opendir(path); |
dir = warn_opendir(path); |
685 |
if (dir == NULL) { |
if (dir == NULL) { |
686 |
exit_code = EXIT_FAILURE; |
exit_code = EXIT_FAILURE; |
687 |
return NULL; /* could not open the dir */ |
return NULL; /* could not open the dir */ |
688 |
} |
} |
689 |
|
dn = NULL; |
690 |
|
nfiles = 0; |
691 |
while ((entry = readdir(dir)) != NULL) { |
while ((entry = readdir(dir)) != NULL) { |
692 |
char *fullname; |
char *fullname; |
693 |
|
|
707 |
free(fullname); |
free(fullname); |
708 |
continue; |
continue; |
709 |
} |
} |
710 |
cur->allocated = 1; |
cur->fname_allocated = 1; |
711 |
cur->next = dn; |
cur->next = dn; |
712 |
dn = cur; |
dn = cur; |
713 |
nfiles++; |
nfiles++; |
714 |
} |
} |
715 |
closedir(dir); |
closedir(dir); |
716 |
|
|
717 |
|
if (dn == NULL) |
718 |
|
return NULL; |
719 |
|
|
720 |
/* now that we know how many files there are |
/* now that we know how many files there are |
721 |
* allocate memory for an array to hold dnode pointers |
* allocate memory for an array to hold dnode pointers |
722 |
*/ |
*/ |
723 |
if (dn == NULL) |
*nfiles_p = nfiles; |
|
return NULL; |
|
724 |
dnp = dnalloc(nfiles); |
dnp = dnalloc(nfiles); |
725 |
for (i = 0, cur = dn; i < nfiles; i++) { |
for (i = 0; /* i < nfiles - detected via !dn below */; i++) { |
726 |
dnp[i] = cur; /* save pointer to node in array */ |
dnp[i] = dn; /* save pointer to node in array */ |
727 |
cur = cur->next; |
dn = dn->next; |
728 |
|
if (!dn) |
729 |
|
break; |
730 |
} |
} |
731 |
|
|
732 |
return dnp; |
return dnp; |
733 |
} |
} |
734 |
|
|
735 |
|
|
736 |
static int list_single(const struct dnode *dn) |
static int print_name(const char *name) |
737 |
{ |
{ |
738 |
int i, column = 0; |
if (option_mask32 & OPT_Q) { |
739 |
|
#if ENABLE_FEATURE_ASSUME_UNICODE |
740 |
#if ENABLE_FEATURE_LS_TIMESTAMPS |
unsigned len = 2 + unicode_strlen(name); |
741 |
char *filetime; |
#else |
742 |
time_t ttime, age; |
unsigned len = 2; |
743 |
#endif |
#endif |
744 |
|
putchar('"'); |
745 |
|
while (*name) { |
746 |
|
if (*name == '"') { |
747 |
|
putchar('\\'); |
748 |
|
len++; |
749 |
|
} |
750 |
|
putchar(*name++); |
751 |
|
if (!ENABLE_FEATURE_ASSUME_UNICODE) |
752 |
|
len++; |
753 |
|
} |
754 |
|
putchar('"'); |
755 |
|
return len; |
756 |
|
} |
757 |
|
/* No -Q: */ |
758 |
|
#if ENABLE_FEATURE_ASSUME_UNICODE |
759 |
|
fputs(name, stdout); |
760 |
|
return unicode_strlen(name); |
761 |
|
#else |
762 |
|
return printf("%s", name); |
763 |
|
#endif |
764 |
|
} |
765 |
|
|
766 |
|
|
767 |
|
static NOINLINE unsigned list_single(const struct dnode *dn) |
768 |
|
{ |
769 |
|
unsigned column = 0; |
770 |
|
char *lpath = lpath; /* for compiler */ |
771 |
#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR |
#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR |
772 |
struct stat info; |
struct stat info; |
773 |
char append; |
char append; |
774 |
#endif |
#endif |
775 |
|
|
776 |
|
/* Never happens: |
777 |
if (dn->fullname == NULL) |
if (dn->fullname == NULL) |
778 |
return 0; |
return 0; |
779 |
|
*/ |
780 |
|
|
|
#if ENABLE_FEATURE_LS_TIMESTAMPS |
|
|
ttime = dn->dstat.st_mtime; /* the default time */ |
|
|
if (all_fmt & TIME_ACCESS) |
|
|
ttime = dn->dstat.st_atime; |
|
|
if (all_fmt & TIME_CHANGE) |
|
|
ttime = dn->dstat.st_ctime; |
|
|
filetime = ctime(&ttime); |
|
|
#endif |
|
781 |
#if ENABLE_FEATURE_LS_FILETYPES |
#if ENABLE_FEATURE_LS_FILETYPES |
782 |
append = append_char(dn->dstat.st_mode); |
append = append_char(dn->dstat.st_mode); |
783 |
#endif |
#endif |
784 |
|
|
785 |
for (i = 0; i <= 31; i++) { |
/* Do readlink early, so that if it fails, error message |
786 |
switch (all_fmt & (1 << i)) { |
* does not appear *inside* the "ls -l" line */ |
787 |
case LIST_INO: |
if (all_fmt & LIST_SYMLINK) |
788 |
column += printf("%7ld ", (long) dn->dstat.st_ino); |
if (S_ISLNK(dn->dstat.st_mode)) |
789 |
break; |
lpath = xmalloc_readlink_or_warn(dn->fullname); |
790 |
case LIST_BLOCKS: |
|
791 |
column += printf("%4"OFF_FMT"d ", (off_t) dn->dstat.st_blocks >> 1); |
if (all_fmt & LIST_INO) |
792 |
break; |
column += printf("%7llu ", (long long) dn->dstat.st_ino); |
793 |
case LIST_MODEBITS: |
if (all_fmt & LIST_BLOCKS) |
794 |
column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode)); |
column += printf("%4"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1)); |
795 |
break; |
if (all_fmt & LIST_MODEBITS) |
796 |
case LIST_NLINKS: |
column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode)); |
797 |
column += printf("%4ld ", (long) dn->dstat.st_nlink); |
if (all_fmt & LIST_NLINKS) |
798 |
break; |
column += printf("%4lu ", (long) dn->dstat.st_nlink); |
|
case LIST_ID_NAME: |
|
799 |
#if ENABLE_FEATURE_LS_USERNAME |
#if ENABLE_FEATURE_LS_USERNAME |
800 |
printf("%-8.8s %-8.8s", |
if (all_fmt & LIST_ID_NAME) { |
801 |
|
if (option_mask32 & OPT_g) { |
802 |
|
column += printf("%-8.8s ", |
803 |
|
get_cached_username(dn->dstat.st_uid)); |
804 |
|
} else { |
805 |
|
column += printf("%-8.8s %-8.8s ", |
806 |
get_cached_username(dn->dstat.st_uid), |
get_cached_username(dn->dstat.st_uid), |
807 |
get_cached_groupname(dn->dstat.st_gid)); |
get_cached_groupname(dn->dstat.st_gid)); |
808 |
column += 17; |
} |
809 |
break; |
} |
810 |
#endif |
#endif |
811 |
case LIST_ID_NUMERIC: |
if (all_fmt & LIST_ID_NUMERIC) { |
812 |
column += printf("%-8d %-8d", dn->dstat.st_uid, dn->dstat.st_gid); |
if (option_mask32 & OPT_g) |
813 |
break; |
column += printf("%-8u ", (int) dn->dstat.st_uid); |
814 |
case LIST_SIZE: |
else |
815 |
case LIST_DEV: |
column += printf("%-8u %-8u ", |
816 |
if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) { |
(int) dn->dstat.st_uid, |
817 |
column += printf("%4d, %3d ", (int) major(dn->dstat.st_rdev), |
(int) dn->dstat.st_gid); |
818 |
(int) minor(dn->dstat.st_rdev)); |
} |
819 |
|
if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) { |
820 |
|
if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) { |
821 |
|
column += printf("%4u, %3u ", |
822 |
|
(int) major(dn->dstat.st_rdev), |
823 |
|
(int) minor(dn->dstat.st_rdev)); |
824 |
|
} else { |
825 |
|
if (all_fmt & LS_DISP_HR) { |
826 |
|
column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ", |
827 |
|
/* print st_size, show one fractional, use suffixes */ |
828 |
|
make_human_readable_str(dn->dstat.st_size, 1, 0) |
829 |
|
); |
830 |
} else { |
} else { |
831 |
if (all_fmt & LS_DISP_HR) { |
column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size); |
|
column += printf("%9s ", |
|
|
make_human_readable_str(dn->dstat.st_size, 1, 0)); |
|
|
} else { |
|
|
column += printf("%9"OFF_FMT"d ", (off_t) dn->dstat.st_size); |
|
|
} |
|
832 |
} |
} |
833 |
break; |
} |
834 |
|
} |
835 |
#if ENABLE_FEATURE_LS_TIMESTAMPS |
#if ENABLE_FEATURE_LS_TIMESTAMPS |
836 |
case LIST_FULLTIME: |
if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) { |
837 |
printf("%24.24s ", filetime); |
char *filetime; |
838 |
column += 25; |
time_t ttime = dn->dstat.st_mtime; |
839 |
break; |
if (all_fmt & TIME_ACCESS) |
840 |
case LIST_DATE_TIME: |
ttime = dn->dstat.st_atime; |
841 |
if ((all_fmt & LIST_FULLTIME) == 0) { |
if (all_fmt & TIME_CHANGE) |
842 |
/* current_time_t ~== time(NULL) */ |
ttime = dn->dstat.st_ctime; |
843 |
age = current_time_t - ttime; |
filetime = ctime(&ttime); |
844 |
printf("%6.6s ", filetime + 4); |
/* filetime's format: "Wed Jun 30 21:49:08 1993\n" */ |
845 |
if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) { |
if (all_fmt & LIST_FULLTIME) |
846 |
/* hh:mm if less than 6 months old */ |
column += printf("%.24s ", filetime); |
847 |
printf("%5.5s ", filetime + 11); |
else { /* LIST_DATE_TIME */ |
848 |
} else { |
/* current_time_t ~== time(NULL) */ |
849 |
printf(" %4.4s ", filetime + 20); |
time_t age = current_time_t - ttime; |
850 |
} |
printf("%.6s ", filetime + 4); /* "Jun 30" */ |
851 |
column += 13; |
if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) { |
852 |
|
/* hh:mm if less than 6 months old */ |
853 |
|
printf("%.5s ", filetime + 11); |
854 |
|
} else { /* year. buggy if year > 9999 ;) */ |
855 |
|
printf(" %.4s ", filetime + 20); |
856 |
} |
} |
857 |
break; |
column += 13; |
858 |
|
} |
859 |
|
} |
860 |
#endif |
#endif |
861 |
#if ENABLE_SELINUX |
#if ENABLE_SELINUX |
862 |
case LIST_CONTEXT: |
if (all_fmt & LIST_CONTEXT) { |
863 |
{ |
column += printf("%-32s ", dn->sid ? dn->sid : "unknown"); |
864 |
char context[80]; |
freecon(dn->sid); |
865 |
int len = 0; |
} |
|
|
|
|
if (dn->sid) { |
|
|
/* I assume sid initilized with NULL */ |
|
|
len = strlen(dn->sid) + 1; |
|
|
safe_strncpy(context, dn->sid, len); |
|
|
freecon(dn->sid); |
|
|
} else { |
|
|
safe_strncpy(context, "unknown", 8); |
|
|
} |
|
|
printf("%-32s ", context); |
|
|
column += MAX(33, len); |
|
|
} |
|
|
break; |
|
866 |
#endif |
#endif |
867 |
case LIST_FILENAME: |
if (all_fmt & LIST_FILENAME) { |
|
errno = 0; |
|
868 |
#if ENABLE_FEATURE_LS_COLOR |
#if ENABLE_FEATURE_LS_COLOR |
869 |
if (show_color && !lstat(dn->fullname, &info)) { |
if (show_color) { |
870 |
printf("\033[%d;%dm", bgcolor(info.st_mode), |
info.st_mode = 0; /* for fgcolor() */ |
871 |
fgcolor(info.st_mode)); |
lstat(dn->fullname, &info); |
872 |
} |
printf("\033[%u;%um", bold(info.st_mode), |
873 |
|
fgcolor(info.st_mode)); |
874 |
|
} |
875 |
#endif |
#endif |
876 |
#if ENABLE_FEATURE_ASSUME_UNICODE |
column += print_name(dn->name); |
877 |
printf("%s", dn->name); |
if (show_color) { |
878 |
column += mbstrlen(dn->name); |
printf("\033[0m"); |
879 |
#else |
} |
880 |
column += printf("%s", dn->name); |
} |
881 |
|
if (all_fmt & LIST_SYMLINK) { |
882 |
|
if (S_ISLNK(dn->dstat.st_mode) && lpath) { |
883 |
|
printf(" -> "); |
884 |
|
#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR |
885 |
|
#if ENABLE_FEATURE_LS_COLOR |
886 |
|
info.st_mode = 0; /* for fgcolor() */ |
887 |
#endif |
#endif |
888 |
if (show_color) { |
if (stat(dn->fullname, &info) == 0) { |
889 |
printf("\033[0m"); |
append = append_char(info.st_mode); |
890 |
} |
} |
|
break; |
|
|
case LIST_SYMLINK: |
|
|
if (S_ISLNK(dn->dstat.st_mode)) { |
|
|
char *lpath = xmalloc_readlink_or_warn(dn->fullname); |
|
|
if (!lpath) break; |
|
|
printf(" -> "); |
|
|
#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR |
|
|
if (!stat(dn->fullname, &info)) { |
|
|
append = append_char(info.st_mode); |
|
|
} |
|
891 |
#endif |
#endif |
892 |
#if ENABLE_FEATURE_LS_COLOR |
#if ENABLE_FEATURE_LS_COLOR |
893 |
if (show_color) { |
if (show_color) { |
894 |
errno = 0; |
printf("\033[%u;%um", bold(info.st_mode), |
895 |
printf("\033[%d;%dm", bgcolor(info.st_mode), |
fgcolor(info.st_mode)); |
896 |
fgcolor(info.st_mode)); |
} |
|
} |
|
897 |
#endif |
#endif |
898 |
column += printf("%s", lpath) + 4; |
column += print_name(lpath) + 4; |
899 |
if (show_color) { |
if (show_color) { |
900 |
printf("\033[0m"); |
printf("\033[0m"); |
|
} |
|
|
free(lpath); |
|
901 |
} |
} |
902 |
break; |
free(lpath); |
903 |
|
} |
904 |
|
} |
905 |
#if ENABLE_FEATURE_LS_FILETYPES |
#if ENABLE_FEATURE_LS_FILETYPES |
906 |
case LIST_FILETYPE: |
if (all_fmt & LIST_FILETYPE) { |
907 |
if (append) { |
if (append) { |
908 |
putchar(append); |
putchar(append); |
909 |
column++; |
column++; |
|
} |
|
|
break; |
|
|
#endif |
|
910 |
} |
} |
911 |
} |
} |
912 |
|
#endif |
913 |
|
|
914 |
return column; |
return column; |
915 |
} |
} |
916 |
|
|
917 |
|
|
|
/* "[-]Cadil1", POSIX mandated options, busybox always supports */ |
|
|
/* "[-]gnsx", POSIX non-mandated options, busybox always supports */ |
|
|
/* "[-]Ak" GNU options, busybox always supports */ |
|
|
/* "[-]FLRctur", POSIX mandated options, busybox optionally supports */ |
|
|
/* "[-]p", POSIX non-mandated options, busybox optionally supports */ |
|
|
/* "[-]SXvThw", GNU options, busybox optionally supports */ |
|
|
/* "[-]K", SELinux mandated options, busybox optionally supports */ |
|
|
/* "[-]e", I think we made this one up */ |
|
|
static const char ls_options[] ALIGN1 = |
|
|
"Cadil1gnsxAk" |
|
|
USE_FEATURE_LS_TIMESTAMPS("cetu") |
|
|
USE_FEATURE_LS_SORTFILES("SXrv") |
|
|
USE_FEATURE_LS_FILETYPES("Fp") |
|
|
USE_FEATURE_LS_FOLLOWLINKS("L") |
|
|
USE_FEATURE_LS_RECURSIVE("R") |
|
|
USE_FEATURE_HUMAN_READABLE("h") |
|
|
USE_SELINUX("K") |
|
|
USE_FEATURE_AUTOWIDTH("T:w:") |
|
|
USE_SELINUX("Z"); |
|
|
|
|
|
enum { |
|
|
LIST_MASK_TRIGGER = 0, |
|
|
STYLE_MASK_TRIGGER = STYLE_MASK, |
|
|
DISP_MASK_TRIGGER = DISP_ROWS, |
|
|
SORT_MASK_TRIGGER = SORT_MASK, |
|
|
}; |
|
|
|
|
|
static const unsigned opt_flags[] = { |
|
|
LIST_SHORT | STYLE_COLUMNS, /* C */ |
|
|
DISP_HIDDEN | DISP_DOT, /* a */ |
|
|
DISP_NOLIST, /* d */ |
|
|
LIST_INO, /* i */ |
|
|
LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */ |
|
|
LIST_SHORT | STYLE_SINGLE, /* 1 */ |
|
|
0, /* g - ingored */ |
|
|
LIST_ID_NUMERIC, /* n */ |
|
|
LIST_BLOCKS, /* s */ |
|
|
DISP_ROWS, /* x */ |
|
|
DISP_HIDDEN, /* A */ |
|
|
ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */ |
|
|
#if ENABLE_FEATURE_LS_TIMESTAMPS |
|
|
TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */ |
|
|
LIST_FULLTIME, /* e */ |
|
|
ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */ |
|
|
TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */ |
|
|
#endif |
|
|
#if ENABLE_FEATURE_LS_SORTFILES |
|
|
SORT_SIZE, /* S */ |
|
|
SORT_EXT, /* X */ |
|
|
SORT_REVERSE, /* r */ |
|
|
SORT_VERSION, /* v */ |
|
|
#endif |
|
|
#if ENABLE_FEATURE_LS_FILETYPES |
|
|
LIST_FILETYPE | LIST_EXEC, /* F */ |
|
|
LIST_FILETYPE, /* p */ |
|
|
#endif |
|
|
#if ENABLE_FEATURE_LS_FOLLOWLINKS |
|
|
FOLLOW_LINKS, /* L */ |
|
|
#endif |
|
|
#if ENABLE_FEATURE_LS_RECURSIVE |
|
|
DISP_RECURSIVE, /* R */ |
|
|
#endif |
|
|
#if ENABLE_FEATURE_HUMAN_READABLE |
|
|
LS_DISP_HR, /* h */ |
|
|
#endif |
|
|
#if ENABLE_SELINUX |
|
|
LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */ |
|
|
#endif |
|
|
#if ENABLE_FEATURE_AUTOWIDTH |
|
|
0, 0, /* T, w - ignored */ |
|
|
#endif |
|
|
#if ENABLE_SELINUX |
|
|
LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */ |
|
|
#endif |
|
|
(1U<<31) |
|
|
}; |
|
|
|
|
|
|
|
|
/* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */ |
|
|
#if ENABLE_FEATURE_LS_COLOR |
|
|
/* long option entry used only for --color, which has no short option |
|
|
* equivalent */ |
|
|
static const char ls_color_opt[] ALIGN1 = |
|
|
"color\0" Optional_argument "\xff" /* no short equivalent */ |
|
|
; |
|
|
#endif |
|
|
|
|
|
|
|
|
int ls_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
|
918 |
int ls_main(int argc UNUSED_PARAM, char **argv) |
int ls_main(int argc UNUSED_PARAM, char **argv) |
919 |
{ |
{ |
920 |
struct dnode **dnd; |
struct dnode **dnd; |
923 |
struct dnode *dn; |
struct dnode *dn; |
924 |
struct dnode *cur; |
struct dnode *cur; |
925 |
unsigned opt; |
unsigned opt; |
926 |
int nfiles; |
unsigned nfiles; |
927 |
int dnfiles; |
unsigned dnfiles; |
928 |
int dndirs; |
unsigned dndirs; |
929 |
int i; |
unsigned i; |
930 |
|
#if ENABLE_FEATURE_LS_COLOR |
931 |
|
/* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */ |
932 |
|
/* coreutils 6.10: |
933 |
|
* # ls --color=BOGUS |
934 |
|
* ls: invalid argument 'BOGUS' for '--color' |
935 |
|
* Valid arguments are: |
936 |
|
* 'always', 'yes', 'force' |
937 |
|
* 'never', 'no', 'none' |
938 |
|
* 'auto', 'tty', 'if-tty' |
939 |
|
* (and substrings: "--color=alwa" work too) |
940 |
|
*/ |
941 |
|
static const char ls_longopts[] ALIGN1 = |
942 |
|
"color\0" Optional_argument "\xff"; /* no short equivalent */ |
943 |
|
static const char color_str[] ALIGN1 = |
944 |
|
"always\0""yes\0""force\0" |
945 |
|
"auto\0""tty\0""if-tty\0"; |
946 |
/* need to initialize since --color has _an optional_ argument */ |
/* need to initialize since --color has _an optional_ argument */ |
947 |
USE_FEATURE_LS_COLOR(const char *color_opt = "always";) |
const char *color_opt = color_str; /* "always" */ |
948 |
|
#endif |
949 |
|
|
950 |
INIT_G(); |
INIT_G(); |
951 |
|
|
952 |
|
init_unicode(); |
953 |
|
|
954 |
all_fmt = LIST_SHORT | |
all_fmt = LIST_SHORT | |
955 |
(ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD)); |
(ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD)); |
956 |
|
|
957 |
#if ENABLE_FEATURE_AUTOWIDTH |
#if ENABLE_FEATURE_AUTOWIDTH |
958 |
/* Obtain the terminal width */ |
/* obtain the terminal width */ |
959 |
get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL); |
get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL); |
960 |
/* Go one less... */ |
/* go one less... */ |
961 |
terminal_width--; |
terminal_width--; |
962 |
#endif |
#endif |
963 |
|
|
964 |
/* process options */ |
/* process options */ |
965 |
USE_FEATURE_LS_COLOR(applet_long_options = ls_color_opt;) |
IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;) |
966 |
#if ENABLE_FEATURE_AUTOWIDTH |
#if ENABLE_FEATURE_AUTOWIDTH |
967 |
opt_complementary = "T+:w+"; /* -T N, -w N */ |
opt_complementary = "T+:w+"; /* -T N, -w N */ |
968 |
opt = getopt32(argv, ls_options, &tabstops, &terminal_width |
opt = getopt32(argv, ls_options, &tabstops, &terminal_width |
969 |
USE_FEATURE_LS_COLOR(, &color_opt)); |
IF_FEATURE_LS_COLOR(, &color_opt)); |
970 |
#else |
#else |
971 |
opt = getopt32(argv, ls_options USE_FEATURE_LS_COLOR(, &color_opt)); |
opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt)); |
972 |
#endif |
#endif |
973 |
for (i = 0; opt_flags[i] != (1U<<31); i++) { |
for (i = 0; opt_flags[i] != (1U<<31); i++) { |
974 |
if (opt & (1 << i)) { |
if (opt & (1 << i)) { |
1001 |
if (!p || (p[0] && strcmp(p, "none") != 0)) |
if (!p || (p[0] && strcmp(p, "none") != 0)) |
1002 |
show_color = 1; |
show_color = 1; |
1003 |
} |
} |
1004 |
if (opt & (1 << i)) { /* next flag after short options */ |
if (opt & OPT_color) { |
1005 |
if (strcmp("always", color_opt) == 0) |
if (color_opt[0] == 'n') |
|
show_color = 1; |
|
|
else if (strcmp("never", color_opt) == 0) |
|
1006 |
show_color = 0; |
show_color = 0; |
1007 |
else if (strcmp("auto", color_opt) == 0 && isatty(STDOUT_FILENO)) |
else switch (index_in_substrings(color_str, color_opt)) { |
1008 |
show_color = 1; |
case 3: |
1009 |
|
case 4: |
1010 |
|
case 5: |
1011 |
|
if (isatty(STDOUT_FILENO)) { |
1012 |
|
case 0: |
1013 |
|
case 1: |
1014 |
|
case 2: |
1015 |
|
show_color = 1; |
1016 |
|
} |
1017 |
|
} |
1018 |
} |
} |
1019 |
#endif |
#endif |
1020 |
|
|
1048 |
dn = NULL; |
dn = NULL; |
1049 |
nfiles = 0; |
nfiles = 0; |
1050 |
do { |
do { |
1051 |
/* ls w/o -l follows links on command line */ |
/* NB: follow links on command line unless -l or -s */ |
1052 |
cur = my_stat(*argv, *argv, !(all_fmt & STYLE_LONG)); |
cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS))); |
1053 |
argv++; |
argv++; |
1054 |
if (!cur) |
if (!cur) |
1055 |
continue; |
continue; |
1056 |
cur->allocated = 0; |
cur->fname_allocated = 0; |
1057 |
cur->next = dn; |
cur->next = dn; |
1058 |
dn = cur; |
dn = cur; |
1059 |
nfiles++; |
nfiles++; |
1060 |
} while (*argv); |
} while (*argv); |
1061 |
|
|
1062 |
|
/* nfiles _may_ be 0 here - try "ls doesnt_exist" */ |
1063 |
|
if (nfiles == 0) |
1064 |
|
return exit_code; |
1065 |
|
|
1066 |
/* now that we know how many files there are |
/* now that we know how many files there are |
1067 |
* allocate memory for an array to hold dnode pointers |
* allocate memory for an array to hold dnode pointers |
1068 |
*/ |
*/ |
1069 |
dnp = dnalloc(nfiles); |
dnp = dnalloc(nfiles); |
1070 |
for (i = 0, cur = dn; i < nfiles; i++) { |
for (i = 0; /* i < nfiles - detected via !dn below */; i++) { |
1071 |
dnp[i] = cur; /* save pointer to node in array */ |
dnp[i] = dn; /* save pointer to node in array */ |
1072 |
cur = cur->next; |
dn = dn->next; |
1073 |
|
if (!dn) |
1074 |
|
break; |
1075 |
} |
} |
1076 |
|
|
1077 |
if (all_fmt & DISP_NOLIST) { |
if (all_fmt & DISP_NOLIST) { |
1078 |
dnsort(dnp, nfiles); |
dnsort(dnp, nfiles); |
1079 |
if (nfiles > 0) |
showfiles(dnp, nfiles); |
|
showfiles(dnp, nfiles); |
|
1080 |
} else { |
} else { |
1081 |
dnd = splitdnarray(dnp, nfiles, SPLIT_DIR); |
dnd = splitdnarray(dnp, SPLIT_DIR); |
1082 |
dnf = splitdnarray(dnp, nfiles, SPLIT_FILE); |
dnf = splitdnarray(dnp, SPLIT_FILE); |
1083 |
dndirs = countdirs(dnp, nfiles); |
dndirs = count_dirs(dnp, SPLIT_DIR); |
1084 |
dnfiles = nfiles - dndirs; |
dnfiles = nfiles - dndirs; |
1085 |
if (dnfiles > 0) { |
if (dnfiles > 0) { |
1086 |
dnsort(dnf, dnfiles); |
dnsort(dnf, dnfiles); |
1090 |
} |
} |
1091 |
if (dndirs > 0) { |
if (dndirs > 0) { |
1092 |
dnsort(dnd, dndirs); |
dnsort(dnd, dndirs); |
1093 |
showdirs(dnd, dndirs, dnfiles == 0); |
showdirs(dnd, dnfiles == 0); |
1094 |
if (ENABLE_FEATURE_CLEAN_UP) |
if (ENABLE_FEATURE_CLEAN_UP) |
1095 |
free(dnd); |
free(dnd); |
1096 |
} |
} |
1097 |
} |
} |
1098 |
if (ENABLE_FEATURE_CLEAN_UP) |
if (ENABLE_FEATURE_CLEAN_UP) |
1099 |
dfree(dnp, nfiles); |
dfree(dnp); |
1100 |
return exit_code; |
return exit_code; |
1101 |
} |
} |