xref: /illumos-gate/usr/src/ucbcmd/ls/ls.c (revision 01a009817ac98af8e944784cebf0afd690b7cae7)
1 /* Portions Copyright 2006 Stephen P. Potter */
2 
3 /*
4  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
5  * Use is subject to license terms.
6  */
7 
8 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
9 /*	  All Rights Reserved	*/
10 
11 /*
12  * Copyright (c) 1980 Regents of the University of California.
13  * All rights reserved.  The Berkeley software License Agreement
14  * specifies the terms and conditions for redistribution.
15  */
16 
17 /*
18  * ls
19  *
20  * 4.2bsd version for symbolic links, variable length
21  * directory entries, block size in the inode, etc.
22  */
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include <stddef.h>
29 #include <dirent.h>
30 #include <ctype.h>
31 #include <time.h>
32 #include <limits.h>
33 #include <locale.h>
34 #include <errno.h>
35 #include <sys/types.h>
36 #include <sys/param.h>
37 #include <sys/stat.h>
38 #include <sys/termios.h>
39 #include <sys/mkdev.h>
40 #include <sys/acl.h>
41 
42 #define	dbtokb(nb)	((nb) / (1024 / DEV_BSIZE))
43 
44 struct afile {
45 	char	ftype;		/* file type, e.g. 'd', 'c', 'f' */
46 	ino_t	fnum;		/* inode number of file */
47 	short	fflags;		/* mode&~S_IFMT, perhaps ISARG */
48 	nlink_t	fnl;		/* number of links */
49 	uid_t	fuid;		/* owner id */
50 	gid_t	fgid;		/* group id */
51 	off_t	fsize;		/* file size */
52 	blkcnt_t	fblks;		/* number of blocks used */
53 	time_t	fmtime;		/* time (modify or access or create) */
54 	char	*fname;		/* file name */
55 	char	*flinkto;	/* symbolic link value */
56 	char	acl;		/* acl access flag */
57 };
58 
59 #define	ISARG	0x8000		/* extra ``mode'' */
60 
61 static struct subdirs {
62 	char	*sd_name;
63 	struct	subdirs *sd_next;
64 } *subdirs;
65 
66 static	int	aflg, dflg, gflg, lflg, sflg, tflg, uflg, iflg, fflg, cflg;
67 static	int	rflg = 1;
68 static	int	qflg, Aflg, Cflg, Fflg, Lflg, Rflg;
69 
70 static	int	usetabs;
71 
72 static	time_t	now, sixmonthsago, onehourfromnow;
73 
74 static	char	*dotp = ".";
75 
76 static	struct	winsize win;
77 static	int	twidth;
78 
79 static	struct	afile *gstat(struct afile *, char *, int, off_t *);
80 static	int	fcmp(const void *, const void *);
81 static	char	*cat(char *, char *);
82 static	char	*savestr(char *);
83 static	char	*fmtentry(struct afile *);
84 static	char	*getname(), *getgroup();
85 static	void	formatd(char *, int);
86 static	void	formatf(struct afile *, struct afile *);
87 static	off_t	getdir(char *, struct afile **, struct afile **);
88 
89 int
90 main(int argc, char **argv)
91 {
92 	int i;
93 	struct afile *fp0, *fplast;
94 	register struct afile *fp;
95 	struct termios trbuf;
96 
97 	argc--, argv++;
98 	if (getuid() == 0)
99 		Aflg++;
100 	(void) time(&now);
101 	sixmonthsago = now - 6L*30L*24L*60L*60L;
102 	onehourfromnow = now + 60L*60L;
103 	now += 60;
104 	twidth = 80;
105 	if (isatty(1)) {
106 		qflg = Cflg = 1;
107 		(void) ioctl(1, TCGETS, &trbuf);
108 		if (ioctl(1, TIOCGWINSZ, &win) != -1)
109 			twidth = (win.ws_col == 0 ? 80 : win.ws_col);
110 		if ((trbuf.c_oflag & TABDLY) != TAB3)
111 			usetabs = 1;
112 	} else
113 		usetabs = 1;
114 
115 	(void) setlocale(LC_ALL, "");		/* set local environment */
116 
117 	while (argc > 0 && **argv == '-') {
118 		(*argv)++;
119 		while (**argv) {
120 			switch (*(*argv)++) {
121 			case 'C':
122 				Cflg = 1; break;
123 			case 'q':
124 				qflg = 1; break;
125 			case '1':
126 				Cflg = 0; break;
127 			case 'a':
128 				aflg++; break;
129 			case 'A':
130 				Aflg++; break;
131 			case 'c':
132 				cflg++; break;
133 			case 's':
134 				sflg++; break;
135 			case 'd':
136 				dflg++; break;
137 			case 'g':
138 				gflg++; break;
139 			case 'l':
140 				lflg++; break;
141 			case 'r':
142 				rflg = -1; break;
143 			case 't':
144 				tflg++; break;
145 			case 'u':
146 				uflg++; break;
147 			case 'i':
148 				iflg++; break;
149 			case 'f':
150 				fflg++; break;
151 			case 'L':
152 				Lflg++; break;
153 			case 'F':
154 				Fflg++; break;
155 			case 'R':
156 				Rflg++; break;
157 			}
158 		}
159 		argc--, argv++;
160 	}
161 	if (fflg) {
162 		aflg++; lflg = 0; sflg = 0; tflg = 0;
163 	}
164 	if (lflg)
165 		Cflg = 0;
166 	if (argc == 0) {
167 		argc++;
168 		argv = &dotp;
169 	}
170 	fp = (struct afile *)calloc(argc, sizeof (struct afile));
171 	if (fp == 0) {
172 		(void) fprintf(stderr, "ls: out of memory\n");
173 		exit(1);
174 	}
175 	fp0 = fp;
176 	for (i = 0; i < argc; i++) {
177 		if (gstat(fp, *argv, 1, (off_t *)0)) {
178 			fp->fname = *argv;
179 			fp->fflags |= ISARG;
180 			fp++;
181 		}
182 		argv++;
183 	}
184 	fplast = fp;
185 	qsort(fp0, fplast - fp0, sizeof (struct afile), fcmp);
186 	if (dflg) {
187 		formatf(fp0, fplast);
188 		exit(0);
189 	}
190 	if (fflg)
191 		fp = fp0;
192 	else {
193 		for (fp = fp0; fp < fplast && fp->ftype != 'd'; fp++)
194 			continue;
195 		formatf(fp0, fp);
196 	}
197 	if (fp < fplast) {
198 		if (fp > fp0)
199 			(void) printf("\n");
200 		for (;;) {
201 			formatd(fp->fname, argc > 1);
202 			while (subdirs) {
203 				struct subdirs *t;
204 
205 				t = subdirs; subdirs = t->sd_next;
206 				(void) printf("\n");
207 				formatd(t->sd_name, 1);
208 				free(t->sd_name);
209 				free(t);
210 			}
211 			if (++fp == fplast)
212 				break;
213 			(void) printf("\n");
214 		}
215 	}
216 	return (0);
217 }
218 
219 static void
220 formatd(char *name, int title)
221 {
222 	register struct afile *fp;
223 	register struct subdirs *dp;
224 	struct afile *dfp0, *dfplast;
225 	off_t nkb;
226 
227 	nkb = getdir(name, &dfp0, &dfplast);
228 	if (dfp0 == 0)
229 		return;
230 	if (fflg == 0)
231 		qsort(dfp0, dfplast - dfp0, sizeof (struct afile), fcmp);
232 	if (title)
233 		(void) printf("%s:\n", name);
234 	if (lflg || sflg)
235 		(void) printf("total %lld\n", nkb);
236 	formatf(dfp0, dfplast);
237 	if (Rflg)
238 		for (fp = dfplast - 1; fp >= dfp0; fp--) {
239 			if (fp->ftype != 'd' ||
240 			    strcmp(fp->fname, ".") == 0 ||
241 			    strcmp(fp->fname, "..") == 0)
242 				continue;
243 			dp = (struct subdirs *)malloc(sizeof (struct subdirs));
244 			dp->sd_name = savestr(cat(name, fp->fname));
245 			dp->sd_next = subdirs; subdirs = dp;
246 		}
247 	for (fp = dfp0; fp < dfplast; fp++) {
248 		if ((fp->fflags&ISARG) == 0 && fp->fname)
249 			free(fp->fname);
250 		if (fp->flinkto)
251 			free(fp->flinkto);
252 	}
253 	free(dfp0);
254 }
255 
256 static off_t
257 getdir(char *dir, struct afile **pfp0, struct afile **pfplast)
258 {
259 	register struct afile *fp;
260 	DIR *dirp;
261 	register struct dirent *dp;
262 	off_t nb;
263 	size_t nent = 20;
264 
265 	/*
266 	 * This code (opendir, readdir, and the "for" loop) is arranged in
267 	 * this strange manner to handle the case where UNIX lets root open
268 	 * any directory for reading, but NFS does not let root read the
269 	 * openned directory.
270 	 */
271 	*pfp0 = *pfplast = NULL;
272 	if ((dirp = opendir(dir)) == NULL) {
273 		(void) printf("%s unreadable\n", dir);	/* not stderr! */
274 		return (0);
275 	}
276 	errno = 0;
277 	if (((dp = readdir(dirp)) == NULL) && (errno != 0)) {
278 		/* root reading across NFS can get to this error case */
279 		(void) printf("%s unreadable\n", dir);	/* not stderr! */
280 		(void) closedir(dirp);
281 		return (0);
282 	}
283 	fp = *pfp0 = (struct afile *)calloc(nent, sizeof (struct afile));
284 	*pfplast = *pfp0 + nent;
285 	for (nb = 0; dp != NULL; dp = readdir(dirp)) {
286 		if (dp->d_ino == 0)
287 			continue;
288 		if (aflg == 0 && dp->d_name[0] == '.' &&
289 		    (Aflg == 0 || dp->d_name[1] == 0 ||
290 		    dp->d_name[1] == '.' && dp->d_name[2] == 0))
291 			continue;
292 		if (gstat(fp, cat(dir, dp->d_name), Fflg+Rflg, &nb) == 0)
293 			continue;
294 		fp->fnum = dp->d_ino;
295 		fp->fname = savestr(dp->d_name);
296 		fp++;
297 		if (fp == *pfplast) {
298 			*pfp0 = (struct afile *)realloc((char *)*pfp0,
299 			    2 * nent * sizeof (struct afile));
300 			if (*pfp0 == 0) {
301 				(void) fprintf(stderr, "ls: out of memory\n");
302 				exit(1);
303 			}
304 			fp = *pfp0 + nent;
305 			*pfplast = fp + nent;
306 			nent *= 2;
307 		}
308 	}
309 	(void) closedir(dirp);
310 	*pfplast = fp;
311 	return (dbtokb(nb));
312 }
313 
314 
315 static struct afile *
316 gstat(struct afile *fp, char *file, int statarg, off_t *pnb)
317 {
318 	static struct afile azerofile;
319 	int (*statf)() = Lflg ? stat : lstat;
320 	int cc;
321 	char buf[PATH_MAX];
322 	int aclcnt;
323 	aclent_t *aclp;
324 	aclent_t *tp;
325 	o_mode_t groupperm, mask;
326 	int grouppermfound, maskfound;
327 
328 	*fp = azerofile;
329 	fp->fflags = 0;
330 	fp->fnum = 0;
331 	fp->ftype = '-';
332 	if (statarg || sflg || lflg || tflg) {
333 		struct stat stb, stb1;
334 
335 		if ((*statf)(file, &stb) < 0) {
336 			if (statf == lstat || lstat(file, &stb) < 0) {
337 				if (errno == ENOENT)
338 					(void) fprintf(stderr,
339 					    "%s not found\n", file);
340 				else {
341 					(void) fprintf(stderr, "ls: ");
342 					perror(file);
343 				}
344 				return (0);
345 			}
346 		}
347 		fp->fblks = stb.st_blocks;
348 		fp->fsize = stb.st_size;
349 		switch (stb.st_mode & S_IFMT) {
350 		case S_IFDIR:
351 			fp->ftype = 'd'; break;
352 		case S_IFDOOR:
353 			fp->ftype = 'D'; break;
354 		case S_IFBLK:
355 			fp->ftype = 'b'; fp->fsize = (off_t)stb.st_rdev; break;
356 		case S_IFCHR:
357 			fp->ftype = 'c'; fp->fsize = (off_t)stb.st_rdev; break;
358 		case S_IFSOCK:
359 			fp->ftype = 's'; fp->fsize = 0LL; break;
360 		case S_IFIFO:
361 			fp->ftype = 'p'; fp->fsize = 0LL; break;
362 		case S_IFLNK:
363 			fp->ftype = 'l';
364 			if (lflg) {
365 				cc = readlink(file, buf, BUFSIZ);
366 				if (cc >= 0) {
367 					/*
368 					 * here we follow the symbolic
369 					 * link to generate the proper
370 					 * Fflg marker for the object,
371 					 * eg, /bin -> /pub/bin/
372 					 */
373 					buf[cc] = 0;
374 					if (Fflg && !stat(file, &stb1))
375 						switch (stb1.st_mode & S_IFMT) {
376 						case S_IFDIR:
377 							buf[cc++] = '/';
378 							break;
379 						case S_IFDOOR:
380 							buf[cc++] = '>';
381 							break;
382 						case S_IFIFO:
383 							buf[cc++] = '|';
384 							break;
385 						case S_IFSOCK:
386 							buf[cc++] = '=';
387 							break;
388 						default:
389 							if ((stb1.st_mode &
390 							    ~S_IFMT) & 0111)
391 								buf[cc++] = '*';
392 							break;
393 						}
394 					buf[cc] = 0;
395 					fp->flinkto = savestr(buf);
396 				}
397 				break;
398 			}
399 			/*
400 			 *  this is a hack from UCB to avoid having
401 			 *  ls /bin behave differently from ls /bin/
402 			 *  when /bin is a symbolic link.  We hack the
403 			 *  hack to have that happen, but only for
404 			 *  explicit arguments, by inspecting pnb.
405 			 */
406 			if (pnb != (off_t *)0 || stat(file, &stb1) < 0)
407 				break;
408 			if ((stb1.st_mode & S_IFMT) == S_IFDIR) {
409 				stb = stb1;
410 				fp->ftype = 'd';
411 				fp->fsize = stb.st_size;
412 				fp->fblks = stb.st_blocks;
413 			}
414 			break;
415 		}
416 		fp->fnum = stb.st_ino;
417 		fp->fflags = stb.st_mode & ~S_IFMT;
418 		fp->fnl = stb.st_nlink;
419 		fp->fuid = stb.st_uid;
420 		fp->fgid = stb.st_gid;
421 
422 		/* ACL: check acl entries count */
423 		if ((aclcnt = acl(file, GETACLCNT, 0, NULL)) >
424 		    MIN_ACL_ENTRIES) {
425 
426 			/* this file has a non-trivial acl */
427 
428 			fp->acl = '+';
429 
430 			/*
431 			 * For files with non-trivial acls, the
432 			 * effective group permissions are the
433 			 * intersection of the GROUP_OBJ value and
434 			 * the CLASS_OBJ (acl mask) value. Determine
435 			 * both the GROUP_OBJ and CLASS_OBJ for this
436 			 * file and insert the logical AND of those
437 			 * two values in the group permissions field
438 			 * of the lflags value for this file.
439 			 */
440 
441 			if ((aclp = (aclent_t *)malloc(
442 			    (sizeof (aclent_t)) * aclcnt)) == NULL) {
443 				perror("ls");
444 				exit(2);
445 			}
446 
447 			if (acl(file, GETACL, aclcnt, aclp) < 0) {
448 				free(aclp);
449 				(void) fprintf(stderr, "ls: ");
450 				perror(file);
451 				return (0);
452 			}
453 
454 			/*
455 			 * Until found in acl list, assume maximum
456 			 * permissions for both group and mask.  (Just
457 			 * in case the acl lacks either value for
458 			 * some reason.)
459 			 */
460 			groupperm = 07;
461 			mask = 07;
462 			grouppermfound = 0;
463 			maskfound = 0;
464 			for (tp = aclp; aclcnt--; tp++) {
465 				if (tp->a_type == GROUP_OBJ) {
466 					groupperm = tp->a_perm;
467 					grouppermfound = 1;
468 					continue;
469 				}
470 				if (tp->a_type == CLASS_OBJ) {
471 					mask = tp->a_perm;
472 					maskfound = 1;
473 				}
474 				if (grouppermfound && maskfound)
475 					break;
476 			}
477 
478 			free(aclp);
479 
480 			/* reset all the group bits */
481 			fp->fflags &= ~S_IRWXG;
482 
483 			/*
484 			 * Now set them to the logical AND of the
485 			 * GROUP_OBJ permissions and the acl mask.
486 			 */
487 
488 			fp->fflags |= (groupperm & mask) << 3;
489 		} else
490 			fp->acl = ' ';
491 
492 		if (uflg)
493 			fp->fmtime = stb.st_atime;
494 		else if (cflg)
495 			fp->fmtime = stb.st_ctime;
496 		else
497 			fp->fmtime = stb.st_mtime;
498 		if (pnb)
499 			*pnb += stb.st_blocks;
500 	}
501 	return (fp);
502 }
503 
504 static void
505 formatf(struct afile *fp0, struct afile *fplast)
506 {
507 	register struct afile *fp;
508 	int width = 0, w, nentry = fplast - fp0;
509 	int i, j, columns, lines;
510 	char *cp;
511 
512 	if (fp0 == fplast)
513 		return;
514 	if (lflg || Cflg == 0)
515 		columns = 1;
516 	else {
517 		for (fp = fp0; fp < fplast; fp++) {
518 			int len = strlen(fmtentry(fp));
519 
520 			if (len > width)
521 				width = len;
522 		}
523 		if (usetabs)
524 			width = (width + 8) &~ 7;
525 		else
526 			width += 2;
527 		columns = twidth / width;
528 		if (columns == 0)
529 			columns = 1;
530 	}
531 	lines = (nentry + columns - 1) / columns;
532 	for (i = 0; i < lines; i++) {
533 		for (j = 0; j < columns; j++) {
534 			fp = fp0 + j * lines + i;
535 			cp = fmtentry(fp);
536 			(void) printf("%s", cp);
537 			if (fp + lines >= fplast) {
538 				(void) printf("\n");
539 				break;
540 			}
541 			w = strlen(cp);
542 			while (w < width)
543 				if (usetabs) {
544 					w = (w + 8) &~ 7;
545 					(void) putchar('\t');
546 				} else {
547 					w++;
548 					(void) putchar(' ');
549 				}
550 		}
551 	}
552 }
553 
554 static int
555 fcmp(const void *arg1, const void *arg2)
556 {
557 	const struct afile *f1 = arg1;
558 	const struct afile *f2 = arg2;
559 
560 	if (dflg == 0 && fflg == 0) {
561 		if ((f1->fflags&ISARG) && f1->ftype == 'd') {
562 			if ((f2->fflags&ISARG) == 0 || f2->ftype != 'd')
563 				return (1);
564 		} else {
565 			if ((f2->fflags&ISARG) && f2->ftype == 'd')
566 				return (-1);
567 		}
568 	}
569 	if (tflg) {
570 		if (f2->fmtime == f1->fmtime)
571 			return (0);
572 		if (f2->fmtime > f1->fmtime)
573 			return (rflg);
574 		return (-rflg);
575 	}
576 	return (rflg * strcmp(f1->fname, f2->fname));
577 }
578 
579 static char *
580 cat(char *dir, char *file)
581 {
582 	static char dfile[BUFSIZ];
583 
584 	if (strlen(dir)+1+strlen(file)+1 > BUFSIZ) {
585 		(void) fprintf(stderr, "ls: filename too long\n");
586 		exit(1);
587 	}
588 	if (strcmp(dir, "") == 0 || strcmp(dir, ".") == 0) {
589 		(void) strcpy(dfile, file);
590 		return (dfile);
591 	}
592 	(void) strcpy(dfile, dir);
593 	if (dir[strlen(dir) - 1] != '/' && *file != '/')
594 		(void) strcat(dfile, "/");
595 	(void) strcat(dfile, file);
596 	return (dfile);
597 }
598 
599 static char *
600 savestr(char *str)
601 {
602 	char *cp = malloc(strlen(str) + 1);
603 
604 	if (cp == NULL) {
605 		(void) fprintf(stderr, "ls: out of memory\n");
606 		exit(1);
607 	}
608 	(void) strcpy(cp, str);
609 	return (cp);
610 }
611 
612 static	char	*fmtinum(struct afile *);
613 static	char	*fmtsize(struct afile *);
614 static	char	*fmtlstuff(struct afile *);
615 static	char	*fmtmode(char *, int);
616 
617 static char *
618 fmtentry(struct afile *fp)
619 {
620 	static char fmtres[BUFSIZ];
621 	register char *cp, *dp;
622 
623 	(void) sprintf(fmtres, "%s%s%s",
624 	    iflg ? fmtinum(fp) : "",
625 	    sflg ? fmtsize(fp) : "",
626 	    lflg ? fmtlstuff(fp) : "");
627 	dp = &fmtres[strlen(fmtres)];
628 	for (cp = fp->fname; *cp; cp++)
629 		if (qflg && !isprint((unsigned char)*cp))
630 			*dp++ = '?';
631 		else
632 			*dp++ = *cp;
633 	/* avoid both "->" and trailing marks */
634 	if (Fflg && ! (lflg && fp->flinkto)) {
635 		if (fp->ftype == 'd')
636 			*dp++ = '/';
637 		else if (fp->ftype == 'D')
638 			*dp++ = '>';
639 		else if (fp->ftype == 'p')
640 			*dp++ = '|';
641 		else if (fp->ftype == 'l')
642 			*dp++ = '@';
643 		else if (fp->ftype == 's')
644 			*dp++ = '=';
645 		else if (fp->fflags & 0111)
646 			*dp++ = '*';
647 	}
648 	if (lflg && fp->flinkto) {
649 		(void) strcpy(dp, " -> "); dp += 4;
650 		for (cp = fp->flinkto; *cp; cp++)
651 			if (qflg && !isprint((unsigned char) *cp))
652 				*dp++ = '?';
653 			else
654 				*dp++ = *cp;
655 	}
656 	*dp++ = 0;
657 	return (fmtres);
658 }
659 
660 static char *
661 fmtinum(struct afile *p)
662 {
663 	static char inumbuf[12];
664 
665 	(void) sprintf(inumbuf, "%10llu ", p->fnum);
666 	return (inumbuf);
667 }
668 
669 static char *
670 fmtsize(struct afile *p)
671 {
672 	static char sizebuf[32];
673 
674 	(void) sprintf(sizebuf, (off_t)dbtokb(p->fblks) < 10000 ? "%4lld " : \
675 	    "%lld ", (off_t)dbtokb(p->fblks));
676 	return (sizebuf);
677 }
678 
679 static char *
680 fmtlstuff(struct afile *p)
681 {
682 	static char lstuffbuf[256];
683 	char gname[32], uname[32], fsize[32], ftime[32];
684 	register char *lp = lstuffbuf;
685 
686 	/* type mode uname gname fsize ftime */
687 /* get uname */
688 	{
689 		char *cp = getname(p->fuid);
690 		(void) sprintf(uname, "%-8s ", cp);
691 	}
692 /* get gname */
693 	if (gflg) {
694 		char *cp = getgroup(p->fgid);
695 		(void) sprintf(gname, "%-8s ", cp);
696 	}
697 /* get fsize */
698 	if (p->ftype == 'b' || p->ftype == 'c')
699 		(void) sprintf(fsize, "%3ld,%4ld",
700 		    major(p->fsize), minor(p->fsize));
701 	else if (p->ftype == 's')
702 		(void) sprintf(fsize, "%8d", 0);
703 	else
704 		(void) sprintf(fsize, p->fsize < 100000000 ? "%8lld" : \
705 		    "%lld", p->fsize);
706 /* get ftime */
707 	{
708 		char *cp = ctime(&p->fmtime);
709 		if ((p->fmtime < sixmonthsago) || (p->fmtime > onehourfromnow))
710 			(void) sprintf(ftime, " %-7.7s %-4.4s ", cp+4, cp+20);
711 		else
712 			(void) sprintf(ftime, " %-12.12s ", cp+4);
713 	}
714 /* splat */
715 	*lp++ = p->ftype;
716 	lp = fmtmode(lp, p->fflags);
717 	(void) sprintf(lp, "%c%3ld %s%s%s%s",
718 	    p->acl, p->fnl, uname, gflg ? gname : "", fsize, ftime);
719 	return (lstuffbuf);
720 }
721 
722 static	int	m1[] =
723 	{ 1, S_IREAD>>0, 'r', '-' };
724 static	int	m2[] =
725 	{ 1, S_IWRITE>>0, 'w', '-' };
726 static	int	m3[] =
727 	{ 3, S_ISUID|(S_IEXEC>>0), 's', S_IEXEC>>0, 'x', S_ISUID, 'S', '-' };
728 static	int	m4[] =
729 	{ 1, S_IREAD>>3, 'r', '-' };
730 static	int	m5[] =
731 	{ 1, S_IWRITE>>3, 'w', '-' };
732 static	int	m6[] =
733 	{ 3, S_ISGID|(S_IEXEC>>3), 's', S_IEXEC>>3, 'x', S_ISGID, 'S', '-' };
734 static	int	m7[] =
735 	{ 1, S_IREAD>>6, 'r', '-' };
736 static	int	m8[] =
737 	{ 1, S_IWRITE>>6, 'w', '-' };
738 static	int	m9[] =
739 	{ 3, S_ISVTX|(S_IEXEC>>6), 't', S_ISVTX, 'T', S_IEXEC>>6, 'x', '-' };
740 
741 static	int	*m[] = { m1, m2, m3, m4, m5, m6, m7, m8, m9};
742 
743 static char *
744 fmtmode(char *lp, int flags)
745 {
746 	int **mp;
747 
748 	for (mp = &m[0]; mp < &m[sizeof (m)/sizeof (m[0])]; ) {
749 		register int *pairp = *mp++;
750 		register int n = *pairp++;
751 
752 		while (n-- > 0) {
753 			if ((flags&*pairp) == *pairp) {
754 				pairp++;
755 				break;
756 			} else
757 				pairp += 2;
758 		}
759 		*lp++ = *pairp;
760 	}
761 	return (lp);
762 }
763 
764 /* rest should be done with nameserver or database */
765 
766 #include <pwd.h>
767 #include <grp.h>
768 #include <utmpx.h>
769 
770 #define	NMAX	(sizeof (((struct utmpx *)0)->ut_name))
771 #define	SCPYN(a, b)	strncpy(a, b, NMAX)
772 
773 
774 static struct cachenode {	/* this struct must be zeroed before using */
775 	struct cachenode *lesschild;	/* subtree whose entries < val */
776 	struct cachenode *grtrchild;	/* subtree whose entries > val */
777 	int val;			/* the uid or gid of this entry */
778 	int initted;			/* name has been filled in */
779 	char name[NMAX+1];		/* the string that val maps to */
780 } *names, *groups;
781 
782 static struct cachenode *
783 findincache(struct cachenode **head, id_t val)
784 {
785 	register struct cachenode **parent = head;
786 	register struct cachenode *c = *parent;
787 
788 	while (c != NULL) {
789 		if (val == c->val) {
790 			/* found it */
791 			return (c);
792 		} else if (val < c->val) {
793 			parent = &c->lesschild;
794 			c = c->lesschild;
795 		} else {
796 			parent = &c->grtrchild;
797 			c = c->grtrchild;
798 		}
799 	}
800 
801 	/* not in the cache, make a new entry for it */
802 	*parent = c = (struct cachenode *)calloc(1, sizeof (struct cachenode));
803 	c->val = val;
804 	return (c);
805 }
806 
807 static char *
808 getname(uid_t uid)
809 {
810 	struct cachenode *c;
811 	struct passwd *pw;
812 
813 	c = findincache(&names, uid);
814 	if (c->initted == 0) {
815 		if ((pw = getpwuid(uid)) != NULL) {
816 			(void) SCPYN(&c->name[0], pw->pw_name);
817 		} else {
818 			(void) sprintf(&c->name[0], "%-8lu ", uid);
819 		}
820 		c->initted = 1;
821 	}
822 	return (&c->name[0]);
823 }
824 
825 static char *
826 getgroup(gid_t gid)
827 {
828 	struct cachenode *c;
829 	struct group *gr;
830 
831 	c = findincache(&groups, gid);
832 	if (c->initted == 0) {
833 		if ((gr = getgrgid(gid)) != NULL) {
834 			(void) SCPYN(&c->name[0], gr->gr_name);
835 		} else {
836 			(void) sprintf(&c->name[0], "%-8lu ", gid);
837 		}
838 		c->initted = 1;
839 	}
840 	return (&c->name[0]);
841 }
842