blob: 3e6980509e609edf9aa879387955319986566ddd [file] [log] [blame]
Erik Andersene49d5ec2000-02-08 19:58:47 +00001/* vi: set sw=4 ts=4: */
Eric Andersen9d3aba71999-10-06 09:04:55 +00002/*
Eric Andersencc8ed391999-10-05 16:24:54 +00003 * tiny-ls.c version 0.1.0: A minimalist 'ls'
4 * Copyright (C) 1996 Brian Candler <B.Candler@pobox.com>
Eric Andersen11c65522000-09-07 17:24:47 +00005 *
Bernhard Reutner-Fischercb448162006-04-12 07:35:12 +00006 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
Eric Andersencc8ed391999-10-05 16:24:54 +00007 */
8
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00009/* [date unknown. Perhaps before year 2000]
Eric Andersencc8ed391999-10-05 16:24:54 +000010 * 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
Eric Andersen77d92682001-05-23 20:32:09 +000012 * (i.e., the ones I couldn't live without :-) All features which involve
Eric Andersencc8ed391999-10-05 16:24:54 +000013 * linking in substantial chunks of libc can be disabled.
14 *
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
17 * it more portable.
18 *
19 * KNOWN BUGS:
Denys Vlasenko87c150c2009-10-03 01:14:15 +020020 * 1. hidden files can make column width too large
Erik Andersen9ffdaa62000-02-11 21:55:04 +000021 *
Eric Andersencc8ed391999-10-05 16:24:54 +000022 * NON-OPTIMAL BEHAVIOUR:
23 * 1. autowidth reads directories twice
24 * 2. if you do a short directory listing without filetype characters
25 * appended, there's no need to stat each one
26 * PORTABILITY:
27 * 1. requires lstat (BSD) - how do you do it without?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000028 *
29 * [2009-03]
30 * ls sorts listing now, and supports almost all options.
Eric Andersencc8ed391999-10-05 16:24:54 +000031 */
Denis Vlasenkob6adbf12007-05-26 19:00:18 +000032#include "libbb.h"
Denys Vlasenko42a8fd02009-07-11 21:36:13 +020033#include "unicode.h"
Denis Vlasenko99912ca2007-04-10 15:43:37 +000034
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +000035
Denis Vlasenko99912ca2007-04-10 15:43:37 +000036/* This is a NOEXEC applet. Be very careful! */
37
Eric Andersenf1142c52001-02-20 06:16:29 +000038
Denis Vlasenko245f91b2009-03-09 22:37:23 +000039#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
Denis Vlasenko5e34ff22009-04-21 11:09:40 +000045# undef IF_FEATURE_LS_TIMESTAMPS
46# undef IF_NOT_FEATURE_LS_TIMESTAMPS
Denis Vlasenko245f91b2009-03-09 22:37:23 +000047# define CONFIG_FEATURE_LS_TIMESTAMPS 1
48# define ENABLE_FEATURE_LS_TIMESTAMPS 1
Denis Vlasenko5e34ff22009-04-21 11:09:40 +000049# define IF_FEATURE_LS_TIMESTAMPS(...) __VA_ARGS__
50# define IF_NOT_FEATURE_LS_TIMESTAMPS(...)
Denis Vlasenko245f91b2009-03-09 22:37:23 +000051#endif
52
53
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000054enum {
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000055TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
56COLUMN_GAP = 2, /* includes the file type char */
57
58/* what is the overall style of the listing */
59STYLE_COLUMNS = 1 << 21, /* fill columns */
60STYLE_LONG = 2 << 21, /* one record per line, extended info */
61STYLE_SINGLE = 3 << 21, /* one record per line */
62STYLE_MASK = STYLE_SINGLE,
Eric Andersencc8ed391999-10-05 16:24:54 +000063
Eric Andersen11c65522000-09-07 17:24:47 +000064/* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
65/* what file information will be listed */
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000066LIST_INO = 1 << 0,
67LIST_BLOCKS = 1 << 1,
68LIST_MODEBITS = 1 << 2,
69LIST_NLINKS = 1 << 3,
70LIST_ID_NAME = 1 << 4,
71LIST_ID_NUMERIC = 1 << 5,
72LIST_CONTEXT = 1 << 6,
73LIST_SIZE = 1 << 7,
Denis Vlasenko3a014b82009-03-21 19:11:23 +000074//LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000075LIST_DATE_TIME = 1 << 9,
76LIST_FULLTIME = 1 << 10,
77LIST_FILENAME = 1 << 11,
78LIST_SYMLINK = 1 << 12,
79LIST_FILETYPE = 1 << 13,
80LIST_EXEC = 1 << 14,
81LIST_MASK = (LIST_EXEC << 1) - 1,
Eric Andersen11c65522000-09-07 17:24:47 +000082
83/* what files will be displayed */
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000084DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */
Denis Vlasenko656f7462006-10-28 13:02:55 +000085DISP_HIDDEN = 1 << 16, /* show filenames starting with . */
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000086DISP_DOT = 1 << 17, /* show . and .. */
87DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */
88DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */
89DISP_ROWS = 1 << 20, /* print across rows */
90DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
Manuel Novoa III cad53642003-03-19 09:13:01 +000091
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000092/* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
93SORT_FORWARD = 0, /* sort in reverse order */
94SORT_REVERSE = 1 << 27, /* sort in reverse order */
Eric Andersen11c65522000-09-07 17:24:47 +000095
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000096SORT_NAME = 0, /* sort by file name */
97SORT_SIZE = 1 << 28, /* sort by file size */
98SORT_ATIME = 2 << 28, /* sort by last access time */
99SORT_CTIME = 3 << 28, /* sort by last change time */
100SORT_MTIME = 4 << 28, /* sort by last modification time */
101SORT_VERSION = 5 << 28, /* sort by version */
102SORT_EXT = 6 << 28, /* sort by file name extension */
103SORT_DIR = 7 << 28, /* sort by file or directory */
104SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
Eric Andersencc8ed391999-10-05 16:24:54 +0000105
Eric Andersen11c65522000-09-07 17:24:47 +0000106/* which of the three times will be used */
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000107TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
108TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
109TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
Manuel Novoa III cad53642003-03-19 09:13:01 +0000110
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000111FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
Eric Andersencc8ed391999-10-05 16:24:54 +0000112
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000113LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
Eric Andersencc8ed391999-10-05 16:24:54 +0000114
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000115LIST_SHORT = LIST_FILENAME,
116LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
117 LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
118
119SPLIT_DIR = 1,
120SPLIT_FILE = 0,
121SPLIT_SUBDIR = 2,
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000122};
Eric Andersencc8ed391999-10-05 16:24:54 +0000123
Denis Vlasenko248ce912009-03-03 14:09:04 +0000124/* "[-]Cadil1", POSIX mandated options, busybox always supports */
125/* "[-]gnsx", POSIX non-mandated options, busybox always supports */
126/* "[-]Q" GNU option? busybox always supports */
127/* "[-]Ak" GNU options, busybox always supports */
128/* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
129/* "[-]p", POSIX non-mandated options, busybox optionally supports */
130/* "[-]SXvThw", GNU options, busybox optionally supports */
131/* "[-]K", SELinux mandated options, busybox optionally supports */
132/* "[-]e", I think we made this one up */
133static const char ls_options[] ALIGN1 =
134 "Cadil1gnsxQAk" /* 13 opts, total 13 */
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000135 IF_FEATURE_LS_TIMESTAMPS("cetu") /* 4, 17 */
136 IF_FEATURE_LS_SORTFILES("SXrv") /* 4, 21 */
137 IF_FEATURE_LS_FILETYPES("Fp") /* 2, 23 */
138 IF_FEATURE_LS_FOLLOWLINKS("L") /* 1, 24 */
139 IF_FEATURE_LS_RECURSIVE("R") /* 1, 25 */
140 IF_FEATURE_HUMAN_READABLE("h") /* 1, 26 */
Denys Vlasenko55083632009-07-02 14:25:51 +0200141 IF_SELINUX("KZ") /* 2, 28 */
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000142 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
Denis Vlasenko248ce912009-03-03 14:09:04 +0000143 ;
144enum {
145 //OPT_C = (1 << 0),
146 //OPT_a = (1 << 1),
147 //OPT_d = (1 << 2),
148 //OPT_i = (1 << 3),
149 //OPT_l = (1 << 4),
150 //OPT_1 = (1 << 5),
151 OPT_g = (1 << 6),
152 //OPT_n = (1 << 7),
153 //OPT_s = (1 << 8),
154 //OPT_x = (1 << 9),
155 OPT_Q = (1 << 10),
156 //OPT_A = (1 << 11),
157 //OPT_k = (1 << 12),
Denys Vlasenko55083632009-07-02 14:25:51 +0200158 OPTBIT_color = 13
159 + 4 * ENABLE_FEATURE_LS_TIMESTAMPS
160 + 4 * ENABLE_FEATURE_LS_SORTFILES
161 + 2 * ENABLE_FEATURE_LS_FILETYPES
162 + 1 * ENABLE_FEATURE_LS_FOLLOWLINKS
163 + 1 * ENABLE_FEATURE_LS_RECURSIVE
164 + 1 * ENABLE_FEATURE_HUMAN_READABLE
165 + 2 * ENABLE_SELINUX
166 + 2 * ENABLE_FEATURE_AUTOWIDTH,
167 OPT_color = 1 << OPTBIT_color,
Denis Vlasenko248ce912009-03-03 14:09:04 +0000168};
169
170enum {
171 LIST_MASK_TRIGGER = 0,
172 STYLE_MASK_TRIGGER = STYLE_MASK,
173 DISP_MASK_TRIGGER = DISP_ROWS,
174 SORT_MASK_TRIGGER = SORT_MASK,
175};
176
177/* TODO: simple toggles may be stored as OPT_xxx bits instead */
178static const unsigned opt_flags[] = {
179 LIST_SHORT | STYLE_COLUMNS, /* C */
180 DISP_HIDDEN | DISP_DOT, /* a */
181 DISP_NOLIST, /* d */
182 LIST_INO, /* i */
183 LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */
184 LIST_SHORT | STYLE_SINGLE, /* 1 */
185 0, /* g (don't show group) - handled via OPT_g */
186 LIST_ID_NUMERIC, /* n */
187 LIST_BLOCKS, /* s */
188 DISP_ROWS, /* x */
189 0, /* Q (quote filename) - handled via OPT_Q */
190 DISP_HIDDEN, /* A */
191 ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */
192#if ENABLE_FEATURE_LS_TIMESTAMPS
193 TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */
194 LIST_FULLTIME, /* e */
195 ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */
196 TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */
197#endif
198#if ENABLE_FEATURE_LS_SORTFILES
199 SORT_SIZE, /* S */
200 SORT_EXT, /* X */
201 SORT_REVERSE, /* r */
202 SORT_VERSION, /* v */
203#endif
204#if ENABLE_FEATURE_LS_FILETYPES
205 LIST_FILETYPE | LIST_EXEC, /* F */
206 LIST_FILETYPE, /* p */
207#endif
208#if ENABLE_FEATURE_LS_FOLLOWLINKS
209 FOLLOW_LINKS, /* L */
210#endif
211#if ENABLE_FEATURE_LS_RECURSIVE
212 DISP_RECURSIVE, /* R */
213#endif
214#if ENABLE_FEATURE_HUMAN_READABLE
215 LS_DISP_HR, /* h */
216#endif
217#if ENABLE_SELINUX
218 LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */
219#endif
220#if ENABLE_SELINUX
221 LIST_MODEBITS|LIST_ID_NAME|LIST_CONTEXT, /* Z */
222#endif
223 (1U<<31)
224 /* options after Z are not processed through opt_flags:
225 * T, w - ignored
226 */
227};
228
229
Eric Andersen11c65522000-09-07 17:24:47 +0000230/*
231 * a directory entry and its stat info are stored here
232 */
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200233struct dnode {
234 const char *name; /* the dir entry name */
235 const char *fullname; /* the dir entry name */
236 struct dnode *next; /* point at the next node */
237 smallint fname_allocated;
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000238 struct stat dstat; /* the file stat info */
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000239 IF_SELINUX(security_context_t sid;)
Eric Andersen11c65522000-09-07 17:24:47 +0000240};
Eric Andersen11c65522000-09-07 17:24:47 +0000241
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000242struct globals {
243#if ENABLE_FEATURE_LS_COLOR
244 smallint show_color;
245#endif
Denis Vlasenko4a689e92008-06-18 16:38:22 +0000246 smallint exit_code;
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000247 unsigned all_fmt;
Denis Vlasenko5c759602006-10-28 12:37:16 +0000248#if ENABLE_FEATURE_AUTOWIDTH
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000249 unsigned tabstops; // = COLUMN_GAP;
250 unsigned terminal_width; // = TERMINAL_WIDTH;
251#endif
252#if ENABLE_FEATURE_LS_TIMESTAMPS
253 /* Do time() just once. Saves one syscall per file for "ls -l" */
254 time_t current_time_t;
255#endif
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +0100256} FIX_ALIASING;
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000257#define G (*(struct globals*)&bb_common_bufsiz1)
258#if ENABLE_FEATURE_LS_COLOR
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +0200259# define show_color (G.show_color )
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000260#else
261enum { show_color = 0 };
262#endif
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +0200263#define exit_code (G.exit_code )
264#define all_fmt (G.all_fmt )
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000265#if ENABLE_FEATURE_AUTOWIDTH
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +0200266# define tabstops (G.tabstops )
267# define terminal_width (G.terminal_width)
Eric Andersenf5d5e772001-01-24 23:34:48 +0000268#else
Denis Vlasenko5c759602006-10-28 12:37:16 +0000269enum {
270 tabstops = COLUMN_GAP,
271 terminal_width = TERMINAL_WIDTH,
272};
Eric Andersen11c65522000-09-07 17:24:47 +0000273#endif
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000274#define current_time_t (G.current_time_t)
Denis Vlasenko7049ff82008-06-25 09:53:17 +0000275#define INIT_G() do { \
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +0200276 /* we have to zero it out because of NOEXEC */ \
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000277 memset(&G, 0, sizeof(G)); \
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000278 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
279 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
280 IF_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
Denis Vlasenko7049ff82008-06-25 09:53:17 +0000281} while (0)
Eric Andersencc8ed391999-10-05 16:24:54 +0000282
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000283
Denis Vlasenkodc757aa2007-06-30 08:04:05 +0000284static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
Matt Kraai9a6e67c2000-10-13 18:03:21 +0000285{
Glenn L McGrath4d001292003-01-06 01:11:50 +0000286 struct stat dstat;
287 struct dnode *cur;
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000288 IF_SELINUX(security_context_t sid = NULL;)
Glenn L McGrath4d001292003-01-06 01:11:50 +0000289
Denis Vlasenko2110aa92007-02-28 23:14:06 +0000290 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
Denis Vlasenko5c759602006-10-28 12:37:16 +0000291#if ENABLE_SELINUX
Bernhard Reutner-Fischerd2c306e2006-05-29 12:10:23 +0000292 if (is_selinux_enabled()) {
Denis Vlasenko5c759602006-10-28 12:37:16 +0000293 getfilecon(fullname, &sid);
Rob Landley60158cb2005-05-03 06:25:50 +0000294 }
Eric Andersen9e480452003-07-03 10:07:04 +0000295#endif
Bernhard Reutner-Fischerd2c306e2006-05-29 12:10:23 +0000296 if (stat(fullname, &dstat)) {
Denis Vlasenko0c97c9d2007-10-01 11:58:38 +0000297 bb_simple_perror_msg(fullname);
Denis Vlasenko4a689e92008-06-18 16:38:22 +0000298 exit_code = EXIT_FAILURE;
Glenn L McGrath4d001292003-01-06 01:11:50 +0000299 return 0;
Matt Kraai9a6e67c2000-10-13 18:03:21 +0000300 }
Denis Vlasenko5c759602006-10-28 12:37:16 +0000301 } else {
302#if ENABLE_SELINUX
303 if (is_selinux_enabled()) {
Denis Vlasenko2110aa92007-02-28 23:14:06 +0000304 lgetfilecon(fullname, &sid);
Rob Landley60158cb2005-05-03 06:25:50 +0000305 }
Eric Andersen9e480452003-07-03 10:07:04 +0000306#endif
Bernhard Reutner-Fischerd2c306e2006-05-29 12:10:23 +0000307 if (lstat(fullname, &dstat)) {
Denis Vlasenko0c97c9d2007-10-01 11:58:38 +0000308 bb_simple_perror_msg(fullname);
Denis Vlasenko4a689e92008-06-18 16:38:22 +0000309 exit_code = EXIT_FAILURE;
Eric Andersen9e480452003-07-03 10:07:04 +0000310 return 0;
311 }
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000312 }
Glenn L McGrath4d001292003-01-06 01:11:50 +0000313
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200314 cur = xmalloc(sizeof(*cur));
Glenn L McGrath4d001292003-01-06 01:11:50 +0000315 cur->fullname = fullname;
316 cur->name = name;
317 cur->dstat = dstat;
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000318 IF_SELINUX(cur->sid = sid;)
Glenn L McGrath4d001292003-01-06 01:11:50 +0000319 return cur;
Eric Andersen79565b62000-08-11 18:10:21 +0000320}
321
Denis Vlasenkoc1969f62009-03-18 22:39:34 +0000322/* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
323 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
324 * 3/7:multiplexed char/block device)
325 * and we use 0 for unknown and 15 for executables (see below) */
326#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
327#define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
328#define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
329/* 036 black foreground 050 black background
330 037 red foreground 051 red background
331 040 green foreground 052 green background
332 041 brown foreground 053 brown background
333 042 blue foreground 054 blue background
334 043 magenta (purple) foreground 055 magenta background
335 044 cyan (light blue) foreground 056 cyan background
336 045 gray foreground 057 white background
337*/
338#define COLOR(mode) ( \
339 /*un fi chr dir blk file link sock exe */ \
340 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
341 [TYPEINDEX(mode)])
342/* Select normal (0) [actually "reset all"] or bold (1)
343 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
344 * let's use 7 for "impossible" types, just for fun)
345 * Note: coreutils 6.9 uses inverted red for setuid binaries.
346 */
347#define ATTR(mode) ( \
348 /*un fi chr dir blk file link sock exe */ \
349 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
350 [TYPEINDEX(mode)])
351
Denis Vlasenko5c759602006-10-28 12:37:16 +0000352#if ENABLE_FEATURE_LS_COLOR
Denis Vlasenkoc1969f62009-03-18 22:39:34 +0000353/* mode of zero is interpreted as "unknown" (stat failed) */
Eric Andersen3ad0bd92002-03-20 09:13:48 +0000354static char fgcolor(mode_t mode)
355{
Rob Landley9947a242006-06-15 22:11:10 +0000356 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
Eric Andersen3ad0bd92002-03-20 09:13:48 +0000357 return COLOR(0xF000); /* File is executable ... */
358 return COLOR(mode);
359}
Denis Vlasenkoc1969f62009-03-18 22:39:34 +0000360static char bold(mode_t mode)
Eric Andersen3ad0bd92002-03-20 09:13:48 +0000361{
Rob Landley9947a242006-06-15 22:11:10 +0000362 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
Eric Andersen3ad0bd92002-03-20 09:13:48 +0000363 return ATTR(0xF000); /* File is executable ... */
364 return ATTR(mode);
365}
366#endif
367
Denis Vlasenko5c759602006-10-28 12:37:16 +0000368#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
Eric Andersen11c65522000-09-07 17:24:47 +0000369static char append_char(mode_t mode)
Eric Andersen79565b62000-08-11 18:10:21 +0000370{
Manuel Novoa III cad53642003-03-19 09:13:01 +0000371 if (!(all_fmt & LIST_FILETYPE))
Eric Andersen11c65522000-09-07 17:24:47 +0000372 return '\0';
Rob Landley9947a242006-06-15 22:11:10 +0000373 if (S_ISDIR(mode))
374 return '/';
375 if (!(all_fmt & LIST_EXEC))
376 return '\0';
377 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000378 return '*';
379 return APPCHAR(mode);
Eric Andersen11c65522000-09-07 17:24:47 +0000380}
381#endif
Eric Andersen79565b62000-08-11 18:10:21 +0000382
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200383static unsigned count_dirs(struct dnode **dn, int which)
Eric Andersen11c65522000-09-07 17:24:47 +0000384{
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200385 unsigned dirs, all;
Eric Andersen11c65522000-09-07 17:24:47 +0000386
Denis Vlasenko5c759602006-10-28 12:37:16 +0000387 if (!dn)
388 return 0;
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200389
390 dirs = all = 0;
391 for (; *dn; dn++) {
Denis Vlasenkodc757aa2007-06-30 08:04:05 +0000392 const char *name;
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200393
394 all++;
395 if (!S_ISDIR((*dn)->dstat.st_mode))
Denis Vlasenko5c759602006-10-28 12:37:16 +0000396 continue;
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200397 name = (*dn)->name;
398 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
399 /* or if it's not . or .. */
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200400 || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
Denis Vlasenko5c759602006-10-28 12:37:16 +0000401 ) {
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000402 dirs++;
Denis Vlasenko5c759602006-10-28 12:37:16 +0000403 }
Eric Andersen11c65522000-09-07 17:24:47 +0000404 }
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200405 return which != SPLIT_FILE ? dirs : all - dirs;
Eric Andersen11c65522000-09-07 17:24:47 +0000406}
407
Eric Andersen11c65522000-09-07 17:24:47 +0000408/* get memory to hold an array of pointers */
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200409static struct dnode **dnalloc(unsigned num)
Eric Andersen11c65522000-09-07 17:24:47 +0000410{
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000411 if (num < 1)
Denis Vlasenko5c759602006-10-28 12:37:16 +0000412 return NULL;
Eric Andersen11c65522000-09-07 17:24:47 +0000413
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200414 num++; /* so that we have terminating NULL */
Denis Vlasenko5c759602006-10-28 12:37:16 +0000415 return xzalloc(num * sizeof(struct dnode *));
Eric Andersen11c65522000-09-07 17:24:47 +0000416}
417
Denis Vlasenko5c759602006-10-28 12:37:16 +0000418#if ENABLE_FEATURE_LS_RECURSIVE
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200419static void dfree(struct dnode **dnp)
Eric Andersen11c65522000-09-07 17:24:47 +0000420{
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200421 unsigned i;
Eric Andersen11c65522000-09-07 17:24:47 +0000422
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000423 if (dnp == NULL)
424 return;
Eric Andersen11c65522000-09-07 17:24:47 +0000425
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200426 for (i = 0; dnp[i]; i++) {
Rob Landley26314862006-05-02 19:46:52 +0000427 struct dnode *cur = dnp[i];
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200428 if (cur->fname_allocated)
Denys Vlasenkoffd47742009-10-03 11:42:33 +0200429 free((char*)cur->fullname);
430 free(cur);
Eric Andersen11c65522000-09-07 17:24:47 +0000431 }
Denys Vlasenkoffd47742009-10-03 11:42:33 +0200432 free(dnp);
Eric Andersen11c65522000-09-07 17:24:47 +0000433}
Rob Landleyc44bc982006-05-28 01:19:06 +0000434#else
Denis Vlasenko2405ad62007-01-19 21:24:17 +0000435#define dfree(...) ((void)0)
Eric Andersen20aab262001-07-19 22:28:02 +0000436#endif
Eric Andersen11c65522000-09-07 17:24:47 +0000437
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200438/* Returns NULL-terminated malloced vector of pointers (or NULL) */
439static struct dnode **splitdnarray(struct dnode **dn, int which)
Eric Andersen11c65522000-09-07 17:24:47 +0000440{
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200441 unsigned dncnt, d;
Eric Andersen11c65522000-09-07 17:24:47 +0000442 struct dnode **dnp;
443
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200444 if (dn == NULL)
Denis Vlasenko5c759602006-10-28 12:37:16 +0000445 return NULL;
Eric Andersen11c65522000-09-07 17:24:47 +0000446
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200447 /* count how many dirs or files there are */
448 dncnt = count_dirs(dn, which);
Eric Andersen11c65522000-09-07 17:24:47 +0000449
450 /* allocate a file array and a dir array */
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000451 dnp = dnalloc(dncnt);
Eric Andersen11c65522000-09-07 17:24:47 +0000452
453 /* copy the entrys into the file or dir array */
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200454 for (d = 0; *dn; dn++) {
455 if (S_ISDIR((*dn)->dstat.st_mode)) {
Denis Vlasenkodc757aa2007-06-30 08:04:05 +0000456 const char *name;
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200457
Denis Vlasenko5c759602006-10-28 12:37:16 +0000458 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
459 continue;
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200460 name = (*dn)->name;
Denis Vlasenko5c759602006-10-28 12:37:16 +0000461 if ((which & SPLIT_DIR)
462 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
463 ) {
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200464 dnp[d++] = *dn;
Manuel Novoa III cad53642003-03-19 09:13:01 +0000465 }
466 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200467 dnp[d++] = *dn;
Eric Andersen11c65522000-09-07 17:24:47 +0000468 }
469 }
Denis Vlasenko5c759602006-10-28 12:37:16 +0000470 return dnp;
Eric Andersen11c65522000-09-07 17:24:47 +0000471}
472
Denis Vlasenko5c759602006-10-28 12:37:16 +0000473#if ENABLE_FEATURE_LS_SORTFILES
Rob Landley425e7582006-05-03 20:22:03 +0000474static int sortcmp(const void *a, const void *b)
Eric Andersen11c65522000-09-07 17:24:47 +0000475{
Rob Landley425e7582006-05-03 20:22:03 +0000476 struct dnode *d1 = *(struct dnode **)a;
477 struct dnode *d2 = *(struct dnode **)b;
Denis Vlasenko5c759602006-10-28 12:37:16 +0000478 unsigned sort_opts = all_fmt & SORT_MASK;
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100479 off_t dif;
Eric Andersen11c65522000-09-07 17:24:47 +0000480
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000481 dif = 0; /* assume SORT_NAME */
482 // TODO: use pre-initialized function pointer
483 // instead of branch forest
Eric Andersen11c65522000-09-07 17:24:47 +0000484 if (sort_opts == SORT_SIZE) {
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100485 dif = (d2->dstat.st_size - d1->dstat.st_size);
Eric Andersen11c65522000-09-07 17:24:47 +0000486 } else if (sort_opts == SORT_ATIME) {
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100487 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
Eric Andersen11c65522000-09-07 17:24:47 +0000488 } else if (sort_opts == SORT_CTIME) {
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100489 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
Eric Andersen11c65522000-09-07 17:24:47 +0000490 } else if (sort_opts == SORT_MTIME) {
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100491 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
Eric Andersen11c65522000-09-07 17:24:47 +0000492 } else if (sort_opts == SORT_DIR) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000493 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000494 /* } else if (sort_opts == SORT_VERSION) { */
495 /* } else if (sort_opts == SORT_EXT) { */
Eric Andersen11c65522000-09-07 17:24:47 +0000496 }
Eric Andersen11c65522000-09-07 17:24:47 +0000497 if (dif == 0) {
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100498 /* sort by name, or tie_breaker for other sorts */
499 if (ENABLE_LOCALE_SUPPORT)
500 dif = strcoll(d1->name, d2->name);
501 else
502 dif = strcmp(d1->name, d2->name);
Eric Andersen11c65522000-09-07 17:24:47 +0000503 }
504
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100505 /* Make dif fit into an int */
506 if (sizeof(dif) > sizeof(int)) {
Denys Vlasenko6b01b712010-01-24 22:52:21 +0100507 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
508 /* shift leaving only "int" worth of bits */
509 if (dif != 0) {
510 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100511 }
Eric Andersen11c65522000-09-07 17:24:47 +0000512 }
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100513
514 return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
Eric Andersen11c65522000-09-07 17:24:47 +0000515}
516
Rob Landley425e7582006-05-03 20:22:03 +0000517static void dnsort(struct dnode **dn, int size)
Eric Andersen11c65522000-09-07 17:24:47 +0000518{
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000519 qsort(dn, size, sizeof(*dn), sortcmp);
Eric Andersen11c65522000-09-07 17:24:47 +0000520}
Rob Landleyea224be2006-06-18 20:20:07 +0000521#else
Denis Vlasenko2405ad62007-01-19 21:24:17 +0000522#define dnsort(dn, size) ((void)0)
Eric Andersen11c65522000-09-07 17:24:47 +0000523#endif
524
Rob Landleyea224be2006-06-18 20:20:07 +0000525
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100526static unsigned calc_name_len(const char *name)
Eric Andersen11c65522000-09-07 17:24:47 +0000527{
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100528 unsigned len;
529 uni_stat_t uni_stat;
Eric Andersen11c65522000-09-07 17:24:47 +0000530
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100531 // TODO: quote tab as \t, etc, if -Q
532 name = printable_string(&uni_stat, name);
Eric Andersen11c65522000-09-07 17:24:47 +0000533
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100534 if (!(option_mask32 & OPT_Q)) {
535 return uni_stat.unicode_width;
536 }
537
538 len = 2 + uni_stat.unicode_width;
539 while (*name) {
540 if (*name == '"' || *name == '\\') {
541 len++;
Manuel Novoa III cad53642003-03-19 09:13:01 +0000542 }
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100543 name++;
Eric Andersen11c65522000-09-07 17:24:47 +0000544 }
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100545 return len;
Eric Andersen11c65522000-09-07 17:24:47 +0000546}
547
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000548
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100549/* Return the number of used columns.
550 * Note that only STYLE_COLUMNS uses return value.
551 * STYLE_SINGLE and STYLE_LONG don't care.
552 * coreutils 7.2 also supports:
553 * ls -b (--escape) = octal escapes (although it doesn't look like working)
554 * ls -N (--literal) = not escape at all
Denys Vlasenko0683d4d2009-10-03 10:53:36 +0200555 */
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100556static unsigned print_name(const char *name)
Denys Vlasenko87c150c2009-10-03 01:14:15 +0200557{
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100558 unsigned len;
559 uni_stat_t uni_stat;
560
561 // TODO: quote tab as \t, etc, if -Q
562 name = printable_string(&uni_stat, name);
563
564 if (!(option_mask32 & OPT_Q)) {
565 fputs(name, stdout);
566 return uni_stat.unicode_width;
Denys Vlasenko87c150c2009-10-03 01:14:15 +0200567 }
568
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100569 len = 2 + uni_stat.unicode_width;
570 putchar('"');
571 while (*name) {
572 if (*name == '"' || *name == '\\') {
573 putchar('\\');
574 len++;
Eric Andersen11c65522000-09-07 17:24:47 +0000575 }
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100576 putchar(*name++);
Eric Andersen11c65522000-09-07 17:24:47 +0000577 }
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100578 putchar('"');
579 return len;
Eric Andersen11c65522000-09-07 17:24:47 +0000580}
581
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100582/* Return the number of used columns.
583 * Note that only STYLE_COLUMNS uses return value,
584 * STYLE_SINGLE and STYLE_LONG don't care.
585 */
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200586static NOINLINE unsigned list_single(const struct dnode *dn)
Eric Andersen11c65522000-09-07 17:24:47 +0000587{
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200588 unsigned column = 0;
Denis Vlasenko4d3a8122009-03-27 17:22:00 +0000589 char *lpath = lpath; /* for compiler */
Denis Vlasenko5c759602006-10-28 12:37:16 +0000590#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
Eric Andersen11c65522000-09-07 17:24:47 +0000591 struct stat info;
592 char append;
593#endif
594
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200595 /* Never happens:
Glenn L McGrath4d001292003-01-06 01:11:50 +0000596 if (dn->fullname == NULL)
Denis Vlasenko5c759602006-10-28 12:37:16 +0000597 return 0;
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200598 */
Eric Andersen11c65522000-09-07 17:24:47 +0000599
Denis Vlasenko5c759602006-10-28 12:37:16 +0000600#if ENABLE_FEATURE_LS_FILETYPES
Eric Andersen11c65522000-09-07 17:24:47 +0000601 append = append_char(dn->dstat.st_mode);
602#endif
603
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000604 /* Do readlink early, so that if it fails, error message
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100605 * does not appear *inside* the "ls -l" line */
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000606 if (all_fmt & LIST_SYMLINK)
607 if (S_ISLNK(dn->dstat.st_mode))
608 lpath = xmalloc_readlink_or_warn(dn->fullname);
609
610 if (all_fmt & LIST_INO)
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100611 column += printf("%7llu ", (long long) dn->dstat.st_ino);
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000612 if (all_fmt & LIST_BLOCKS)
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100613 column += printf("%4"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000614 if (all_fmt & LIST_MODEBITS)
615 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
616 if (all_fmt & LIST_NLINKS)
617 column += printf("%4lu ", (long) dn->dstat.st_nlink);
Denis Vlasenko5c759602006-10-28 12:37:16 +0000618#if ENABLE_FEATURE_LS_USERNAME
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000619 if (all_fmt & LIST_ID_NAME) {
620 if (option_mask32 & OPT_g) {
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100621 column += printf("%-8.8s ",
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000622 get_cached_username(dn->dstat.st_uid));
623 } else {
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100624 column += printf("%-8.8s %-8.8s ",
Denis Vlasenko2405ad62007-01-19 21:24:17 +0000625 get_cached_username(dn->dstat.st_uid),
626 get_cached_groupname(dn->dstat.st_gid));
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000627 }
628 }
Eric Andersen11c65522000-09-07 17:24:47 +0000629#endif
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000630 if (all_fmt & LIST_ID_NUMERIC) {
631 if (option_mask32 & OPT_g)
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100632 column += printf("%-8u ", (int) dn->dstat.st_uid);
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000633 else
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100634 column += printf("%-8u %-8u ",
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000635 (int) dn->dstat.st_uid,
636 (int) dn->dstat.st_gid);
637 }
638 if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
639 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
640 column += printf("%4u, %3u ",
641 (int) major(dn->dstat.st_rdev),
642 (int) minor(dn->dstat.st_rdev));
643 } else {
644 if (all_fmt & LS_DISP_HR) {
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100645 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
Denys Vlasenko0bf44d02009-10-13 01:25:09 +0200646 /* print st_size, show one fractional, use suffixes */
647 make_human_readable_str(dn->dstat.st_size, 1, 0)
648 );
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000649 } else {
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000650 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000651 }
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000652 }
653 }
Denis Vlasenko5c759602006-10-28 12:37:16 +0000654#if ENABLE_FEATURE_LS_TIMESTAMPS
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100655 if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
656 char *filetime;
657 time_t ttime = dn->dstat.st_mtime;
658 if (all_fmt & TIME_ACCESS)
659 ttime = dn->dstat.st_atime;
660 if (all_fmt & TIME_CHANGE)
661 ttime = dn->dstat.st_ctime;
662 filetime = ctime(&ttime);
663 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
664 if (all_fmt & LIST_FULLTIME)
665 column += printf("%.24s ", filetime);
666 else { /* LIST_DATE_TIME */
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000667 /* current_time_t ~== time(NULL) */
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100668 time_t age = current_time_t - ttime;
669 printf("%.6s ", filetime + 4); /* "Jun 30" */
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000670 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
671 /* hh:mm if less than 6 months old */
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100672 printf("%.5s ", filetime + 11);
673 } else { /* year. buggy if year > 9999 ;) */
674 printf(" %.4s ", filetime + 20);
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000675 }
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000676 column += 13;
677 }
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100678 }
Eric Andersen11c65522000-09-07 17:24:47 +0000679#endif
Denis Vlasenko5c759602006-10-28 12:37:16 +0000680#if ENABLE_SELINUX
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000681 if (all_fmt & LIST_CONTEXT) {
682 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
683 freecon(dn->sid);
684 }
Eric Andersen9e480452003-07-03 10:07:04 +0000685#endif
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000686 if (all_fmt & LIST_FILENAME) {
Denis Vlasenkoc61852a2006-11-29 11:09:43 +0000687#if ENABLE_FEATURE_LS_COLOR
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000688 if (show_color) {
689 info.st_mode = 0; /* for fgcolor() */
690 lstat(dn->fullname, &info);
691 printf("\033[%u;%um", bold(info.st_mode),
692 fgcolor(info.st_mode));
693 }
694#endif
695 column += print_name(dn->name);
696 if (show_color) {
697 printf("\033[0m");
698 }
699 }
700 if (all_fmt & LIST_SYMLINK) {
701 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
702 printf(" -> ");
703#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
704#if ENABLE_FEATURE_LS_COLOR
705 info.st_mode = 0; /* for fgcolor() */
706#endif
707 if (stat(dn->fullname, &info) == 0) {
708 append = append_char(info.st_mode);
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000709 }
Denis Vlasenkoc61852a2006-11-29 11:09:43 +0000710#endif
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000711#if ENABLE_FEATURE_LS_COLOR
712 if (show_color) {
713 printf("\033[%u;%um", bold(info.st_mode),
714 fgcolor(info.st_mode));
715 }
716#endif
717 column += print_name(lpath) + 4;
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000718 if (show_color) {
719 printf("\033[0m");
720 }
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000721 free(lpath);
Eric Andersen11c65522000-09-07 17:24:47 +0000722 }
723 }
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000724#if ENABLE_FEATURE_LS_FILETYPES
725 if (all_fmt & LIST_FILETYPE) {
726 if (append) {
727 putchar(append);
728 column++;
729 }
730 }
731#endif
Eric Andersen11c65522000-09-07 17:24:47 +0000732
Glenn L McGrath4d001292003-01-06 01:11:50 +0000733 return column;
Eric Andersen11c65522000-09-07 17:24:47 +0000734}
735
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100736static void showfiles(struct dnode **dn, unsigned nfiles)
737{
738 unsigned i, ncols, nrows, row, nc;
739 unsigned column = 0;
740 unsigned nexttab = 0;
741 unsigned column_width = 0; /* used only by STYLE_COLUMNS */
742
743 if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
744 ncols = 1;
745 } else {
746 /* find the longest file name, use that as the column width */
747 for (i = 0; dn[i]; i++) {
748 int len = calc_name_len(dn[i]->name);
749 if (column_width < len)
750 column_width = len;
751 }
752 column_width += tabstops +
753 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
754 ((all_fmt & LIST_INO) ? 8 : 0) +
755 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
756 ncols = (int) (terminal_width / column_width);
757 }
758
759 if (ncols > 1) {
760 nrows = nfiles / ncols;
761 if (nrows * ncols < nfiles)
762 nrows++; /* round up fractionals */
763 } else {
764 nrows = nfiles;
765 ncols = 1;
766 }
767
768 for (row = 0; row < nrows; row++) {
769 for (nc = 0; nc < ncols; nc++) {
770 /* reach into the array based on the column and row */
771 if (all_fmt & DISP_ROWS)
772 i = (row * ncols) + nc; /* display across row */
773 else
774 i = (nc * nrows) + row; /* display by column */
775 if (i < nfiles) {
776 if (column > 0) {
777 nexttab -= column;
778 printf("%*s", nexttab, "");
779 column += nexttab;
780 }
781 nexttab = column + column_width;
782 column += list_single(dn[i]);
783 }
784 }
785 putchar('\n');
786 column = 0;
787 }
788}
789
790
791#if ENABLE_DESKTOP
792/* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
793 * If any of the -l, -n, -s options is specified, each list
794 * of files within the directory shall be preceded by a
795 * status line indicating the number of file system blocks
796 * occupied by files in the directory in 512-byte units if
797 * the -k option is not specified, or 1024-byte units if the
798 * -k option is specified, rounded up to the next integral
799 * number of units.
800 */
801/* by Jorgen Overgaard (jorgen AT antistaten.se) */
802static off_t calculate_blocks(struct dnode **dn)
803{
804 uoff_t blocks = 1;
805 if (dn) {
806 while (*dn) {
807 /* st_blocks is in 512 byte blocks */
808 blocks += (*dn)->dstat.st_blocks;
809 dn++;
810 }
811 }
812
813 /* Even though standard says use 512 byte blocks, coreutils use 1k */
814 /* Actually, we round up by calculating (blocks + 1) / 2,
815 * "+ 1" was done when we initialized blocks to 1 */
816 return blocks >> 1;
817}
818#endif
819
820
821static struct dnode **list_dir(const char *, unsigned *);
822
823static void showdirs(struct dnode **dn, int first)
824{
825 unsigned nfiles;
826 unsigned dndirs;
827 struct dnode **subdnp;
828 struct dnode **dnd;
829
830 /* Never happens:
831 if (dn == NULL || ndirs < 1) {
832 return;
833 }
834 */
835
836 for (; *dn; dn++) {
837 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
838 if (!first)
839 bb_putchar('\n');
840 first = 0;
841 printf("%s:\n", (*dn)->fullname);
842 }
843 subdnp = list_dir((*dn)->fullname, &nfiles);
844#if ENABLE_DESKTOP
845 if ((all_fmt & STYLE_MASK) == STYLE_LONG)
846 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
847#endif
848 if (nfiles > 0) {
849 /* list all files at this level */
850 dnsort(subdnp, nfiles);
851 showfiles(subdnp, nfiles);
852 if (ENABLE_FEATURE_LS_RECURSIVE
853 && (all_fmt & DISP_RECURSIVE)
854 ) {
855 /* recursive - list the sub-dirs */
856 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
857 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
858 if (dndirs > 0) {
859 dnsort(dnd, dndirs);
860 showdirs(dnd, 0);
861 /* free the array of dnode pointers to the dirs */
862 free(dnd);
863 }
864 }
865 /* free the dnodes and the fullname mem */
866 dfree(subdnp);
867 }
868 }
869}
870
871
872/* Returns NULL-terminated malloced vector of pointers (or NULL) */
873static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
874{
875 struct dnode *dn, *cur, **dnp;
876 struct dirent *entry;
877 DIR *dir;
878 unsigned i, nfiles;
879
880 /* Never happens:
881 if (path == NULL)
882 return NULL;
883 */
884
885 *nfiles_p = 0;
886 dir = warn_opendir(path);
887 if (dir == NULL) {
888 exit_code = EXIT_FAILURE;
889 return NULL; /* could not open the dir */
890 }
891 dn = NULL;
892 nfiles = 0;
893 while ((entry = readdir(dir)) != NULL) {
894 char *fullname;
895
896 /* are we going to list the file- it may be . or .. or a hidden file */
897 if (entry->d_name[0] == '.') {
898 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
899 && !(all_fmt & DISP_DOT)
900 ) {
901 continue;
902 }
903 if (!(all_fmt & DISP_HIDDEN))
904 continue;
905 }
906 fullname = concat_path_file(path, entry->d_name);
907 cur = my_stat(fullname, bb_basename(fullname), 0);
908 if (!cur) {
909 free(fullname);
910 continue;
911 }
912 cur->fname_allocated = 1;
913 cur->next = dn;
914 dn = cur;
915 nfiles++;
916 }
917 closedir(dir);
918
919 if (dn == NULL)
920 return NULL;
921
922 /* now that we know how many files there are
923 * allocate memory for an array to hold dnode pointers
924 */
925 *nfiles_p = nfiles;
926 dnp = dnalloc(nfiles);
927 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
928 dnp[i] = dn; /* save pointer to node in array */
929 dn = dn->next;
930 if (!dn)
931 break;
932 }
933
934 return dnp;
935}
936
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000937
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000938int ls_main(int argc UNUSED_PARAM, char **argv)
Eric Andersen11c65522000-09-07 17:24:47 +0000939{
Glenn L McGrath792cae52004-01-18 05:15:16 +0000940 struct dnode **dnd;
941 struct dnode **dnf;
942 struct dnode **dnp;
943 struct dnode *dn;
944 struct dnode *cur;
Denis Vlasenko67b23e62006-10-03 21:00:06 +0000945 unsigned opt;
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200946 unsigned nfiles;
947 unsigned dnfiles;
948 unsigned dndirs;
949 unsigned i;
Denys Vlasenkoae05dd42009-07-03 12:22:19 +0200950#if ENABLE_FEATURE_LS_COLOR
951 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
952 /* coreutils 6.10:
953 * # ls --color=BOGUS
954 * ls: invalid argument 'BOGUS' for '--color'
955 * Valid arguments are:
956 * 'always', 'yes', 'force'
957 * 'never', 'no', 'none'
958 * 'auto', 'tty', 'if-tty'
959 * (and substrings: "--color=alwa" work too)
960 */
961 static const char ls_longopts[] ALIGN1 =
962 "color\0" Optional_argument "\xff"; /* no short equivalent */
963 static const char color_str[] ALIGN1 =
964 "always\0""yes\0""force\0"
965 "auto\0""tty\0""if-tty\0";
Denis Vlasenko51f1b6c2008-07-15 05:21:47 +0000966 /* need to initialize since --color has _an optional_ argument */
Denys Vlasenkoae05dd42009-07-03 12:22:19 +0200967 const char *color_opt = color_str; /* "always" */
968#endif
Glenn L McGrath792cae52004-01-18 05:15:16 +0000969
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000970 INIT_G();
Denis Vlasenkoe0554432007-01-19 22:03:06 +0000971
Denys Vlasenko28055022010-01-04 20:49:58 +0100972 init_unicode();
Denys Vlasenko42a8fd02009-07-11 21:36:13 +0200973
Rob Landley2b8a05a2006-06-20 17:43:01 +0000974 all_fmt = LIST_SHORT |
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000975 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
Glenn L McGrath792cae52004-01-18 05:15:16 +0000976
Denis Vlasenko5c759602006-10-28 12:37:16 +0000977#if ENABLE_FEATURE_AUTOWIDTH
Denys Vlasenkoae05dd42009-07-03 12:22:19 +0200978 /* obtain the terminal width */
Denis Vlasenkof893da82007-08-09 08:27:24 +0000979 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
Denys Vlasenkoae05dd42009-07-03 12:22:19 +0200980 /* go one less... */
Eric Andersen8efe9672003-09-15 08:33:45 +0000981 terminal_width--;
Eric Andersen6d687812003-11-04 23:16:48 +0000982#endif
Eric Andersen11c65522000-09-07 17:24:47 +0000983
Eric Andersen11c65522000-09-07 17:24:47 +0000984 /* process options */
Denys Vlasenkoae05dd42009-07-03 12:22:19 +0200985 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
Denis Vlasenko5c759602006-10-28 12:37:16 +0000986#if ENABLE_FEATURE_AUTOWIDTH
Denis Vlasenko1d426652008-03-17 09:09:09 +0000987 opt_complementary = "T+:w+"; /* -T N, -w N */
988 opt = getopt32(argv, ls_options, &tabstops, &terminal_width
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000989 IF_FEATURE_LS_COLOR(, &color_opt));
Glenn L McGrath792cae52004-01-18 05:15:16 +0000990#else
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000991 opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
Manuel Novoa III cad53642003-03-19 09:13:01 +0000992#endif
Eric Andersend07cf592004-02-05 13:52:03 +0000993 for (i = 0; opt_flags[i] != (1U<<31); i++) {
Glenn L McGrath792cae52004-01-18 05:15:16 +0000994 if (opt & (1 << i)) {
Denis Vlasenko5c759602006-10-28 12:37:16 +0000995 unsigned flags = opt_flags[i];
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000996
Denis Vlasenko5c759602006-10-28 12:37:16 +0000997 if (flags & LIST_MASK_TRIGGER)
Manuel Novoa III cad53642003-03-19 09:13:01 +0000998 all_fmt &= ~LIST_MASK;
Denis Vlasenko5c759602006-10-28 12:37:16 +0000999 if (flags & STYLE_MASK_TRIGGER)
Manuel Novoa III cad53642003-03-19 09:13:01 +00001000 all_fmt &= ~STYLE_MASK;
Denis Vlasenko94cf69f2006-10-28 12:37:51 +00001001 if (flags & SORT_MASK_TRIGGER)
Manuel Novoa III cad53642003-03-19 09:13:01 +00001002 all_fmt &= ~SORT_MASK;
Denis Vlasenko5c759602006-10-28 12:37:16 +00001003 if (flags & DISP_MASK_TRIGGER)
Manuel Novoa III cad53642003-03-19 09:13:01 +00001004 all_fmt &= ~DISP_MASK;
Denis Vlasenko5c759602006-10-28 12:37:16 +00001005 if (flags & TIME_MASK)
Manuel Novoa III cad53642003-03-19 09:13:01 +00001006 all_fmt &= ~TIME_MASK;
Denis Vlasenko5c759602006-10-28 12:37:16 +00001007 if (flags & LIST_CONTEXT)
Eric Andersen9e480452003-07-03 10:07:04 +00001008 all_fmt |= STYLE_SINGLE;
Denis Vlasenko2110aa92007-02-28 23:14:06 +00001009 /* huh?? opt cannot be 'l' */
1010 //if (LS_DISP_HR && opt == 'l')
1011 // all_fmt &= ~LS_DISP_HR;
Manuel Novoa III cad53642003-03-19 09:13:01 +00001012 all_fmt |= flags;
1013 }
Eric Andersen11c65522000-09-07 17:24:47 +00001014 }
1015
Denis Vlasenko5c759602006-10-28 12:37:16 +00001016#if ENABLE_FEATURE_LS_COLOR
1017 /* find color bit value - last position for short getopt */
1018 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1019 char *p = getenv("LS_COLORS");
1020 /* LS_COLORS is unset, or (not empty && not "none") ? */
Denis Vlasenko51f1b6c2008-07-15 05:21:47 +00001021 if (!p || (p[0] && strcmp(p, "none") != 0))
Tim Rikerc1ef7bd2006-01-25 00:08:53 +00001022 show_color = 1;
Denis Vlasenko5c759602006-10-28 12:37:16 +00001023 }
Denys Vlasenkoae05dd42009-07-03 12:22:19 +02001024 if (opt & OPT_color) {
1025 if (color_opt[0] == 'n')
Denis Vlasenko5c759602006-10-28 12:37:16 +00001026 show_color = 0;
Denys Vlasenkoae05dd42009-07-03 12:22:19 +02001027 else switch (index_in_substrings(color_str, color_opt)) {
1028 case 3:
1029 case 4:
1030 case 5:
1031 if (isatty(STDOUT_FILENO)) {
1032 case 0:
1033 case 1:
1034 case 2:
1035 show_color = 1;
1036 }
1037 }
Paul Fox156dc412005-08-01 19:33:30 +00001038 }
1039#endif
1040
Eric Andersen11c65522000-09-07 17:24:47 +00001041 /* sort out which command line options take precedence */
Denis Vlasenko5c759602006-10-28 12:37:16 +00001042 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
Manuel Novoa III cad53642003-03-19 09:13:01 +00001043 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
Rob Landleyea224be2006-06-18 20:20:07 +00001044 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1045 if (all_fmt & TIME_CHANGE)
1046 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1047 if (all_fmt & TIME_ACCESS)
1048 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1049 }
Manuel Novoa III cad53642003-03-19 09:13:01 +00001050 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1051 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
Denis Vlasenko5c759602006-10-28 12:37:16 +00001052 if (ENABLE_FEATURE_LS_USERNAME)
1053 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1054 all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
Eric Andersenc7bda1c2004-03-15 08:29:22 +00001055
Eric Andersen11c65522000-09-07 17:24:47 +00001056 /* choose a display format */
Rob Landleyea224be2006-06-18 20:20:07 +00001057 if (!(all_fmt & STYLE_MASK))
Eric Andersen70060d22004-03-27 10:02:48 +00001058 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
Eric Andersen11c65522000-09-07 17:24:47 +00001059
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +00001060 argv += optind;
1061 if (!argv[0])
1062 *--argv = (char*)".";
"Vladimir N. Oleynik"a8c23aa2005-09-05 15:06:57 +00001063
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +00001064 if (argv[1])
1065 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
Eric Andersen11c65522000-09-07 17:24:47 +00001066
Denis Vlasenko5c759602006-10-28 12:37:16 +00001067 /* stuff the command line file names into a dnode array */
Glenn L McGrathe3906fc2002-08-22 18:13:54 +00001068 dn = NULL;
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +00001069 nfiles = 0;
1070 do {
Denis Vlasenko11a6f9b2009-03-03 13:20:22 +00001071 /* NB: follow links on command line unless -l or -s */
1072 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +00001073 argv++;
Glenn L McGrath4d001292003-01-06 01:11:50 +00001074 if (!cur)
Eric Andersen11c65522000-09-07 17:24:47 +00001075 continue;
Denys Vlasenko76c7d952009-10-03 01:15:47 +02001076 cur->fname_allocated = 0;
Glenn L McGrathe3906fc2002-08-22 18:13:54 +00001077 cur->next = dn;
1078 dn = cur;
Eric Andersen11c65522000-09-07 17:24:47 +00001079 nfiles++;
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +00001080 } while (*argv);
Eric Andersen11c65522000-09-07 17:24:47 +00001081
Denys Vlasenko76c7d952009-10-03 01:15:47 +02001082 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1083 if (nfiles == 0)
1084 return exit_code;
1085
Eric Andersen11c65522000-09-07 17:24:47 +00001086 /* now that we know how many files there are
Denis Vlasenko656f7462006-10-28 13:02:55 +00001087 * allocate memory for an array to hold dnode pointers
1088 */
Glenn L McGrathe3906fc2002-08-22 18:13:54 +00001089 dnp = dnalloc(nfiles);
Denys Vlasenko76c7d952009-10-03 01:15:47 +02001090 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1091 dnp[i] = dn; /* save pointer to node in array */
1092 dn = dn->next;
1093 if (!dn)
1094 break;
Eric Andersen11c65522000-09-07 17:24:47 +00001095 }
1096
Manuel Novoa III cad53642003-03-19 09:13:01 +00001097 if (all_fmt & DISP_NOLIST) {
Denis Vlasenko94cf69f2006-10-28 12:37:51 +00001098 dnsort(dnp, nfiles);
Denys Vlasenko76c7d952009-10-03 01:15:47 +02001099 showfiles(dnp, nfiles);
Eric Andersen11c65522000-09-07 17:24:47 +00001100 } else {
Denys Vlasenkocae409c2009-10-03 11:43:48 +02001101 dnd = splitdnarray(dnp, SPLIT_DIR);
1102 dnf = splitdnarray(dnp, SPLIT_FILE);
1103 dndirs = count_dirs(dnp, SPLIT_DIR);
Glenn L McGrathe3906fc2002-08-22 18:13:54 +00001104 dnfiles = nfiles - dndirs;
Eric Andersen11c65522000-09-07 17:24:47 +00001105 if (dnfiles > 0) {
Denis Vlasenko94cf69f2006-10-28 12:37:51 +00001106 dnsort(dnf, dnfiles);
Eric Andersen11c65522000-09-07 17:24:47 +00001107 showfiles(dnf, dnfiles);
Rob Landley26314862006-05-02 19:46:52 +00001108 if (ENABLE_FEATURE_CLEAN_UP)
1109 free(dnf);
Eric Andersen11c65522000-09-07 17:24:47 +00001110 }
1111 if (dndirs > 0) {
Denis Vlasenko94cf69f2006-10-28 12:37:51 +00001112 dnsort(dnd, dndirs);
Denys Vlasenkocae409c2009-10-03 11:43:48 +02001113 showdirs(dnd, dnfiles == 0);
Rob Landley26314862006-05-02 19:46:52 +00001114 if (ENABLE_FEATURE_CLEAN_UP)
1115 free(dnd);
Eric Andersen11c65522000-09-07 17:24:47 +00001116 }
1117 }
Rob Landley26314862006-05-02 19:46:52 +00001118 if (ENABLE_FEATURE_CLEAN_UP)
Denys Vlasenkocae409c2009-10-03 11:43:48 +02001119 dfree(dnp);
Denis Vlasenko4a689e92008-06-18 16:38:22 +00001120 return exit_code;
Eric Andersencc8ed391999-10-05 16:24:54 +00001121}