xref: /illumos-gate/usr/src/cmd/fs.d/ufs/fsck/pass2.c (revision ebc6491a)
1355d6bb5Sswilcox /*
2355d6bb5Sswilcox  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3355d6bb5Sswilcox  * Use is subject to license terms.
4355d6bb5Sswilcox  */
5355d6bb5Sswilcox 
6355d6bb5Sswilcox /*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
7*ebc6491aSToomas Soome /*	All Rights Reserved	*/
8355d6bb5Sswilcox 
9355d6bb5Sswilcox /*
10355d6bb5Sswilcox  * Copyright (c) 1980, 1986, 1990 The Regents of the University of California.
11355d6bb5Sswilcox  * All rights reserved.
12355d6bb5Sswilcox  *
13355d6bb5Sswilcox  * Redistribution and use in source and binary forms are permitted
14355d6bb5Sswilcox  * provided that: (1) source distributions retain this entire copyright
15355d6bb5Sswilcox  * notice and comment, and (2) distributions including binaries display
16355d6bb5Sswilcox  * the following acknowledgement:  ``This product includes software
17355d6bb5Sswilcox  * developed by the University of California, Berkeley and its contributors''
18355d6bb5Sswilcox  * in the documentation or other materials provided with the distribution
19355d6bb5Sswilcox  * and in all advertising materials mentioning features or use of this
20355d6bb5Sswilcox  * software. Neither the name of the University nor the names of its
21355d6bb5Sswilcox  * contributors may be used to endorse or promote products derived
22355d6bb5Sswilcox  * from this software without specific prior written permission.
23355d6bb5Sswilcox  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
24355d6bb5Sswilcox  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
25355d6bb5Sswilcox  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
26355d6bb5Sswilcox  */
27355d6bb5Sswilcox 
28355d6bb5Sswilcox #include <stdio.h>
29355d6bb5Sswilcox #include <stdlib.h>
30355d6bb5Sswilcox #include <sys/param.h>
31355d6bb5Sswilcox #include <sys/types.h>
32355d6bb5Sswilcox #include <sys/sysmacros.h>
33355d6bb5Sswilcox #include <sys/mntent.h>
34355d6bb5Sswilcox #include <sys/fs/ufs_fs.h>
35355d6bb5Sswilcox #include <sys/vnode.h>
36355d6bb5Sswilcox #include <sys/fs/ufs_inode.h>
37355d6bb5Sswilcox #define	_KERNEL
38355d6bb5Sswilcox #include <sys/fs/ufs_fsdir.h>
39355d6bb5Sswilcox #undef _KERNEL
40355d6bb5Sswilcox #include <string.h>
41355d6bb5Sswilcox #include "fsck.h"
42355d6bb5Sswilcox 
43355d6bb5Sswilcox #define	MINDIRSIZE	(sizeof (struct dirtemplate))
44355d6bb5Sswilcox 
45355d6bb5Sswilcox static int blksort(const void *, const void *);
46b9a41fd3Sswilcox static int pass2check(struct inodesc *);
47355d6bb5Sswilcox 
48355d6bb5Sswilcox void
pass2(void)49b9a41fd3Sswilcox pass2(void)
50355d6bb5Sswilcox {
51*ebc6491aSToomas Soome 	struct dinode		*dp, *dp2, *dpattr;
52*ebc6491aSToomas Soome 	struct inoinfo		**inpp, *inp;
53*ebc6491aSToomas Soome 	struct inoinfo		**inpend;
54*ebc6491aSToomas Soome 	struct inodesc		curino;
55*ebc6491aSToomas Soome 	struct inodesc		ldesc;
56*ebc6491aSToomas Soome 	struct dinode		dino;
57*ebc6491aSToomas Soome 	char			pathbuf[MAXPATHLEN + 1];
58355d6bb5Sswilcox 	int			found;
59355d6bb5Sswilcox 	int			dirtype;
60355d6bb5Sswilcox 	caddr_t			errmsg;
61355d6bb5Sswilcox 	struct shadowclientinfo *sci;
62355d6bb5Sswilcox 
63355d6bb5Sswilcox 	switch (statemap[UFSROOTINO] & ~INDELAYD) {
64355d6bb5Sswilcox 	case USTATE:
65355d6bb5Sswilcox 		pfatal("ROOT INODE UNALLOCATED");
66355d6bb5Sswilcox 		if (reply("ALLOCATE") == 0) {
67355d6bb5Sswilcox 			errexit("Program terminated.");
68355d6bb5Sswilcox 		}
69355d6bb5Sswilcox 		if (allocdir(UFSROOTINO, UFSROOTINO, 0755, 0) != UFSROOTINO)
70355d6bb5Sswilcox 			errexit("CANNOT ALLOCATE ROOT INODE\n");
71355d6bb5Sswilcox 		break;
72355d6bb5Sswilcox 
73355d6bb5Sswilcox 	case DCLEAR:
74355d6bb5Sswilcox 		pfatal("DUPS/BAD IN ROOT INODE");
75355d6bb5Sswilcox 		if (reply("REALLOCATE") == 1) {
76355d6bb5Sswilcox 			freeino(UFSROOTINO, TI_NOPARENT);
77355d6bb5Sswilcox 			if (allocdir(UFSROOTINO, UFSROOTINO,
78355d6bb5Sswilcox 			    0755, 0) != UFSROOTINO)
79355d6bb5Sswilcox 				errexit("CANNOT ALLOCATE ROOT INODE\n");
80355d6bb5Sswilcox 			break;
81355d6bb5Sswilcox 		}
82355d6bb5Sswilcox 		if (reply("CONTINUE") == 0) {
83355d6bb5Sswilcox 			errexit("Program terminated.");
84355d6bb5Sswilcox 		}
85355d6bb5Sswilcox 		break;
86355d6bb5Sswilcox 
87355d6bb5Sswilcox 	case FSTATE:
88355d6bb5Sswilcox 	case FCLEAR:
89355d6bb5Sswilcox 	case FZLINK:
90355d6bb5Sswilcox 	case SSTATE:
91355d6bb5Sswilcox 	case SCLEAR:
92355d6bb5Sswilcox 		pfatal("ROOT INODE NOT DIRECTORY");
93355d6bb5Sswilcox 		if (reply("REALLOCATE") == 1) {
94355d6bb5Sswilcox 			freeino(UFSROOTINO, TI_NOPARENT);
95355d6bb5Sswilcox 			if (allocdir(UFSROOTINO, UFSROOTINO, 0755, 0) !=
96355d6bb5Sswilcox 			    UFSROOTINO)
97355d6bb5Sswilcox 				errexit("CANNOT ALLOCATE ROOT INODE\n");
98355d6bb5Sswilcox 			break;
99355d6bb5Sswilcox 		}
100355d6bb5Sswilcox 		if (reply("FIX") == 0) {
101355d6bb5Sswilcox 			ckfini();
102355d6bb5Sswilcox 			errexit("Program terminated.");
103355d6bb5Sswilcox 		}
104355d6bb5Sswilcox 		dp = ginode(UFSROOTINO);
105355d6bb5Sswilcox 		dp->di_mode &= ~IFMT;
106355d6bb5Sswilcox 		dp->di_mode |= IFDIR;
107355d6bb5Sswilcox 		inodirty();
108355d6bb5Sswilcox 		break;
109355d6bb5Sswilcox 
110355d6bb5Sswilcox 	case DSTATE:
111355d6bb5Sswilcox 	case DZLINK:
112355d6bb5Sswilcox 		break;
113355d6bb5Sswilcox 
114355d6bb5Sswilcox 	default:
115355d6bb5Sswilcox 		errexit("BAD STATE 0x%x FOR ROOT INODE\n",
116*ebc6491aSToomas Soome 		    statemap[UFSROOTINO]);
117355d6bb5Sswilcox 	}
118355d6bb5Sswilcox 	statemap[UFSROOTINO] = DFOUND;
119355d6bb5Sswilcox 
120355d6bb5Sswilcox 	/*
121355d6bb5Sswilcox 	 * Technically, we do know who the parent is.  However,
122355d6bb5Sswilcox 	 * if this is set, then we'll get confused during the
123355d6bb5Sswilcox 	 * second-dir-entry-is-dotdot test for the root inode.
124355d6bb5Sswilcox 	 */
125355d6bb5Sswilcox 	inp = getinoinfo(UFSROOTINO);
126355d6bb5Sswilcox 	if (inp != NULL && inp->i_dotdot != 0)
127355d6bb5Sswilcox 		inp->i_dotdot = 0;
128355d6bb5Sswilcox 
129355d6bb5Sswilcox 	/*
130355d6bb5Sswilcox 	 * Sort the directory list into disk block order.  There's no
131355d6bb5Sswilcox 	 * requirement to do this, but it may help improve our i/o times
132355d6bb5Sswilcox 	 * somewhat.
133355d6bb5Sswilcox 	 */
134355d6bb5Sswilcox 	qsort((void *)inpsort, (size_t)inplast, sizeof (*inpsort), blksort);
135355d6bb5Sswilcox 	/*
136355d6bb5Sswilcox 	 * Check the integrity of each directory.  In general, we treat
137355d6bb5Sswilcox 	 * attribute directories just like normal ones.  Only the handling
138355d6bb5Sswilcox 	 * of .. is really different.
139355d6bb5Sswilcox 	 */
140355d6bb5Sswilcox 	(void) memset(&dino, 0, sizeof (struct dinode));
141355d6bb5Sswilcox 	dino.di_mode = IFDIR;
142355d6bb5Sswilcox 	inpend = &inpsort[inplast];
143355d6bb5Sswilcox 	for (inpp = inpsort; inpp < inpend; inpp++) {
144355d6bb5Sswilcox 		inp = *inpp;
145355d6bb5Sswilcox 
146355d6bb5Sswilcox 		if (inp->i_isize == 0)
147355d6bb5Sswilcox 			continue;
148355d6bb5Sswilcox 
149355d6bb5Sswilcox 		/* != DSTATE also covers case of == USTATE */
150355d6bb5Sswilcox 		if (((statemap[inp->i_number] & STMASK) != DSTATE) ||
151355d6bb5Sswilcox 		    ((statemap[inp->i_number] & INCLEAR) == INCLEAR))
152355d6bb5Sswilcox 			continue;
153355d6bb5Sswilcox 
154355d6bb5Sswilcox 		if (inp->i_isize < (offset_t)MINDIRSIZE) {
155355d6bb5Sswilcox 			direrror(inp->i_number, "DIRECTORY TOO SHORT");
156355d6bb5Sswilcox 			inp->i_isize = (offset_t)roundup(MINDIRSIZE, DIRBLKSIZ);
157355d6bb5Sswilcox 			if (reply("FIX") == 1) {
158355d6bb5Sswilcox 				dp = ginode(inp->i_number);
159355d6bb5Sswilcox 				dp->di_size = (u_offset_t)inp->i_isize;
160355d6bb5Sswilcox 				inodirty();
161355d6bb5Sswilcox 			} else {
162355d6bb5Sswilcox 				iscorrupt = 1;
163355d6bb5Sswilcox 			}
164355d6bb5Sswilcox 		}
165355d6bb5Sswilcox 		if ((inp->i_isize & (offset_t)(DIRBLKSIZ - 1)) != 0) {
166355d6bb5Sswilcox 			getpathname(pathbuf, inp->i_number, inp->i_number);
167355d6bb5Sswilcox 			pwarn("DIRECTORY %s: LENGTH %lld NOT MULTIPLE OF %d",
168355d6bb5Sswilcox 			    pathbuf, (longlong_t)inp->i_isize, DIRBLKSIZ);
169355d6bb5Sswilcox 			inp->i_isize = roundup(inp->i_isize,
170*ebc6491aSToomas Soome 			    (offset_t)DIRBLKSIZ);
171355d6bb5Sswilcox 			if (preen || reply("ADJUST") == 1) {
172355d6bb5Sswilcox 				dp = ginode(inp->i_number);
173355d6bb5Sswilcox 				dp->di_size =
174*ebc6491aSToomas Soome 				    (u_offset_t)roundup(inp->i_isize,
175*ebc6491aSToomas Soome 				    (offset_t)DIRBLKSIZ);
176355d6bb5Sswilcox 				inodirty();
177355d6bb5Sswilcox 				if (preen)
178355d6bb5Sswilcox 					(void) printf(" (ADJUSTED)\n");
179355d6bb5Sswilcox 			} else {
180355d6bb5Sswilcox 				iscorrupt = 1;
181355d6bb5Sswilcox 			}
182355d6bb5Sswilcox 		}
183355d6bb5Sswilcox 		dp = ginode(inp->i_number);
184355d6bb5Sswilcox 		if ((dp->di_mode & IFMT) == IFATTRDIR &&
185355d6bb5Sswilcox 		    (dp->di_cflags & IXATTR) == 0) {
186355d6bb5Sswilcox 			pwarn("ATTRIBUTE DIRECTORY  I=%d  MISSING IXATTR FLAG",
187355d6bb5Sswilcox 			    inp->i_number);
188355d6bb5Sswilcox 			if (preen || reply("CORRECT") == 1) {
189355d6bb5Sswilcox 				dp->di_cflags |= IXATTR;
190355d6bb5Sswilcox 				inodirty();
191355d6bb5Sswilcox 				if (preen)
192355d6bb5Sswilcox 					(void) printf(" (CORRECTED)\n");
193355d6bb5Sswilcox 			}
194355d6bb5Sswilcox 		}
195355d6bb5Sswilcox 		dp = &dino;
196355d6bb5Sswilcox 		dp->di_size = (u_offset_t)inp->i_isize;
197355d6bb5Sswilcox 		(void) memmove((void *)&dp->di_db[0], (void *)&inp->i_blks[0],
198*ebc6491aSToomas Soome 		    inp->i_blkssize);
199355d6bb5Sswilcox 		init_inodesc(&curino);
200355d6bb5Sswilcox 		curino.id_type = DATA;
201b9a41fd3Sswilcox 		curino.id_func = pass2check;
202355d6bb5Sswilcox 		curino.id_number = inp->i_number;
203355d6bb5Sswilcox 		curino.id_parent = inp->i_parent;
204355d6bb5Sswilcox 		curino.id_fix = DONTKNOW;
205355d6bb5Sswilcox 		(void) ckinode(dp, &curino, CKI_TRAVERSE);
206355d6bb5Sswilcox 
207355d6bb5Sswilcox 		/*
208355d6bb5Sswilcox 		 * Make sure we mark attrdirs as DFOUND, since they won't
209355d6bb5Sswilcox 		 * be located during normal scan of standard directories.
210355d6bb5Sswilcox 		 */
211355d6bb5Sswilcox 		if (curino.id_parent == 0) {
212355d6bb5Sswilcox 			dpattr = ginode(inp->i_number);
213355d6bb5Sswilcox 			if ((dpattr->di_mode & IFMT) == IFATTRDIR) {
214355d6bb5Sswilcox 				for (sci = attrclientinfo; sci != NULL;
215355d6bb5Sswilcox 				    sci = sci->next) {
216355d6bb5Sswilcox 					if (sci->shadow == inp->i_number) {
217355d6bb5Sswilcox 						curino.id_parent =
218355d6bb5Sswilcox 						    sci->clients->client[0];
219355d6bb5Sswilcox 						statemap[inp->i_number] =
220355d6bb5Sswilcox 						    DFOUND;
221355d6bb5Sswilcox 						inp->i_parent =
222355d6bb5Sswilcox 						    curino.id_parent;
223355d6bb5Sswilcox 					}
224355d6bb5Sswilcox 				}
225355d6bb5Sswilcox 			}
226355d6bb5Sswilcox 		}
227355d6bb5Sswilcox 	}
228355d6bb5Sswilcox 	/*
229355d6bb5Sswilcox 	 * Now that the parents of all directories have been found,
230355d6bb5Sswilcox 	 * make another pass to verify the value of ..
231355d6bb5Sswilcox 	 */
232355d6bb5Sswilcox 	for (inpp = inpsort; inpp < inpend; inpp++) {
233355d6bb5Sswilcox 		inp = *inpp;
234355d6bb5Sswilcox 		if (inp->i_parent == 0 || inp->i_isize == 0)
235355d6bb5Sswilcox 			continue;
236355d6bb5Sswilcox 		/*
237355d6bb5Sswilcox 		 * There are only directories in inpsort[], so only
238355d6bb5Sswilcox 		 * directory-related states need to be checked.  There
239355d6bb5Sswilcox 		 * should never be any flags associated with USTATE.
240355d6bb5Sswilcox 		 */
241*ebc6491aSToomas Soome 		if ((statemap[inp->i_number] & (STMASK | INCLEAR)) == DCLEAR ||
242355d6bb5Sswilcox 		    statemap[inp->i_number] == USTATE) {
243355d6bb5Sswilcox 			continue;
244355d6bb5Sswilcox 		}
245355d6bb5Sswilcox 		if (statemap[inp->i_parent] == DFOUND &&
246355d6bb5Sswilcox 		    S_IS_DUNFOUND(statemap[inp->i_number])) {
247355d6bb5Sswilcox 			statemap[inp->i_number] = DFOUND |
248*ebc6491aSToomas Soome 			    (statemap[inp->i_number] & INCLEAR);
249355d6bb5Sswilcox 		}
250355d6bb5Sswilcox 		if (inp->i_dotdot == inp->i_parent ||
251355d6bb5Sswilcox 		    inp->i_dotdot == (fsck_ino_t)-1) {
252355d6bb5Sswilcox 			continue;
253355d6bb5Sswilcox 		}
254355d6bb5Sswilcox 		if (inp->i_dotdot == 0) {
255355d6bb5Sswilcox 			inp->i_dotdot = inp->i_parent;
256355d6bb5Sswilcox 			fileerror(inp->i_parent, inp->i_number,
257355d6bb5Sswilcox 			    "MISSING '..'");
258355d6bb5Sswilcox 			if (reply("FIX") == 0) {
259355d6bb5Sswilcox 				iscorrupt = 1;
260355d6bb5Sswilcox 				continue;
261355d6bb5Sswilcox 			}
262355d6bb5Sswilcox 			dp = ginode(inp->i_number);
263355d6bb5Sswilcox 			found = 0;
264355d6bb5Sswilcox 			dirtype = (dp->di_mode & IFMT);
265355d6bb5Sswilcox 
266355d6bb5Sswilcox 			/*
267355d6bb5Sswilcox 			 * See if this is an attrdir that we located in pass1.
268355d6bb5Sswilcox 			 * i.e. it was on an i_oeftflag of some other inode.
269355d6bb5Sswilcox 			 * if it isn't found then we have an orphaned attrdir
270355d6bb5Sswilcox 			 * that needs to be tossed into lost+found.
271355d6bb5Sswilcox 			 */
272355d6bb5Sswilcox 			if (dirtype == IFATTRDIR) {
273355d6bb5Sswilcox 				for (sci = attrclientinfo;
274355d6bb5Sswilcox 				    sci != NULL;
275355d6bb5Sswilcox 				    sci = sci->next) {
276355d6bb5Sswilcox 					if (sci->shadow == inp->i_number) {
277355d6bb5Sswilcox 						inp->i_parent =
278355d6bb5Sswilcox 						    sci->clients->client[0];
279355d6bb5Sswilcox 						found = 1;
280355d6bb5Sswilcox 					}
281355d6bb5Sswilcox 				}
282355d6bb5Sswilcox 			}
283355d6bb5Sswilcox 
284355d6bb5Sswilcox 			/*
285355d6bb5Sswilcox 			 * We've already proven there's no "..", so this
286355d6bb5Sswilcox 			 * can't create a duplicate.
287355d6bb5Sswilcox 			 */
288355d6bb5Sswilcox 			if (makeentry(inp->i_number, inp->i_parent, "..")) {
289355d6bb5Sswilcox 
290355d6bb5Sswilcox 				/*
291355d6bb5Sswilcox 				 * is it an orphaned attrdir?
292355d6bb5Sswilcox 				 */
293355d6bb5Sswilcox 				if (dirtype == IFATTRDIR && found == 0) {
294355d6bb5Sswilcox 					/*
295355d6bb5Sswilcox 					 * Throw it into lost+found
296355d6bb5Sswilcox 					 */
297355d6bb5Sswilcox 					if (linkup(inp->i_number, lfdir,
298355d6bb5Sswilcox 					    NULL) == 0) {
299355d6bb5Sswilcox 						pwarn(
300355d6bb5Sswilcox 			    "Unable to move attrdir I=%d to lost+found\n",
301355d6bb5Sswilcox 						    inp->i_number);
302355d6bb5Sswilcox 						iscorrupt = 1;
303355d6bb5Sswilcox 					}
304355d6bb5Sswilcox 					maybe_convert_attrdir_to_dir(
305355d6bb5Sswilcox 					    inp->i_number);
306355d6bb5Sswilcox 				}
307355d6bb5Sswilcox 				if (dirtype == IFDIR) {
308355d6bb5Sswilcox 					LINK_RANGE(errmsg,
309355d6bb5Sswilcox 					    lncntp[inp->i_parent], -1);
310355d6bb5Sswilcox 					if (errmsg != NULL) {
311355d6bb5Sswilcox 						LINK_CLEAR(errmsg,
312355d6bb5Sswilcox 						    inp->i_parent, IFDIR,
313355d6bb5Sswilcox 						    &ldesc);
314355d6bb5Sswilcox 						if (statemap[inp->i_parent] !=
315355d6bb5Sswilcox 						    USTATE) {
316355d6bb5Sswilcox 							/*
317355d6bb5Sswilcox 							 * iscorrupt is
318355d6bb5Sswilcox 							 * already set
319355d6bb5Sswilcox 							 */
320355d6bb5Sswilcox 							continue;
321355d6bb5Sswilcox 						}
322355d6bb5Sswilcox 					}
323355d6bb5Sswilcox 					TRACK_LNCNTP(inp->i_parent,
324355d6bb5Sswilcox 					    lncntp[inp->i_parent]--);
325355d6bb5Sswilcox 				}
326355d6bb5Sswilcox 
327355d6bb5Sswilcox 				continue;
328355d6bb5Sswilcox 			}
329355d6bb5Sswilcox 			pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
330355d6bb5Sswilcox 			iscorrupt = 1;
331355d6bb5Sswilcox 			inp->i_dotdot = (fsck_ino_t)-1;
332355d6bb5Sswilcox 			continue;
333355d6bb5Sswilcox 		}
334355d6bb5Sswilcox 
335355d6bb5Sswilcox 		dp2 = ginode(inp->i_parent);
336355d6bb5Sswilcox 
337355d6bb5Sswilcox 		if ((dp2->di_mode & IFMT) == IFATTRDIR) {
338355d6bb5Sswilcox 			continue;
339355d6bb5Sswilcox 		}
340355d6bb5Sswilcox 		fileerror(inp->i_parent, inp->i_number,
341*ebc6491aSToomas Soome 		    "BAD INODE NUMBER FOR '..'");
342355d6bb5Sswilcox 		if (reply("FIX") == 0) {
343355d6bb5Sswilcox 			iscorrupt = 1;
344355d6bb5Sswilcox 			continue;
345355d6bb5Sswilcox 		}
346355d6bb5Sswilcox 
347355d6bb5Sswilcox 		LINK_RANGE(errmsg, lncntp[inp->i_dotdot], 1);
348355d6bb5Sswilcox 		if (errmsg != NULL) {
349355d6bb5Sswilcox 			LINK_CLEAR(errmsg, inp->i_dotdot, IFDIR, &ldesc);
350355d6bb5Sswilcox 			if (statemap[inp->i_dotdot] != USTATE) {
351355d6bb5Sswilcox 				/* iscorrupt is already set */
352355d6bb5Sswilcox 				continue;
353355d6bb5Sswilcox 			}
354355d6bb5Sswilcox 		}
355355d6bb5Sswilcox 		TRACK_LNCNTP(inp->i_dotdot, lncntp[inp->i_dotdot]++);
356355d6bb5Sswilcox 
357355d6bb5Sswilcox 		LINK_RANGE(errmsg, lncntp[inp->i_parent], -1);
358355d6bb5Sswilcox 		if (errmsg != NULL) {
359355d6bb5Sswilcox 			LINK_CLEAR(errmsg, inp->i_parent, IFDIR, &ldesc);
360355d6bb5Sswilcox 			if (statemap[inp->i_parent] != USTATE) {
361355d6bb5Sswilcox 				/* iscorrupt is already set */
362355d6bb5Sswilcox 				continue;
363355d6bb5Sswilcox 			}
364355d6bb5Sswilcox 		}
365355d6bb5Sswilcox 		TRACK_LNCNTP(inp->i_parent, lncntp[inp->i_parent]--);
366355d6bb5Sswilcox 
367355d6bb5Sswilcox 		inp->i_dotdot = inp->i_parent;
368355d6bb5Sswilcox 		(void) changeino(inp->i_number, "..", inp->i_parent);
369355d6bb5Sswilcox 	}
370355d6bb5Sswilcox 	/*
371355d6bb5Sswilcox 	 * Mark all the directories that can be found from the root.
372355d6bb5Sswilcox 	 */
373355d6bb5Sswilcox 	propagate();
374355d6bb5Sswilcox }
375355d6bb5Sswilcox 
376355d6bb5Sswilcox /*
377355d6bb5Sswilcox  * Sanity-check a single directory entry.  Which entry is being
378355d6bb5Sswilcox  * examined is tracked via idesc->id_entryno.  There are two
379355d6bb5Sswilcox  * special ones, 0 (.) and 1 (..).  Those have to exist in order
380355d6bb5Sswilcox  * in the first two locations in the directory, and have the usual
381355d6bb5Sswilcox  * properties.  All other entries have to not be for either of
382355d6bb5Sswilcox  * the special two, and the inode they reference has to be
383355d6bb5Sswilcox  * reasonable.
384355d6bb5Sswilcox  *
385355d6bb5Sswilcox  * This is only called from dirscan(), which looks for the
386355d6bb5Sswilcox  * ALTERED flag after each invocation.  If it finds it, the
387355d6bb5Sswilcox  * relevant buffer gets pushed out, so we don't have to worry
388355d6bb5Sswilcox  * about it here.
389355d6bb5Sswilcox  */
390355d6bb5Sswilcox #define	PASS2B_PROMPT	"REMOVE DIRECTORY ENTRY FROM I=%d"
391355d6bb5Sswilcox 
392355d6bb5Sswilcox static int
pass2check(struct inodesc * idesc)393b9a41fd3Sswilcox pass2check(struct inodesc *idesc)
394355d6bb5Sswilcox {
395355d6bb5Sswilcox 	struct direct *dirp = idesc->id_dirp;
396355d6bb5Sswilcox 	struct inodesc ldesc;
397355d6bb5Sswilcox 	struct inoinfo *inp;
398355d6bb5Sswilcox 	short reclen, entrysize;
399355d6bb5Sswilcox 	int ret = 0;
400355d6bb5Sswilcox 	int act, update_lncntp;
401355d6bb5Sswilcox 	struct dinode *dp, *pdirp, *attrdirp;
402355d6bb5Sswilcox 	caddr_t errmsg;
403355d6bb5Sswilcox 	struct direct proto;
404355d6bb5Sswilcox 	char namebuf[MAXPATHLEN + 1];
405355d6bb5Sswilcox 	char pathbuf[MAXPATHLEN + 1];
406355d6bb5Sswilcox 	int isattr;
407355d6bb5Sswilcox 	int pdirtype;
408355d6bb5Sswilcox 	int breakout = 0;
409355d6bb5Sswilcox 	int dontreconnect;
410355d6bb5Sswilcox 
411355d6bb5Sswilcox 	if (idesc->id_entryno != 0)
412355d6bb5Sswilcox 		goto chk1;
413355d6bb5Sswilcox 	/*
414355d6bb5Sswilcox 	 * check for "."
415355d6bb5Sswilcox 	 */
416355d6bb5Sswilcox 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
417355d6bb5Sswilcox 		if (dirp->d_ino != idesc->id_number) {
418355d6bb5Sswilcox 			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
419355d6bb5Sswilcox 			dirp->d_ino = idesc->id_number;
420355d6bb5Sswilcox 			if (reply("FIX") == 1) {
421355d6bb5Sswilcox 				ret |= ALTERED;
422355d6bb5Sswilcox 			} else {
423355d6bb5Sswilcox 				iscorrupt = 1;
424355d6bb5Sswilcox 			}
425355d6bb5Sswilcox 		}
426355d6bb5Sswilcox 		goto chk1;
427355d6bb5Sswilcox 	}
428355d6bb5Sswilcox 	/*
429355d6bb5Sswilcox 	 * Build up a new one, and make sure there's room to put
430355d6bb5Sswilcox 	 * it where it belongs.
431355d6bb5Sswilcox 	 */
432355d6bb5Sswilcox 	direrror(idesc->id_number, "MISSING '.'");
433355d6bb5Sswilcox 	proto.d_ino = idesc->id_number;
434355d6bb5Sswilcox 	proto.d_namlen = 1;
435355d6bb5Sswilcox 	(void) strcpy(proto.d_name, ".");
436355d6bb5Sswilcox 	entrysize = DIRSIZ(&proto);
437355d6bb5Sswilcox 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
438355d6bb5Sswilcox 		pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
439*ebc6491aSToomas Soome 		    dirp->d_name);
440355d6bb5Sswilcox 		iscorrupt = 1;
441355d6bb5Sswilcox 	} else if ((int)dirp->d_reclen < entrysize) {
442355d6bb5Sswilcox 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
443355d6bb5Sswilcox 		iscorrupt = 1;
444355d6bb5Sswilcox 	} else if ((int)dirp->d_reclen < 2 * entrysize) {
445355d6bb5Sswilcox 		/*
446355d6bb5Sswilcox 		 * No room for another entry after us ("." is the
447355d6bb5Sswilcox 		 * smallest entry you can have), so just put all
448355d6bb5Sswilcox 		 * of the old entry's space into the new entry.
449355d6bb5Sswilcox 		 *
450355d6bb5Sswilcox 		 * Because we don't touch id_entryno, we end up going
451355d6bb5Sswilcox 		 * through the chk2 tests as well.
452355d6bb5Sswilcox 		 */
453355d6bb5Sswilcox 		proto.d_reclen = dirp->d_reclen;
454355d6bb5Sswilcox 		(void) memmove((void *)dirp, (void *)&proto,
455355d6bb5Sswilcox 		    (size_t)entrysize);
456355d6bb5Sswilcox 		if (reply("FIX") == 1) {
457355d6bb5Sswilcox 			ret |= ALTERED;
458355d6bb5Sswilcox 		} else {
459355d6bb5Sswilcox 			iscorrupt = 1;
460355d6bb5Sswilcox 		}
461355d6bb5Sswilcox 	} else {
462355d6bb5Sswilcox 		/*
463355d6bb5Sswilcox 		 * There's enough room for an entire additional entry
464355d6bb5Sswilcox 		 * after this, so create the "." entry and follow it
465355d6bb5Sswilcox 		 * with an empty entry that covers the rest of the
466355d6bb5Sswilcox 		 * space.
467355d6bb5Sswilcox 		 *
468355d6bb5Sswilcox 		 * The increment of id_entryno means we'll skip the
469355d6bb5Sswilcox 		 * "." case of chk1, doing the ".." tests instead.
470355d6bb5Sswilcox 		 * Since we know that there's not a ".." where it
471355d6bb5Sswilcox 		 * should be (because we just created an empty entry
472355d6bb5Sswilcox 		 * there), that's the best way of getting it recreated
473355d6bb5Sswilcox 		 * as well.
474355d6bb5Sswilcox 		 */
475355d6bb5Sswilcox 		reclen = dirp->d_reclen - entrysize;
476355d6bb5Sswilcox 		proto.d_reclen = entrysize;
477355d6bb5Sswilcox 		(void) memmove((void *)dirp, (void *)&proto,
478355d6bb5Sswilcox 		    (size_t)entrysize);
479355d6bb5Sswilcox 		idesc->id_entryno++;
480355d6bb5Sswilcox 		/*
481355d6bb5Sswilcox 		 * Make sure the link count is in range before updating
482355d6bb5Sswilcox 		 * it.  This makes the assumption that the link count
483355d6bb5Sswilcox 		 * for this inode included one for ".", even though
484355d6bb5Sswilcox 		 * there wasn't a "." entry.  Even if that's not true,
485355d6bb5Sswilcox 		 * it's a reasonable working hypothesis, and the link
486355d6bb5Sswilcox 		 * count verification done in pass4 will fix it for
487355d6bb5Sswilcox 		 * us anyway.
488355d6bb5Sswilcox 		 */
489355d6bb5Sswilcox 		LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1);
490355d6bb5Sswilcox 		if (errmsg != NULL) {
491355d6bb5Sswilcox 			LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc);
492355d6bb5Sswilcox 			if (statemap[dirp->d_ino] == USTATE) {
493355d6bb5Sswilcox 				/*
494355d6bb5Sswilcox 				 * The inode got zapped, so reset the
495355d6bb5Sswilcox 				 * directory entry.  Extend it to also
496355d6bb5Sswilcox 				 * cover the space we were going to make
497355d6bb5Sswilcox 				 * into a new entry.
498355d6bb5Sswilcox 				 */
499355d6bb5Sswilcox 				dirp->d_ino = 0;
500355d6bb5Sswilcox 				dirp->d_reclen += reclen;
501355d6bb5Sswilcox 				ret |= ALTERED;
502355d6bb5Sswilcox 				return (ret);
503355d6bb5Sswilcox 			}
504355d6bb5Sswilcox 		}
505355d6bb5Sswilcox 
506355d6bb5Sswilcox 		/*
507355d6bb5Sswilcox 		 * Create the new empty entry.
508355d6bb5Sswilcox 		 */
509355d6bb5Sswilcox 		/* LINTED pointer cast alignment (entrysize is valid) */
510355d6bb5Sswilcox 		dirp = (struct direct *)((char *)(dirp) + entrysize);
511355d6bb5Sswilcox 		(void) memset((void *)dirp, 0, (size_t)reclen);
512355d6bb5Sswilcox 		dirp->d_reclen = reclen;
513355d6bb5Sswilcox 
514355d6bb5Sswilcox 		/*
515355d6bb5Sswilcox 		 * Did the user want us to create a new "."?  This
516355d6bb5Sswilcox 		 * query assumes that the direrror(MISSING) was the
517355d6bb5Sswilcox 		 * last thing printed, so if the LINK_RANGE() check
518355d6bb5Sswilcox 		 * fails, it can't pass through here.
519355d6bb5Sswilcox 		 */
520355d6bb5Sswilcox 		if (reply("FIX") == 1) {
521355d6bb5Sswilcox 			TRACK_LNCNTP(idesc->id_number,
522355d6bb5Sswilcox 			    lncntp[idesc->id_number]--);
523355d6bb5Sswilcox 			ret |= ALTERED;
524355d6bb5Sswilcox 		} else {
525355d6bb5Sswilcox 			iscorrupt = 1;
526355d6bb5Sswilcox 		}
527355d6bb5Sswilcox 	}
528355d6bb5Sswilcox 
529355d6bb5Sswilcox 	/*
530355d6bb5Sswilcox 	 * XXX The next few lines are needed whether we're processing "."
531355d6bb5Sswilcox 	 * or "..".  However, there are some extra steps still needed
532355d6bb5Sswilcox 	 * for the former, hence the big block of code for
533355d6bb5Sswilcox 	 * id_entryno == 0.  Alternatively, there could be a label just
534355d6bb5Sswilcox 	 * before this comment, and everything through the end of that
535355d6bb5Sswilcox 	 * block moved there.  In some ways, that might make the
536355d6bb5Sswilcox 	 * control flow more logical (factoring out to separate functions
537355d6bb5Sswilcox 	 * would be even better).
538355d6bb5Sswilcox 	 */
539355d6bb5Sswilcox 
540355d6bb5Sswilcox chk1:
541355d6bb5Sswilcox 	if (idesc->id_entryno > 1)
542355d6bb5Sswilcox 		goto chk2;
543355d6bb5Sswilcox 	inp = getinoinfo(idesc->id_number);
544355d6bb5Sswilcox 	if (inp == NULL) {
545355d6bb5Sswilcox 		/*
546355d6bb5Sswilcox 		 * This is a can't-happen, since inodes get cached before
547355d6bb5Sswilcox 		 * we get called on them.
548355d6bb5Sswilcox 		 */
549b9a41fd3Sswilcox 		errexit("pass2check got NULL from getinoinfo at chk1 I=%d\n",
550*ebc6491aSToomas Soome 		    idesc->id_number);
551355d6bb5Sswilcox 	}
552355d6bb5Sswilcox 	proto.d_ino = inp->i_parent;
553355d6bb5Sswilcox 	proto.d_namlen = 2;
554355d6bb5Sswilcox 	(void) strcpy(proto.d_name, "..");
555355d6bb5Sswilcox 	entrysize = DIRSIZ(&proto);
556355d6bb5Sswilcox 	if (idesc->id_entryno == 0) {
557355d6bb5Sswilcox 		/*
558355d6bb5Sswilcox 		 * We may not actually need to split things up, but if
559355d6bb5Sswilcox 		 * there's room to do so, we should, as that implies
560355d6bb5Sswilcox 		 * that the "." entry is larger than it is supposed
561355d6bb5Sswilcox 		 * to be, and therefore there's something wrong, albeit
562355d6bb5Sswilcox 		 * possibly harmlessly so.
563355d6bb5Sswilcox 		 */
564355d6bb5Sswilcox 		reclen = DIRSIZ(dirp);
565355d6bb5Sswilcox 		if ((int)dirp->d_reclen < reclen + entrysize) {
566355d6bb5Sswilcox 			/*
567355d6bb5Sswilcox 			 * Not enough room for inserting a ".." after
568355d6bb5Sswilcox 			 * the "." entry.
569355d6bb5Sswilcox 			 */
570355d6bb5Sswilcox 			goto chk2;
571355d6bb5Sswilcox 		}
572355d6bb5Sswilcox 		/*
573355d6bb5Sswilcox 		 * There's enough room for an entire additional entry
574355d6bb5Sswilcox 		 * after "."'s, so split it up.  There's no reason "."
575355d6bb5Sswilcox 		 * should be bigger than the minimum, so shrink it to
576355d6bb5Sswilcox 		 * fit, too.  Since by the time we're done with this
577355d6bb5Sswilcox 		 * part, dirp will be pointing at where ".." should be,
578355d6bb5Sswilcox 		 * update id_entryno to show that that's the entry
579355d6bb5Sswilcox 		 * we're on.
580355d6bb5Sswilcox 		 */
581355d6bb5Sswilcox 		proto.d_reclen = dirp->d_reclen - reclen;
582355d6bb5Sswilcox 		dirp->d_reclen = reclen;
583355d6bb5Sswilcox 		idesc->id_entryno++;
584355d6bb5Sswilcox 		if (dirp->d_ino > 0 && dirp->d_ino <= maxino) {
585355d6bb5Sswilcox 			/*
586355d6bb5Sswilcox 			 * Account for the link to ourselves.
587355d6bb5Sswilcox 			 */
588355d6bb5Sswilcox 			LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1);
589355d6bb5Sswilcox 			if (errmsg != NULL) {
590355d6bb5Sswilcox 				LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc);
591355d6bb5Sswilcox 				if (statemap[dirp->d_ino] == USTATE) {
592355d6bb5Sswilcox 					/*
593355d6bb5Sswilcox 					 * We were going to split the entry
594355d6bb5Sswilcox 					 * up, but the link count overflowed.
595355d6bb5Sswilcox 					 * Since we got rid of the inode,
596355d6bb5Sswilcox 					 * we need to also zap the directory
597355d6bb5Sswilcox 					 * entry, and restoring the original
598355d6bb5Sswilcox 					 * state of things is the least-bad
599355d6bb5Sswilcox 					 * result.
600355d6bb5Sswilcox 					 */
601355d6bb5Sswilcox 					dirp->d_ino = 0;
602355d6bb5Sswilcox 					dirp->d_reclen += proto.d_reclen;
603355d6bb5Sswilcox 					ret |= ALTERED;
604355d6bb5Sswilcox 					return (ret);
605355d6bb5Sswilcox 				}
606355d6bb5Sswilcox 			}
607355d6bb5Sswilcox 			TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino]--);
608355d6bb5Sswilcox 			/*
609355d6bb5Sswilcox 			 * Make sure the new entry doesn't get interpreted
610355d6bb5Sswilcox 			 * as having actual content.
611355d6bb5Sswilcox 			 */
612355d6bb5Sswilcox 			/* LINTED pointer cast alignment (reclen is valid) */
613355d6bb5Sswilcox 			dirp = (struct direct *)((char *)(dirp) + reclen);
614355d6bb5Sswilcox 			(void) memset((void *)dirp, 0, (size_t)proto.d_reclen);
615355d6bb5Sswilcox 			dirp->d_reclen = proto.d_reclen;
616355d6bb5Sswilcox 		} else {
617355d6bb5Sswilcox 			/*
618355d6bb5Sswilcox 			 * Everything was fine, up until we realized that
619355d6bb5Sswilcox 			 * the indicated inode was impossible.  By clearing
620355d6bb5Sswilcox 			 * d_ino here, we'll trigger the recreation of it
621355d6bb5Sswilcox 			 * down below, using i_parent.  Unlike the other
622355d6bb5Sswilcox 			 * half of this if(), we're everything so it shows
623355d6bb5Sswilcox 			 * that we're still on the "." entry.
624355d6bb5Sswilcox 			 */
625355d6bb5Sswilcox 			fileerror(idesc->id_number, dirp->d_ino,
626*ebc6491aSToomas Soome 			    "I OUT OF RANGE");
627355d6bb5Sswilcox 			dirp->d_ino = 0;
628355d6bb5Sswilcox 			if (reply("FIX") == 1) {
629355d6bb5Sswilcox 				ret |= ALTERED;
630355d6bb5Sswilcox 			} else {
631355d6bb5Sswilcox 				iscorrupt = 1;
632355d6bb5Sswilcox 			}
633355d6bb5Sswilcox 		}
634355d6bb5Sswilcox 	}
635355d6bb5Sswilcox 	/*
636355d6bb5Sswilcox 	 * Record this ".." inode, but only if we haven't seen one before.
637355d6bb5Sswilcox 	 * If this isn't the first, it'll get cleared below, and so we
638355d6bb5Sswilcox 	 * want to remember the entry that'll still be around later.
639355d6bb5Sswilcox 	 */
640355d6bb5Sswilcox 	if (dirp->d_ino != 0 && inp->i_dotdot == 0 &&
641355d6bb5Sswilcox 	    strcmp(dirp->d_name, "..") == 0) {
642355d6bb5Sswilcox 		inp->i_dotdot = dirp->d_ino;
643355d6bb5Sswilcox 		goto chk2;
644355d6bb5Sswilcox 	}
645355d6bb5Sswilcox 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
646355d6bb5Sswilcox 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
647355d6bb5Sswilcox 		pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
648*ebc6491aSToomas Soome 		    dirp->d_name);
649355d6bb5Sswilcox 		iscorrupt = 1;
650355d6bb5Sswilcox 		inp->i_dotdot = (fsck_ino_t)-1;
651355d6bb5Sswilcox 	} else if ((int)dirp->d_reclen < entrysize) {
652355d6bb5Sswilcox 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
653355d6bb5Sswilcox 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
654355d6bb5Sswilcox 		/* XXX Same consideration as immediately above. */
655355d6bb5Sswilcox 		iscorrupt = 1;
656355d6bb5Sswilcox 		inp->i_dotdot = (fsck_ino_t)-1;
657355d6bb5Sswilcox 	} else if (inp->i_parent != 0) {
658355d6bb5Sswilcox 		/*
659355d6bb5Sswilcox 		 * We know the parent, so fix now.
660355d6bb5Sswilcox 		 */
661355d6bb5Sswilcox 		proto.d_ino = inp->i_dotdot = inp->i_parent;
662355d6bb5Sswilcox 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
663355d6bb5Sswilcox 		/*
664355d6bb5Sswilcox 		 * Lint won't be quiet about d_reclen being set but not
665355d6bb5Sswilcox 		 * used.  It apparently doesn't understand the implications
666355d6bb5Sswilcox 		 * of calling memmove(), and won't believe us that it's ok.
667355d6bb5Sswilcox 		 */
668355d6bb5Sswilcox 		proto.d_reclen = dirp->d_reclen;
669355d6bb5Sswilcox 		(void) memmove((void *)dirp, (void *)&proto,
670355d6bb5Sswilcox 		    (size_t)entrysize);
671355d6bb5Sswilcox 		if (reply("FIX") == 1) {
672355d6bb5Sswilcox 			ret |= ALTERED;
673355d6bb5Sswilcox 		} else {
674355d6bb5Sswilcox 			iscorrupt = 1;
675355d6bb5Sswilcox 		}
676355d6bb5Sswilcox 	} else if (inp->i_number == UFSROOTINO) {
677355d6bb5Sswilcox 		/*
678355d6bb5Sswilcox 		 * Always know parent of root inode, so fix now.
679355d6bb5Sswilcox 		 */
680355d6bb5Sswilcox 		proto.d_ino = inp->i_dotdot = inp->i_parent = UFSROOTINO;
681355d6bb5Sswilcox 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
682355d6bb5Sswilcox 		/*
683355d6bb5Sswilcox 		 * Lint won't be quiet about d_reclen being set but not
684355d6bb5Sswilcox 		 * used.  It apparently doesn't understand the implications
685355d6bb5Sswilcox 		 * of calling memmove(), and won't believe us that it's ok.
686355d6bb5Sswilcox 		 */
687355d6bb5Sswilcox 		proto.d_reclen = dirp->d_reclen;
688355d6bb5Sswilcox 		(void) memmove((void *)dirp, (void *)&proto, (size_t)entrysize);
689355d6bb5Sswilcox 		if (reply("FIX") == 1) {
690355d6bb5Sswilcox 			ret |= ALTERED;
691355d6bb5Sswilcox 		} else {
692355d6bb5Sswilcox 			iscorrupt = 1;
693355d6bb5Sswilcox 		}
694355d6bb5Sswilcox 	}
695355d6bb5Sswilcox 	idesc->id_entryno++;
696355d6bb5Sswilcox 	if (dirp->d_ino != 0) {
697355d6bb5Sswilcox 		LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1);
698355d6bb5Sswilcox 		if (errmsg != NULL) {
699355d6bb5Sswilcox 			LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc);
700355d6bb5Sswilcox 			if (statemap[dirp->d_ino] == USTATE) {
701355d6bb5Sswilcox 				dirp->d_ino = 0;
702355d6bb5Sswilcox 				ret |= ALTERED;
703355d6bb5Sswilcox 			}
704355d6bb5Sswilcox 		}
705355d6bb5Sswilcox 		TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino]--);
706355d6bb5Sswilcox 	}
707355d6bb5Sswilcox 	return (ret|KEEPON);
708355d6bb5Sswilcox chk2:
709355d6bb5Sswilcox 	if (dirp->d_ino == 0)
710355d6bb5Sswilcox 		return (ret|KEEPON);
711355d6bb5Sswilcox 	if (dirp->d_namlen <= 2 &&
712355d6bb5Sswilcox 	    dirp->d_name[0] == '.' &&
713355d6bb5Sswilcox 	    idesc->id_entryno >= 2) {
714355d6bb5Sswilcox 		if (dirp->d_namlen == 1) {
715355d6bb5Sswilcox 			direrror(idesc->id_number, "EXTRA '.' ENTRY");
716355d6bb5Sswilcox 			dirp->d_ino = 0;
717355d6bb5Sswilcox 			if (reply("FIX") == 1) {
718355d6bb5Sswilcox 				ret |= ALTERED;
719355d6bb5Sswilcox 			} else {
720355d6bb5Sswilcox 				iscorrupt = 1;
721355d6bb5Sswilcox 			}
722355d6bb5Sswilcox 			return (KEEPON | ret);
723355d6bb5Sswilcox 		}
724355d6bb5Sswilcox 		if (dirp->d_name[1] == '.') {
725355d6bb5Sswilcox 			direrror(idesc->id_number, "EXTRA '..' ENTRY");
726355d6bb5Sswilcox 			dirp->d_ino = 0;
727355d6bb5Sswilcox 			if (reply("FIX") == 1) {
728355d6bb5Sswilcox 				ret |= ALTERED;
729355d6bb5Sswilcox 			} else {
730355d6bb5Sswilcox 				iscorrupt = 1;
731355d6bb5Sswilcox 			}
732355d6bb5Sswilcox 			return (KEEPON | ret);
733355d6bb5Sswilcox 		}
734355d6bb5Sswilcox 	}
735355d6bb5Sswilcox 	/*
736355d6bb5Sswilcox 	 * Because of this increment, all tests for skipping . and ..
737355d6bb5Sswilcox 	 * below are ``> 2'', not ``> 1'' as would logically be expected.
738355d6bb5Sswilcox 	 */
739355d6bb5Sswilcox 	idesc->id_entryno++;
740355d6bb5Sswilcox 	act = -1;
741355d6bb5Sswilcox 	/*
742355d6bb5Sswilcox 	 * The obvious check would be for d_ino < UFSROOTINO.  However,
743355d6bb5Sswilcox 	 * 1 is a valid inode number.  Although it isn't currently used,
744355d6bb5Sswilcox 	 * as it was once the bad block list, there's nothing to prevent
745355d6bb5Sswilcox 	 * it from acquiring a new purpose in the future.  So, don't
746355d6bb5Sswilcox 	 * arbitrarily disallow it.  We don't test for <= zero, because
747355d6bb5Sswilcox 	 * d_ino is unsigned.
748355d6bb5Sswilcox 	 */
749355d6bb5Sswilcox 	update_lncntp = 0;
750355d6bb5Sswilcox 	if (dirp->d_ino > maxino || dirp->d_ino == 0) {
751355d6bb5Sswilcox 		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
752355d6bb5Sswilcox 		act = (reply(PASS2B_PROMPT, idesc->id_number) == 1);
753355d6bb5Sswilcox 	} else {
754355d6bb5Sswilcox again:
755355d6bb5Sswilcox 		update_lncntp = 0;
756355d6bb5Sswilcox 		switch (statemap[dirp->d_ino] & ~(INDELAYD)) {
757355d6bb5Sswilcox 		case USTATE:
758355d6bb5Sswilcox 			if (idesc->id_entryno <= 2)
759355d6bb5Sswilcox 				break;
760355d6bb5Sswilcox 			fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
761355d6bb5Sswilcox 			act = (reply(PASS2B_PROMPT, idesc->id_number) == 1);
762355d6bb5Sswilcox 			break;
763355d6bb5Sswilcox 
764355d6bb5Sswilcox 		case DCLEAR:
765355d6bb5Sswilcox 		case FCLEAR:
766355d6bb5Sswilcox 		case SCLEAR:
767355d6bb5Sswilcox 			if (idesc->id_entryno <= 2)
768355d6bb5Sswilcox 				break;
769355d6bb5Sswilcox 			dp = ginode(dirp->d_ino);
770355d6bb5Sswilcox 			if (statemap[dirp->d_ino] == DCLEAR) {
771355d6bb5Sswilcox 				errmsg = ((dp->di_mode & IFMT) == IFATTRDIR) ?
772355d6bb5Sswilcox 			    "REFERENCE TO ZERO LENGTH ATTRIBUTE DIRECTORY" :
773355d6bb5Sswilcox 			    "REFERENCE TO ZERO LENGTH DIRECTORY";
774355d6bb5Sswilcox 				inp = getinoinfo(dirp->d_ino);
775355d6bb5Sswilcox 				if (inp == NULL) {
776355d6bb5Sswilcox 					/*
777355d6bb5Sswilcox 					 * The inode doesn't exist, as all
778355d6bb5Sswilcox 					 * should be cached by now.  This
779355d6bb5Sswilcox 					 * gets caught by the range check
780355d6bb5Sswilcox 					 * above, and so it is a can't-happen
781355d6bb5Sswilcox 					 * at this point.
782355d6bb5Sswilcox 					 */
783b9a41fd3Sswilcox 					errexit("pass2check found a zero-len "
784*ebc6491aSToomas Soome 					    "reference to bad I=%d\n",
785*ebc6491aSToomas Soome 					    dirp->d_ino);
786355d6bb5Sswilcox 				}
787355d6bb5Sswilcox 				if (inp->i_parent != 0) {
788355d6bb5Sswilcox 					(void) printf(
789355d6bb5Sswilcox 		    "Multiple links to I=%d, link counts wrong, rerun fsck\n",
790355d6bb5Sswilcox 					    inp->i_number);
791355d6bb5Sswilcox 					iscorrupt = 1;
792355d6bb5Sswilcox 				}
793355d6bb5Sswilcox 			} else if (statemap[dirp->d_ino] == SCLEAR) {
794355d6bb5Sswilcox 				/*
795355d6bb5Sswilcox 				 * In theory, this is a can't-happen,
796355d6bb5Sswilcox 				 * because shadows don't appear in directory
797355d6bb5Sswilcox 				 * entries.  However, an inode might've
798355d6bb5Sswilcox 				 * been reused without a stale directory
799355d6bb5Sswilcox 				 * entry having been cleared, so check
800355d6bb5Sswilcox 				 * for it just in case.  We'll check for
801355d6bb5Sswilcox 				 * the no-dir-entry shadows in pass3b().
802355d6bb5Sswilcox 				 */
803355d6bb5Sswilcox 				errmsg = "ZERO LENGTH SHADOW";
804355d6bb5Sswilcox 			} else {
805355d6bb5Sswilcox 				errmsg = "DUP/BAD";
806355d6bb5Sswilcox 			}
807355d6bb5Sswilcox 			fileerror(idesc->id_number, dirp->d_ino, errmsg);
808355d6bb5Sswilcox 			if ((act = reply(PASS2B_PROMPT, idesc->id_number)) == 1)
809355d6bb5Sswilcox 				break;
810355d6bb5Sswilcox 			/*
811355d6bb5Sswilcox 			 * Not doing anything about it, so just try
812355d6bb5Sswilcox 			 * again as whatever the base type was.
813355d6bb5Sswilcox 			 *
814355d6bb5Sswilcox 			 * fileerror() invalidated dp.  Lint thinks this
815355d6bb5Sswilcox 			 * is unnecessary, but we know better.
816355d6bb5Sswilcox 			 */
817355d6bb5Sswilcox 			dp = ginode(dirp->d_ino);
818355d6bb5Sswilcox 			statemap[dirp->d_ino] &= STMASK;
819355d6bb5Sswilcox 			TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino] = 0);
820355d6bb5Sswilcox 			goto again;
821355d6bb5Sswilcox 
822355d6bb5Sswilcox 		case DSTATE:
823355d6bb5Sswilcox 		case DZLINK:
824355d6bb5Sswilcox 			if (statemap[idesc->id_number] == DFOUND) {
825355d6bb5Sswilcox 				statemap[dirp->d_ino] = DFOUND;
826355d6bb5Sswilcox 			}
827355d6bb5Sswilcox 			/* FALLTHROUGH */
828355d6bb5Sswilcox 
829355d6bb5Sswilcox 		case DFOUND:
830355d6bb5Sswilcox 			/*
831355d6bb5Sswilcox 			 * This is encouraging the best-practice of not
832355d6bb5Sswilcox 			 * hard-linking directories.  It's legal (see POSIX),
833355d6bb5Sswilcox 			 * but not a good idea.  So, don't consider it an
834355d6bb5Sswilcox 			 * instance of corruption, but offer to nuke it.
835355d6bb5Sswilcox 			 */
836355d6bb5Sswilcox 			inp = getinoinfo(dirp->d_ino);
837355d6bb5Sswilcox 			if (inp == NULL) {
838355d6bb5Sswilcox 				/*
839355d6bb5Sswilcox 				 * Same can't-happen argument as in the
840355d6bb5Sswilcox 				 * zero-len case above.
841355d6bb5Sswilcox 				 */
842b9a41fd3Sswilcox 				errexit("pass2check found bad reference to "
843*ebc6491aSToomas Soome 				    "hard-linked directory I=%d\n",
844*ebc6491aSToomas Soome 				    dirp->d_ino);
845355d6bb5Sswilcox 			}
846355d6bb5Sswilcox 			dp = ginode(idesc->id_number);
847355d6bb5Sswilcox 			if (inp->i_parent != 0 && idesc->id_entryno > 2 &&
848355d6bb5Sswilcox 			    ((dp->di_mode & IFMT) != IFATTRDIR)) {
849355d6bb5Sswilcox 				/*
850355d6bb5Sswilcox 				 * XXX For nested dirs, this can report
851355d6bb5Sswilcox 				 * the same name for both paths.
852355d6bb5Sswilcox 				 */
853355d6bb5Sswilcox 				getpathname(pathbuf, idesc->id_number,
854355d6bb5Sswilcox 				    dirp->d_ino);
855355d6bb5Sswilcox 				getpathname(namebuf, dirp->d_ino, dirp->d_ino);
856355d6bb5Sswilcox 				pwarn(
857355d6bb5Sswilcox 		    "%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s\n",
858355d6bb5Sswilcox 				    pathbuf, namebuf);
859*ebc6491aSToomas Soome 				if (preen) {
860355d6bb5Sswilcox 					(void) printf(" (IGNORED)\n");
861*ebc6491aSToomas Soome 				} else {
862*ebc6491aSToomas Soome 					act = reply(PASS2B_PROMPT,
863*ebc6491aSToomas Soome 					    idesc->id_number);
864*ebc6491aSToomas Soome 					if (act == 1) {
865*ebc6491aSToomas Soome 						update_lncntp = 1;
866*ebc6491aSToomas Soome 						broke_dir_link = 1;
867*ebc6491aSToomas Soome 						break;
868*ebc6491aSToomas Soome 					}
869355d6bb5Sswilcox 				}
870355d6bb5Sswilcox 			}
871355d6bb5Sswilcox 
872355d6bb5Sswilcox 			if ((idesc->id_entryno > 2) &&
873*ebc6491aSToomas Soome 			    (inp->i_extattr != idesc->id_number)) {
874355d6bb5Sswilcox 				inp->i_parent = idesc->id_number;
875355d6bb5Sswilcox 			}
876355d6bb5Sswilcox 			/* FALLTHROUGH */
877355d6bb5Sswilcox 
878355d6bb5Sswilcox 		case FSTATE:
879355d6bb5Sswilcox 		case FZLINK:
880355d6bb5Sswilcox 			/*
881355d6bb5Sswilcox 			 * There's nothing to do for normal file-like
882355d6bb5Sswilcox 			 * things.  Extended attributes come through
883355d6bb5Sswilcox 			 * here as well, though, and for them, .. may point
884355d6bb5Sswilcox 			 * to a file.  In this situation we don't want
885355d6bb5Sswilcox 			 * to decrement link count as it was already
886355d6bb5Sswilcox 			 * decremented when the entry was seen in the
887355d6bb5Sswilcox 			 * directory it actually lives in.
888355d6bb5Sswilcox 			 */
889355d6bb5Sswilcox 			pdirp = ginode(idesc->id_number);
890355d6bb5Sswilcox 			pdirtype = (pdirp->di_mode & IFMT);
891355d6bb5Sswilcox 			dp = ginode(dirp->d_ino);
892355d6bb5Sswilcox 			isattr = (dp->di_cflags & IXATTR);
893355d6bb5Sswilcox 			act = -1;
894355d6bb5Sswilcox 			if (pdirtype == IFATTRDIR &&
895355d6bb5Sswilcox 			    (strcmp(dirp->d_name, "..") == 0)) {
896355d6bb5Sswilcox 				dontreconnect = 0;
897355d6bb5Sswilcox 				if (dp->di_oeftflag != 0) {
898355d6bb5Sswilcox 					attrdirp = ginode(dp->di_oeftflag);
899355d6bb5Sswilcox 
900355d6bb5Sswilcox 					/*
901355d6bb5Sswilcox 					 * is it really an attrdir?
902355d6bb5Sswilcox 					 * if so, then don't do anything.
903355d6bb5Sswilcox 					 */
904355d6bb5Sswilcox 
905355d6bb5Sswilcox 					if ((attrdirp->di_mode & IFMT) ==
906355d6bb5Sswilcox 					    IFATTRDIR)
907355d6bb5Sswilcox 						dontreconnect = 1;
908355d6bb5Sswilcox 					dp = ginode(dirp->d_ino);
909355d6bb5Sswilcox 				}
910355d6bb5Sswilcox 				/*
911355d6bb5Sswilcox 				 * Rare corner case - the attrdir's ..
912355d6bb5Sswilcox 				 * points to the attrdir itself.
913355d6bb5Sswilcox 				 */
914355d6bb5Sswilcox 				if (dirp->d_ino == idesc->id_number) {
915355d6bb5Sswilcox 					dontreconnect = 1;
916355d6bb5Sswilcox 					TRACK_LNCNTP(idesc->id_number,
917355d6bb5Sswilcox 					    lncntp[idesc->id_number]--);
918355d6bb5Sswilcox 				}
919355d6bb5Sswilcox 				/*
920355d6bb5Sswilcox 				 * Lets see if we have an orphaned attrdir
921355d6bb5Sswilcox 				 * that thinks it belongs to this file.
922355d6bb5Sswilcox 				 * Only re-connect it if the current
923355d6bb5Sswilcox 				 * attrdir is 0 or not an attrdir.
924355d6bb5Sswilcox 				 */
925355d6bb5Sswilcox 				if ((dp->di_oeftflag != idesc->id_number) &&
926355d6bb5Sswilcox 				    (dontreconnect == 0)) {
927355d6bb5Sswilcox 					fileerror(idesc->id_number,
928355d6bb5Sswilcox 					    dirp->d_ino,
929355d6bb5Sswilcox 					    "Attribute directory I=%d not "
930355d6bb5Sswilcox 					    "attached to file I=%d\n",
931355d6bb5Sswilcox 					    idesc->id_number, dirp->d_ino);
932355d6bb5Sswilcox 					if ((act = reply("FIX")) == 1) {
933355d6bb5Sswilcox 						dp = ginode(dirp->d_ino);
934355d6bb5Sswilcox 						if (debug)
935355d6bb5Sswilcox 							(void) printf(
936355d6bb5Sswilcox 				    "debug: changing i=%d's oeft from %d ",
937355d6bb5Sswilcox 							    dirp->d_ino,
938355d6bb5Sswilcox 							    dp->di_oeftflag);
939355d6bb5Sswilcox 						dp->di_oeftflag =
940355d6bb5Sswilcox 						    idesc->id_number;
941355d6bb5Sswilcox 						if (debug)
942355d6bb5Sswilcox 							(void) printf("to %d\n",
943355d6bb5Sswilcox 							    dp->di_oeftflag);
944355d6bb5Sswilcox 						inodirty();
945355d6bb5Sswilcox 						registershadowclient(
946355d6bb5Sswilcox 						    idesc->id_number,
947355d6bb5Sswilcox 						    dirp->d_ino,
948355d6bb5Sswilcox 						    &attrclientinfo);
949355d6bb5Sswilcox 					}
950355d6bb5Sswilcox 					dp = ginode(dirp->d_ino);
951355d6bb5Sswilcox 				}
952355d6bb5Sswilcox 
953355d6bb5Sswilcox 				/*
954355d6bb5Sswilcox 				 * This can only be true if we've modified
955355d6bb5Sswilcox 				 * an inode/xattr connection, and we
956355d6bb5Sswilcox 				 * don't keep track of those in the link
957355d6bb5Sswilcox 				 * counts.  So, skipping the checks just
958355d6bb5Sswilcox 				 * after this is not a problem.
959355d6bb5Sswilcox 				 */
960355d6bb5Sswilcox 				if (act > 0)
961355d6bb5Sswilcox 					return (KEEPON | ALTERED);
962355d6bb5Sswilcox 
963355d6bb5Sswilcox 				/*
964355d6bb5Sswilcox 				 * Don't screw up link counts for directories.
965355d6bb5Sswilcox 				 * If we aren't careful we can perform
966355d6bb5Sswilcox 				 * an extra decrement, since the .. of
967355d6bb5Sswilcox 				 * an attrdir could be either a file or a
968355d6bb5Sswilcox 				 * directory.  If it's a file then its link
969355d6bb5Sswilcox 				 * should be correct after it is seen when the
970355d6bb5Sswilcox 				 * directory it lives in scanned.
971355d6bb5Sswilcox 				 */
972355d6bb5Sswilcox 				if ((pdirtype == IFATTRDIR) &&
973355d6bb5Sswilcox 				    ((dp->di_mode & IFMT) == IFDIR))
974355d6bb5Sswilcox 						breakout = 1;
975355d6bb5Sswilcox 				if ((dp->di_mode & IFMT) != IFDIR)
976355d6bb5Sswilcox 					breakout = 1;
977355d6bb5Sswilcox 
978355d6bb5Sswilcox 			} else if ((pdirtype != IFATTRDIR) ||
979355d6bb5Sswilcox 			    (strcmp(dirp->d_name, ".") != 0)) {
980355d6bb5Sswilcox 				if ((pdirtype == IFDIR) && isattr) {
981355d6bb5Sswilcox 					fileerror(idesc->id_number,
982355d6bb5Sswilcox 					    dirp->d_ino,
983355d6bb5Sswilcox 					    "File should NOT be marked as "
984355d6bb5Sswilcox 					    "extended attribute\n");
985355d6bb5Sswilcox 					if ((act = reply("FIX")) == 1) {
986355d6bb5Sswilcox 						dp = ginode(dirp->d_ino);
987355d6bb5Sswilcox 						if (debug)
988355d6bb5Sswilcox 							(void) printf(
989355d6bb5Sswilcox 				    "changing i=%d's cflags from 0x%x to ",
990355d6bb5Sswilcox 							    dirp->d_ino,
991355d6bb5Sswilcox 							    dp->di_cflags);
992355d6bb5Sswilcox 
993355d6bb5Sswilcox 						dp->di_cflags &= ~IXATTR;
994355d6bb5Sswilcox 						if (debug)
995355d6bb5Sswilcox 							(void) printf("0x%x\n",
996355d6bb5Sswilcox 							    dp->di_cflags);
997355d6bb5Sswilcox 						inodirty();
998355d6bb5Sswilcox 						if ((dp->di_mode & IFMT) ==
999355d6bb5Sswilcox 						    IFATTRDIR) {
1000355d6bb5Sswilcox 							dp->di_mode &=
1001355d6bb5Sswilcox 							    ~IFATTRDIR;
1002355d6bb5Sswilcox 							dp->di_mode |= IFDIR;
1003355d6bb5Sswilcox 							inodirty();
1004355d6bb5Sswilcox 							pdirp = ginode(
1005355d6bb5Sswilcox 							    idesc->id_number);
1006355d6bb5Sswilcox 							if (pdirp->di_oeftflag
1007*ebc6491aSToomas Soome 							    != 0) {
1008355d6bb5Sswilcox 							pdirp->di_oeftflag = 0;
1009355d6bb5Sswilcox 								inodirty();
1010355d6bb5Sswilcox 							}
1011355d6bb5Sswilcox 						}
1012355d6bb5Sswilcox 					}
1013355d6bb5Sswilcox 				} else {
1014355d6bb5Sswilcox 					if (pdirtype == IFATTRDIR &&
1015355d6bb5Sswilcox 					    (isattr == 0)) {
1016355d6bb5Sswilcox 						fileerror(idesc->id_number,
1017355d6bb5Sswilcox 						    dirp->d_ino,
1018355d6bb5Sswilcox 						    "File should BE marked as "
1019355d6bb5Sswilcox 						    "extended attribute\n");
1020355d6bb5Sswilcox 						if ((act = reply("FIX")) == 1) {
1021355d6bb5Sswilcox 							dp = ginode(
1022355d6bb5Sswilcox 							    dirp->d_ino);
1023355d6bb5Sswilcox 							dp->di_cflags |= IXATTR;
1024355d6bb5Sswilcox 							/*
1025355d6bb5Sswilcox 							 * Make sure it's a file
1026355d6bb5Sswilcox 							 * while we're at it.
1027355d6bb5Sswilcox 							 */
1028355d6bb5Sswilcox 							dp->di_mode &= ~IFMT;
1029355d6bb5Sswilcox 							dp->di_mode |= IFREG;
1030355d6bb5Sswilcox 							inodirty();
1031355d6bb5Sswilcox 						}
1032355d6bb5Sswilcox 					}
1033355d6bb5Sswilcox 				}
1034355d6bb5Sswilcox 
1035355d6bb5Sswilcox 			}
1036355d6bb5Sswilcox 			if (breakout == 0 || dontreconnect == 0) {
1037355d6bb5Sswilcox 				TRACK_LNCNTP(dirp->d_ino,
1038355d6bb5Sswilcox 				    lncntp[dirp->d_ino]--);
1039355d6bb5Sswilcox 				if (act > 0)
1040355d6bb5Sswilcox 					return (KEEPON | ALTERED);
1041355d6bb5Sswilcox 			}
1042355d6bb5Sswilcox 			break;
1043355d6bb5Sswilcox 
1044355d6bb5Sswilcox 		case SSTATE:
1045355d6bb5Sswilcox 			errmsg = "ACL IN DIRECTORY";
1046355d6bb5Sswilcox 			fileerror(idesc->id_number, dirp->d_ino, errmsg);
1047355d6bb5Sswilcox 			act = (reply(PASS2B_PROMPT, idesc->id_number) == 1);
1048355d6bb5Sswilcox 			break;
1049355d6bb5Sswilcox 
1050355d6bb5Sswilcox 		default:
1051355d6bb5Sswilcox 			errexit("BAD STATE 0x%x FOR INODE I=%d",
1052355d6bb5Sswilcox 			    statemap[dirp->d_ino], dirp->d_ino);
1053355d6bb5Sswilcox 		}
1054355d6bb5Sswilcox 	}
1055355d6bb5Sswilcox 
1056355d6bb5Sswilcox 	if (act == 0) {
1057355d6bb5Sswilcox 		iscorrupt = 1;
1058355d6bb5Sswilcox 	}
1059355d6bb5Sswilcox 
1060355d6bb5Sswilcox 	if (act <= 0)
1061355d6bb5Sswilcox 		return (ret|KEEPON);
1062355d6bb5Sswilcox 
1063355d6bb5Sswilcox 	if (update_lncntp) {
1064355d6bb5Sswilcox 		LINK_RANGE(errmsg, lncntp[idesc->id_number], 1);
1065355d6bb5Sswilcox 		if (errmsg != NULL) {
1066355d6bb5Sswilcox 			LINK_CLEAR(errmsg, idesc->id_number, IFDIR, &ldesc);
1067355d6bb5Sswilcox 			if (statemap[idesc->id_number] == USTATE) {
1068355d6bb5Sswilcox 				idesc->id_number = 0;
1069355d6bb5Sswilcox 				ret |= ALTERED;
1070355d6bb5Sswilcox 			}
1071355d6bb5Sswilcox 		}
1072355d6bb5Sswilcox 		TRACK_LNCNTP(idesc->id_number, lncntp[idesc->id_number]++);
1073355d6bb5Sswilcox 	}
1074355d6bb5Sswilcox 
1075355d6bb5Sswilcox 	dirp->d_ino = 0;
1076355d6bb5Sswilcox 
1077355d6bb5Sswilcox 	return (ret|KEEPON|ALTERED);
1078355d6bb5Sswilcox }
1079355d6bb5Sswilcox 
1080355d6bb5Sswilcox #undef	PASS2B_PROMPT
1081355d6bb5Sswilcox 
1082355d6bb5Sswilcox /*
1083355d6bb5Sswilcox  * Routine to sort disk blocks.
1084355d6bb5Sswilcox  */
1085355d6bb5Sswilcox static int
blksort(const void * arg1,const void * arg2)1086355d6bb5Sswilcox blksort(const void *arg1, const void *arg2)
1087355d6bb5Sswilcox {
1088355d6bb5Sswilcox 	const struct inoinfo **inpp1 = (const struct inoinfo **)arg1;
1089355d6bb5Sswilcox 	const struct inoinfo **inpp2 = (const struct inoinfo **)arg2;
1090355d6bb5Sswilcox 
1091355d6bb5Sswilcox 	return ((*inpp1)->i_blks[0] - (*inpp2)->i_blks[0]);
1092355d6bb5Sswilcox }
1093