ls.c revision 46684
1/*
2 * Copyright (c) 1989, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Michael Fischbein.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 *    must display the following acknowledgement:
18 *	This product includes software developed by the University of
19 *	California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 *    may be used to endorse or promote products derived from this software
22 *    without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#ifndef lint
38static const char copyright[] =
39"@(#) Copyright (c) 1989, 1993, 1994\n\
40	The Regents of the University of California.  All rights reserved.\n";
41#endif /* not lint */
42
43#ifndef lint
44#if 0
45static char sccsid[] = "@(#)ls.c	8.5 (Berkeley) 4/2/94";
46#else
47static const char rcsid[] =
48	"$Id: ls.c,v 1.23 1998/08/02 22:47:11 hoek Exp $";
49#endif
50#endif /* not lint */
51
52#include <sys/types.h>
53#include <sys/stat.h>
54#include <sys/ioctl.h>
55
56#include <dirent.h>
57#include <err.h>
58#include <errno.h>
59#include <fts.h>
60#include <stdio.h>
61#include <stdlib.h>
62#include <string.h>
63#include <unistd.h>
64#include <locale.h>
65
66#include "ls.h"
67#include "extern.h"
68
69static void	 display __P((FTSENT *, FTSENT *));
70static u_quad_t	 makenines __P((u_long));
71static int	 mastercmp __P((const FTSENT **, const FTSENT **));
72static void	 traverse __P((int, char **, int));
73
74static void (*printfcn) __P((DISPLAY *));
75static int (*sortfcn) __P((const FTSENT *, const FTSENT *));
76
77long blocksize;			/* block size units */
78int termwidth = 80;		/* default terminal width */
79
80/* flags */
81int f_accesstime;		/* use time of last access */
82int f_column;			/* columnated format */
83int f_flags;			/* show flags associated with a file */
84int f_inode;			/* print inode */
85int f_kblocks;			/* print size in kilobytes */
86int f_listdir;			/* list actual directory, not contents */
87int f_listdot;			/* list files beginning with . */
88int f_longform;			/* long listing format */
89int f_nonprint;			/* show unprintables as ? */
90int f_nosort;			/* don't sort output */
91int f_octal;			/* show unprintables as \xxx */
92int f_octal_escape;		/* like f_octal but use C escapes if possible */
93int f_recursive;		/* ls subdirectories also */
94int f_reversesort;		/* reverse whatever sort is used */
95int f_sectime;			/* print the real time for all files */
96int f_singlecol;		/* use single column output */
97int f_size;			/* list size in short listing */
98int f_statustime;		/* use time of last mode change */
99int f_notabs;			/* don't use tab-separated multi-col output */
100int f_timesort;			/* sort by time vice name */
101int f_type;			/* add type character for non-regular files */
102int f_whiteout;			/* show whiteout entries */
103
104int rval;
105
106int
107main(argc, argv)
108	int argc;
109	char *argv[];
110{
111	static char dot[] = ".", *dotav[] = { dot, NULL };
112	struct winsize win;
113	int ch, fts_options, notused;
114	char *p;
115
116	(void) setlocale(LC_ALL, "");
117
118	/* Terminal defaults to -Cq, non-terminal defaults to -1. */
119	if (isatty(STDOUT_FILENO)) {
120		if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == -1 ||
121		    !win.ws_col) {
122			if ((p = getenv("COLUMNS")) != NULL)
123				termwidth = atoi(p);
124		}
125		else
126			termwidth = win.ws_col;
127		f_column = f_nonprint = 1;
128	} else {
129		f_singlecol = 1;
130		/* retrieve environment variable, in case of explicit -C */
131		if ((p = getenv("COLUMNS")))
132			termwidth = atoi(p);
133	}
134
135	/* Root is -A automatically. */
136	if (!getuid())
137		f_listdot = 1;
138
139	fts_options = FTS_PHYSICAL;
140	while ((ch = getopt(argc, argv, "1ABCFHLPRTWabcdfgikloqrstu")) != -1) {
141		switch (ch) {
142		/*
143		 * The -1, -C and -l options all override each other so shell
144		 * aliasing works right.
145		 */
146		case '1':
147			f_singlecol = 1;
148			f_column = f_longform = 0;
149			break;
150		case 'B':
151			f_nonprint = 0;
152			f_octal = 1;
153		        f_octal_escape = 0;
154			break;
155		case 'C':
156			f_column = 1;
157			f_longform = f_singlecol = 0;
158			break;
159		case 'l':
160			f_longform = 1;
161			f_column = f_singlecol = 0;
162			break;
163		/* The -c and -u options override each other. */
164		case 'c':
165			f_statustime = 1;
166			f_accesstime = 0;
167			break;
168		case 'u':
169			f_accesstime = 1;
170			f_statustime = 0;
171			break;
172		case 'F':
173			f_type = 1;
174			break;
175		case 'H':
176		        fts_options |= FTS_COMFOLLOW;
177			break;
178		case 'L':
179			fts_options &= ~FTS_PHYSICAL;
180			fts_options |= FTS_LOGICAL;
181			break;
182		case 'P':
183		        fts_options &= ~FTS_COMFOLLOW;
184			fts_options &= ~FTS_LOGICAL;
185			fts_options |= FTS_PHYSICAL;
186			break;
187		case 'R':
188			f_recursive = 1;
189			break;
190		case 'a':
191			fts_options |= FTS_SEEDOT;
192			/* FALLTHROUGH */
193		case 'A':
194			f_listdot = 1;
195			break;
196		/* The -d option turns off the -R option. */
197		case 'd':
198			f_listdir = 1;
199			f_recursive = 0;
200			break;
201		case 'f':
202			f_nosort = 1;
203			break;
204		case 'g':		/* Compatibility with 4.3BSD. */
205			break;
206		case 'i':
207			f_inode = 1;
208			break;
209		case 'k':
210			f_kblocks = 1;
211			break;
212		case 'o':
213			f_flags = 1;
214			break;
215		case 'q':
216			f_nonprint = 1;
217			f_octal = 0;
218		        f_octal_escape = 0;
219			break;
220		case 'r':
221			f_reversesort = 1;
222			break;
223		case 's':
224			f_size = 1;
225			break;
226		case 'T':
227			f_sectime = 1;
228			break;
229		case 't':
230			f_timesort = 1;
231			break;
232		case 'W':
233			f_whiteout = 1;
234			break;
235		case 'b':
236			f_nonprint = 0;
237		        f_octal = 0;
238			f_octal_escape = 1;
239			break;
240		default:
241		case '?':
242			usage();
243		}
244	}
245	argc -= optind;
246	argv += optind;
247
248	/*
249	 * If not -F, -i, -l, -s or -t options, don't require stat
250	 * information.
251	 */
252	if (!f_inode && !f_longform && !f_size && !f_timesort && !f_type)
253		fts_options |= FTS_NOSTAT;
254
255	/*
256	 * If not -F, -d or -l options, follow any symbolic links listed on
257	 * the command line.
258	 */
259	if (!f_longform && !f_listdir && !f_type)
260		fts_options |= FTS_COMFOLLOW;
261
262	/*
263	 * If -W, show whiteout entries
264	 */
265#ifdef FTS_WHITEOUT
266	if (f_whiteout)
267		fts_options |= FTS_WHITEOUT;
268#endif
269
270	/* If -l or -s, figure out block size. */
271	if (f_longform || f_size) {
272		if (f_kblocks)
273			blocksize = 2;
274		else {
275			(void)getbsize(&notused, &blocksize);
276			blocksize /= 512;
277		}
278	}
279
280	/* Select a sort function. */
281	if (f_reversesort) {
282		if (!f_timesort)
283			sortfcn = revnamecmp;
284		else if (f_accesstime)
285			sortfcn = revacccmp;
286		else if (f_statustime)
287			sortfcn = revstatcmp;
288		else /* Use modification time. */
289			sortfcn = revmodcmp;
290	} else {
291		if (!f_timesort)
292			sortfcn = namecmp;
293		else if (f_accesstime)
294			sortfcn = acccmp;
295		else if (f_statustime)
296			sortfcn = statcmp;
297		else /* Use modification time. */
298			sortfcn = modcmp;
299	}
300
301	/* Select a print function. */
302	if (f_singlecol)
303		printfcn = printscol;
304	else if (f_longform)
305		printfcn = printlong;
306	else
307		printfcn = printcol;
308
309	if (argc)
310		traverse(argc, argv, fts_options);
311	else
312		traverse(1, dotav, fts_options);
313	exit(rval);
314}
315
316static int output;			/* If anything output. */
317
318/*
319 * Traverse() walks the logical directory structure specified by the argv list
320 * in the order specified by the mastercmp() comparison function.  During the
321 * traversal it passes linked lists of structures to display() which represent
322 * a superset (may be exact set) of the files to be displayed.
323 */
324static void
325traverse(argc, argv, options)
326	int argc, options;
327	char *argv[];
328{
329	FTS *ftsp;
330	FTSENT *p, *chp;
331	int ch_options;
332
333	if ((ftsp =
334	    fts_open(argv, options, f_nosort ? NULL : mastercmp)) == NULL)
335		err(1, NULL);
336
337	display(NULL, fts_children(ftsp, 0));
338	if (f_listdir)
339		return;
340
341	/*
342	 * If not recursing down this tree and don't need stat info, just get
343	 * the names.
344	 */
345	ch_options = !f_recursive && options & FTS_NOSTAT ? FTS_NAMEONLY : 0;
346
347	while ((p = fts_read(ftsp)) != NULL)
348		switch (p->fts_info) {
349		case FTS_DC:
350			warnx("%s: directory causes a cycle", p->fts_name);
351			break;
352		case FTS_DNR:
353		case FTS_ERR:
354			warnx("%s: %s", p->fts_name, strerror(p->fts_errno));
355			rval = 1;
356			break;
357		case FTS_D:
358			if (p->fts_level != FTS_ROOTLEVEL &&
359			    p->fts_name[0] == '.' && !f_listdot)
360				break;
361
362			/*
363			 * If already output something, put out a newline as
364			 * a separator.  If multiple arguments, precede each
365			 * directory with its name.
366			 */
367			if (output)
368				(void)printf("\n%s:\n", p->fts_path);
369			else if (argc > 1) {
370				(void)printf("%s:\n", p->fts_path);
371				output = 1;
372			}
373
374			chp = fts_children(ftsp, ch_options);
375			display(p, chp);
376
377			if (!f_recursive && chp != NULL)
378				(void)fts_set(ftsp, p, FTS_SKIP);
379			break;
380		}
381	if (errno)
382		err(1, "fts_read");
383}
384
385/*
386 * Display() takes a linked list of FTSENT structures and passes the list
387 * along with any other necessary information to the print function.  P
388 * points to the parent directory of the display list.
389 */
390static void
391display(p, list)
392	FTSENT *p, *list;
393{
394	struct stat *sp;
395	DISPLAY d;
396	FTSENT *cur;
397	NAMES *np;
398	u_quad_t maxsize;
399	u_long btotal, maxblock, maxinode, maxlen, maxnlink;
400	int bcfile, flen, glen, ulen, maxflags, maxgroup, maxuser;
401	char *initmax;
402	int entries, needstats;
403	char *user, *group, *flags, buf[20];	/* 32 bits == 10 digits */
404
405	/*
406	 * If list is NULL there are two possibilities: that the parent
407	 * directory p has no children, or that fts_children() returned an
408	 * error.  We ignore the error case since it will be replicated
409	 * on the next call to fts_read() on the post-order visit to the
410	 * directory p, and will be signaled in traverse().
411	 */
412	if (list == NULL)
413		return;
414
415	needstats = f_inode || f_longform || f_size;
416	flen = 0;
417	btotal = 0;
418	initmax = getenv("LS_COLWIDTHS");
419	/* Fields match -lios order.  New ones should be added at the end. */
420	if (initmax != NULL && *initmax != '\0') {
421		char *initmax2, *jinitmax;
422		int ninitmax;
423
424		/* Fill-in "::" as "0:0:0" for the sake of scanf. */
425		jinitmax = initmax2 = malloc(strlen(initmax) * 2 + 2);
426		if (jinitmax == NULL)
427			err(1, NULL);
428		if (*initmax == ':')
429			strcpy(initmax2, "0:"), initmax2 += 2;
430		else
431			*initmax2++ = *initmax, *initmax2 = '\0';
432		for (initmax++; *initmax != '\0'; initmax++) {
433			if (initmax[-1] == ':' && initmax[0] == ':') {
434				*initmax2++ = '0';
435				*initmax2++ = initmax[0];
436				initmax2[1] = '\0';
437			} else {
438				*initmax2++ = initmax[0];
439				initmax2[1] = '\0';
440			}
441		}
442		if (initmax2[-1] == ':') strcpy(initmax2, "0");
443
444		ninitmax = sscanf(jinitmax,
445		    " %lu : %lu : %lu : %i : %i : %i : %qu : %lu ",
446		    &maxinode, &maxblock, &maxnlink, &maxuser,
447		    &maxgroup, &maxflags, &maxsize, &maxlen);
448		f_notabs = 1;
449		switch (ninitmax) {
450		 case 0: maxinode = 0;
451		 case 1: maxblock = 0;
452		 case 2: maxnlink = 0;
453		 case 3: maxuser  = 0;
454		 case 4: maxgroup = 0;
455		 case 5: maxflags = 0;
456		 case 6: maxsize  = 0;
457		 case 7: maxlen   = 0, f_notabs = 0;
458		}
459		maxinode = makenines(maxinode);
460		maxblock = makenines(maxblock);
461		maxnlink = makenines(maxnlink);
462		maxsize = makenines(maxsize);
463	} else if (initmax == NULL || *initmax == '\0')
464		maxblock = maxinode = maxlen = maxnlink =
465		    maxuser = maxgroup = maxflags = maxsize = 0;
466	bcfile = 0;
467	flags = NULL;
468	for (cur = list, entries = 0; cur; cur = cur->fts_link) {
469		if (cur->fts_info == FTS_ERR || cur->fts_info == FTS_NS) {
470			warnx("%s: %s",
471			    cur->fts_name, strerror(cur->fts_errno));
472			cur->fts_number = NO_PRINT;
473			rval = 1;
474			continue;
475		}
476
477		/*
478		 * P is NULL if list is the argv list, to which different rules
479		 * apply.
480		 */
481		if (p == NULL) {
482			/* Directories will be displayed later. */
483			if (cur->fts_info == FTS_D && !f_listdir) {
484				cur->fts_number = NO_PRINT;
485				continue;
486			}
487		} else {
488			/* Only display dot file if -a/-A set. */
489			if (cur->fts_name[0] == '.' && !f_listdot) {
490				cur->fts_number = NO_PRINT;
491				continue;
492			}
493		}
494		if (f_nonprint)
495			prcopy(cur->fts_name, cur->fts_name, cur->fts_namelen);
496		if (cur->fts_namelen > maxlen)
497			maxlen = cur->fts_namelen;
498		if (f_octal || f_octal_escape) {
499		        int t = len_octal(cur->fts_name, cur->fts_namelen);
500			if (t > maxlen) maxlen = t;
501		}
502		if (needstats) {
503			sp = cur->fts_statp;
504			if (sp->st_blocks > maxblock)
505				maxblock = sp->st_blocks;
506			if (sp->st_ino > maxinode)
507				maxinode = sp->st_ino;
508			if (sp->st_nlink > maxnlink)
509				maxnlink = sp->st_nlink;
510			if (sp->st_size > maxsize)
511				maxsize = sp->st_size;
512
513			btotal += sp->st_blocks;
514			if (f_longform) {
515				user = user_from_uid(sp->st_uid, 0);
516				if ((ulen = strlen(user)) > maxuser)
517					maxuser = ulen;
518				group = group_from_gid(sp->st_gid, 0);
519				if ((glen = strlen(group)) > maxgroup)
520					maxgroup = glen;
521				if (f_flags) {
522					flags =
523					    flags_to_string(sp->st_flags, "-");
524					if ((flen = strlen(flags)) > maxflags)
525						maxflags = flen;
526				} else
527					flen = 0;
528
529				if ((np = malloc(sizeof(NAMES) +
530				    ulen + glen + flen + 3)) == NULL)
531					err(1, NULL);
532
533				np->user = &np->data[0];
534				(void)strcpy(np->user, user);
535				np->group = &np->data[ulen + 1];
536				(void)strcpy(np->group, group);
537
538				if (S_ISCHR(sp->st_mode) ||
539				    S_ISBLK(sp->st_mode))
540					bcfile = 1;
541
542				if (f_flags) {
543					np->flags = &np->data[ulen + glen + 2];
544				  	(void)strcpy(np->flags, flags);
545				}
546				cur->fts_pointer = np;
547			}
548		}
549		++entries;
550	}
551
552	if (!entries)
553		return;
554
555	d.list = list;
556	d.entries = entries;
557	d.maxlen = maxlen;
558	if (needstats) {
559		d.bcfile = bcfile;
560		d.btotal = btotal;
561		(void)snprintf(buf, sizeof(buf), "%lu", maxblock);
562		d.s_block = strlen(buf);
563		d.s_flags = maxflags;
564		d.s_group = maxgroup;
565		(void)snprintf(buf, sizeof(buf), "%lu", maxinode);
566		d.s_inode = strlen(buf);
567		(void)snprintf(buf, sizeof(buf), "%lu", maxnlink);
568		d.s_nlink = strlen(buf);
569		(void)snprintf(buf, sizeof(buf), "%qu", maxsize);
570		d.s_size = strlen(buf);
571		d.s_user = maxuser;
572	}
573
574	printfcn(&d);
575	output = 1;
576
577	if (f_longform)
578		for (cur = list; cur; cur = cur->fts_link)
579			free(cur->fts_pointer);
580}
581
582/*
583 * Ordering for mastercmp:
584 * If ordering the argv (fts_level = FTS_ROOTLEVEL) return non-directories
585 * as larger than directories.  Within either group, use the sort function.
586 * All other levels use the sort function.  Error entries remain unsorted.
587 */
588static int
589mastercmp(a, b)
590	const FTSENT **a, **b;
591{
592	int a_info, b_info;
593
594	a_info = (*a)->fts_info;
595	if (a_info == FTS_ERR)
596		return (0);
597	b_info = (*b)->fts_info;
598	if (b_info == FTS_ERR)
599		return (0);
600
601	if (a_info == FTS_NS || b_info == FTS_NS)
602		return (namecmp(*a, *b));
603
604	if (a_info != b_info &&
605	    (*a)->fts_level == FTS_ROOTLEVEL && !f_listdir) {
606		if (a_info == FTS_D)
607			return (1);
608		if (b_info == FTS_D)
609			return (-1);
610	}
611	return (sortfcn(*a, *b));
612}
613
614/*
615 * Makenines() returns (10**n)-1.  This is useful for converting a width
616 * into a number that wide in decimal.
617 */
618static u_quad_t
619makenines(n)
620	u_long n;
621{
622	u_long i;
623	u_quad_t reg;
624
625	reg = 1;
626	/* Use a loop instead of pow(), since all values of n are small. */
627	for (i = 0; i < n; i++)
628		reg *= 10;
629	reg--;
630
631	return reg;
632}
633