blob: 1197f7d715714202a98a69aa0d1ec1642832ba28 [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 {
Manuel Novoa III cad53642003-03-19 09:13:01 +000055
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000056TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */
57COLUMN_GAP = 2, /* includes the file type char */
58
59/* what is the overall style of the listing */
60STYLE_COLUMNS = 1 << 21, /* fill columns */
61STYLE_LONG = 2 << 21, /* one record per line, extended info */
62STYLE_SINGLE = 3 << 21, /* one record per line */
63STYLE_MASK = STYLE_SINGLE,
Eric Andersencc8ed391999-10-05 16:24:54 +000064
Eric Andersen11c65522000-09-07 17:24:47 +000065/* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */
66/* what file information will be listed */
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000067LIST_INO = 1 << 0,
68LIST_BLOCKS = 1 << 1,
69LIST_MODEBITS = 1 << 2,
70LIST_NLINKS = 1 << 3,
71LIST_ID_NAME = 1 << 4,
72LIST_ID_NUMERIC = 1 << 5,
73LIST_CONTEXT = 1 << 6,
74LIST_SIZE = 1 << 7,
Denis Vlasenko3a014b82009-03-21 19:11:23 +000075//LIST_DEV = 1 << 8, - unused, synonym to LIST_SIZE
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000076LIST_DATE_TIME = 1 << 9,
77LIST_FULLTIME = 1 << 10,
78LIST_FILENAME = 1 << 11,
79LIST_SYMLINK = 1 << 12,
80LIST_FILETYPE = 1 << 13,
81LIST_EXEC = 1 << 14,
82LIST_MASK = (LIST_EXEC << 1) - 1,
Eric Andersen11c65522000-09-07 17:24:47 +000083
84/* what files will be displayed */
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000085DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */
Denis Vlasenko656f7462006-10-28 13:02:55 +000086DISP_HIDDEN = 1 << 16, /* show filenames starting with . */
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000087DISP_DOT = 1 << 17, /* show . and .. */
88DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */
89DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */
90DISP_ROWS = 1 << 20, /* print across rows */
91DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1),
Manuel Novoa III cad53642003-03-19 09:13:01 +000092
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000093/* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */
94SORT_FORWARD = 0, /* sort in reverse order */
95SORT_REVERSE = 1 << 27, /* sort in reverse order */
Eric Andersen11c65522000-09-07 17:24:47 +000096
Denis Vlasenko94cf69f2006-10-28 12:37:51 +000097SORT_NAME = 0, /* sort by file name */
98SORT_SIZE = 1 << 28, /* sort by file size */
99SORT_ATIME = 2 << 28, /* sort by last access time */
100SORT_CTIME = 3 << 28, /* sort by last change time */
101SORT_MTIME = 4 << 28, /* sort by last modification time */
102SORT_VERSION = 5 << 28, /* sort by version */
103SORT_EXT = 6 << 28, /* sort by file name extension */
104SORT_DIR = 7 << 28, /* sort by file or directory */
105SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES,
Eric Andersencc8ed391999-10-05 16:24:54 +0000106
Eric Andersen11c65522000-09-07 17:24:47 +0000107/* which of the three times will be used */
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000108TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
109TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS,
110TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS,
Manuel Novoa III cad53642003-03-19 09:13:01 +0000111
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000112FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS,
Eric Andersencc8ed391999-10-05 16:24:54 +0000113
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000114LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE,
Eric Andersencc8ed391999-10-05 16:24:54 +0000115
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000116LIST_SHORT = LIST_FILENAME,
117LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \
118 LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK,
119
120SPLIT_DIR = 1,
121SPLIT_FILE = 0,
122SPLIT_SUBDIR = 2,
123
124};
Eric Andersencc8ed391999-10-05 16:24:54 +0000125
Denis Vlasenko248ce912009-03-03 14:09:04 +0000126/* "[-]Cadil1", POSIX mandated options, busybox always supports */
127/* "[-]gnsx", POSIX non-mandated options, busybox always supports */
128/* "[-]Q" GNU option? busybox always supports */
129/* "[-]Ak" GNU options, busybox always supports */
130/* "[-]FLRctur", POSIX mandated options, busybox optionally supports */
131/* "[-]p", POSIX non-mandated options, busybox optionally supports */
132/* "[-]SXvThw", GNU options, busybox optionally supports */
133/* "[-]K", SELinux mandated options, busybox optionally supports */
134/* "[-]e", I think we made this one up */
135static const char ls_options[] ALIGN1 =
136 "Cadil1gnsxQAk" /* 13 opts, total 13 */
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000137 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 */
Denys Vlasenko55083632009-07-02 14:25:51 +0200143 IF_SELINUX("KZ") /* 2, 28 */
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000144 IF_FEATURE_AUTOWIDTH("T:w:") /* 2, 30 */
Denis Vlasenko248ce912009-03-03 14:09:04 +0000145 ;
146enum {
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),
Denys Vlasenko55083632009-07-02 14:25:51 +0200160 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,
Denis Vlasenko248ce912009-03-03 14:09:04 +0000170};
171
172enum {
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 */
180static 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
Eric Andersen11c65522000-09-07 17:24:47 +0000232/*
233 * a directory entry and its stat info are stored here
234 */
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200235struct dnode {
236 const char *name; /* the dir entry name */
237 const char *fullname; /* the dir entry name */
238 struct dnode *next; /* point at the next node */
239 smallint fname_allocated;
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000240 struct stat dstat; /* the file stat info */
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000241 IF_SELINUX(security_context_t sid;)
Eric Andersen11c65522000-09-07 17:24:47 +0000242};
Eric Andersen11c65522000-09-07 17:24:47 +0000243
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000244struct globals {
245#if ENABLE_FEATURE_LS_COLOR
246 smallint show_color;
247#endif
Denis Vlasenko4a689e92008-06-18 16:38:22 +0000248 smallint exit_code;
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000249 unsigned all_fmt;
Denis Vlasenko5c759602006-10-28 12:37:16 +0000250#if ENABLE_FEATURE_AUTOWIDTH
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000251 unsigned tabstops; // = COLUMN_GAP;
252 unsigned terminal_width; // = TERMINAL_WIDTH;
253#endif
254#if ENABLE_FEATURE_LS_TIMESTAMPS
255 /* Do time() just once. Saves one syscall per file for "ls -l" */
256 time_t current_time_t;
257#endif
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +0100258} FIX_ALIASING;
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000259#define G (*(struct globals*)&bb_common_bufsiz1)
260#if ENABLE_FEATURE_LS_COLOR
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +0200261# define show_color (G.show_color )
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000262#else
263enum { show_color = 0 };
264#endif
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +0200265#define exit_code (G.exit_code )
266#define all_fmt (G.all_fmt )
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000267#if ENABLE_FEATURE_AUTOWIDTH
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +0200268# define tabstops (G.tabstops )
269# define terminal_width (G.terminal_width)
Eric Andersenf5d5e772001-01-24 23:34:48 +0000270#else
Denis Vlasenko5c759602006-10-28 12:37:16 +0000271enum {
272 tabstops = COLUMN_GAP,
273 terminal_width = TERMINAL_WIDTH,
274};
Eric Andersen11c65522000-09-07 17:24:47 +0000275#endif
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000276#define current_time_t (G.current_time_t)
Denis Vlasenko7049ff82008-06-25 09:53:17 +0000277#define INIT_G() do { \
Denys Vlasenko1b34d4f2009-09-30 02:39:57 +0200278 /* we have to zero it out because of NOEXEC */ \
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000279 memset(&G, 0, sizeof(G)); \
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000280 IF_FEATURE_AUTOWIDTH(tabstops = COLUMN_GAP;) \
281 IF_FEATURE_AUTOWIDTH(terminal_width = TERMINAL_WIDTH;) \
282 IF_FEATURE_LS_TIMESTAMPS(time(&current_time_t);) \
Denis Vlasenko7049ff82008-06-25 09:53:17 +0000283} while (0)
Eric Andersencc8ed391999-10-05 16:24:54 +0000284
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000285
Denis Vlasenkodc757aa2007-06-30 08:04:05 +0000286static struct dnode *my_stat(const char *fullname, const char *name, int force_follow)
Matt Kraai9a6e67c2000-10-13 18:03:21 +0000287{
Glenn L McGrath4d001292003-01-06 01:11:50 +0000288 struct stat dstat;
289 struct dnode *cur;
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000290 IF_SELINUX(security_context_t sid = NULL;)
Glenn L McGrath4d001292003-01-06 01:11:50 +0000291
Denis Vlasenko2110aa92007-02-28 23:14:06 +0000292 if ((all_fmt & FOLLOW_LINKS) || force_follow) {
Denis Vlasenko5c759602006-10-28 12:37:16 +0000293#if ENABLE_SELINUX
Bernhard Reutner-Fischerd2c306e2006-05-29 12:10:23 +0000294 if (is_selinux_enabled()) {
Denis Vlasenko5c759602006-10-28 12:37:16 +0000295 getfilecon(fullname, &sid);
Rob Landley60158cb2005-05-03 06:25:50 +0000296 }
Eric Andersen9e480452003-07-03 10:07:04 +0000297#endif
Bernhard Reutner-Fischerd2c306e2006-05-29 12:10:23 +0000298 if (stat(fullname, &dstat)) {
Denis Vlasenko0c97c9d2007-10-01 11:58:38 +0000299 bb_simple_perror_msg(fullname);
Denis Vlasenko4a689e92008-06-18 16:38:22 +0000300 exit_code = EXIT_FAILURE;
Glenn L McGrath4d001292003-01-06 01:11:50 +0000301 return 0;
Matt Kraai9a6e67c2000-10-13 18:03:21 +0000302 }
Denis Vlasenko5c759602006-10-28 12:37:16 +0000303 } else {
304#if ENABLE_SELINUX
305 if (is_selinux_enabled()) {
Denis Vlasenko2110aa92007-02-28 23:14:06 +0000306 lgetfilecon(fullname, &sid);
Rob Landley60158cb2005-05-03 06:25:50 +0000307 }
Eric Andersen9e480452003-07-03 10:07:04 +0000308#endif
Bernhard Reutner-Fischerd2c306e2006-05-29 12:10:23 +0000309 if (lstat(fullname, &dstat)) {
Denis Vlasenko0c97c9d2007-10-01 11:58:38 +0000310 bb_simple_perror_msg(fullname);
Denis Vlasenko4a689e92008-06-18 16:38:22 +0000311 exit_code = EXIT_FAILURE;
Eric Andersen9e480452003-07-03 10:07:04 +0000312 return 0;
313 }
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000314 }
Glenn L McGrath4d001292003-01-06 01:11:50 +0000315
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200316 cur = xmalloc(sizeof(*cur));
Glenn L McGrath4d001292003-01-06 01:11:50 +0000317 cur->fullname = fullname;
318 cur->name = name;
319 cur->dstat = dstat;
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000320 IF_SELINUX(cur->sid = sid;)
Glenn L McGrath4d001292003-01-06 01:11:50 +0000321 return cur;
Eric Andersen79565b62000-08-11 18:10:21 +0000322}
323
Denis Vlasenkoc1969f62009-03-18 22:39:34 +0000324/* FYI type values: 1:fifo 2:char 4:dir 6:blk 8:file 10:link 12:socket
325 * (various wacky OSes: 13:Sun door 14:BSD whiteout 5:XENIX named file
326 * 3/7:multiplexed char/block device)
327 * and we use 0 for unknown and 15 for executables (see below) */
328#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
329#define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
330#define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)])
331/* 036 black foreground 050 black background
332 037 red foreground 051 red background
333 040 green foreground 052 green background
334 041 brown foreground 053 brown background
335 042 blue foreground 054 blue background
336 043 magenta (purple) foreground 055 magenta background
337 044 cyan (light blue) foreground 056 cyan background
338 045 gray foreground 057 white background
339*/
340#define COLOR(mode) ( \
341 /*un fi chr dir blk file link sock exe */ \
342 "\037\043\043\045\042\045\043\043\000\045\044\045\043\045\045\040" \
343 [TYPEINDEX(mode)])
344/* Select normal (0) [actually "reset all"] or bold (1)
345 * (other attributes are 2:dim 4:underline 5:blink 7:reverse,
346 * let's use 7 for "impossible" types, just for fun)
347 * Note: coreutils 6.9 uses inverted red for setuid binaries.
348 */
349#define ATTR(mode) ( \
350 /*un fi chr dir blk file link sock exe */ \
351 "\01\00\01\07\01\07\01\07\00\07\01\07\01\07\07\01" \
352 [TYPEINDEX(mode)])
353
Denis Vlasenko5c759602006-10-28 12:37:16 +0000354#if ENABLE_FEATURE_LS_COLOR
Denis Vlasenkoc1969f62009-03-18 22:39:34 +0000355/* mode of zero is interpreted as "unknown" (stat failed) */
Eric Andersen3ad0bd92002-03-20 09:13:48 +0000356static char fgcolor(mode_t mode)
357{
Rob Landley9947a242006-06-15 22:11:10 +0000358 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
Eric Andersen3ad0bd92002-03-20 09:13:48 +0000359 return COLOR(0xF000); /* File is executable ... */
360 return COLOR(mode);
361}
Denis Vlasenkoc1969f62009-03-18 22:39:34 +0000362static char bold(mode_t mode)
Eric Andersen3ad0bd92002-03-20 09:13:48 +0000363{
Rob Landley9947a242006-06-15 22:11:10 +0000364 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
Eric Andersen3ad0bd92002-03-20 09:13:48 +0000365 return ATTR(0xF000); /* File is executable ... */
366 return ATTR(mode);
367}
368#endif
369
Denis Vlasenko5c759602006-10-28 12:37:16 +0000370#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
Eric Andersen11c65522000-09-07 17:24:47 +0000371static char append_char(mode_t mode)
Eric Andersen79565b62000-08-11 18:10:21 +0000372{
Manuel Novoa III cad53642003-03-19 09:13:01 +0000373 if (!(all_fmt & LIST_FILETYPE))
Eric Andersen11c65522000-09-07 17:24:47 +0000374 return '\0';
Rob Landley9947a242006-06-15 22:11:10 +0000375 if (S_ISDIR(mode))
376 return '/';
377 if (!(all_fmt & LIST_EXEC))
378 return '\0';
379 if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000380 return '*';
381 return APPCHAR(mode);
Eric Andersen11c65522000-09-07 17:24:47 +0000382}
383#endif
Eric Andersen79565b62000-08-11 18:10:21 +0000384
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200385static unsigned count_dirs(struct dnode **dn, int which)
Eric Andersen11c65522000-09-07 17:24:47 +0000386{
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200387 unsigned dirs, all;
Eric Andersen11c65522000-09-07 17:24:47 +0000388
Denis Vlasenko5c759602006-10-28 12:37:16 +0000389 if (!dn)
390 return 0;
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200391
392 dirs = all = 0;
393 for (; *dn; dn++) {
Denis Vlasenkodc757aa2007-06-30 08:04:05 +0000394 const char *name;
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200395
396 all++;
397 if (!S_ISDIR((*dn)->dstat.st_mode))
Denis Vlasenko5c759602006-10-28 12:37:16 +0000398 continue;
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200399 name = (*dn)->name;
400 if (which != SPLIT_SUBDIR /* if not requested to skip . / .. */
401 /* or if it's not . or .. */
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200402 || name[0] != '.' || (name[1] && (name[1] != '.' || name[2]))
Denis Vlasenko5c759602006-10-28 12:37:16 +0000403 ) {
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000404 dirs++;
Denis Vlasenko5c759602006-10-28 12:37:16 +0000405 }
Eric Andersen11c65522000-09-07 17:24:47 +0000406 }
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200407 return which != SPLIT_FILE ? dirs : all - dirs;
Eric Andersen11c65522000-09-07 17:24:47 +0000408}
409
Eric Andersen11c65522000-09-07 17:24:47 +0000410/* get memory to hold an array of pointers */
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200411static struct dnode **dnalloc(unsigned num)
Eric Andersen11c65522000-09-07 17:24:47 +0000412{
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000413 if (num < 1)
Denis Vlasenko5c759602006-10-28 12:37:16 +0000414 return NULL;
Eric Andersen11c65522000-09-07 17:24:47 +0000415
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200416 num++; /* so that we have terminating NULL */
Denis Vlasenko5c759602006-10-28 12:37:16 +0000417 return xzalloc(num * sizeof(struct dnode *));
Eric Andersen11c65522000-09-07 17:24:47 +0000418}
419
Denis Vlasenko5c759602006-10-28 12:37:16 +0000420#if ENABLE_FEATURE_LS_RECURSIVE
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200421static void dfree(struct dnode **dnp)
Eric Andersen11c65522000-09-07 17:24:47 +0000422{
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200423 unsigned i;
Eric Andersen11c65522000-09-07 17:24:47 +0000424
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000425 if (dnp == NULL)
426 return;
Eric Andersen11c65522000-09-07 17:24:47 +0000427
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200428 for (i = 0; dnp[i]; i++) {
Rob Landley26314862006-05-02 19:46:52 +0000429 struct dnode *cur = dnp[i];
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200430 if (cur->fname_allocated)
Denys Vlasenkoffd47742009-10-03 11:42:33 +0200431 free((char*)cur->fullname);
432 free(cur);
Eric Andersen11c65522000-09-07 17:24:47 +0000433 }
Denys Vlasenkoffd47742009-10-03 11:42:33 +0200434 free(dnp);
Eric Andersen11c65522000-09-07 17:24:47 +0000435}
Rob Landleyc44bc982006-05-28 01:19:06 +0000436#else
Denis Vlasenko2405ad62007-01-19 21:24:17 +0000437#define dfree(...) ((void)0)
Eric Andersen20aab262001-07-19 22:28:02 +0000438#endif
Eric Andersen11c65522000-09-07 17:24:47 +0000439
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200440/* Returns NULL-terminated malloced vector of pointers (or NULL) */
441static struct dnode **splitdnarray(struct dnode **dn, int which)
Eric Andersen11c65522000-09-07 17:24:47 +0000442{
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200443 unsigned dncnt, d;
Eric Andersen11c65522000-09-07 17:24:47 +0000444 struct dnode **dnp;
445
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200446 if (dn == NULL)
Denis Vlasenko5c759602006-10-28 12:37:16 +0000447 return NULL;
Eric Andersen11c65522000-09-07 17:24:47 +0000448
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200449 /* count how many dirs or files there are */
450 dncnt = count_dirs(dn, which);
Eric Andersen11c65522000-09-07 17:24:47 +0000451
452 /* allocate a file array and a dir array */
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000453 dnp = dnalloc(dncnt);
Eric Andersen11c65522000-09-07 17:24:47 +0000454
455 /* copy the entrys into the file or dir array */
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200456 for (d = 0; *dn; dn++) {
457 if (S_ISDIR((*dn)->dstat.st_mode)) {
Denis Vlasenkodc757aa2007-06-30 08:04:05 +0000458 const char *name;
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200459
Denis Vlasenko5c759602006-10-28 12:37:16 +0000460 if (!(which & (SPLIT_DIR|SPLIT_SUBDIR)))
461 continue;
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200462 name = (*dn)->name;
Denis Vlasenko5c759602006-10-28 12:37:16 +0000463 if ((which & SPLIT_DIR)
464 || name[0]!='.' || (name[1] && (name[1]!='.' || name[2]))
465 ) {
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200466 dnp[d++] = *dn;
Manuel Novoa III cad53642003-03-19 09:13:01 +0000467 }
468 } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) {
Denys Vlasenkocae409c2009-10-03 11:43:48 +0200469 dnp[d++] = *dn;
Eric Andersen11c65522000-09-07 17:24:47 +0000470 }
471 }
Denis Vlasenko5c759602006-10-28 12:37:16 +0000472 return dnp;
Eric Andersen11c65522000-09-07 17:24:47 +0000473}
474
Denis Vlasenko5c759602006-10-28 12:37:16 +0000475#if ENABLE_FEATURE_LS_SORTFILES
Rob Landley425e7582006-05-03 20:22:03 +0000476static int sortcmp(const void *a, const void *b)
Eric Andersen11c65522000-09-07 17:24:47 +0000477{
Rob Landley425e7582006-05-03 20:22:03 +0000478 struct dnode *d1 = *(struct dnode **)a;
479 struct dnode *d2 = *(struct dnode **)b;
Denis Vlasenko5c759602006-10-28 12:37:16 +0000480 unsigned sort_opts = all_fmt & SORT_MASK;
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100481 off_t dif;
Eric Andersen11c65522000-09-07 17:24:47 +0000482
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000483 dif = 0; /* assume SORT_NAME */
484 // TODO: use pre-initialized function pointer
485 // instead of branch forest
Eric Andersen11c65522000-09-07 17:24:47 +0000486 if (sort_opts == SORT_SIZE) {
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100487 dif = (d2->dstat.st_size - d1->dstat.st_size);
Eric Andersen11c65522000-09-07 17:24:47 +0000488 } else if (sort_opts == SORT_ATIME) {
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100489 dif = (d2->dstat.st_atime - d1->dstat.st_atime);
Eric Andersen11c65522000-09-07 17:24:47 +0000490 } else if (sort_opts == SORT_CTIME) {
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100491 dif = (d2->dstat.st_ctime - d1->dstat.st_ctime);
Eric Andersen11c65522000-09-07 17:24:47 +0000492 } else if (sort_opts == SORT_MTIME) {
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100493 dif = (d2->dstat.st_mtime - d1->dstat.st_mtime);
Eric Andersen11c65522000-09-07 17:24:47 +0000494 } else if (sort_opts == SORT_DIR) {
Manuel Novoa III cad53642003-03-19 09:13:01 +0000495 dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode);
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000496 /* } else if (sort_opts == SORT_VERSION) { */
497 /* } else if (sort_opts == SORT_EXT) { */
Eric Andersen11c65522000-09-07 17:24:47 +0000498 }
Eric Andersen11c65522000-09-07 17:24:47 +0000499 if (dif == 0) {
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100500 /* sort by name, or tie_breaker for other sorts */
501 if (ENABLE_LOCALE_SUPPORT)
502 dif = strcoll(d1->name, d2->name);
503 else
504 dif = strcmp(d1->name, d2->name);
Eric Andersen11c65522000-09-07 17:24:47 +0000505 }
506
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100507 /* Make dif fit into an int */
508 if (sizeof(dif) > sizeof(int)) {
Denys Vlasenko6b01b712010-01-24 22:52:21 +0100509 enum { BITS_TO_SHIFT = 8 * (sizeof(dif) - sizeof(int)) };
510 /* shift leaving only "int" worth of bits */
511 if (dif != 0) {
512 dif = 1 | (int)((uoff_t)dif >> BITS_TO_SHIFT);
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100513 }
Eric Andersen11c65522000-09-07 17:24:47 +0000514 }
Denys Vlasenko7be97c52010-01-18 13:02:27 +0100515
516 return (all_fmt & SORT_REVERSE) ? -(int)dif : (int)dif;
Eric Andersen11c65522000-09-07 17:24:47 +0000517}
518
Rob Landley425e7582006-05-03 20:22:03 +0000519static void dnsort(struct dnode **dn, int size)
Eric Andersen11c65522000-09-07 17:24:47 +0000520{
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000521 qsort(dn, size, sizeof(*dn), sortcmp);
Eric Andersen11c65522000-09-07 17:24:47 +0000522}
Rob Landleyea224be2006-06-18 20:20:07 +0000523#else
Denis Vlasenko2405ad62007-01-19 21:24:17 +0000524#define dnsort(dn, size) ((void)0)
Eric Andersen11c65522000-09-07 17:24:47 +0000525#endif
526
Rob Landleyea224be2006-06-18 20:20:07 +0000527
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100528static unsigned calc_name_len(const char *name)
Eric Andersen11c65522000-09-07 17:24:47 +0000529{
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100530 unsigned len;
531 uni_stat_t uni_stat;
Eric Andersen11c65522000-09-07 17:24:47 +0000532
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100533 // TODO: quote tab as \t, etc, if -Q
534 name = printable_string(&uni_stat, name);
Eric Andersen11c65522000-09-07 17:24:47 +0000535
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100536 if (!(option_mask32 & OPT_Q)) {
537 return uni_stat.unicode_width;
538 }
539
540 len = 2 + uni_stat.unicode_width;
541 while (*name) {
542 if (*name == '"' || *name == '\\') {
543 len++;
Manuel Novoa III cad53642003-03-19 09:13:01 +0000544 }
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100545 name++;
Eric Andersen11c65522000-09-07 17:24:47 +0000546 }
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100547 return len;
Eric Andersen11c65522000-09-07 17:24:47 +0000548}
549
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000550
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100551/* Return the number of used columns.
552 * Note that only STYLE_COLUMNS uses return value.
553 * STYLE_SINGLE and STYLE_LONG don't care.
554 * coreutils 7.2 also supports:
555 * ls -b (--escape) = octal escapes (although it doesn't look like working)
556 * ls -N (--literal) = not escape at all
Denys Vlasenko0683d4d2009-10-03 10:53:36 +0200557 */
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100558static unsigned print_name(const char *name)
Denys Vlasenko87c150c2009-10-03 01:14:15 +0200559{
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100560 unsigned len;
561 uni_stat_t uni_stat;
562
563 // TODO: quote tab as \t, etc, if -Q
564 name = printable_string(&uni_stat, name);
565
566 if (!(option_mask32 & OPT_Q)) {
567 fputs(name, stdout);
568 return uni_stat.unicode_width;
Denys Vlasenko87c150c2009-10-03 01:14:15 +0200569 }
570
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100571 len = 2 + uni_stat.unicode_width;
572 putchar('"');
573 while (*name) {
574 if (*name == '"' || *name == '\\') {
575 putchar('\\');
576 len++;
Eric Andersen11c65522000-09-07 17:24:47 +0000577 }
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100578 putchar(*name++);
Eric Andersen11c65522000-09-07 17:24:47 +0000579 }
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100580 putchar('"');
581 return len;
Eric Andersen11c65522000-09-07 17:24:47 +0000582}
583
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100584/* Return the number of used columns.
585 * Note that only STYLE_COLUMNS uses return value,
586 * STYLE_SINGLE and STYLE_LONG don't care.
587 */
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200588static NOINLINE unsigned list_single(const struct dnode *dn)
Eric Andersen11c65522000-09-07 17:24:47 +0000589{
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200590 unsigned column = 0;
Denis Vlasenko4d3a8122009-03-27 17:22:00 +0000591 char *lpath = lpath; /* for compiler */
Denis Vlasenko5c759602006-10-28 12:37:16 +0000592#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
Eric Andersen11c65522000-09-07 17:24:47 +0000593 struct stat info;
594 char append;
595#endif
596
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200597 /* Never happens:
Glenn L McGrath4d001292003-01-06 01:11:50 +0000598 if (dn->fullname == NULL)
Denis Vlasenko5c759602006-10-28 12:37:16 +0000599 return 0;
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200600 */
Eric Andersen11c65522000-09-07 17:24:47 +0000601
Denis Vlasenko5c759602006-10-28 12:37:16 +0000602#if ENABLE_FEATURE_LS_FILETYPES
Eric Andersen11c65522000-09-07 17:24:47 +0000603 append = append_char(dn->dstat.st_mode);
604#endif
605
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000606 /* Do readlink early, so that if it fails, error message
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100607 * does not appear *inside* the "ls -l" line */
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000608 if (all_fmt & LIST_SYMLINK)
609 if (S_ISLNK(dn->dstat.st_mode))
610 lpath = xmalloc_readlink_or_warn(dn->fullname);
611
612 if (all_fmt & LIST_INO)
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100613 column += printf("%7llu ", (long long) dn->dstat.st_ino);
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000614 if (all_fmt & LIST_BLOCKS)
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100615 column += printf("%4"OFF_FMT"u ", (off_t) (dn->dstat.st_blocks >> 1));
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000616 if (all_fmt & LIST_MODEBITS)
617 column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode));
618 if (all_fmt & LIST_NLINKS)
619 column += printf("%4lu ", (long) dn->dstat.st_nlink);
Denis Vlasenko5c759602006-10-28 12:37:16 +0000620#if ENABLE_FEATURE_LS_USERNAME
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000621 if (all_fmt & LIST_ID_NAME) {
622 if (option_mask32 & OPT_g) {
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100623 column += printf("%-8.8s ",
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000624 get_cached_username(dn->dstat.st_uid));
625 } else {
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100626 column += printf("%-8.8s %-8.8s ",
Denis Vlasenko2405ad62007-01-19 21:24:17 +0000627 get_cached_username(dn->dstat.st_uid),
628 get_cached_groupname(dn->dstat.st_gid));
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000629 }
630 }
Eric Andersen11c65522000-09-07 17:24:47 +0000631#endif
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000632 if (all_fmt & LIST_ID_NUMERIC) {
633 if (option_mask32 & OPT_g)
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100634 column += printf("%-8u ", (int) dn->dstat.st_uid);
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000635 else
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100636 column += printf("%-8u %-8u ",
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000637 (int) dn->dstat.st_uid,
638 (int) dn->dstat.st_gid);
639 }
640 if (all_fmt & (LIST_SIZE /*|LIST_DEV*/ )) {
641 if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) {
642 column += printf("%4u, %3u ",
643 (int) major(dn->dstat.st_rdev),
644 (int) minor(dn->dstat.st_rdev));
645 } else {
646 if (all_fmt & LS_DISP_HR) {
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100647 column += printf("%"HUMAN_READABLE_MAX_WIDTH_STR"s ",
Denys Vlasenko0bf44d02009-10-13 01:25:09 +0200648 /* print st_size, show one fractional, use suffixes */
649 make_human_readable_str(dn->dstat.st_size, 1, 0)
650 );
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000651 } else {
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000652 column += printf("%9"OFF_FMT"u ", (off_t) dn->dstat.st_size);
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000653 }
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000654 }
655 }
Denis Vlasenko5c759602006-10-28 12:37:16 +0000656#if ENABLE_FEATURE_LS_TIMESTAMPS
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100657 if (all_fmt & (LIST_FULLTIME|LIST_DATE_TIME)) {
658 char *filetime;
659 time_t ttime = dn->dstat.st_mtime;
660 if (all_fmt & TIME_ACCESS)
661 ttime = dn->dstat.st_atime;
662 if (all_fmt & TIME_CHANGE)
663 ttime = dn->dstat.st_ctime;
664 filetime = ctime(&ttime);
665 /* filetime's format: "Wed Jun 30 21:49:08 1993\n" */
666 if (all_fmt & LIST_FULLTIME)
667 column += printf("%.24s ", filetime);
668 else { /* LIST_DATE_TIME */
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000669 /* current_time_t ~== time(NULL) */
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100670 time_t age = current_time_t - ttime;
671 printf("%.6s ", filetime + 4); /* "Jun 30" */
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000672 if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
673 /* hh:mm if less than 6 months old */
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100674 printf("%.5s ", filetime + 11);
675 } else { /* year. buggy if year > 9999 ;) */
676 printf(" %.4s ", filetime + 20);
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000677 }
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000678 column += 13;
679 }
Denys Vlasenko9c3b84a2010-01-18 01:55:00 +0100680 }
Eric Andersen11c65522000-09-07 17:24:47 +0000681#endif
Denis Vlasenko5c759602006-10-28 12:37:16 +0000682#if ENABLE_SELINUX
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000683 if (all_fmt & LIST_CONTEXT) {
684 column += printf("%-32s ", dn->sid ? dn->sid : "unknown");
685 freecon(dn->sid);
686 }
Eric Andersen9e480452003-07-03 10:07:04 +0000687#endif
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000688 if (all_fmt & LIST_FILENAME) {
Denis Vlasenkoc61852a2006-11-29 11:09:43 +0000689#if ENABLE_FEATURE_LS_COLOR
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000690 if (show_color) {
691 info.st_mode = 0; /* for fgcolor() */
692 lstat(dn->fullname, &info);
693 printf("\033[%u;%um", bold(info.st_mode),
694 fgcolor(info.st_mode));
695 }
696#endif
697 column += print_name(dn->name);
698 if (show_color) {
699 printf("\033[0m");
700 }
701 }
702 if (all_fmt & LIST_SYMLINK) {
703 if (S_ISLNK(dn->dstat.st_mode) && lpath) {
704 printf(" -> ");
705#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR
706#if ENABLE_FEATURE_LS_COLOR
707 info.st_mode = 0; /* for fgcolor() */
708#endif
709 if (stat(dn->fullname, &info) == 0) {
710 append = append_char(info.st_mode);
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000711 }
Denis Vlasenkoc61852a2006-11-29 11:09:43 +0000712#endif
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000713#if ENABLE_FEATURE_LS_COLOR
714 if (show_color) {
715 printf("\033[%u;%um", bold(info.st_mode),
716 fgcolor(info.st_mode));
717 }
718#endif
719 column += print_name(lpath) + 4;
Glenn L McGrathe3906fc2002-08-22 18:13:54 +0000720 if (show_color) {
721 printf("\033[0m");
722 }
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000723 free(lpath);
Eric Andersen11c65522000-09-07 17:24:47 +0000724 }
725 }
Denis Vlasenko3a014b82009-03-21 19:11:23 +0000726#if ENABLE_FEATURE_LS_FILETYPES
727 if (all_fmt & LIST_FILETYPE) {
728 if (append) {
729 putchar(append);
730 column++;
731 }
732 }
733#endif
Eric Andersen11c65522000-09-07 17:24:47 +0000734
Glenn L McGrath4d001292003-01-06 01:11:50 +0000735 return column;
Eric Andersen11c65522000-09-07 17:24:47 +0000736}
737
Denys Vlasenkod8528b82010-01-31 05:15:38 +0100738static void showfiles(struct dnode **dn, unsigned nfiles)
739{
740 unsigned i, ncols, nrows, row, nc;
741 unsigned column = 0;
742 unsigned nexttab = 0;
743 unsigned column_width = 0; /* used only by STYLE_COLUMNS */
744
745 if (all_fmt & STYLE_LONG) { /* STYLE_LONG or STYLE_SINGLE */
746 ncols = 1;
747 } else {
748 /* find the longest file name, use that as the column width */
749 for (i = 0; dn[i]; i++) {
750 int len = calc_name_len(dn[i]->name);
751 if (column_width < len)
752 column_width = len;
753 }
754 column_width += tabstops +
755 IF_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + )
756 ((all_fmt & LIST_INO) ? 8 : 0) +
757 ((all_fmt & LIST_BLOCKS) ? 5 : 0);
758 ncols = (int) (terminal_width / column_width);
759 }
760
761 if (ncols > 1) {
762 nrows = nfiles / ncols;
763 if (nrows * ncols < nfiles)
764 nrows++; /* round up fractionals */
765 } else {
766 nrows = nfiles;
767 ncols = 1;
768 }
769
770 for (row = 0; row < nrows; row++) {
771 for (nc = 0; nc < ncols; nc++) {
772 /* reach into the array based on the column and row */
773 if (all_fmt & DISP_ROWS)
774 i = (row * ncols) + nc; /* display across row */
775 else
776 i = (nc * nrows) + row; /* display by column */
777 if (i < nfiles) {
778 if (column > 0) {
779 nexttab -= column;
780 printf("%*s", nexttab, "");
781 column += nexttab;
782 }
783 nexttab = column + column_width;
784 column += list_single(dn[i]);
785 }
786 }
787 putchar('\n');
788 column = 0;
789 }
790}
791
792
793#if ENABLE_DESKTOP
794/* http://www.opengroup.org/onlinepubs/9699919799/utilities/ls.html
795 * If any of the -l, -n, -s options is specified, each list
796 * of files within the directory shall be preceded by a
797 * status line indicating the number of file system blocks
798 * occupied by files in the directory in 512-byte units if
799 * the -k option is not specified, or 1024-byte units if the
800 * -k option is specified, rounded up to the next integral
801 * number of units.
802 */
803/* by Jorgen Overgaard (jorgen AT antistaten.se) */
804static off_t calculate_blocks(struct dnode **dn)
805{
806 uoff_t blocks = 1;
807 if (dn) {
808 while (*dn) {
809 /* st_blocks is in 512 byte blocks */
810 blocks += (*dn)->dstat.st_blocks;
811 dn++;
812 }
813 }
814
815 /* Even though standard says use 512 byte blocks, coreutils use 1k */
816 /* Actually, we round up by calculating (blocks + 1) / 2,
817 * "+ 1" was done when we initialized blocks to 1 */
818 return blocks >> 1;
819}
820#endif
821
822
823static struct dnode **list_dir(const char *, unsigned *);
824
825static void showdirs(struct dnode **dn, int first)
826{
827 unsigned nfiles;
828 unsigned dndirs;
829 struct dnode **subdnp;
830 struct dnode **dnd;
831
832 /* Never happens:
833 if (dn == NULL || ndirs < 1) {
834 return;
835 }
836 */
837
838 for (; *dn; dn++) {
839 if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
840 if (!first)
841 bb_putchar('\n');
842 first = 0;
843 printf("%s:\n", (*dn)->fullname);
844 }
845 subdnp = list_dir((*dn)->fullname, &nfiles);
846#if ENABLE_DESKTOP
847 if ((all_fmt & STYLE_MASK) == STYLE_LONG)
848 printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
849#endif
850 if (nfiles > 0) {
851 /* list all files at this level */
852 dnsort(subdnp, nfiles);
853 showfiles(subdnp, nfiles);
854 if (ENABLE_FEATURE_LS_RECURSIVE
855 && (all_fmt & DISP_RECURSIVE)
856 ) {
857 /* recursive - list the sub-dirs */
858 dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
859 dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
860 if (dndirs > 0) {
861 dnsort(dnd, dndirs);
862 showdirs(dnd, 0);
863 /* free the array of dnode pointers to the dirs */
864 free(dnd);
865 }
866 }
867 /* free the dnodes and the fullname mem */
868 dfree(subdnp);
869 }
870 }
871}
872
873
874/* Returns NULL-terminated malloced vector of pointers (or NULL) */
875static struct dnode **list_dir(const char *path, unsigned *nfiles_p)
876{
877 struct dnode *dn, *cur, **dnp;
878 struct dirent *entry;
879 DIR *dir;
880 unsigned i, nfiles;
881
882 /* Never happens:
883 if (path == NULL)
884 return NULL;
885 */
886
887 *nfiles_p = 0;
888 dir = warn_opendir(path);
889 if (dir == NULL) {
890 exit_code = EXIT_FAILURE;
891 return NULL; /* could not open the dir */
892 }
893 dn = NULL;
894 nfiles = 0;
895 while ((entry = readdir(dir)) != NULL) {
896 char *fullname;
897
898 /* are we going to list the file- it may be . or .. or a hidden file */
899 if (entry->d_name[0] == '.') {
900 if ((!entry->d_name[1] || (entry->d_name[1] == '.' && !entry->d_name[2]))
901 && !(all_fmt & DISP_DOT)
902 ) {
903 continue;
904 }
905 if (!(all_fmt & DISP_HIDDEN))
906 continue;
907 }
908 fullname = concat_path_file(path, entry->d_name);
909 cur = my_stat(fullname, bb_basename(fullname), 0);
910 if (!cur) {
911 free(fullname);
912 continue;
913 }
914 cur->fname_allocated = 1;
915 cur->next = dn;
916 dn = cur;
917 nfiles++;
918 }
919 closedir(dir);
920
921 if (dn == NULL)
922 return NULL;
923
924 /* now that we know how many files there are
925 * allocate memory for an array to hold dnode pointers
926 */
927 *nfiles_p = nfiles;
928 dnp = dnalloc(nfiles);
929 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
930 dnp[i] = dn; /* save pointer to node in array */
931 dn = dn->next;
932 if (!dn)
933 break;
934 }
935
936 return dnp;
937}
938
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000939
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000940int ls_main(int argc UNUSED_PARAM, char **argv)
Eric Andersen11c65522000-09-07 17:24:47 +0000941{
Glenn L McGrath792cae52004-01-18 05:15:16 +0000942 struct dnode **dnd;
943 struct dnode **dnf;
944 struct dnode **dnp;
945 struct dnode *dn;
946 struct dnode *cur;
Denis Vlasenko67b23e62006-10-03 21:00:06 +0000947 unsigned opt;
Denys Vlasenko76c7d952009-10-03 01:15:47 +0200948 unsigned nfiles;
949 unsigned dnfiles;
950 unsigned dndirs;
951 unsigned i;
Denys Vlasenkoae05dd42009-07-03 12:22:19 +0200952#if ENABLE_FEATURE_LS_COLOR
953 /* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */
954 /* coreutils 6.10:
955 * # ls --color=BOGUS
956 * ls: invalid argument 'BOGUS' for '--color'
957 * Valid arguments are:
958 * 'always', 'yes', 'force'
959 * 'never', 'no', 'none'
960 * 'auto', 'tty', 'if-tty'
961 * (and substrings: "--color=alwa" work too)
962 */
963 static const char ls_longopts[] ALIGN1 =
964 "color\0" Optional_argument "\xff"; /* no short equivalent */
965 static const char color_str[] ALIGN1 =
966 "always\0""yes\0""force\0"
967 "auto\0""tty\0""if-tty\0";
Denis Vlasenko51f1b6c2008-07-15 05:21:47 +0000968 /* need to initialize since --color has _an optional_ argument */
Denys Vlasenkoae05dd42009-07-03 12:22:19 +0200969 const char *color_opt = color_str; /* "always" */
970#endif
Glenn L McGrath792cae52004-01-18 05:15:16 +0000971
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +0000972 INIT_G();
Denis Vlasenkoe0554432007-01-19 22:03:06 +0000973
Denys Vlasenko28055022010-01-04 20:49:58 +0100974 init_unicode();
Denys Vlasenko42a8fd02009-07-11 21:36:13 +0200975
Rob Landley2b8a05a2006-06-20 17:43:01 +0000976 all_fmt = LIST_SHORT |
Denis Vlasenko94cf69f2006-10-28 12:37:51 +0000977 (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD));
Glenn L McGrath792cae52004-01-18 05:15:16 +0000978
Denis Vlasenko5c759602006-10-28 12:37:16 +0000979#if ENABLE_FEATURE_AUTOWIDTH
Denys Vlasenkoae05dd42009-07-03 12:22:19 +0200980 /* obtain the terminal width */
Denis Vlasenkof893da82007-08-09 08:27:24 +0000981 get_terminal_width_height(STDIN_FILENO, &terminal_width, NULL);
Denys Vlasenkoae05dd42009-07-03 12:22:19 +0200982 /* go one less... */
Eric Andersen8efe9672003-09-15 08:33:45 +0000983 terminal_width--;
Eric Andersen6d687812003-11-04 23:16:48 +0000984#endif
Eric Andersen11c65522000-09-07 17:24:47 +0000985
Eric Andersen11c65522000-09-07 17:24:47 +0000986 /* process options */
Denys Vlasenkoae05dd42009-07-03 12:22:19 +0200987 IF_FEATURE_LS_COLOR(applet_long_options = ls_longopts;)
Denis Vlasenko5c759602006-10-28 12:37:16 +0000988#if ENABLE_FEATURE_AUTOWIDTH
Denis Vlasenko1d426652008-03-17 09:09:09 +0000989 opt_complementary = "T+:w+"; /* -T N, -w N */
990 opt = getopt32(argv, ls_options, &tabstops, &terminal_width
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000991 IF_FEATURE_LS_COLOR(, &color_opt));
Glenn L McGrath792cae52004-01-18 05:15:16 +0000992#else
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000993 opt = getopt32(argv, ls_options IF_FEATURE_LS_COLOR(, &color_opt));
Manuel Novoa III cad53642003-03-19 09:13:01 +0000994#endif
Eric Andersend07cf592004-02-05 13:52:03 +0000995 for (i = 0; opt_flags[i] != (1U<<31); i++) {
Glenn L McGrath792cae52004-01-18 05:15:16 +0000996 if (opt & (1 << i)) {
Denis Vlasenko5c759602006-10-28 12:37:16 +0000997 unsigned flags = opt_flags[i];
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000998
Denis Vlasenko5c759602006-10-28 12:37:16 +0000999 if (flags & LIST_MASK_TRIGGER)
Manuel Novoa III cad53642003-03-19 09:13:01 +00001000 all_fmt &= ~LIST_MASK;
Denis Vlasenko5c759602006-10-28 12:37:16 +00001001 if (flags & STYLE_MASK_TRIGGER)
Manuel Novoa III cad53642003-03-19 09:13:01 +00001002 all_fmt &= ~STYLE_MASK;
Denis Vlasenko94cf69f2006-10-28 12:37:51 +00001003 if (flags & SORT_MASK_TRIGGER)
Manuel Novoa III cad53642003-03-19 09:13:01 +00001004 all_fmt &= ~SORT_MASK;
Denis Vlasenko5c759602006-10-28 12:37:16 +00001005 if (flags & DISP_MASK_TRIGGER)
Manuel Novoa III cad53642003-03-19 09:13:01 +00001006 all_fmt &= ~DISP_MASK;
Denis Vlasenko5c759602006-10-28 12:37:16 +00001007 if (flags & TIME_MASK)
Manuel Novoa III cad53642003-03-19 09:13:01 +00001008 all_fmt &= ~TIME_MASK;
Denis Vlasenko5c759602006-10-28 12:37:16 +00001009 if (flags & LIST_CONTEXT)
Eric Andersen9e480452003-07-03 10:07:04 +00001010 all_fmt |= STYLE_SINGLE;
Denis Vlasenko2110aa92007-02-28 23:14:06 +00001011 /* huh?? opt cannot be 'l' */
1012 //if (LS_DISP_HR && opt == 'l')
1013 // all_fmt &= ~LS_DISP_HR;
Manuel Novoa III cad53642003-03-19 09:13:01 +00001014 all_fmt |= flags;
1015 }
Eric Andersen11c65522000-09-07 17:24:47 +00001016 }
1017
Denis Vlasenko5c759602006-10-28 12:37:16 +00001018#if ENABLE_FEATURE_LS_COLOR
1019 /* find color bit value - last position for short getopt */
1020 if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) {
1021 char *p = getenv("LS_COLORS");
1022 /* LS_COLORS is unset, or (not empty && not "none") ? */
Denis Vlasenko51f1b6c2008-07-15 05:21:47 +00001023 if (!p || (p[0] && strcmp(p, "none") != 0))
Tim Rikerc1ef7bd2006-01-25 00:08:53 +00001024 show_color = 1;
Denis Vlasenko5c759602006-10-28 12:37:16 +00001025 }
Denys Vlasenkoae05dd42009-07-03 12:22:19 +02001026 if (opt & OPT_color) {
1027 if (color_opt[0] == 'n')
Denis Vlasenko5c759602006-10-28 12:37:16 +00001028 show_color = 0;
Denys Vlasenkoae05dd42009-07-03 12:22:19 +02001029 else switch (index_in_substrings(color_str, color_opt)) {
1030 case 3:
1031 case 4:
1032 case 5:
1033 if (isatty(STDOUT_FILENO)) {
1034 case 0:
1035 case 1:
1036 case 2:
1037 show_color = 1;
1038 }
1039 }
Paul Fox156dc412005-08-01 19:33:30 +00001040 }
1041#endif
1042
Eric Andersen11c65522000-09-07 17:24:47 +00001043 /* sort out which command line options take precedence */
Denis Vlasenko5c759602006-10-28 12:37:16 +00001044 if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST))
Manuel Novoa III cad53642003-03-19 09:13:01 +00001045 all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */
Rob Landleyea224be2006-06-18 20:20:07 +00001046 if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) {
1047 if (all_fmt & TIME_CHANGE)
1048 all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME;
1049 if (all_fmt & TIME_ACCESS)
1050 all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME;
1051 }
Manuel Novoa III cad53642003-03-19 09:13:01 +00001052 if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */
1053 all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC);
Denis Vlasenko5c759602006-10-28 12:37:16 +00001054 if (ENABLE_FEATURE_LS_USERNAME)
1055 if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC))
1056 all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */
Eric Andersenc7bda1c2004-03-15 08:29:22 +00001057
Eric Andersen11c65522000-09-07 17:24:47 +00001058 /* choose a display format */
Rob Landleyea224be2006-06-18 20:20:07 +00001059 if (!(all_fmt & STYLE_MASK))
Eric Andersen70060d22004-03-27 10:02:48 +00001060 all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE);
Eric Andersen11c65522000-09-07 17:24:47 +00001061
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +00001062 argv += optind;
1063 if (!argv[0])
1064 *--argv = (char*)".";
"Vladimir N. Oleynik"a8c23aa2005-09-05 15:06:57 +00001065
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +00001066 if (argv[1])
1067 all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */
Eric Andersen11c65522000-09-07 17:24:47 +00001068
Denis Vlasenko5c759602006-10-28 12:37:16 +00001069 /* stuff the command line file names into a dnode array */
Glenn L McGrathe3906fc2002-08-22 18:13:54 +00001070 dn = NULL;
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +00001071 nfiles = 0;
1072 do {
Denis Vlasenko11a6f9b2009-03-03 13:20:22 +00001073 /* NB: follow links on command line unless -l or -s */
1074 cur = my_stat(*argv, *argv, !(all_fmt & (STYLE_LONG|LIST_BLOCKS)));
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +00001075 argv++;
Glenn L McGrath4d001292003-01-06 01:11:50 +00001076 if (!cur)
Eric Andersen11c65522000-09-07 17:24:47 +00001077 continue;
Denys Vlasenko76c7d952009-10-03 01:15:47 +02001078 cur->fname_allocated = 0;
Glenn L McGrathe3906fc2002-08-22 18:13:54 +00001079 cur->next = dn;
1080 dn = cur;
Eric Andersen11c65522000-09-07 17:24:47 +00001081 nfiles++;
Denis Vlasenkoc7497ea2008-06-13 11:16:09 +00001082 } while (*argv);
Eric Andersen11c65522000-09-07 17:24:47 +00001083
Denys Vlasenko76c7d952009-10-03 01:15:47 +02001084 /* nfiles _may_ be 0 here - try "ls doesnt_exist" */
1085 if (nfiles == 0)
1086 return exit_code;
1087
Eric Andersen11c65522000-09-07 17:24:47 +00001088 /* now that we know how many files there are
Denis Vlasenko656f7462006-10-28 13:02:55 +00001089 * allocate memory for an array to hold dnode pointers
1090 */
Glenn L McGrathe3906fc2002-08-22 18:13:54 +00001091 dnp = dnalloc(nfiles);
Denys Vlasenko76c7d952009-10-03 01:15:47 +02001092 for (i = 0; /* i < nfiles - detected via !dn below */; i++) {
1093 dnp[i] = dn; /* save pointer to node in array */
1094 dn = dn->next;
1095 if (!dn)
1096 break;
Eric Andersen11c65522000-09-07 17:24:47 +00001097 }
1098
Manuel Novoa III cad53642003-03-19 09:13:01 +00001099 if (all_fmt & DISP_NOLIST) {
Denis Vlasenko94cf69f2006-10-28 12:37:51 +00001100 dnsort(dnp, nfiles);
Denys Vlasenko76c7d952009-10-03 01:15:47 +02001101 showfiles(dnp, nfiles);
Eric Andersen11c65522000-09-07 17:24:47 +00001102 } else {
Denys Vlasenkocae409c2009-10-03 11:43:48 +02001103 dnd = splitdnarray(dnp, SPLIT_DIR);
1104 dnf = splitdnarray(dnp, SPLIT_FILE);
1105 dndirs = count_dirs(dnp, SPLIT_DIR);
Glenn L McGrathe3906fc2002-08-22 18:13:54 +00001106 dnfiles = nfiles - dndirs;
Eric Andersen11c65522000-09-07 17:24:47 +00001107 if (dnfiles > 0) {
Denis Vlasenko94cf69f2006-10-28 12:37:51 +00001108 dnsort(dnf, dnfiles);
Eric Andersen11c65522000-09-07 17:24:47 +00001109 showfiles(dnf, dnfiles);
Rob Landley26314862006-05-02 19:46:52 +00001110 if (ENABLE_FEATURE_CLEAN_UP)
1111 free(dnf);
Eric Andersen11c65522000-09-07 17:24:47 +00001112 }
1113 if (dndirs > 0) {
Denis Vlasenko94cf69f2006-10-28 12:37:51 +00001114 dnsort(dnd, dndirs);
Denys Vlasenkocae409c2009-10-03 11:43:48 +02001115 showdirs(dnd, dnfiles == 0);
Rob Landley26314862006-05-02 19:46:52 +00001116 if (ENABLE_FEATURE_CLEAN_UP)
1117 free(dnd);
Eric Andersen11c65522000-09-07 17:24:47 +00001118 }
1119 }
Rob Landley26314862006-05-02 19:46:52 +00001120 if (ENABLE_FEATURE_CLEAN_UP)
Denys Vlasenkocae409c2009-10-03 11:43:48 +02001121 dfree(dnp);
Denis Vlasenko4a689e92008-06-18 16:38:22 +00001122 return exit_code;
Eric Andersencc8ed391999-10-05 16:24:54 +00001123}