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