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