ls.c revision 2889
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 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
44static char sccsid[] = "@(#)ls.c	8.5 (Berkeley) 4/2/94";
45#endif /* not lint */
46
47#include <sys/types.h>
48#include <sys/stat.h>
49#include <sys/ioctl.h>
50
51#include <dirent.h>
52#include <err.h>
53#include <errno.h>
54#include <fts.h>
55#include <stdio.h>
56#include <stdlib.h>
57#include <string.h>
58#include <unistd.h>
59
60#include "ls.h"
61#include "extern.h"
62
63static void	 display __P((FTSENT *, FTSENT *));
64static int	 mastercmp __P((const FTSENT **, const FTSENT **));
65static void	 traverse __P((int, char **, int));
66
67static void (*printfcn) __P((DISPLAY *));
68static int (*sortfcn) __P((const FTSENT *, const FTSENT *));
69
70long blocksize;			/* block size units */
71int termwidth = 80;		/* default terminal width */
72
73/* flags */
74int f_accesstime;		/* use time of last access */
75int f_column;			/* columnated format */
76int f_flags;			/* show flags associated with a file */
77int f_inode;			/* print inode */
78int f_kblocks;			/* print size in kilobytes */
79int f_listdir;			/* list actual directory, not contents */
80int f_listdot;			/* list files beginning with . */
81int f_longform;			/* long listing format */
82int f_newline;			/* if precede with newline */
83int f_nonprint;			/* show unprintables as ? */
84int f_nosort;			/* don't sort output */
85int f_recursive;		/* ls subdirectories also */
86int f_reversesort;		/* reverse whatever sort is used */
87int f_sectime;			/* print the real time for all files */
88int f_singlecol;		/* use single column output */
89int f_size;			/* list size in short listing */
90int f_statustime;		/* use time of last mode change */
91int f_dirname;			/* if precede with directory name */
92int f_timesort;			/* sort by time vice name */
93int f_type;			/* add type character for non-regular files */
94
95int
96main(argc, argv)
97	int argc;
98	char *argv[];
99{
100	static char dot[] = ".", *dotav[] = { dot, NULL };
101	struct winsize win;
102	int ch, fts_options, notused;
103	char *p;
104
105	/* Terminal defaults to -Cq, non-terminal defaults to -1. */
106	if (isatty(STDOUT_FILENO)) {
107		if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == -1 ||
108		    !win.ws_col) {
109			if ((p = getenv("COLUMNS")) != NULL)
110				termwidth = atoi(p);
111		}
112		else
113			termwidth = win.ws_col;
114		f_column = f_nonprint = 1;
115	} else
116		f_singlecol = 1;
117
118	/* Root is -A automatically. */
119	if (!getuid())
120		f_listdot = 1;
121
122	fts_options = FTS_PHYSICAL;
123	while ((ch = getopt(argc, argv, "1ACFLRTacdfgikloqrstu")) != EOF) {
124		switch (ch) {
125		/*
126		 * The -1, -C and -l options all override each other so shell
127		 * aliasing works right.
128		 */
129		case '1':
130			f_singlecol = 1;
131			f_column = f_longform = 0;
132			break;
133		case 'C':
134			f_column = 1;
135			f_longform = f_singlecol = 0;
136			break;
137		case 'l':
138			f_longform = 1;
139			f_column = f_singlecol = 0;
140			break;
141		/* The -c and -u options override each other. */
142		case 'c':
143			f_statustime = 1;
144			f_accesstime = 0;
145			break;
146		case 'u':
147			f_accesstime = 1;
148			f_statustime = 0;
149			break;
150		case 'F':
151			f_type = 1;
152			break;
153		case 'L':
154			fts_options &= ~FTS_PHYSICAL;
155			fts_options |= FTS_LOGICAL;
156			break;
157		case 'R':
158			f_recursive = 1;
159			break;
160		case 'a':
161			fts_options |= FTS_SEEDOT;
162			/* FALLTHROUGH */
163		case 'A':
164			f_listdot = 1;
165			break;
166		/* The -d option turns off the -R option. */
167		case 'd':
168			f_listdir = 1;
169			f_recursive = 0;
170			break;
171		case 'f':
172			f_nosort = 1;
173			break;
174		case 'g':		/* Compatibility with 4.3BSD. */
175			break;
176		case 'i':
177			f_inode = 1;
178			break;
179		case 'k':
180			f_kblocks = 1;
181			break;
182		case 'o':
183			f_flags = 1;
184			break;
185		case 'q':
186			f_nonprint = 1;
187			break;
188		case 'r':
189			f_reversesort = 1;
190			break;
191		case 's':
192			f_size = 1;
193			break;
194		case 'T':
195			f_sectime = 1;
196			break;
197		case 't':
198			f_timesort = 1;
199			break;
200		default:
201		case '?':
202			usage();
203		}
204	}
205	argc -= optind;
206	argv += optind;
207
208	/*
209	 * If not -F, -i, -l, -s or -t options, don't require stat
210	 * information.
211	 */
212	if (!f_inode && !f_longform && !f_size && !f_timesort && !f_type)
213		fts_options |= FTS_NOSTAT;
214
215	/*
216	 * If not -F, -d or -l options, follow any symbolic links listed on
217	 * the command line.
218	 */
219	if (!f_longform && !f_listdir && !f_type)
220		fts_options |= FTS_COMFOLLOW;
221
222	/* If -l or -s, figure out block size. */
223	if (f_longform || f_size) {
224		(void)getbsize(&notused, &blocksize);
225		    blocksize /= 512;
226		if (f_kblocks)
227		    blocksize *= 2;
228	}
229
230	/* Select a sort function. */
231	if (f_reversesort) {
232		if (!f_timesort)
233			sortfcn = revnamecmp;
234		else if (f_accesstime)
235			sortfcn = revacccmp;
236		else if (f_statustime)
237			sortfcn = revstatcmp;
238		else /* Use modification time. */
239			sortfcn = revmodcmp;
240	} else {
241		if (!f_timesort)
242			sortfcn = namecmp;
243		else if (f_accesstime)
244			sortfcn = acccmp;
245		else if (f_statustime)
246			sortfcn = statcmp;
247		else /* Use modification time. */
248			sortfcn = modcmp;
249	}
250
251	/* Select a print function. */
252	if (f_singlecol)
253		printfcn = printscol;
254	else if (f_longform)
255		printfcn = printlong;
256	else
257		printfcn = printcol;
258
259	if (argc)
260		traverse(argc, argv, fts_options);
261	else
262		traverse(1, dotav, fts_options);
263	exit(0);
264}
265
266static int output;			/* If anything output. */
267
268/*
269 * Traverse() walks the logical directory structure specified by the argv list
270 * in the order specified by the mastercmp() comparison function.  During the
271 * traversal it passes linked lists of structures to display() which represent
272 * a superset (may be exact set) of the files to be displayed.
273 */
274static void
275traverse(argc, argv, options)
276	int argc, options;
277	char *argv[];
278{
279	FTS *ftsp;
280	FTSENT *p, *chp;
281	int ch_options;
282
283	if ((ftsp =
284	    fts_open(argv, options, f_nosort ? NULL : mastercmp)) == NULL)
285		err(1, NULL);
286
287	display(NULL, fts_children(ftsp, 0));
288	if (f_listdir)
289		return;
290
291	/*
292	 * If not recursing down this tree and don't need stat info, just get
293	 * the names.
294	 */
295	ch_options = !f_recursive && options & FTS_NOSTAT ? FTS_NAMEONLY : 0;
296
297	while ((p = fts_read(ftsp)) != NULL)
298		switch (p->fts_info) {
299		case FTS_DC:
300			warnx("%s: directory causes a cycle", p->fts_name);
301			break;
302		case FTS_DNR:
303		case FTS_ERR:
304			warnx("%s: %s", p->fts_name, strerror(p->fts_errno));
305			break;
306		case FTS_D:
307			if (p->fts_level != FTS_ROOTLEVEL &&
308			    p->fts_name[0] == '.' && !f_listdot)
309				break;
310
311			/*
312			 * If already output something, put out a newline as
313			 * a separator.  If multiple arguments, precede each
314			 * directory with its name.
315			 */
316			if (output)
317				(void)printf("\n%s:\n", p->fts_path);
318			else if (argc > 1) {
319				(void)printf("%s:\n", p->fts_path);
320				output = 1;
321			}
322
323			chp = fts_children(ftsp, ch_options);
324			display(p, chp);
325
326			if (!f_recursive && chp != NULL)
327				(void)fts_set(ftsp, p, FTS_SKIP);
328			break;
329		}
330	if (errno)
331		err(1, "fts_read");
332}
333
334/*
335 * Display() takes a linked list of FTSENT structures and passes the list
336 * along with any other necessary information to the print function.  P
337 * points to the parent directory of the display list.
338 */
339static void
340display(p, list)
341	FTSENT *p, *list;
342{
343	struct stat *sp;
344	DISPLAY d;
345	FTSENT *cur;
346	NAMES *np;
347	u_quad_t maxsize;
348	u_long btotal, maxblock, maxinode, maxlen, maxnlink;
349	int bcfile, flen, glen, ulen, maxflags, maxgroup, maxuser;
350	int entries, needstats;
351	char *user, *group, *flags, buf[20];	/* 32 bits == 10 digits */
352
353	/*
354	 * If list is NULL there are two possibilities: that the parent
355	 * directory p has no children, or that fts_children() returned an
356	 * error.  We ignore the error case since it will be replicated
357	 * on the next call to fts_read() on the post-order visit to the
358	 * directory p, and will be signalled in traverse().
359	 */
360	if (list == NULL)
361		return;
362
363	needstats = f_inode || f_longform || f_size;
364	flen = 0;
365	btotal = maxblock = maxinode = maxlen = maxnlink = 0;
366	bcfile = 0;
367	maxuser = maxgroup = maxflags = 0;
368	maxsize = 0;
369	for (cur = list, entries = 0; cur; cur = cur->fts_link) {
370		if (cur->fts_info == FTS_ERR || cur->fts_info == FTS_NS) {
371			warnx("%s: %s",
372			    cur->fts_name, strerror(cur->fts_errno));
373			cur->fts_number = NO_PRINT;
374			continue;
375		}
376
377		/*
378		 * P is NULL if list is the argv list, to which different rules
379		 * apply.
380		 */
381		if (p == NULL) {
382			/* Directories will be displayed later. */
383			if (cur->fts_info == FTS_D && !f_listdir) {
384				cur->fts_number = NO_PRINT;
385				continue;
386			}
387		} else {
388			/* Only display dot file if -a/-A set. */
389			if (cur->fts_name[0] == '.' && !f_listdot) {
390				cur->fts_number = NO_PRINT;
391				continue;
392			}
393		}
394		if (f_nonprint)
395			prcopy(cur->fts_name, cur->fts_name, cur->fts_namelen);
396		if (cur->fts_namelen > maxlen)
397			maxlen = cur->fts_namelen;
398		if (needstats) {
399			sp = cur->fts_statp;
400			if (sp->st_blocks > maxblock)
401				maxblock = sp->st_blocks;
402			if (sp->st_ino > maxinode)
403				maxinode = sp->st_ino;
404			if (sp->st_nlink > maxnlink)
405				maxnlink = sp->st_nlink;
406			if (sp->st_size > maxsize)
407				maxsize = sp->st_size;
408
409			btotal += sp->st_blocks;
410			if (f_longform) {
411				user = user_from_uid(sp->st_uid, 0);
412				if ((ulen = strlen(user)) > maxuser)
413					maxuser = ulen;
414				group = group_from_gid(sp->st_gid, 0);
415				if ((glen = strlen(group)) > maxgroup)
416					maxgroup = glen;
417				if (f_flags) {
418					flags =
419					    flags_to_string(sp->st_flags, "-");
420					if ((flen = strlen(flags)) > maxflags)
421						maxflags = flen;
422				} else
423					flen = 0;
424
425				if ((np = malloc(sizeof(NAMES) +
426				    ulen + glen + flen + 3)) == NULL)
427					err(1, NULL);
428
429				np->user = &np->data[0];
430				(void)strcpy(np->user, user);
431				np->group = &np->data[ulen + 1];
432				(void)strcpy(np->group, group);
433
434				if (S_ISCHR(sp->st_mode) ||
435				    S_ISBLK(sp->st_mode))
436					bcfile = 1;
437
438				if (f_flags) {
439					np->flags = &np->data[ulen + glen + 2];
440				  	(void)strcpy(np->flags, flags);
441				}
442				cur->fts_pointer = np;
443			}
444		}
445		++entries;
446	}
447
448	if (!entries)
449		return;
450
451	d.list = list;
452	d.entries = entries;
453	d.maxlen = maxlen;
454	if (needstats) {
455		d.bcfile = bcfile;
456		d.btotal = btotal;
457		(void)snprintf(buf, sizeof(buf), "%lu", maxblock);
458		d.s_block = strlen(buf);
459		d.s_flags = maxflags;
460		d.s_group = maxgroup;
461		(void)snprintf(buf, sizeof(buf), "%lu", maxinode);
462		d.s_inode = strlen(buf);
463		(void)snprintf(buf, sizeof(buf), "%lu", maxnlink);
464		d.s_nlink = strlen(buf);
465		(void)snprintf(buf, sizeof(buf), "%qu", maxsize);
466		d.s_size = strlen(buf);
467		d.s_user = maxuser;
468	}
469
470	printfcn(&d);
471	output = 1;
472
473	if (f_longform)
474		for (cur = list; cur; cur = cur->fts_link)
475			free(cur->fts_pointer);
476}
477
478/*
479 * Ordering for mastercmp:
480 * If ordering the argv (fts_level = FTS_ROOTLEVEL) return non-directories
481 * as larger than directories.  Within either group, use the sort function.
482 * All other levels use the sort function.  Error entries remain unsorted.
483 */
484static int
485mastercmp(a, b)
486	const FTSENT **a, **b;
487{
488	int a_info, b_info;
489
490	a_info = (*a)->fts_info;
491	if (a_info == FTS_ERR)
492		return (0);
493	b_info = (*b)->fts_info;
494	if (b_info == FTS_ERR)
495		return (0);
496
497	if (a_info == FTS_NS || b_info == FTS_NS)
498		return (namecmp(*a, *b));
499
500	if (a_info == b_info)
501		return (sortfcn(*a, *b));
502
503	if ((*a)->fts_level == FTS_ROOTLEVEL)
504		if (a_info == FTS_D)
505			return (1);
506		else if (b_info == FTS_D)
507			return (-1);
508		else
509			return (sortfcn(*a, *b));
510	else
511		return (sortfcn(*a, *b));
512}
513