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