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