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