1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1989-2012 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Eclipse Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.eclipse.org/org/documents/epl-v10.html *
11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) *
12 * *
13 * Information and Software Systems Research *
14 * AT&T Research *
15 * Florham Park NJ *
16 * *
17 * Glenn Fowler <gsf@research.att.com> *
18 * *
19 ***********************************************************************/
20 #pragma prototyped
21 /*
22 * Glenn Fowler
23 * AT&T Research
24 *
25 * ls -- list file status
26 */
27
28 #define TIME_ISO "%Q/%m-%d+%H:%M/%Y-%m-%d /"
29 #define TIME_LONG_ISO "%_K"
30 #define TIME_FULL_ISO "%_EK"
31 #define TIME_LOCALE "%c"
32
33 static const char usage[] =
34 "[-?\n@(#)$Id: ls (AT&T Research) 2012-04-20 $\n]"
35 USAGE_LICENSE
36 "[+NAME?ls - list files and/or directories]"
37 "[+DESCRIPTION?For each directory argument \bls\b lists the contents; for each"
38 " file argument the name and requested information are listed."
39 " The directory \b.\b is assumed if no file arguments appear."
40 " The listing is sorted by file name by default, except that file"
41 " arguments are listed before directories.]"
42 "[+?Multi-column terminal output display width is determined by \bioctl\b(2)"
43 " and/or the \bCOLUMNS\b environment variable.]"
44 "[+?\bgetconf PATH_RESOLVE\b determines how symbolic links are handled. This"
45 " can be explicitly overridden by the \b--logical\b, \b--metaphysical\b,"
46 " and \b--physical\b options below. \bPATH_RESOLVE\b can be one of:]{"
47 " [+logical?Follow all symbolic links.]"
48 " [+metaphysical?Follow command argument symbolic links,"
49 " otherwise don't follow.]"
50 " [+physical?Don't follow symbolic links.]"
51 "}"
52
53 "[a:all?List entries starting with \b.\b; turns off \b--almost-all\b.]"
54 "[A:almost-all?List all entries but \b.\b and \b..\b; turns off \b--all\b.]"
55 "[b:escape?Print escapes for nongraphic characters.]"
56 "[B:ignore-backups?Do not list entries ending with ~.]"
57 "[c:ctime?Sort by change time; list ctime with \b--long\b.]"
58 "[C:multi-column?List entries by columns.]"
59 "[d:directory?List directory entries instead of contents.]"
60 "[D:define?Define \akey\a with optional \avalue\a. \avalue\a will be expanded"
61 " when \b%(\b\akey\a\b)\b is specified in \b--format\b. \akey\a may"
62 " override internal \b--format\b identifiers.]:[key[=value]]]"
63 "[e:long-iso|long-time?Equivalent to \b--long --time-style=long-iso\b.]"
64 "[E:full-iso|full-time?Equivalent to \b--long --time-style=full-iso\b.]"
65 "[f:force?Force each argument to be interpreted as a directory and list"
66 " the name found in each slot in the physical directory order. Turns"
67 " on \b-aU\b and turns off \b-lrst\b. The results are undefined for"
68 " non-directory arguments.]"
69 "[Z:format?Append to the listing format string. \aformat\a follows"
70 " \bprintf\b(3) conventions, except that \bsfio\b(3) inline ids"
71 " are used instead of arguments:"
72 " %[-+]][\awidth\a[.\aprecis\a[.\abase\a]]]]]](\aid\a[:\asubformat\a]])\achar\a."
73 " If \achar\a is \bs\b then the string form of the item is listed,"
74 " otherwise the corresponding numeric form is listed. \asubformat\a"
75 " overrides the default formatting for \aid\a. Supported \aid\as"
76 " and \asubformat\as are:]:[format]{"
77 " [+atime?access time]"
78 " [+blocks?size in blocks]"
79 " [+ctime?change time]"
80 " [+dev?major/minor device numbers]"
81 " [+device?major/minor device numbers if block or character special device]"
82 " [+devmajor?major device number]"
83 " [+devminor?minor device number]"
84 " [+dir.blocks?directory blocks]"
85 " [+dir.bytes?directory size in bytes]"
86 " [+dir.count?directory entry count]"
87 " [+dir.files?directory file count]"
88 " [+flags?command line flags in effect]"
89 " [+gid?group id]"
90 " [+header?listing header]"
91 " [+ino?serial number]"
92 " [+linkop?link operation: -> for symbolic, empty otherwise]"
93 " [+linkname?symbolic link text]"
94 " [+linkpath?symbolic link text]"
95 " [+mark?file or directory mark character]"
96 " [+markdir?directory mark character]"
97 " [+mode?access mode]"
98 " [+mtime?modification time]"
99 " [+name?entry name]"
100 " [+nlink?hard link count]"
101 " [+path?file path from original root dir]"
102 " [+perm?access permissions]"
103 " [+size?file size in bytes]"
104 " [+summary?listing summary info]"
105 " [+total.blocks?running total block count]"
106 " [+total.bytes?running total size in bytes]"
107 " [+total.files?running total file count]"
108 " [+trailer?listing trailer]"
109 " [+uid?owner id]"
110 " [+view?3d fs view level, 0 for the top or 2d]"
111 " [+----?subformats ----]"
112 " [+case\b::\bp\b\a1\a::\bs\b\a1\a::...::\bp\b\an\a::\bs\b\an\a?Expands"
113 " to \bs\b\ai\a if the value of \aid\a matches the shell"
114 " pattern \bp\b\ai\a, or the empty string if there is no"
115 " match.]"
116 " [+mode?The integral value as a \bfmtmode\b(3) string.]"
117 " [+perm?The integral value as a \bfmtperm\b(3) string.]"
118 " [+time[=\aformat\a]]?The integral value as a \bstrftime\b(3)"
119 " string. For example,"
120 " \b--format=\"%8(mtime)u %(ctime:time=%H:%M:%S)s\"\b"
121 " lists the mtime in seconds since the epoch and the"
122 " ctime as hours:minutes:seconds.]"
123 " }"
124 "[F:classify?Append a character for typing each entry. Turns on \b--physical\b.]"
125 "[g:group?\b--long\b with no owner info.]"
126 "[G?\b--long\b with no group info.]"
127 "[h:scale|binary-scale|human-readable?Scale sizes to powers of 1024 { Ki Mi Gi Ti Pi Xi }.]"
128 "[i:inode?List the file serial number.]"
129 "[I:ignore?Do not list implied entries matching shell \apattern\a.]:[pattern]"
130 "[k:kilobytes?Use 1024 blocks instead of 512.]"
131 "[K:shell-quote?Enclose entry names in shell $'...' if necessary.]"
132 "[l:long|verbose?Use a long listing format.]"
133 "[m:commas|comma-list?List names as comma separated list.]"
134 "[n:numeric-uid-gid?List numeric user and group ids instead of names.]"
135 "[N:literal|show-controls-chars?Print raw entry names (don't treat e.g. control characters specially).]"
136 "[o:owner?\b--long\b with no group info.]"
137 "[O?\b--long\b with no owner info.]"
138 "[p:markdir?Append / to each directory name.]"
139 "[q:hide-control-chars?Print ? instead of non graphic characters.]"
140 "[Q:quote-name?Enclose all entry names in \"...\".]"
141 "[J:quote-style|quoting-style?Quote entry names according to \astyle\a:]:[style:=question]{"
142 " [c:C?C \"...\" quote.]"
143 " [e:escape?\b\\\b escape if necessary.]"
144 " [l:literal?No quoting.]"
145 " [q:question?Replace unprintable characters with \b?\b.]"
146 " [s:shell?Shell $'...' quote if necessary.]"
147 " [S:shell-always?Shell $'...' every name.]"
148 "}"
149 "[r:reverse?Reverse order while sorting.]"
150 "[R:recursive?List subdirectories recursively.]"
151 "[s:size?Print size of each file, in blocks.]"
152 "[S:bysize?Sort by file size.]"
153 "[t:?Sort by modification time; list mtime with \b--long\b.]"
154 "[T:tabsize?Ignored by this implementation.]#[columns]"
155 "[u:access?Sort by last access time; list atime with \b--long\b.]"
156 "[U?Equivalent to \b--sort=none\b.]"
157 "[V:colors|colours?\akey\a determines when color is used to distinguish"
158 " types:]:?[key:=never]{"
159 " [n:never?Never use color.]"
160 " [a:always?Always use color.]"
161 " [t:tty|auto?Use color when output is a tty.]"
162 "}"
163 "[w:width?Set the screen width to \ascreen-width\a and the screen height"
164 " to \ascreen-height\a if specified.]:[[screen-heightX]]screen-width]"
165 "[W:time?Display \akey\a time instead of the modification time:]:[key]{"
166 " [a:atime|access|use?access time]"
167 " [c:ctime|status?status change time]"
168 " [m:mtime|time?modify time]"
169 "}"
170 "[x:across?List entries by lines instead of by columns.]"
171 "[X:extension?Sort alphabetically by entry extension.]"
172 "[y:sort?Sort by \akey\a:]:?[key]{"
173 " [a:atime|access|use?Access time.]"
174 " [c:ctime|status?Status change time.]"
175 " [x:extension?File name extension.]"
176 " [m:mtime|time?Modify time.]"
177 " [f:name?File name.]"
178 " [n:none?Don't sort.]"
179 " [s:size|blocks?File size.]"
180 " [v:version?File name version.]"
181 "}"
182 "[Y:layout?Listing layout \akey\a:]:[key]{"
183 " [a:across|horizontal?Multi-column across the page.]"
184 " [c:comma?Comma separated names across the page.]"
185 " [l:long|verbose?Long listing.]"
186 " [v:multi-column|vertical?Multi-column by column.]"
187 " [1:single-column?One column down the page.]"
188 "}"
189 "[z:time-style?List the time according to \astyle\a:]:[style]{"
190 " [i:iso?Equivalent to \b+" TIME_ISO "\b.]"
191 " [10:posix-iso?No change for the C or posix locales, \biso\b otherwise.]"
192 " [f:full-iso?Equivalent to \b+" TIME_FULL_ISO "\b.]"
193 " [l:long-iso?Equivalent to \b+" TIME_LONG_ISO "\b.]"
194 " [11:posix-full-iso?No change for the C or posix locales, \bfull-iso\b"
195 " otherwise.]"
196 " [L:locale?Equivalent to \b+" TIME_LOCALE "\b.]"
197 " [12:+\aformat\a?A \bdate\b(1) +\aformat\a.]"
198 "}"
199 "[1:one-column?List one file per line.]"
200 "[L:logical|follow?Follow symbolic links. The default is determined by"
201 " \bgetconf PATH_RESOLVE\b.]"
202 "[H:metaphysical?Follow command argument symbolic links, otherwise don't"
203 " follow. The default is determined by \bgetconf PATH_RESOLVE\b.]"
204 "[P:physical?Don't follow symbolic links. The default is determined by"
205 " \bgetconf PATH_RESOLVE\b.]"
206 "[101:block-size?Use \ablocksize\a blocks.]#[blocksize]"
207 "[102:decimal-scale|thousands?Scale sizes to powers of 1000 { K M G T P X }.]"
208 "[103:dump?Print the generated \b--format\b string on the standard output"
209 " and exit.]"
210 "[104:testdate?\b--format\b time values newer than \adate\a will be printed"
211 " as \adate\a. Used for regression testing.]:[date]"
212 "[105:testsize?Shift file sizes left \ashift\a bits and set file block counts"
213 " to the file size divided by 512. Used for regression testing.]#[shift]"
214
215 "\n"
216 "\n[ file ... ]\n"
217 "\n"
218 "[+SEE ALSO?\bchmod\b(1), \bfind\b(1), \bgetconf\b(1), \btw\b(1)]"
219 "[+BUGS?Can we add options to something else now?]"
220 ;
221
222 #include <ast.h>
223 #include <ls.h>
224 #include <ctype.h>
225 #include <error.h>
226 #include <ftwalk.h>
227 #include <sfdisc.h>
228 #include <hash.h>
229 #include <tmx.h>
230 #include <fs3d.h>
231
232 #define LS_ACROSS (LS_USER<<0) /* multi-column row order */
233 #define LS_ALL (LS_USER<<1) /* list all */
234 #define LS_ALWAYS (LS_USER<<2) /* always quote */
235 #define LS_COLUMNS (LS_USER<<3) /* multi-column column order */
236 #define LS_COMMAS (LS_USER<<4) /* comma separated name list */
237 #define LS_DIRECTORY (LS_USER<<5) /* list directories as files */
238 #define LS_ESCAPE (LS_USER<<6) /* C escape unprintable chars */
239 #define LS_EXTENSION (LS_USER<<7) /* sort by name extension */
240 #define LS_LABEL (LS_USER<<8) /* label for all dirs */
241 #define LS_MARKDIR (LS_USER<<9) /* marks dirs with / */
242 #define LS_MOST (LS_USER<<10) /* list all but . and .. */
243 #define LS_NOBACKUP (LS_USER<<11) /* omit *~ names */
244 #define LS_NOSTAT (LS_USER<<13) /* leaf FTW_NS ok */
245 #define LS_PRINTABLE (LS_USER<<14) /* ? for non-printable chars */
246 #define LS_QUOTE (LS_USER<<15) /* "..." file names */
247 #define LS_RECURSIVE (LS_USER<<16) /* recursive directory descent */
248 #define LS_SEPARATE (LS_USER<<17) /* dir header needs separator */
249 #define LS_SHELL (LS_USER<<18) /* $'...' file names */
250 #define LS_TIME (LS_USER<<19) /* sort by time */
251
252 #define LS_STAT LS_NOSTAT
253
254 #define VISIBLE(f) ((f)->level<=0||(!state.ignore||!strmatch((f)->name,state.ignore))&&(!(state.lsflags&LS_NOBACKUP)||(f)->name[(f)->namelen-1]!='~')&&((state.lsflags&LS_ALL)||(f)->name[0]!='.'||(state.lsflags&LS_MOST)&&((f)->name[1]&&(f)->name[1]!='.'||(f)->name[2])))
255
256 #define BETWEEN 2 /* space between columns */
257 #define AFTER 1 /* space after last column */
258
259 #define INVISIBLE (-1)
260 #define LISTED (-2)
261
262 #define KEY_environ (-1)
263
264 #define KEY_atime 1
265 #define KEY_blocks 2
266 #define KEY_ctime 3
267 #define KEY_dev 4
268 #define KEY_device 5
269 #define KEY_devmajor 6
270 #define KEY_devminor 7
271 #define KEY_dir_blocks 8
272 #define KEY_dir_bytes 9
273 #define KEY_dir_count 10
274 #define KEY_dir_files 11
275 #define KEY_flags 12
276 #define KEY_gid 13
277 #define KEY_header 14
278 #define KEY_ino 15
279 #define KEY_linkop 16
280 #define KEY_linkpath 17
281 #define KEY_mark 18
282 #define KEY_markdir 19
283 #define KEY_mode 20
284 #define KEY_mtime 21
285 #define KEY_name 22
286 #define KEY_nlink 23
287 #define KEY_path 24
288 #define KEY_perm 25
289 #define KEY_size 26
290 #define KEY_summary 27
291 #define KEY_total_blocks 28
292 #define KEY_total_bytes 29
293 #define KEY_total_files 30
294 #define KEY_trailer 31
295 #define KEY_uid 32
296 #define KEY_view 33
297
298 #if 0
299 #define BLOCKS(st) ((state.blocksize==LS_BLOCKSIZE)?iblocks(st):(state.blocksize>LS_BLOCKSIZE)?(iblocks(st)+state.blocksize/LS_BLOCKSIZE-1)/(state.blocksize/LS_BLOCKSIZE):iblocks(st)*(LS_BLOCKSIZE/state.blocksize))
300 #else
301 #define BLOCKS(st) ((state.blocksize==LS_BLOCKSIZE)?iblocks(st):(iblocks(st)*LS_BLOCKSIZE+state.blocksize-1)/state.blocksize)
302 #endif
303 #define PRINTABLE(s) ((state.lsflags&LS_PRINTABLE)?printable(s):(s))
304
305 typedef int (*Order_f)(Ftw_t*, Ftw_t*);
306
307 typedef struct /* dir/total counts */
308 {
309 Sfulong_t blocks; /* number of blocks */
310 Sfulong_t bytes; /* number of bytes */
311 Sfulong_t files; /* number of files */
312 } Count_t;
313
314 typedef struct /* sfkeyprintf() keys */
315 {
316 char* name; /* key name */
317 short index; /* index */
318 short disable; /* macro being expanded */
319 char* macro; /* macro definition */
320 } Key_t;
321
322 typedef struct /* list state */
323 {
324 Count_t count; /* directory counts */
325 Ftw_t* ftw; /* ftw info */
326 char* dirnam; /* pr() dirnam */
327 int dirlen; /* pr() dirlen */
328 } List_t;
329
330 typedef struct /* program state */
331 {
332 char flags[64]; /* command line option flags */
333 long ftwflags; /* FTW_* flags */
334 long lsflags; /* LS_* flags */
335 long timeflags; /* time LS_* flags */
336 long blocksize; /* file block size */
337 unsigned long directories; /* directory count */
338 unsigned long testdate; /* --format test date */
339 Count_t total; /* total counts */
340 int adjust; /* key() print with adjustment */
341 int comma; /* LS_COMMAS ftw.level crossing */
342 int height; /* output height in lines */
343 int reverse; /* reverse the sort */
344 int scale; /* metric scale power */
345 int testsize; /* st_size left shift */
346 int width; /* output width in chars */
347 char* endflags; /* trailing 0 in flags */
348 char* format; /* sfkeyprintf() format */
349 char* ignore; /* ignore files matching this */
350 char* timefmt; /* time list format */
351 Hash_table_t* keys; /* sfkeyprintf() keys */
352 Sfio_t* tmp; /* tmp string stream */
353 Ftw_t* top; /* top directory -- no label */
354 Order_f order; /* sort comparison function */
355 } State_t;
356
357 static char DEF_header[] =
358 "%(dir.count:case;0;;1;%(path)s:\n;*;\n%(path)s:\n)s"
359 "%(flags:case;*d*;;*[ls]*;total %(dir.blocks)u\n)s"
360 ;
361
362 static Key_t keys[] =
363 {
364 { 0 },
365 { "atime", KEY_atime },
366 { "blocks", KEY_blocks },
367 { "ctime", KEY_ctime },
368 { "dev", KEY_dev },
369 { "device", KEY_device },
370 { "devmajor", KEY_devmajor },
371 { "devminor", KEY_devminor },
372 { "dir.blocks", KEY_dir_blocks },
373 { "dir.bytes", KEY_dir_bytes },
374 { "dir.count", KEY_dir_count },
375 { "dir.files", KEY_dir_files },
376 { "flags", KEY_flags },
377 { "gid", KEY_gid },
378 { "header", KEY_header, 0, DEF_header },
379 { "ino", KEY_ino },
380 { "linkop", KEY_linkop },
381 { "linkpath", KEY_linkpath },
382 { "mark", KEY_mark },
383 { "markdir", KEY_markdir },
384 { "mode", KEY_mode },
385 { "mtime", KEY_mtime },
386 { "name", KEY_name },
387 { "nlink", KEY_nlink },
388 { "path", KEY_path },
389 { "perm", KEY_perm },
390 { "size", KEY_size },
391 { "summary", KEY_summary },
392 { "total.blocks", KEY_total_blocks },
393 { "total.bytes", KEY_total_bytes },
394 { "total.files", KEY_total_files },
395 { "trailer", KEY_trailer },
396 { "uid", KEY_uid },
397 { "view", KEY_view },
398
399 /* aliases */
400
401 { "linkname", KEY_linkpath },
402 };
403
404 static State_t state;
405
406 /*
407 * return a copy of s with unprintable chars replaced by ?
408 */
409
410 static char*
printable(register char * s)411 printable(register char* s)
412 {
413 register char* t;
414 register char* p;
415 register int c;
416
417 static char* prdata;
418 static int prsize;
419
420 if (state.lsflags & LS_ESCAPE)
421 {
422 if (!(state.lsflags & LS_QUOTE))
423 return fmtesc(s);
424 if (state.lsflags & LS_SHELL)
425 return fmtquote(s, "$'", "'", strlen(s), (state.lsflags & LS_ALWAYS) ? FMT_ALWAYS : 0);
426 return fmtquote(s, "\"", "\"", strlen(s), FMT_ALWAYS);
427 }
428 c = strlen(s) + 4;
429 if (c > prsize)
430 {
431 prsize = roundof(c, 512);
432 if (!(prdata = newof(prdata, char, prsize, 0)))
433 error(3, "out of space");
434 }
435 t = prdata;
436 if (state.lsflags & LS_QUOTE)
437 *t++ = '"';
438 if (!mbwide())
439 while (c = *s++)
440 *t++ = (iscntrl(c) || !isprint(c)) ? '?' : c;
441 else
442 for (p = s; c = mbchar(s);)
443 if (c < 0)
444 {
445 s++;
446 *t++ = '?';
447 }
448 else if (mbwidth(c) <= 0)
449 *t++ = '?';
450 else
451 while (p < s)
452 *t++ = *p++;
453 if (state.lsflags & LS_QUOTE)
454 *t++ = '"';
455 *t = 0;
456 return prdata;
457 }
458
459 /*
460 * sfkeyprintf() lookup
461 */
462
463 static int
key(void * handle,register Sffmt_t * fp,const char * arg,char ** ps,Sflong_t * pn)464 key(void* handle, register Sffmt_t* fp, const char* arg, char** ps, Sflong_t* pn)
465 {
466 register Ftw_t* ftw;
467 register struct stat* st;
468 register char* s = 0;
469 register Sflong_t n = 0;
470 register Key_t* kp;
471 List_t* lp;
472 Time_t t;
473
474 static Sfio_t* mp;
475 static const char fmt_mode[] = "mode";
476 static const char fmt_perm[] = "perm";
477 static const char fmt_time[] = "time";
478
479 if (!fp->t_str)
480 return 0;
481 if (lp = (List_t*)handle)
482 {
483 ftw = lp->ftw;
484 st = &ftw->statb;
485 }
486 else
487 {
488 ftw = 0;
489 st = 0;
490 }
491 t = TMX_NOTIME;
492 if (!(kp = (Key_t*)hashget(state.keys, fp->t_str)))
493 {
494 if (*fp->t_str != '$')
495 {
496 error(3, "%s: unknown format key", fp->t_str);
497 return 0;
498 }
499 if (!(kp = newof(0, Key_t, 1, 0)))
500 error(3, "out of space");
501 kp->name = hashput(state.keys, 0, kp);
502 kp->macro = getenv(fp->t_str + 1);
503 kp->index = KEY_environ;
504 kp->disable = 1;
505 }
506 if (kp->macro && !kp->disable)
507 {
508 kp->disable = 1;
509 if (!mp && !(mp = sfstropen()))
510 error(3, "out of space");
511 sfkeyprintf(mp, handle, kp->macro, key, NiL);
512 if (!(s = sfstruse(mp)))
513 error(3, "out of space");
514 kp->disable = 0;
515 }
516 else switch (kp->index)
517 {
518 case KEY_atime:
519 if (st)
520 {
521 n = st->st_atime;
522 t = tmxgetatime(st);
523 }
524 if (!arg)
525 arg = state.timefmt;
526 break;
527 case KEY_blocks:
528 if (st)
529 n = BLOCKS(st);
530 break;
531 case KEY_ctime:
532 if (st)
533 {
534 n = st->st_ctime;
535 t = tmxgetctime(st);
536 }
537 if (!arg)
538 arg = state.timefmt;
539 break;
540 case KEY_dev:
541 if (st)
542 s = fmtdev(st);
543 break;
544 case KEY_device:
545 if (st && (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)))
546 s = fmtdev(st);
547 else
548 return 0;
549 break;
550 case KEY_devmajor:
551 if (st)
552 n = (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) ? major(idevice(st)) : major(st->st_dev);
553 break;
554 case KEY_devminor:
555 if (st)
556 n = (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) ? minor(idevice(st)) : minor(st->st_dev);
557 break;
558 case KEY_dir_blocks:
559 if (!state.scale)
560 {
561 if (lp)
562 n = lp->count.blocks;
563 break;
564 }
565 /*FALLTHROUGH*/
566 case KEY_dir_bytes:
567 if (lp)
568 n = lp->count.bytes;
569 if (state.scale)
570 {
571 s = fmtscale(n, state.scale);
572 fp->fmt = 's';
573 }
574 break;
575 case KEY_dir_count:
576 if (ftw != state.top)
577 {
578 if (state.lsflags & LS_SEPARATE)
579 n = state.directories;
580 else if (state.lsflags & LS_LABEL)
581 n = 1;
582 }
583 break;
584 case KEY_dir_files:
585 if (lp)
586 n = lp->count.files;
587 break;
588 case KEY_environ:
589 if (!(s = kp->macro))
590 return 0;
591 break;
592 case KEY_flags:
593 s = state.flags;
594 break;
595 case KEY_gid:
596 if (st)
597 {
598 if (fp->fmt == 's')
599 s = fmtgid(st->st_gid);
600 else
601 n = st->st_gid;
602 }
603 break;
604 case KEY_ino:
605 if (st)
606 n = st->st_ino;
607 break;
608 case KEY_linkpath:
609 if (ftw && (ftw->info & FTW_SL))
610 {
611 char* dirnam;
612 int c;
613
614 static char* txtdata;
615 static int txtsize;
616
617 if ((st->st_size + 1) > txtsize)
618 {
619 txtsize = roundof(st->st_size + 1, 512);
620 if (!(txtdata = newof(txtdata, char, txtsize, 0)))
621 error(3, "out of space");
622 }
623 if (*ftw->name == '/' || !lp->dirnam)
624 dirnam = ftw->name;
625 else
626 {
627 sfprintf(state.tmp, "%s/%s", lp->dirnam + streq(lp->dirnam, "/"), ftw->name);
628 if (!(dirnam = sfstruse(state.tmp)))
629 error(3, "out of space");
630 }
631 c = pathgetlink(dirnam, txtdata, txtsize);
632 if (c > 0)
633 s = PRINTABLE(txtdata);
634 }
635 else
636 return 0;
637 break;
638 case KEY_linkop:
639 if (ftw && (ftw->info & FTW_SL))
640 s = "->";
641 else
642 return 0;
643 break;
644 case KEY_mark:
645 if (!st)
646 return 0;
647 else if (S_ISLNK(st->st_mode))
648 s = "@";
649 else if (S_ISDIR(st->st_mode))
650 s = "/";
651 #ifdef S_ISDOOR
652 else if (S_ISDOOR(st->st_mode))
653 s = ">";
654 #endif
655 else if (S_ISFIFO(st->st_mode))
656 s = "|";
657 #ifdef S_ISSOCK
658 else if (S_ISSOCK(st->st_mode))
659 s = "=";
660 #endif
661 else if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode))
662 s = "$";
663 else if (st->st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
664 s = "*";
665 else
666 return 0;
667 break;
668 case KEY_markdir:
669 if (!st || !S_ISDIR(st->st_mode))
670 return 0;
671 s = "/";
672 break;
673 case KEY_mode:
674 if (st)
675 n = st->st_mode;
676 if (!arg)
677 arg = fmt_mode;
678 break;
679 case KEY_mtime:
680 if (st)
681 {
682 n = st->st_mtime;
683 t = tmxgetmtime(st);
684 }
685 if (!arg)
686 arg = state.timefmt;
687 break;
688 case KEY_name:
689 if (ftw)
690 s = PRINTABLE(ftw->name);
691 break;
692 case KEY_nlink:
693 if (st)
694 n = st->st_nlink;
695 break;
696 case KEY_path:
697 if (ftw)
698 s = ftw->path ? PRINTABLE(ftw->path) : PRINTABLE(ftw->name);
699 break;
700 case KEY_perm:
701 if (st)
702 n = st->st_mode & S_IPERM;
703 if (!arg)
704 arg = fmt_perm;
705 break;
706 case KEY_size:
707 if (st)
708 {
709 n = st->st_size;
710 if (state.scale)
711 {
712 s = fmtscale(n, state.scale);
713 fp->fmt = 's';
714 }
715 }
716 break;
717 case KEY_total_blocks:
718 if (!state.scale)
719 {
720 n = state.total.blocks;
721 break;
722 }
723 /*FALLTHROUGH*/
724 case KEY_total_bytes:
725 n = state.total.bytes;
726 if (state.scale)
727 {
728 s = fmtscale(n, state.scale);
729 fp->fmt = 's';
730 }
731 break;
732 case KEY_total_files:
733 n = state.total.files;
734 break;
735 case KEY_uid:
736 if (st)
737 {
738 if (fp->fmt == 's')
739 s = fmtuid(st->st_uid);
740 else
741 n = st->st_uid;
742 }
743 break;
744 case KEY_view:
745 if (st)
746 n = iview(st);
747 break;
748 default:
749 return 0;
750 }
751 if (s)
752 {
753 *ps = s;
754 if (mbwide())
755 {
756 register char* p;
757 int w;
758 int i;
759
760 for (p = s; w = mbchar(s); p = s)
761 if (w < 0)
762 s++;
763 else if ((i = mbwidth(w)) >= 0)
764 state.adjust -= (s - p) + i - 2;
765 }
766 }
767 else if (fp->fmt == 's' && arg)
768 {
769 if (strneq(arg, fmt_mode, sizeof(fmt_mode) - 1))
770 *ps = fmtmode(n, 0);
771 else if (strneq(arg, fmt_perm, sizeof(fmt_perm) - 1))
772 *ps = fmtperm(n & S_IPERM);
773 else
774 {
775 if (strneq(arg, fmt_time, sizeof(fmt_time) - 1))
776 {
777 arg += sizeof(fmt_time) - 1;
778 if (*arg == '=')
779 arg++;
780 }
781 if (!*arg)
782 arg = state.timefmt;
783 if ((unsigned long)n >= state.testdate)
784 {
785 n = state.testdate;
786 t = TMX_NOTIME;
787 }
788 *ps = t == TMX_NOTIME ? fmttime(arg, (time_t)n) : fmttmx(arg, t);
789 }
790 }
791 else
792 *pn = n;
793 return 1;
794 }
795
796 /*
797 * print info on a single file
798 * parent directory name is dirnam of dirlen chars
799 */
800
801 static void
pr(register List_t * lp,Ftw_t * ftw,register int fill)802 pr(register List_t* lp, Ftw_t* ftw, register int fill)
803 {
804 if (state.testsize)
805 {
806 ftw->statb.st_size <<= state.testsize;
807 ftw->statb.st_blocks = ftw->statb.st_size / LS_BLOCKSIZE;
808 }
809 #ifdef S_ISLNK
810 /*
811 * -H == --hairbrained
812 * no way around it - this is bud tugley
813 * symlinks should be no more visible than mount points
814 * but I wear my user hat more than my administrator hat
815 */
816
817 if (ftw->level == 0 && (state.ftwflags & (FTW_META|FTW_PHYSICAL)) == (FTW_META|FTW_PHYSICAL) && !(ftw->info & FTW_D) && !lstat(ftw->path ? ftw->path : ftw->name, &ftw->statb) && S_ISLNK(ftw->statb.st_mode))
818 ftw->info = FTW_SL;
819 #endif
820 lp->ftw = ftw;
821 state.adjust = 0;
822 fill -= sfkeyprintf(sfstdout, lp, state.format, key, NiL) + state.adjust;
823 if (!(state.lsflags & LS_COMMAS))
824 {
825 if (fill > 0)
826 while (fill-- > 0)
827 sfputc(sfstdout, ' ');
828 else
829 sfputc(sfstdout, '\n');
830 }
831 }
832
833 /*
834 * pr() ftw directory child list in column order
835 * directory name is dirnam of dirlen chars
836 * count is the number of VISIBLE children
837 * length is the length of the longest VISIBLE child
838 */
839
840 static void
col(register List_t * lp,register Ftw_t * ftw,int length)841 col(register List_t* lp, register Ftw_t* ftw, int length)
842 {
843 register Ftw_t* p;
844 register int i;
845 register int n;
846 register int files;
847 register char* s;
848 int w;
849 int a;
850
851 lp->ftw = ftw;
852 if (keys[KEY_header].macro && ftw->level >= 0)
853 sfkeyprintf(sfstdout, lp, keys[KEY_header].macro, key, NiL);
854 if ((files = lp->count.files) > 0)
855 {
856 if (!(state.lsflags & LS_COLUMNS) || length <= 0)
857 {
858 n = w = 1;
859 a = 0;
860 }
861 else
862 {
863 i = ftw->name[1];
864 ftw->name[1] = 0;
865 state.adjust = 2;
866 a = sfkeyprintf(state.tmp, lp, state.format, key, NiL) - 1;
867 w = a + state.adjust + 1;
868 length += w;
869 sfstrseek(state.tmp, 0, SEEK_SET);
870 ftw->name[1] = i;
871 n = ((state.width - (length + BETWEEN + 2)) < 0) ? 1 : 2;
872 }
873 if (state.lsflags & LS_COMMAS)
874 {
875 length = w - 1;
876 i = 0;
877 n = state.width;
878 for (p = ftw->link; p; p = p->link)
879 if (p->local.number != INVISIBLE)
880 {
881 if (!mbwide())
882 w = p->namelen;
883 else
884 for (s = p->name, w = 0; i = mbchar(s);)
885 if (i < 0)
886 {
887 s++;
888 w++;
889 }
890 else if ((n = mbwidth(i)) > 0)
891 w += n;
892 w += a;
893 if ((n -= length + w) < 0)
894 {
895 n = state.width - (length + w);
896 if (i)
897 sfputr(sfstdout, ",\n", -1);
898 }
899 else if (i)
900 sfputr(sfstdout, ", ", -1);
901 pr(lp, p, 0);
902 i = 1;
903 }
904 if (i)
905 sfputc(sfstdout, '\n');
906 }
907 else if (n <= 1)
908 {
909 for (p = ftw->link; p; p = p->link)
910 if (p->local.number != INVISIBLE)
911 pr(lp, p, 0);
912 }
913 else
914 {
915 register Ftw_t** x;
916 int c;
917 int j;
918 int k;
919 int l;
920 int m;
921 int o;
922 int q;
923 int r;
924 int w;
925 int z;
926
927 static unsigned short* siz;
928 static int sizsiz;
929
930 static Ftw_t** vec;
931 static int vecsiz;
932
933 if (files > sizsiz)
934 {
935 sizsiz = roundof(files, 64);
936 if (!(siz = newof(siz, unsigned short, sizsiz, 0)))
937 error(3, "out of space");
938 }
939 if (files > (vecsiz - 1))
940 {
941 vecsiz = roundof(files + 1, 64);
942 if (!(vec = newof(vec, Ftw_t*, vecsiz, 0)))
943 error(3, "out of space");
944 }
945 x = vec;
946 i = 0;
947 for (p = ftw->link; p; p = p->link)
948 if (p->local.number != INVISIBLE)
949 x[i++] = p;
950 n = i / (state.width / (length + BETWEEN)) + 1;
951 o = 0;
952 if ((state.lsflags & LS_ACROSS) && n > 1)
953 {
954 c = (i - 1) / n + 1;
955 do
956 {
957 w = -AFTER;
958 for (j = 0; j < c; j++)
959 {
960 z = 0;
961 for (l = 0, r = j; l < n && r < i; r += c, l++)
962 if (z < (x[r]->namelen + a))
963 z = x[r]->namelen + a;
964 w += z + BETWEEN;
965 }
966 if (w <= state.width)
967 o = n;
968 } while (c < state.width / 2 && (n = (i + c) / (c + 1)) && ++c);
969 n = o ? o : 1;
970 c = (i - 1) / n + 1;
971 k = 0;
972 for (j = 0; j < c; j++)
973 {
974 siz[k] = 0;
975 for (l = 0, r = j; l < n && r < i; r += c, l++)
976 if (siz[k] < x[r]->namelen)
977 siz[k] = x[r]->namelen;
978 siz[k] += a + BETWEEN;
979 k++;
980 }
981 for (j = 0; j <= i; j += c)
982 for (l = 0, w = j; l < k && w < i; l++, w++)
983 pr(lp, x[w], l < (k - 1) && w < (i - 1) ? siz[l] : 0);
984 }
985 else
986 {
987 o = 0;
988 if (n > 1)
989 {
990 if (!(q = i / n))
991 q = 1;
992 for (c = q; (c - q) < 2 && c <= state.width / (BETWEEN + 1); ++c)
993 {
994 n = m = (i + c - 1) / c;
995 if ((r = i - m * c) > state.height)
996 n -= (r + c - 1) / c;
997 for (; n <= m; n++)
998 {
999 w = -AFTER;
1000 j = 0;
1001 while (j < i)
1002 {
1003 z = 0;
1004 for (l = 0; l < n && j < i; j++, l++)
1005 if (z < x[j]->namelen)
1006 z = x[j]->namelen;
1007 w += z + a + BETWEEN;
1008 }
1009 if (w <= state.width)
1010 {
1011 q = c;
1012 o = n;
1013 break;
1014 }
1015 }
1016 }
1017 }
1018 n = o ? o : 1;
1019 j = k = 0;
1020 while (j < i)
1021 {
1022 siz[k] = 0;
1023 for (l = 0; l < n && j < i; j++, l++)
1024 if (siz[k] < x[j]->namelen)
1025 siz[k] = x[j]->namelen;
1026 siz[k] += a + BETWEEN;
1027 k++;
1028 }
1029 for (j = 0; j < n; j++)
1030 for (l = 0, w = j; l < k && w < i; l++, w += n)
1031 pr(lp, x[w], l < (k - 1) && w < (i - n) ? siz[l] : 0);
1032 }
1033 }
1034 }
1035 if (keys[KEY_trailer].macro && ftw->level >= 0)
1036 sfkeyprintf(sfstdout, lp, keys[KEY_trailer].macro, key, NiL);
1037 }
1038
1039 /*
1040 * order() helpers
1041 */
1042
1043 static int
order_none(register Ftw_t * f1,register Ftw_t * f2)1044 order_none(register Ftw_t* f1, register Ftw_t* f2)
1045 {
1046 return 0;
1047 }
1048
1049 static int
order_blocks(register Ftw_t * f1,register Ftw_t * f2)1050 order_blocks(register Ftw_t* f1, register Ftw_t* f2)
1051 {
1052 if (f1->statb.st_size < f2->statb.st_size)
1053 return 1;
1054 if (f1->statb.st_size > f2->statb.st_size)
1055 return -1;
1056 return 0;
1057 }
1058
1059 static int
order_atime(register Ftw_t * f1,register Ftw_t * f2)1060 order_atime(register Ftw_t* f1, register Ftw_t* f2)
1061 {
1062 Time_t t1;
1063 Time_t t2;
1064
1065 t1 = tmxgetatime(&f1->statb);
1066 t2 = tmxgetatime(&f2->statb);
1067 if (t1 < t2)
1068 return 1;
1069 if (t1 > t2)
1070 return -1;
1071 return 0;
1072 }
1073
1074 static int
order_ctime(register Ftw_t * f1,register Ftw_t * f2)1075 order_ctime(register Ftw_t* f1, register Ftw_t* f2)
1076 {
1077 Time_t t1;
1078 Time_t t2;
1079
1080 t1 = tmxgetctime(&f1->statb);
1081 t2 = tmxgetctime(&f2->statb);
1082 if (t1 < t2)
1083 return 1;
1084 if (t1 > t2)
1085 return -1;
1086 return 0;
1087 }
1088
1089 static int
order_mtime(register Ftw_t * f1,register Ftw_t * f2)1090 order_mtime(register Ftw_t* f1, register Ftw_t* f2)
1091 {
1092 Time_t t1;
1093 Time_t t2;
1094
1095 t1 = tmxgetmtime(&f1->statb);
1096 t2 = tmxgetmtime(&f2->statb);
1097 if (t1 < t2)
1098 return 1;
1099 if (t1 > t2)
1100 return -1;
1101 return 0;
1102 }
1103
1104 static int
order_extension(register Ftw_t * f1,register Ftw_t * f2)1105 order_extension(register Ftw_t* f1, register Ftw_t* f2)
1106 {
1107 register int n;
1108 char* x1;
1109 char* x2;
1110
1111 x1 = strrchr(f1->name, '.');
1112 x2 = strrchr(f2->name, '.');
1113 if (x1)
1114 {
1115 if (x2)
1116 n = strcoll(x1, x2);
1117 else
1118 n = 1;
1119 }
1120 else if (x2)
1121 n = -1;
1122 else
1123 n = 0;
1124 if (!n)
1125 n = strcoll(f1->name, f2->name);
1126 return n;
1127 }
1128
1129 static int
order_version(Ftw_t * f1,Ftw_t * f2)1130 order_version(Ftw_t* f1, Ftw_t* f2)
1131 {
1132 return strvcmp(f1->name, f2->name);
1133 }
1134
1135 static int
order_name(Ftw_t * f1,Ftw_t * f2)1136 order_name(Ftw_t* f1, Ftw_t* f2)
1137 {
1138 return strcoll(f1->name, f2->name);
1139 }
1140
1141 /*
1142 * order child entries
1143 */
1144
1145 static int
order(register Ftw_t * f1,register Ftw_t * f2)1146 order(register Ftw_t* f1, register Ftw_t* f2)
1147 {
1148 int n;
1149
1150 if (!(state.lsflags & LS_DIRECTORY) && (state.ftwflags & FTW_MULTIPLE) && f1->level == 0)
1151 {
1152 if (f1->info == FTW_D)
1153 {
1154 if (f2->info != FTW_D)
1155 return 1;
1156 }
1157 else if (f2->info == FTW_D)
1158 return -1;
1159 }
1160 n = (*state.order)(f1, f2);
1161 return state.reverse ? -n : n;
1162 }
1163
1164 /*
1165 * list a directory and its children
1166 */
1167
1168 static void
dir(register Ftw_t * ftw)1169 dir(register Ftw_t* ftw)
1170 {
1171 register Ftw_t* p;
1172 register int length;
1173 int top = 0;
1174 List_t list;
1175
1176 if (ftw->status == FTW_NAME)
1177 {
1178 list.dirlen = ftw->namelen;
1179 list.dirnam = ftw->path + ftw->pathlen - list.dirlen;
1180 }
1181 else
1182 {
1183 list.dirlen = ftw->pathlen;
1184 list.dirnam = ftw->path;
1185 }
1186 if (ftw->level >= 0)
1187 state.directories++;
1188 else
1189 state.top = ftw;
1190 length = 0;
1191 list.count.blocks = 0;
1192 list.count.bytes = 0;
1193 list.count.files = 0;
1194 for (p = ftw->link; p; p = p->link)
1195 {
1196 if (p->level == 0 && p->info == FTW_D && !(state.lsflags & LS_DIRECTORY))
1197 {
1198 p->local.number = INVISIBLE;
1199 top++;
1200 }
1201 else if (VISIBLE(p))
1202 {
1203 if (p->info == FTW_NS)
1204 {
1205 if (ftw->level < 0 || !(state.lsflags & LS_NOSTAT))
1206 {
1207 if (ftw->path[0] == '.' && !ftw->path[1])
1208 error(2, "%s: not found", p->name);
1209 else
1210 error(2, "%s/%s: not found", ftw->path, p->name);
1211 goto invisible;
1212 }
1213 }
1214 else
1215 {
1216 list.count.blocks += BLOCKS(&p->statb);
1217 list.count.bytes += p->statb.st_size;
1218 }
1219 list.count.files++;
1220 if (p->namelen > length)
1221 length = p->namelen;
1222 if (!(state.lsflags & LS_RECURSIVE))
1223 p->status = FTW_SKIP;
1224 }
1225 else
1226 {
1227 invisible:
1228 p->local.number = INVISIBLE;
1229 p->status = FTW_SKIP;
1230 }
1231 }
1232 state.total.blocks += list.count.blocks;
1233 state.total.bytes += list.count.bytes;
1234 state.total.files += list.count.files;
1235 col(&list, ftw, length);
1236 state.lsflags |= LS_SEPARATE;
1237 if (top)
1238 {
1239 if (list.count.files)
1240 {
1241 state.directories++;
1242 state.top = 0;
1243 }
1244 else if (top > 1)
1245 state.top = 0;
1246 else
1247 state.top = ftw->link;
1248 for (p = ftw->link; p; p = p->link)
1249 if (p->level == 0 && p->info == FTW_D)
1250 p->local.number = 0;
1251 }
1252 }
1253
1254 /*
1255 * list info on a single file
1256 */
1257
1258 static int
ls(register Ftw_t * ftw)1259 ls(register Ftw_t* ftw)
1260 {
1261 if (!VISIBLE(ftw))
1262 {
1263 ftw->status = FTW_SKIP;
1264 return 0;
1265 }
1266 switch (ftw->info)
1267 {
1268 case FTW_NS:
1269 if (ftw->parent->info == FTW_DNX)
1270 break;
1271 error(2, "%s: not found", ftw->path);
1272 return 0;
1273 case FTW_DC:
1274 if (state.lsflags & LS_DIRECTORY)
1275 break;
1276 error(2, "%s: directory causes cycle", ftw->path);
1277 return 0;
1278 case FTW_DNR:
1279 if (state.lsflags & LS_DIRECTORY)
1280 break;
1281 error(2, "%s: cannot read directory", ftw->path);
1282 return 0;
1283 case FTW_D:
1284 case FTW_DNX:
1285 if ((state.lsflags & LS_DIRECTORY) && ftw->level >= 0)
1286 break;
1287 if (!(state.lsflags & LS_RECURSIVE))
1288 ftw->status = FTW_SKIP;
1289 else if (ftw->info == FTS_DNX)
1290 {
1291 error(2, "%s: cannot search directory", ftw->path, ftw->level);
1292 ftw->status = FTW_SKIP;
1293 if (ftw->level > 0 && !(state.lsflags & LS_NOSTAT))
1294 return 0;
1295 }
1296 dir(ftw);
1297 return 0;
1298 }
1299 ftw->status = FTW_SKIP;
1300 if (!ftw->level)
1301 {
1302 static List_t list;
1303
1304 list.ftw = ftw;
1305 pr(&list, ftw, 0);
1306 }
1307 return 0;
1308 }
1309
1310 #define set(f) (opt_info.num?(state.lsflags|=(f)):((state.lsflags&=~(f)),0))
1311 #define clr(f) (opt_info.num?(state.lsflags&=~(f)):(state.lsflags|=(f)))
1312
1313 int
main(int argc,register char ** argv)1314 main(int argc, register char** argv)
1315 {
1316 register int n;
1317 register char* s;
1318 char* e;
1319 Key_t* kp;
1320 Sfio_t* fmt;
1321 long lsflags;
1322 int dump = 0;
1323
1324 static char fmt_color[] = "%(mode:case:d*:\\E[01;34m%(name)s\\E[0m:l*:\\E[01;36m%(name)s\\E[0m:*x*:\\E[01;32m%(name)s\\E[0m:*:%(name)s)s";
1325
1326 NoP(argc);
1327 setlocale(LC_ALL, "");
1328 if (s = strrchr(argv[0], '/'))
1329 s++;
1330 else
1331 s = argv[0];
1332 error_info.id = s;
1333 state.ftwflags = ftwflags() | FTW_META | FTW_CHILDREN;
1334 if (!(fmt = sfstropen()) || !(state.tmp = sfstropen()))
1335 error(3, "out of space");
1336 if (!(state.keys = hashalloc(NiL, HASH_name, "keys", 0)))
1337 error(3, "out of space");
1338 for (n = 1; n < elementsof(keys); n++)
1339 hashput(state.keys, keys[n].name, &keys[keys[n].index]);
1340 hashset(state.keys, HASH_ALLOCATE);
1341 if (streq(s, "lc"))
1342 state.lsflags |= LS_COLUMNS;
1343 else if (streq(s, "lf") || streq(s, "lsf"))
1344 state.lsflags |= LS_MARK;
1345 else if (streq(s, "ll"))
1346 state.lsflags |= LS_LONG;
1347 else if (streq(s, "lsr"))
1348 state.lsflags |= LS_RECURSIVE;
1349 else if (streq(s, "lsx"))
1350 state.lsflags |= LS_ACROSS|LS_COLUMNS;
1351 else if (isatty(1))
1352 {
1353 state.lsflags |= LS_COLUMNS;
1354 if (!strmatch(setlocale(LC_ALL, NiL), "*[Uu][Tt][Ff]?(-)8"))
1355 state.lsflags |= LS_PRINTABLE;
1356 }
1357 state.endflags = state.flags;
1358 state.blocksize = 512;
1359 state.testdate = ~0;
1360 state.timefmt = "%?%l";
1361 lsflags = state.lsflags;
1362 while (n = optget(argv, usage))
1363 {
1364 switch (n)
1365 {
1366 case 'a':
1367 set(LS_ALL);
1368 break;
1369 case 'b':
1370 set(LS_PRINTABLE|LS_ESCAPE);
1371 break;
1372 case 'c':
1373 state.lsflags &= ~LS_ATIME;
1374 state.lsflags |= LS_CTIME;
1375 if (!state.order)
1376 state.order = order_ctime;
1377 break;
1378 case 'd':
1379 set(LS_DIRECTORY);
1380 break;
1381 case 'e':
1382 state.lsflags |= LS_LONG;
1383 state.timefmt = TIME_LONG_ISO;
1384 break;
1385 case 'f':
1386 state.lsflags |= LS_ALL;
1387 state.lsflags &= ~(LS_BLOCKS|LS_LONG|LS_TIME);
1388 state.reverse = 0;
1389 state.order = order_none;
1390 break;
1391 case 'g':
1392 case 'O':
1393 if (opt_info.num)
1394 state.lsflags |= LS_LONG|LS_NOUSER;
1395 else
1396 state.lsflags |= LS_LONG|LS_NOGROUP;
1397 break;
1398 case 'h':
1399 state.scale = 1024;
1400 break;
1401 case 'i':
1402 set(LS_INUMBER);
1403 break;
1404 case 'k':
1405 state.blocksize = 1024;
1406 break;
1407 case 'l':
1408 set(LS_LONG);
1409 break;
1410 case 'm':
1411 set(LS_COMMAS);
1412 break;
1413 case 'n':
1414 set(LS_NUMBER);
1415 break;
1416 case 'o':
1417 case 'G':
1418 if (opt_info.num)
1419 state.lsflags |= LS_LONG|LS_NOGROUP;
1420 else
1421 state.lsflags |= LS_LONG|LS_NOUSER;
1422 break;
1423 case 'p':
1424 set(LS_MARKDIR);
1425 break;
1426 case 'q':
1427 set(LS_PRINTABLE);
1428 break;
1429 case 'r':
1430 state.reverse = !!opt_info.num;
1431 break;
1432 case 's':
1433 set(LS_BLOCKS);
1434 break;
1435 case 't':
1436 if (set(LS_TIME) && !state.order)
1437 state.order = order_mtime;
1438 break;
1439 case 'u':
1440 state.lsflags &= ~LS_CTIME;
1441 state.lsflags |= LS_ATIME;
1442 if (!state.order)
1443 state.order = order_atime;
1444 break;
1445 case 'w':
1446 state.width = strtol(opt_info.arg, &e, 0);
1447 if (*e == 'x' || *e == 'X' || *e == '.' || *e == '+')
1448 {
1449 state.height = state.width;
1450 state.width = strtol(e + 1, &e, 0);
1451 }
1452 if (*e)
1453 error(2, "%s: invalid screen width specification at `%s'", opt_info.arg, e);
1454 break;
1455 case 'x':
1456 set(LS_ACROSS|LS_COLUMNS);
1457 break;
1458 case 'y':
1459 if (!opt_info.arg)
1460 state.order = order_none;
1461 else
1462 switch (opt_info.num)
1463 {
1464 case 'a':
1465 state.order = order_atime;
1466 break;
1467 case 'c':
1468 state.order = order_ctime;
1469 break;
1470 case 'f':
1471 state.order = 0;
1472 break;
1473 case 'm':
1474 state.order = order_mtime;
1475 break;
1476 case 'n':
1477 state.order = order_none;
1478 break;
1479 case 's':
1480 state.order = order_blocks;
1481 break;
1482 case 't':
1483 state.order = order_mtime;
1484 break;
1485 case 'v':
1486 state.order = order_version;
1487 break;
1488 case 'x':
1489 state.order = order_extension;
1490 break;
1491 }
1492 break;
1493 case 'z':
1494 switch (opt_info.num)
1495 {
1496 case -10:
1497 if (!strcmp(setlocale(LC_TIME, NiL), "C"))
1498 break;
1499 /*FALLTHROUGH*/
1500 case 'i':
1501 state.timefmt = TIME_ISO;
1502 break;
1503 case -11:
1504 if (!strcmp(setlocale(LC_TIME, NiL), "C"))
1505 break;
1506 /*FALLTHROUGH*/
1507 case 'f':
1508 state.timefmt = TIME_FULL_ISO;
1509 break;
1510 case 'l':
1511 state.timefmt = TIME_LONG_ISO;
1512 break;
1513 case 'L':
1514 state.timefmt = TIME_LOCALE;
1515 break;
1516 case -12:
1517 s = opt_info.arg + 1;
1518 if (strchr(s, '\n'))
1519 {
1520 /*
1521 * gnu compatibility
1522 */
1523
1524 s = sfprints("%%Q\n%s\n", s);
1525 if (!s || !(s = strdup(s)))
1526 error(ERROR_SYSTEM|3, "out of space");
1527 }
1528 state.timefmt = s;
1529 break;
1530 }
1531 break;
1532 case 'A':
1533 state.lsflags |= LS_MOST;
1534 state.lsflags &= ~LS_ALL;
1535 break;
1536 case 'B':
1537 set(LS_NOBACKUP);
1538 break;
1539 case 'C':
1540 set(LS_COLUMNS);
1541 break;
1542 case 'D':
1543 if (s = strchr(opt_info.arg, '='))
1544 *s++ = 0;
1545 if (*opt_info.arg == 'n' && *(opt_info.arg + 1) == 'o')
1546 {
1547 opt_info.arg += 2;
1548 s = 0;
1549 }
1550 if (!(kp = (Key_t*)hashget(state.keys, opt_info.arg)))
1551 {
1552 if (!s)
1553 break;
1554 if (!(kp = newof(0, Key_t, 1, 0)))
1555 error(3, "out of space");
1556 kp->name = hashput(state.keys, 0, kp);
1557 }
1558 if (kp->macro = s)
1559 {
1560 stresc(s);
1561 if (strmatch(s, "*:case:*"))
1562 state.lsflags |= LS_STAT;
1563 }
1564 break;
1565 case 'E':
1566 state.lsflags |= LS_LONG;
1567 state.timefmt = TIME_FULL_ISO;
1568 break;
1569 case 'F':
1570 set(LS_MARK);
1571 break;
1572 case 'H':
1573 state.ftwflags |= FTW_META|FTW_PHYSICAL;
1574 break;
1575 case 'I':
1576 state.ignore = opt_info.arg;
1577 break;
1578 case 'J':
1579 state.lsflags &= ~(LS_ALWAYS|LS_ESCAPE|LS_PRINTABLE|LS_QUOTE|LS_SHELL);
1580 switch (opt_info.num)
1581 {
1582 case 'c':
1583 state.lsflags |= LS_ESCAPE|LS_PRINTABLE|LS_QUOTE;
1584 break;
1585 case 'e':
1586 state.lsflags |= LS_ESCAPE|LS_PRINTABLE;
1587 break;
1588 case 'l':
1589 break;
1590 case 'q':
1591 state.lsflags |= LS_PRINTABLE;
1592 break;
1593 case 's':
1594 state.lsflags |= LS_ESCAPE|LS_PRINTABLE|LS_QUOTE|LS_SHELL;
1595 break;
1596 case 'S':
1597 state.lsflags |= LS_ALWAYS|LS_ESCAPE|LS_PRINTABLE|LS_QUOTE|LS_SHELL;
1598 break;
1599 }
1600 break;
1601 case 'K':
1602 set(LS_PRINTABLE|LS_SHELL|LS_QUOTE|LS_ESCAPE);
1603 break;
1604 case 'L':
1605 state.ftwflags &= ~(FTW_META|FTW_PHYSICAL|FTW_SEEDOTDIR);
1606 break;
1607 case 'N':
1608 clr(LS_PRINTABLE);
1609 break;
1610 case 'P':
1611 state.ftwflags &= ~FTW_META;
1612 state.ftwflags |= FTW_PHYSICAL;
1613 break;
1614 case 'Q':
1615 set(LS_PRINTABLE|LS_QUOTE);
1616 break;
1617 case 'R':
1618 set(LS_RECURSIVE);
1619 break;
1620 case 'S':
1621 state.order = order_blocks;
1622 break;
1623 case 'T':
1624 /* ignored */
1625 break;
1626 case 'U':
1627 state.order = order_none;
1628 break;
1629 case 'V':
1630 switch (opt_info.num)
1631 {
1632 case 't':
1633 if (!isatty(1))
1634 break;
1635 /*FALLTHROUGH*/
1636 case 'a':
1637 if (kp = (Key_t*)hashget(state.keys, "name"))
1638 {
1639 stresc(kp->macro = fmt_color);
1640 state.lsflags |= LS_STAT;
1641 }
1642 break;
1643 }
1644 break;
1645 case 'W':
1646 state.timeflags = 0;
1647 switch (opt_info.num)
1648 {
1649 case 'a':
1650 state.timeflags = LS_ATIME;
1651 break;
1652 case 'c':
1653 state.timeflags = LS_CTIME;
1654 break;
1655 }
1656 break;
1657 case 'X':
1658 set(LS_EXTENSION);
1659 break;
1660 case 'Y':
1661 switch (opt_info.num)
1662 {
1663 case 'a':
1664 state.lsflags |= LS_ACROSS|LS_COLUMNS;
1665 break;
1666 case 'c':
1667 state.lsflags |= LS_COMMAS;
1668 break;
1669 case 'l':
1670 state.lsflags |= LS_LONG;
1671 break;
1672 case 'v':
1673 state.lsflags &= ~LS_ACROSS;
1674 state.lsflags |= LS_COLUMNS;
1675 break;
1676 case '1':
1677 state.lsflags &= ~(LS_ACROSS|LS_COLUMNS);
1678 break;
1679 }
1680 break;
1681 case 'Z':
1682 if (!sfstrtell(fmt))
1683 state.lsflags &= ~LS_COLUMNS;
1684 sfputr(fmt, opt_info.arg, ' ');
1685 break;
1686 case '1':
1687 clr(LS_COLUMNS|LS_PRINTABLE);
1688 break;
1689 case -101:
1690 if (opt_info.num <= 0)
1691 error(3, "%ld: invalid block size", opt_info.num);
1692 state.blocksize = opt_info.num;
1693 break;
1694 case -102:
1695 state.scale = 1000;
1696 break;
1697 case -103:
1698 dump = 1;
1699 break;
1700 case -104:
1701 state.testdate = tmdate(opt_info.arg, &e, NiL);
1702 if (*e)
1703 error(3, "%s: invalid date string", opt_info.arg, opt_info.option);
1704 break;
1705 case -105:
1706 state.testsize = opt_info.num;
1707 break;
1708 case '?':
1709 error(ERROR_USAGE|4, "%s", opt_info.arg);
1710 break;
1711 case ':':
1712 error(2, "%s", opt_info.arg);
1713 break;
1714 default:
1715 error(1, "%s: option not implemented", opt_info.name);
1716 continue;
1717 }
1718 if (!strchr(state.flags, n))
1719 *state.endflags++ = n;
1720 }
1721 argv += opt_info.index;
1722 if (error_info.errors)
1723 error(ERROR_USAGE|4, "%s", optusage(NiL));
1724 if (state.lsflags == (lsflags|LS_TIME))
1725 state.ftwflags |= FTW_SEEDOTDIR; /* keep configure happy */
1726 if (state.lsflags & LS_DIRECTORY)
1727 state.lsflags &= ~LS_RECURSIVE;
1728 if (!state.order)
1729 state.order = order_name;
1730 if (!state.timeflags)
1731 state.timeflags = state.lsflags;
1732 if (state.lsflags & (LS_COLUMNS|LS_COMMAS))
1733 {
1734 if (state.lsflags & LS_LONG)
1735 state.lsflags &= ~(LS_COLUMNS|LS_COMMAS);
1736 else
1737 {
1738 if (!state.width)
1739 {
1740 astwinsize(1, &state.height, &state.width);
1741 if (state.width <= 20)
1742 state.width = 80;
1743 }
1744 if (state.height <= 4)
1745 state.height = 24;
1746 }
1747 }
1748 if (state.lsflags & LS_STAT)
1749 state.lsflags &= ~LS_NOSTAT;
1750 else if (!(state.lsflags & (LS_DIRECTORY|LS_BLOCKS|LS_LONG|LS_MARK|LS_MARKDIR|LS_TIME
1751 #if !_mem_d_fileno_dirent && !_mem_d_ino_dirent
1752 |LS_INUMBER
1753 #endif
1754 )) && !sfstrtell(fmt))
1755 {
1756 state.lsflags |= LS_NOSTAT;
1757 state.ftwflags |= FTW_DELAY|FTW_DOT;
1758 }
1759 if (!sfstrtell(fmt))
1760 {
1761 if (state.lsflags & LS_INUMBER)
1762 sfputr(fmt, "%6(ino)u ", -1);
1763 if (state.lsflags & LS_BLOCKS)
1764 sfputr(fmt, "%5(blocks)u ", -1);
1765 if (state.lsflags & LS_LONG)
1766 {
1767 sfputr(fmt, "%(mode)s %3(nlink)u", -1);
1768 if (!(state.lsflags & LS_NOUSER))
1769 sfprintf(fmt, " %%-8(uid)%c", (state.lsflags & LS_NUMBER) ? 'd' : 's');
1770 if (!(state.lsflags & LS_NOGROUP))
1771 sfprintf(fmt, " %%-8(gid)%c", (state.lsflags & LS_NUMBER) ? 'd' : 's');
1772 sfputr(fmt, " %8(device:case::%(size)u:*:%(device)s)s", -1);
1773 sfprintf(fmt, " %%(%s)s ", (state.timeflags & LS_ATIME) ? "atime" : (state.timeflags & LS_CTIME) ? "ctime" : "mtime");
1774 }
1775 sfputr(fmt, "%(name)s", -1);
1776 if (state.lsflags & LS_MARK)
1777 sfputr(fmt, "%(mark)s", -1);
1778 else if (state.lsflags & LS_MARKDIR)
1779 sfputr(fmt, "%(markdir)s", -1);
1780 if (state.lsflags & LS_LONG)
1781 sfputr(fmt, "%(linkop:case:?*: %(linkop)s %(linkpath)s)s", -1);
1782 }
1783 else
1784 sfstrseek(fmt, -1, SEEK_CUR);
1785 if (!(state.format = sfstruse(fmt)))
1786 error(3, "out of space");
1787 if (dump)
1788 {
1789 sfprintf(sfstdout, "%s\n", state.format);
1790 return 0;
1791 }
1792 stresc(state.format);
1793
1794 /*
1795 * do it
1796 */
1797
1798 if (argv[0])
1799 {
1800 if (argv[1])
1801 state.lsflags |= LS_LABEL;
1802 state.ftwflags |= FTW_MULTIPLE;
1803 ftwalk((char*)argv, ls, state.ftwflags, order);
1804 }
1805 else
1806 ftwalk(".", ls, state.ftwflags, order);
1807 if (keys[KEY_summary].macro)
1808 sfkeyprintf(sfstdout, NiL, keys[KEY_summary].macro, key, NiL);
1809 return error_info.errors != 0;
1810 }
1811