xref: /illumos-gate/usr/src/cmd/fs.d/ufs/ff/ff.c (revision a0955b86cd77e22e80846428a5065e871b6d8eb8)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 /*
30  * Portions of this source code were derived from Berkeley 4.3 BSD
31  * under license from the Regents of the University of California.
32  */
33 
34 #pragma ident	"%Z%%M%	%I%	%E% SMI"
35 
36 /*
37  * ff -- obtain file names from reading filesystem
38  */
39 
40 #define	NB		500
41 #define	MAXNINDIR	(MAXBSIZE / sizeof (daddr32_t))
42 
43 #include <sys/param.h>
44 #include <sys/types.h>
45 #include <sys/mntent.h>
46 #include <sys/vnode.h>
47 #include <sys/fs/ufs_inode.h>
48 #include <sys/stat.h>
49 #include <sys/fs/ufs_fs.h>
50 #include <sys/fs/ufs_fsdir.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <strings.h>
54 #include <errno.h>
55 #include <fcntl.h>
56 #include <unistd.h>
57 #include <pwd.h>
58 #include "roll_log.h"
59 
60 #define	MIN_PHYS_READ	BBSIZE
61 #define	DAY		(24*60*60)
62 
63 
64 union {
65 	struct	fs	sblk;
66 	char xxx[SBSIZE];	/* because fs is variable length */
67 } real_fs;
68 #define	sblock real_fs.sblk
69 
70 struct	dinode  *itab;	/*  = (struct dinode *)itab; */
71 
72 struct 	dinode	*gip;
73 
74 struct ilist {
75 	ino_t	ino;
76 	ushort_t	mode;
77 	uid_t	uid;
78 	gid_t	gid;
79 } ilist[NB];
80 
81 struct	htab
82 {
83 	ino_t	h_ino;
84 	ino_t	h_pino;
85 	int	h_name_index;		/* index into string table */
86 } *htab;
87 char *strngtab;
88 long hsize;
89 int strngloc;
90 int strngtab_size;
91 #define	STRNGTAB_INCR	(1024*16)	/* amount to grow strngtab */
92 #define	MAX_STRNGTAB_INDEX()	(strngtab_size - 1)
93 #define	AVG_PATH_LEN	30		/* average (?) length of name */
94 
95 struct dirstuff {
96 	int loc;
97 	struct dinode *ip;
98 	char dbuf[MAXBSIZE];
99 };
100 int	Aflg = 0;	/* accessed in n days */
101 int	Mflg = 0;	/* modified in n days */
102 int	Nflg = 0;	/* modified more recently than 'file' */
103 int	Cflg = 0;	/* changed within n days */
104 int	aflg = 0;	/* print the names `.'  and  `..' */
105 int	sflg = 0; /* print only special files and files with set-user-ID mode */
106 int	Sflg = 0;	/* print file size */
107 int	iflg = 0;	/* number of inodes being searched for */
108 int	Iflg = 0;	/* do not print i-number */
109 int	Lflg = 0;	/* supplementary list of multiply linked files */
110 int	mflg = 0;
111 int	pflg = 0;	/* a prefix exists */
112 int	uflg = 0;	/* print the owner's login name */
113 int	fi;
114 ino_t	ino;
115 int	nhent;
116 int	nxfile;
117 int	imax;		/* highest inode number */
118 int	inode_reads;
119 int	passwd_lookups;
120 int	Adelay;		/* Access delay */
121 int	Asign;		/* Access sign */
122 int	Mdelay;		/* Modify delay */
123 int	Msign;		/* Modify sign */
124 int	Cdelay;		/* change delay */
125 int	Csign;		/* change sign */
126 time_t	Nage;		/* Last modification time of the file */
127 char	*Lname;		/* filename for supplementary list */
128 FILE	*Lfile;		/* file for supplementary list */
129 
130 /*
131  * Function prototypes
132  */
133 void check(char *file);
134 void pass1(struct dinode *ip);
135 void pass2(struct dinode *ip);
136 void pass3(struct dinode *ip);
137 struct direct *dreaddir(struct dirstuff *dirp);
138 int dotname(struct direct *dp);
139 void pname(FILE *stream, ino_t i, int lev);
140 struct htab *lookup(ino_t i, int ef);
141 void bread(diskaddr_t bno, char *buf, int cnt);
142 diskaddr_t bmap(diskaddr_t i);
143 struct dinode *ginode(ino_t inumber);
144 char *user_name(int uid);
145 int cmp(int a, int b, int s);
146 time_t mod_time(char *file);
147 void out_multilinks();
148 void usage();
149 int extend_strngtab(unsigned int size);
150 
151 long	atol();
152 offset_t llseek();
153 char 	*strcpy();
154 
155 char	*prefix;
156 time_t	Today;
157 int	nerror;
158 
159 
160 extern int	optind;
161 extern char	*optarg;
162 
163 char *subopts [] = {
164 #define	A_FLAG		0
165 	"a",
166 #define	M_FLAG		1
167 	"m",
168 #define	S_FLAG		2
169 	"s",
170 	NULL
171 	};
172 
173 int
174 main(int argc, char *argv[])
175 {
176 	long n;
177 	int	opt;
178 	char	*suboptions,	*value;
179 	char *p;
180 	int first = 0;
181 
182 	Today = time((time_t *)0);
183 	while ((opt = getopt(argc, argv, "Ia:c:i:lm:n:o:p:su")) != EOF) {
184 		switch (opt) {
185 
186 		case 'a':
187 			Aflg++;
188 			Adelay = atoi(optarg);
189 			Asign = optarg[0];
190 			break;
191 
192 		case 'I':
193 			Iflg++;
194 			break;
195 
196 		case 'c':
197 			Cflg++;
198 			Cdelay = atoi(optarg);
199 			Csign = optarg[0];
200 			break;
201 
202 		case 'l':
203 			Lflg++;
204 			Lname = tmpnam((char *)0);
205 			if ((Lfile = fopen(Lname, "w+")) == NULL) {
206 				perror("open");
207 				(void) fprintf(stderr,
208 				"ff: unable to open temp file, -l ignored\n");
209 				Lflg = 0;
210 			}
211 			break;
212 
213 		case 'm':
214 			Mflg++;
215 			Mdelay = atoi(optarg);
216 			Msign = optarg[0];
217 			break;
218 
219 		case 'n':
220 			Nflg++;
221 			Nage = mod_time(optarg);
222 			break;
223 
224 		case 'o':
225 			/*
226 			 * ufs specific options.
227 			 */
228 			suboptions = optarg;
229 
230 			if (*suboptions == '\0')
231 				usage();
232 			while (*suboptions != '\0') {
233 				switch ((getsubopt(&suboptions,
234 							subopts, &value))) {
235 
236 				case A_FLAG:
237 					aflg++;
238 					break;
239 
240 				case M_FLAG:
241 					mflg++;
242 					break;
243 
244 				case S_FLAG:
245 					sflg++;
246 					break;
247 
248 				default:
249 					usage();
250 				}
251 			}
252 			break;
253 
254 		case 'i':
255 			while ((p = (char *)strtok(((first++ == 0) ?
256 			optarg: ((char *)0)), ", ")) != NULL) {
257 				if ((n = atoi(p)) == 0)
258 					break;
259 				ilist[iflg].ino = n;
260 				nxfile = iflg;
261 				iflg++;
262 			}
263 			break;
264 
265 		case 'p':
266 			prefix = optarg;
267 			pflg++;
268 			break;
269 
270 		case 's':
271 			Sflg++;
272 			break;
273 
274 		case 'u':
275 			uflg++;
276 			break;
277 
278 		case '?':
279 			usage();
280 		}
281 	}
282 	argc -= optind;
283 	argv = &argv[optind];
284 	while (argc--) {
285 		check(*argv);
286 		argv++;
287 	}
288 	if (Lflg) {
289 		out_multilinks();
290 	}
291 	if (nerror)
292 		return (32);
293 	return (0);
294 }
295 
296 void
297 check(char *file)
298 {
299 	int i, j, c;
300 
301 	fi = open64(file, 0);
302 	if (fi < 0) {
303 		(void) fprintf(stderr, "ff: cannot open %s\n", file);
304 		nerror++;
305 		return;
306 	}
307 	nhent = 0;
308 	(void) printf("%s:\n", file);
309 	sync();
310 	bread(SBLOCK, (char *)&sblock, SBSIZE);
311 	if ((sblock.fs_magic != FS_MAGIC) &&
312 	    (sblock.fs_magic != MTB_UFS_MAGIC)) {
313 		(void) fprintf(stderr, "%s: not a ufs file system\n", file);
314 		nerror++;
315 		return;
316 	}
317 
318 	if (sblock.fs_magic == FS_MAGIC &&
319 	    (sblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 &&
320 	    sblock.fs_version != UFS_VERSION_MIN)) {
321 		(void) fprintf(stderr, "%s: unrecognized version of UFS: %d\n",
322 		    file, sblock.fs_version);
323 		nerror++;
324 		return;
325 	}
326 
327 	if (sblock.fs_magic == MTB_UFS_MAGIC &&
328 	    (sblock.fs_version > MTB_UFS_VERSION_1 ||
329 	    sblock.fs_version < MTB_UFS_VERSION_MIN)) {
330 		(void) fprintf(stderr, "%s: unrecognized version of UFS: %d\n",
331 		    file, sblock.fs_version);
332 		nerror++;
333 		return;
334 	}
335 
336 	/* If fs is logged, roll the log. */
337 	if (sblock.fs_logbno) {
338 		switch (rl_roll_log(file)) {
339 		case RL_SUCCESS:
340 			/*
341 			 * Reread the superblock.  Rolling the log may have
342 			 * changed it.
343 			 */
344 			bread(SBLOCK, (char *)&sblock, SBSIZE);
345 			break;
346 		case RL_SYSERR:
347 			(void) printf("Warning: Cannot roll log for %s.  %s\n",
348 				file, strerror(errno));
349 			break;
350 		default:
351 			(void) printf("Warning: Cannot roll log for %s.\n ",
352 				file);
353 			break;
354 		}
355 	}
356 
357 
358 	itab = (struct dinode *)calloc(sblock.fs_ipg, sizeof (struct dinode));
359 	imax = sblock.fs_ncg * sblock.fs_ipg;
360 
361 	hsize = sblock.fs_ipg * sblock.fs_ncg - sblock.fs_cstotal.cs_nifree + 1;
362 	htab = (struct htab *)calloc(hsize, sizeof (struct htab));
363 
364 	if (!extend_strngtab(AVG_PATH_LEN * hsize)) {
365 		(void) printf("not enough memory to allocate tables\n");
366 		nerror++;
367 		return;
368 	}
369 	strngloc = 0;
370 
371 	if ((itab == NULL) || (htab == NULL)) {
372 		(void) printf("not enough memory to allocate tables\n");
373 		nerror++;
374 		return;
375 	}
376 	ino = 0;
377 	for (c = 0; c < sblock.fs_ncg; c++) {
378 		bread(fsbtodb(&sblock, cgimin(&sblock, c)), (char *)itab,
379 		    (int)(sblock.fs_ipg * sizeof (struct dinode)));
380 		for (j = 0; j < sblock.fs_ipg; j++) {
381 			if (itab[j].di_smode != 0) {
382 				itab[j].di_mode = itab[j].di_smode;
383 				if (itab[j].di_suid != (o_uid_t)UID_LONG)
384 				itab[j].di_uid = (unsigned int)itab[j].di_suid;
385 				if (itab[j].di_sgid != GID_LONG)
386 				itab[j].di_gid = (unsigned int)itab[j].di_sgid;
387 				pass1(&itab[j]);
388 			}
389 			ino++;
390 		}
391 	}
392 	ilist[nxfile+1].ino = 0;
393 	ino = 0;
394 	for (c = 0; c < sblock.fs_ncg; c++) {
395 		bread(fsbtodb(&sblock, cgimin(&sblock, c)), (char *)itab,
396 		    (int)(sblock.fs_ipg * sizeof (struct dinode)));
397 		for (j = 0; j < sblock.fs_ipg; j++) {
398 			if (itab[j].di_smode != 0) {
399 				itab[j].di_mode = itab[j].di_smode;
400 				pass2(&itab[j]);
401 			}
402 			ino++;
403 		}
404 	}
405 	ino = 0;
406 	for (c = 0; c < sblock.fs_ncg; c++) {
407 		bread(fsbtodb(&sblock, cgimin(&sblock, c)), (char *)itab,
408 		    (int)(sblock.fs_ipg * sizeof (struct dinode)));
409 		for (j = 0; j < sblock.fs_ipg; j++) {
410 			if (itab[j].di_smode != 0) {
411 				itab[j].di_mode = itab[j].di_smode;
412 				pass3(&itab[j]);
413 			}
414 			ino++;
415 		}
416 	}
417 	(void) close(fi);
418 	for (i = iflg; i < NB; i++)
419 		ilist[i].ino = 0;
420 	nxfile = iflg;
421 	free(itab);
422 	free(htab);
423 	free(strngtab);
424 }
425 
426 void
427 pass1(struct dinode *ip)
428 {
429 	int i;
430 
431 	if (mflg)
432 		for (i = 0; i < iflg; i++)
433 			if (ino == ilist[i].ino) {
434 				ilist[i].mode = ip->di_mode;
435 				ilist[i].uid = ip->di_uid;
436 				ilist[i].gid = ip->di_gid;
437 			}
438 	if ((ip->di_mode & IFMT) != IFDIR) {
439 		if (sflg == 0 || nxfile >= NB)
440 			return;
441 		if ((ip->di_mode&IFMT) == IFBLK ||
442 		    (ip->di_mode&IFMT) == IFCHR || ip->di_mode&(ISUID|ISGID)) {
443 			ilist[nxfile].ino = ino;
444 			ilist[nxfile].mode = ip->di_mode;
445 			ilist[nxfile].uid = ip->di_uid;
446 			ilist[nxfile++].gid = ip->di_gid;
447 			return;
448 		}
449 	}
450 	(void) lookup(ino, 1);
451 }
452 
453 void
454 pass2(struct dinode *ip)
455 {
456 	struct direct *dp;
457 	struct dirstuff dirp;
458 	struct htab *hp;
459 
460 	if ((ip->di_mode&IFMT) != IFDIR)
461 		return;
462 	dirp.loc = 0;
463 	dirp.ip = ip;
464 	gip = ip;
465 	for (dp = dreaddir(&dirp); dp != NULL; dp = dreaddir(&dirp)) {
466 		int nmlen;
467 
468 		if (dp->d_ino == 0)
469 			continue;
470 		hp = lookup(dp->d_ino, 0);
471 		if (hp == 0)
472 			continue;
473 		if (dotname(dp))
474 			continue;
475 		hp->h_pino = ino;
476 		nmlen = strlen(dp->d_name);
477 
478 		if (strngloc + nmlen + 1 > MAX_STRNGTAB_INDEX()) {
479 			if (!extend_strngtab(STRNGTAB_INCR)) {
480 				perror("ncheck: can't grow string table\n");
481 				exit(32);
482 			}
483 		}
484 
485 		hp->h_name_index = strngloc;
486 		(void) strcpy(&strngtab[strngloc], dp->d_name);
487 		strngloc += nmlen + 1;
488 	}
489 }
490 
491 void
492 pass3(struct dinode *ip)
493 {
494 	struct direct *dp;
495 	struct dirstuff dirp;
496 	struct dinode   *dip;
497 	int k;
498 
499 	if ((ip->di_mode&IFMT) != IFDIR)
500 		return;
501 	dirp.loc = 0;
502 	dirp.ip = ip;
503 	gip = ip;
504 	for (dp = dreaddir(&dirp); dp != NULL; dp = dreaddir(&dirp)) {
505 		if (aflg == 0 && dotname(dp))
506 			continue;
507 		if (sflg == 0 && iflg == 0)
508 			goto pr;
509 		for (k = 0; ilist[k].ino != 0; k++)
510 			if (ilist[k].ino == dp->d_ino)
511 				break;
512 		if (ilist[k].ino == 0)
513 			continue;
514 		if (mflg)
515 			(void) printf("mode %-6o uid %-5ld gid %-5ld ino ",
516 			    ilist[k].mode, ilist[k].uid, ilist[k].gid);
517 	pr:
518 		if (Sflg || uflg || Aflg || Mflg || Cflg || Nflg || Lflg)
519 			dip = ginode(dp->d_ino);
520 		if ((!Aflg ||
521 		cmp((Today - dip->di_un.di_icom.ic_atime)/DAY, Adelay,
522 		    Asign)) &&
523 		    (!Mflg || cmp((Today - dip->di_un.di_icom.ic_mtime)/DAY,
524 			Mdelay, Msign)) &&
525 		    (!Cflg || cmp((Today - dip->di_un.di_icom.ic_mtime)/DAY,
526 			Cdelay, Csign)) &&
527 		    (!Nflg || cmp(dip->di_un.di_icom.ic_mtime, Nage, '+'))) {
528 			if (Iflg == 0)
529 				(void) printf("%-5u\t", dp->d_ino);
530 			pname(stdout, ino, 0);
531 			(void) printf("/%s", dp->d_name);
532 			if (lookup(dp->d_ino, 0))
533 				(void) printf("/.");
534 			if (Sflg)
535 				(void) printf("\t%6lld",
536 				    dip->di_un.di_icom.ic_lsize);
537 			if (uflg)
538 				(void) printf("\t%s",
539 				    user_name(dip->di_un.di_icom.ic_uid));
540 			(void) printf("\n");
541 			if (Lflg && (dip->di_un.di_icom.ic_nlink > 1)) {
542 				(void) fprintf(Lfile, "%-5u\t",
543 					dp->d_ino);
544 				(void) fprintf(Lfile, "%-5u\t",
545 					dip->di_un.di_icom.ic_nlink);
546 				pname(Lfile, ino, 0);
547 				(void) fprintf(Lfile, "/%s\n", dp->d_name);
548 			}
549 		}
550 	}
551 }
552 
553 
554 
555 /*
556  * get next entry in a directory.
557  */
558 struct direct *
559 dreaddir(struct dirstuff *dirp)
560 {
561 	struct direct *dp;
562 	diskaddr_t lbn, d;
563 
564 	for (;;) {
565 		if (dirp->loc >= (int)dirp->ip->di_size)
566 			return (NULL);
567 		if (blkoff(&sblock, dirp->loc) == 0) {
568 			lbn = lblkno(&sblock, dirp->loc);
569 			d = bmap(lbn);
570 			if (d == 0)
571 				return (NULL);
572 			bread(fsbtodb(&sblock, d), dirp->dbuf,
573 			    (int)dblksize(&sblock, dirp->ip, (int)lbn));
574 		}
575 		dp = (struct direct *)
576 		    (dirp->dbuf + blkoff(&sblock, dirp->loc));
577 		dirp->loc += dp->d_reclen;
578 		if (dp->d_ino == 0)
579 			continue;
580 		return (dp);
581 	}
582 }
583 
584 int
585 dotname(struct direct *dp)
586 {
587 
588 	if (dp->d_name[0] == '.')
589 		if (dp->d_name[1] == 0 ||
590 		    (dp->d_name[1] == '.' && dp->d_name[2] == 0))
591 			return (1);
592 	return (0);
593 }
594 
595 void
596 pname(FILE *stream, ino_t i, int lev)
597 {
598 	struct htab *hp;
599 
600 	if (i == UFSROOTINO)
601 		return;
602 	if ((hp = lookup(i, 0)) == 0) {
603 		(void) fprintf(stream, "???");
604 		return;
605 	}
606 	if (lev > 10) {
607 		(void) fprintf(stream, "...");
608 		return;
609 	}
610 	pname(stream, hp->h_pino, ++lev);
611 	if (pflg)
612 		(void) fprintf(stream, "%s/%s", prefix,
613 			&(strngtab[hp->h_name_index]));
614 	else
615 		(void) fprintf(stream, "/%s",
616 			&(strngtab[hp->h_name_index]));
617 }
618 
619 struct htab *
620 lookup(ino_t i, int ef)
621 {
622 	struct htab *hp;
623 
624 	for (hp = &htab[(int)i%hsize]; hp->h_ino; ) {
625 		if (hp->h_ino == i)
626 			return (hp);
627 		if (++hp >= &htab[hsize])
628 			hp = htab;
629 	}
630 	if (ef == 0)
631 		return (0);
632 	if (++nhent >= hsize) {
633 		(void) fprintf(stderr,
634 		    "ff: hsize of %ld is too small\n", hsize);
635 		exit(32);
636 	}
637 	hp->h_ino = i;
638 	return (hp);
639 }
640 
641 void
642 bread(diskaddr_t bno, char *buf, int cnt)
643 {
644 	int i;
645 	int got;
646 	offset_t offset;
647 
648 	offset = (offset_t)bno * DEV_BSIZE;
649 	if (llseek(fi, offset, 0) == (offset_t)-1) {
650 		(void) fprintf(stderr,
651 		    "ff: llseek error %lx %lx\n",
652 		    ((long *)&offset)[0], ((long *)&offset)[1]);
653 		for (i = 0; i < cnt; i++)
654 			buf[i] = 0;
655 		return;
656 	}
657 
658 	got = read((int)fi, buf, cnt);
659 	if (got != cnt) {
660 		perror("read");
661 		(void) fprintf(stderr,
662 			"ff: (wanted %d got %d blk %lld)\n", cnt, got, bno);
663 		for (i = 0; i < cnt; i++)
664 			buf[i] = 0;
665 	}
666 }
667 
668 diskaddr_t
669 bmap(diskaddr_t i)
670 {
671 	daddr32_t ibuf[MAXNINDIR];
672 
673 	if (i < NDADDR)
674 		return ((diskaddr_t)gip->di_db[i]);
675 	i -= NDADDR;
676 	if (i > NINDIR(&sblock)) {
677 		(void) fprintf(stderr, "ff    : %lu - huge directory\n", ino);
678 		return ((diskaddr_t)0);
679 	}
680 	bread(fsbtodb(&sblock, gip->di_ib[0]), (char *)ibuf, sizeof (ibuf));
681 	return ((diskaddr_t)ibuf[i]);
682 }
683 
684 struct dinode *
685 ginode(ino_t inumber)
686 {
687 	diskaddr_t		iblk;
688 	diskaddr_t		dblk;
689 	int		ioff;
690 	static diskaddr_t	curr_dblk;
691 	static char	buf[MIN_PHYS_READ];
692 	struct dinode	*ibuf;
693 
694 	if (inumber < UFSROOTINO || (int)inumber > imax) {
695 		(void) fprintf(stderr,
696 		    "bad inode number %ld to ginode\n", inumber);
697 		exit(32);
698 	}
699 	iblk = itod(&sblock, (int)inumber);
700 	dblk = fsbtodb(&sblock, iblk);
701 	ioff = itoo(&sblock, (int)inumber);
702 	if (dblk != curr_dblk) {
703 		bread(dblk, &buf[0], sizeof (buf));
704 		curr_dblk = dblk;
705 		inode_reads++;
706 	}
707 	ibuf = (struct dinode *)&buf[0];
708 	ibuf += ioff;
709 	return (ibuf);
710 }
711 
712 #define	HASHNAMESIZE 16
713 
714 struct name_ent {
715 	struct name_ent	*name_nxt;
716 	int		name_uid;
717 	char		*name_string;
718 };
719 struct name_ent *hashtable[HASHNAMESIZE];
720 
721 char *
722 user_name(int uid)
723 {
724 	int		h_index;
725 	struct name_ent	*hp;
726 	struct passwd	*pwent;
727 
728 	h_index = uid % HASHNAMESIZE;
729 	for (hp = hashtable[h_index]; hp != NULL; hp = hp->name_nxt) {
730 		if (hp->name_uid == uid) {
731 			return (hp->name_string);
732 		}
733 	}
734 	hp = (struct name_ent *)calloc(1, sizeof (struct name_ent));
735 	hp->name_nxt = hashtable[h_index];
736 	hp->name_uid = uid;
737 	hashtable[h_index] = hp;
738 	if ((pwent = getpwuid(uid)) == NULL) {
739 		hp->name_string = "unknown";
740 	} else {
741 		hp->name_string = (char *)strdup(pwent->pw_name);
742 	}
743 	passwd_lookups++;
744 
745 	return (hp->name_string);
746 }
747 
748 int
749 cmp(int a, int b, int s)
750 {
751 	if (s == '+')
752 		return (a > b);
753 	if (s == '-')
754 		return (a < -(b));
755 	return (a == b);
756 }
757 
758 /*
759  * We can't do this one by reading the disk directly, since there
760  * is no guarantee that the file is even on a local disk.
761  */
762 time_t
763 mod_time(char *file)
764 {
765 	struct stat64	stat_buf;
766 
767 	if (stat64(file, &stat_buf) < 0) {
768 		(void) fprintf(stderr, "ff: can't stat '%s' - ignored\n", file);
769 		return (0);
770 	}
771 	return (stat_buf.st_mtime);
772 }
773 
774 void
775 out_multilinks()
776 {
777 	int	length;
778 
779 	if ((length = fseek(Lfile, 0L, 2)) < 0) {
780 		perror("fseek");
781 		exit(32);
782 	} else
783 		if ((length = ftell(Lfile)) > 0) {
784 			(void) fprintf(stdout,
785 			    "\nmultilink files\nIno\tLinks\tPathname\n\n");
786 			rewind(Lfile);
787 			while (length-- > 0)
788 				(void) putc(getc(Lfile), stdout);
789 		} else
790 			(void) fprintf(stdout, "No multilink files\n");
791 	(void) fclose(Lfile);
792 }
793 
794 void
795 usage()
796 {
797 	(void) fprintf(stderr,
798 	    "ufs usage: ff [-F ufs] [generic options] [-o a,m,s] special\n");
799 	exit(32);
800 }
801 
802 /*
803  * Extend or create the string table.
804  * Preserves contents.
805  * Return non-zero for success.
806  */
807 int
808 extend_strngtab(unsigned int size)
809 {
810 	strngtab_size += size;
811 	strngtab = (char *)realloc(strngtab, strngtab_size);
812 
813 	return ((int)strngtab);
814 }
815