xref: /netbsd-src/bin/ls/ls.c (revision cda4f8f6ee55684e8d311b86c99ea59191e6b74f)
1 /*
2  * Copyright (c) 1989 The Regents of the University of California.
3  * 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
38 char copyright[] =
39 "@(#) Copyright (c) 1989 The Regents of the University of California.\n\
40  All rights reserved.\n";
41 #endif /* not lint */
42 
43 #ifndef lint
44 static char sccsid[] = "@(#)ls.c	5.48 (Berkeley) 4/3/91";
45 static char rcsid[] = "$Header: /cvsroot/src/bin/ls/ls.c,v 1.5 1993/05/26 10:23:24 deraadt Exp $";
46 #endif /* not lint */
47 
48 #include <sys/param.h>
49 #include <sys/stat.h>
50 #include <sys/ioctl.h>
51 #include <dirent.h>
52 #include <string.h>
53 #include <errno.h>
54 #include <stdio.h>
55 #include "ls.h"
56 
57 int (*sortfcn)(), (*printfcn)();
58 int lstat();
59 char *emalloc();
60 
61 int termwidth = 80;		/* default terminal width */
62 
63 /* flags */
64 int f_accesstime;		/* use time of last access */
65 int f_column;			/* columnated format */
66 int f_group;			/* show group ownership of a file */
67 int f_ignorelink;		/* indirect through symbolic link operands */
68 int f_inode;			/* print inode */
69 int f_kblocks;			/* print size in kilobytes */
70 int f_listalldot;		/* list . and .. as well */
71 int f_listdir;			/* list actual directory, not contents */
72 int f_listdot;			/* list files beginning with . */
73 int f_longform;			/* long listing format */
74 int f_needstat;			/* if need to stat files */
75 int f_newline;			/* if precede with newline */
76 int f_nonprint;			/* show unprintables as ? */
77 int f_nosort;			/* don't sort output */
78 int f_recursive;		/* ls subdirectories also */
79 int f_reversesort;		/* reverse whatever sort is used */
80 int f_sectime;			/* print the real time for all files */
81 int f_singlecol;		/* use single column output */
82 int f_size;			/* list size in short listing */
83 int f_statustime;		/* use time of last mode change */
84 int f_dirname;			/* if precede with directory name */
85 int f_timesort;			/* sort by time vice name */
86 int f_total;			/* if precede with "total" line */
87 int f_type;			/* add type character for non-regular files */
88 
89 int (*statfcn)(), stat(), lstat();
90 
91 main(argc, argv)
92 	int argc;
93 	char **argv;
94 {
95 	extern int optind, stat();
96 	struct winsize win;
97 	int ch;
98 	char *p, *getenv();
99 	int acccmp(), modcmp(), namecmp(), prcopy(), printcol();
100 	int printlong(), printscol(), revacccmp(), revmodcmp(), revnamecmp();
101 	int revstatcmp(), statcmp();
102 
103 	/* terminal defaults to -Cq, non-terminal defaults to -1 */
104 	if (isatty(1)) {
105 		f_nonprint = 1;
106 		if (ioctl(1, TIOCGWINSZ, &win) == -1 || !win.ws_col) {
107 			if (p = getenv("COLUMNS"))
108 				termwidth = atoi(p);
109 		}
110 		else
111 			termwidth = win.ws_col;
112 		f_column = 1;
113 	} else
114 		f_singlecol = 1;
115 
116 	/* root is -A automatically */
117 	if (!getuid())
118 		f_listdot = 1;
119 
120 	while ((ch = getopt(argc, argv, "1ACFLRTacdfgiklqrstu")) != EOF) {
121 		switch (ch) {
122 		/*
123 		 * -1, -C and -l all override each other
124 		 * so shell aliasing works right
125 		 */
126 		case '1':
127 			f_singlecol = 1;
128 			f_column = f_longform = 0;
129 			break;
130 		case 'C':
131 			f_column = 1;
132 			f_longform = f_singlecol = 0;
133 			break;
134 		case 'l':
135 			f_longform = 1;
136 			f_column = f_singlecol = 0;
137 			break;
138 		/* -c and -u override each other */
139 		case 'c':
140 			f_statustime = 1;
141 			f_accesstime = 0;
142 			break;
143 		case 'u':
144 			f_accesstime = 1;
145 			f_statustime = 0;
146 			break;
147 		case 'F':
148 			f_type = 1;
149 			break;
150 		case 'L':
151 			f_ignorelink = 1;
152 			break;
153 		case 'R':
154 			f_recursive = 1;
155 			break;
156 		case 'a':
157 			f_listalldot = 1;
158 			/* FALLTHROUGH */
159 		case 'A':
160 			f_listdot = 1;
161 			break;
162 		case 'd':
163 			f_listdir = 1;
164 			break;
165 		case 'f':
166 			f_nosort = 1;
167 			break;
168 		case 'g':
169 			f_group = 1;
170 			break;
171 		case 'i':
172 			f_inode = 1;
173 			break;
174 		case 'k':
175 			f_kblocks = 1;
176 			break;
177 		case 'q':
178 			f_nonprint = 1;
179 			break;
180 		case 'r':
181 			f_reversesort = 1;
182 			break;
183 		case 's':
184 			f_size = 1;
185 			break;
186 		case 'T':
187 			f_sectime = 1;
188 			break;
189 		case 't':
190 			f_timesort = 1;
191 			break;
192 		default:
193 		case '?':
194 			usage();
195 		}
196 	}
197 	argc -= optind;
198 	argv += optind;
199 
200 	/* -d turns off -R */
201 	if (f_listdir)
202 		f_recursive = 0;
203 
204 	/* if need to stat files */
205 	f_needstat = f_longform || f_recursive || f_timesort ||
206 	    f_size || f_type;
207 
208 	/* select a sort function */
209 	if (f_reversesort) {
210 		if (!f_timesort)
211 			sortfcn = revnamecmp;
212 		else if (f_accesstime)
213 			sortfcn = revacccmp;
214 		else if (f_statustime)
215 			sortfcn = revstatcmp;
216 		else /* use modification time */
217 			sortfcn = revmodcmp;
218 	} else {
219 		if (!f_timesort)
220 			sortfcn = namecmp;
221 		else if (f_accesstime)
222 			sortfcn = acccmp;
223 		else if (f_statustime)
224 			sortfcn = statcmp;
225 		else /* use modification time */
226 			sortfcn = modcmp;
227 	}
228 
229 	/* select a print function */
230 	if (f_singlecol)
231 		printfcn = printscol;
232 	else if (f_longform)
233 		printfcn = printlong;
234 	else
235 		printfcn = printcol;
236 
237 	/* if -l, -d, -R, or -F, and not ignoring the link, use lstat() */
238 	statfcn =
239 	    (f_longform || f_listdir || f_type || f_recursive) && !f_ignorelink ? lstat : stat;
240 
241 	if (!argc) {
242 		static char *nargv[2];
243 		char dot[2];
244 
245 		strcpy(dot, ".");
246 		nargv[0] = dot;
247 		doargs(1, nargv);
248 	} else
249 		doargs(argc, argv);
250 	exit(0);
251 }
252 
253 static char path[MAXPATHLEN + 1];
254 static char *endofpath = path;
255 
256 doargs(argc, argv)
257 	int argc;
258 	char **argv;
259 {
260 	register LS *dstatp, *rstatp;
261 	register int cnt, dircnt, maxlen, regcnt;
262 	LS *dstats, *rstats;
263 	struct stat sb;
264 	char top[MAXPATHLEN + 1];
265 	u_long blocks;
266 
267 	/*
268 	 * walk through the operands, building separate arrays of LS
269 	 * structures for directory and non-directory files.
270 	 */
271 	dstats = rstats = NULL;
272 	for (dircnt = regcnt = 0; *argv; ++argv) {
273 		if (statfcn(*argv, &sb) &&
274 		    (statfcn == lstat || lstat(*argv, &sb))) {
275 			(void)fprintf(stderr,
276 			    "ls: %s: %s\n", *argv, strerror(errno));
277 			if (errno == ENOENT)
278 				continue;
279 			exit(1);
280 		}
281 		if (S_ISDIR(sb.st_mode) && !f_listdir) {
282 			if (!dstats)
283 				dstatp = dstats = (LS *)emalloc((u_int)argc *
284 				    (sizeof(LS)));
285 			dstatp->name = *argv;
286 			dstatp->lstat = sb;
287 			++dstatp;
288 			++dircnt;
289 		}
290 		else {
291 			if (!rstats) {
292 				rstatp = rstats = (LS *)emalloc((u_int)argc *
293 				    (sizeof(LS)));
294 				blocks = 0;
295 				maxlen = -1;
296 			}
297 			rstatp->name = *argv;
298 			rstatp->lstat = sb;
299 
300 			/* save name length for -C format */
301 			rstatp->len = strlen(*argv);
302 
303 			if (f_nonprint)
304 				prcopy(*argv, *argv, rstatp->len);
305 
306 			/* calculate number of blocks if -l/-s formats */
307 			if (f_longform || f_size)
308 				blocks += sb.st_blocks;
309 
310 			/* save max length if -C format */
311 			if (f_column && maxlen < rstatp->len)
312 				maxlen = rstatp->len;
313 
314 			++rstatp;
315 			++regcnt;
316 		}
317 	}
318 	/* display regular files */
319 	if (regcnt) {
320 		rstats[0].lstat.st_btotal = blocks;
321 		rstats[0].lstat.st_maxlen = maxlen;
322 		displaydir(rstats, regcnt);
323 		f_newline = f_dirname = 1;
324 	}
325 	/* display directories */
326 	if (dircnt) {
327 		register char *p;
328 
329 		f_total = 1;
330 		if (dircnt > 1) {
331 			(void)getwd(top);
332 			qsort((char *)dstats, dircnt, sizeof(LS), sortfcn);
333 			f_dirname = 1;
334 		}
335 		for (cnt = 0; cnt < dircnt; ++dstats) {
336 			for (endofpath = path, p = dstats->name;
337 			    *endofpath = *p++; ++endofpath);
338 			subdir(dstats);
339 			f_newline = 1;
340 			if (++cnt < dircnt && chdir(top)) {
341 				(void)fprintf(stderr, "ls: %s: %s\n",
342 				    top, strerror(errno));
343 				exit(1);
344 			}
345 		}
346 	}
347 }
348 
349 displaydir(stats, num)
350 	LS *stats;
351 	register int num;
352 {
353 	register char *p, *savedpath;
354 	LS *lp;
355 
356 	if (num > 1 && !f_nosort) {
357 		u_long save1, save2;
358 
359 		save1 = stats[0].lstat.st_btotal;
360 		save2 = stats[0].lstat.st_maxlen;
361 		qsort((char *)stats, num, sizeof(LS), sortfcn);
362 		stats[0].lstat.st_btotal = save1;
363 		stats[0].lstat.st_maxlen = save2;
364 	}
365 
366 	printfcn(stats, num);
367 
368 	if (f_recursive) {
369 		savedpath = endofpath;
370 		for (lp = stats; num--; ++lp) {
371 			if (!S_ISDIR(lp->lstat.st_mode))
372 				continue;
373 			p = lp->name;
374 			if (p[0] == '.' && (!p[1] || p[1] == '.' && !p[2]))
375 				continue;
376 			if (endofpath != path && endofpath[-1] != '/')
377 				*endofpath++ = '/';
378 			for (; *endofpath = *p++; ++endofpath);
379 			f_newline = f_dirname = f_total = 1;
380 			subdir(lp);
381 			*(endofpath = savedpath) = '\0';
382 		}
383 	}
384 }
385 
386 subdir(lp)
387 	LS *lp;
388 {
389 	LS *stats;
390 	int num;
391 	char *names;
392 
393 	if (f_newline)
394 		(void)putchar('\n');
395 	if (f_dirname)
396 		(void)printf("%s:\n", path);
397 
398 	if (chdir(lp->name)) {
399 		(void)fprintf(stderr, "ls: %s: %s\n", lp->name,
400 		     strerror(errno));
401 		return;
402 	}
403 	if (num = tabdir(lp, &stats, &names)) {
404 		displaydir(stats, num);
405 		(void)free((char *)stats);
406 		(void)free((char *)names);
407 	}
408 	if (chdir("..")) {
409 		(void)fprintf(stderr, "ls: ..: %s\n", strerror(errno));
410 		exit(1);
411 	}
412 }
413 
414 tabdir(lp, s_stats, s_names)
415 	LS *lp, **s_stats;
416 	char **s_names;
417 {
418 	register DIR *dirp;
419 	register int cnt, maxentry, maxlen;
420 	register char *p, *names;
421 	struct dirent *dp;
422 	u_long blocks;
423 	LS *stats;
424 
425 	if (!(dirp = opendir("."))) {
426 		(void)fprintf(stderr, "ls: %s: %s\n", lp->name,
427 		    strerror(errno));
428 		return(0);
429 	}
430 	blocks = maxentry = maxlen = 0;
431 	stats = NULL;
432 	for (cnt = 0; dp = readdir(dirp);) {
433 		/* this does -A and -a */
434 		p = dp->d_name;
435 		if (p[0] == '.') {
436 			if (!f_listdot)
437 				continue;
438 			if (!f_listalldot && (!p[1] || p[1] == '.' && !p[2]))
439 				continue;
440 		}
441 		if (cnt == maxentry) {
442 			if (!maxentry)
443 				*s_names = names =
444 				    emalloc((u_int)lp->lstat.st_size);
445 #define	DEFNUM	256
446 			maxentry += DEFNUM;
447 			if (!(*s_stats = stats = (LS *)realloc((char *)stats,
448 			    (u_int)maxentry * sizeof(LS))))
449 				nomem();
450 		}
451 		if (f_needstat && statfcn(dp->d_name, &stats[cnt].lstat) &&
452 		    statfcn == stat && lstat(dp->d_name, &stats[cnt].lstat)) {
453 			/*
454 			 * don't exit -- this could be an NFS mount that has
455 			 * gone away.  Flush stdout so the messages line up.
456 			 */
457 			(void)fflush(stdout);
458 			(void)fprintf(stderr,
459 			    "ls: %s: %s\n", dp->d_name, strerror(errno));
460 			continue;
461 		}
462 		stats[cnt].name = names;
463 
464 		if (f_nonprint)
465 			prcopy(dp->d_name, names, (int)dp->d_namlen);
466 		else
467 			bcopy(dp->d_name, names, (int)dp->d_namlen);
468 		names += dp->d_namlen;
469 		*names++ = '\0';
470 
471 		/*
472 		 * get the inode from the directory, so the -f flag
473 		 * works right.
474 		 */
475 		stats[cnt].lstat.st_ino = dp->d_ino;
476 
477 		/* save name length for -C format */
478 		stats[cnt].len = dp->d_namlen;
479 
480 		/* calculate number of blocks if -l/-s formats */
481 		if (f_longform || f_size)
482 			blocks += stats[cnt].lstat.st_blocks;
483 
484 		/* save max length if -C format */
485 		if (f_column && maxlen < (int)dp->d_namlen)
486 			maxlen = dp->d_namlen;
487 		++cnt;
488 	}
489 	(void)closedir(dirp);
490 
491 	if (cnt) {
492 		stats[0].lstat.st_btotal = blocks;
493 		stats[0].lstat.st_maxlen = maxlen;
494 	} else if (stats) {
495 		(void)free((char *)stats);
496 		(void)free((char *)names);
497 	}
498 	return(cnt);
499 }
500