xref: /freebsd/sbin/fsck_ffs/pass2.c (revision 4d3fc8b0570b29fb0d6ee9525f104d52176ff0d4)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1980, 1986, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #if 0
33 #ifndef lint
34 static const char sccsid[] = "@(#)pass2.c	8.9 (Berkeley) 4/28/95";
35 #endif /* not lint */
36 #endif
37 #include <sys/cdefs.h>
38 __FBSDID("$FreeBSD$");
39 
40 #include <sys/param.h>
41 #include <sys/sysctl.h>
42 
43 #include <ufs/ufs/dinode.h>
44 #include <ufs/ufs/dir.h>
45 #include <ufs/ffs/fs.h>
46 
47 #include <err.h>
48 #include <errno.h>
49 #include <stdint.h>
50 #include <string.h>
51 
52 #include "fsck.h"
53 
54 #define MINDIRSIZE	(sizeof (struct dirtemplate))
55 
56 static int fix_extraneous(struct inoinfo *, struct inodesc *);
57 static int deleteentry(struct inodesc *);
58 static int blksort(const void *, const void *);
59 static int pass2check(struct inodesc *);
60 
61 void
62 pass2(void)
63 {
64 	struct inode ip;
65 	union dinode *dp;
66 	struct inoinfo **inpp, *inp;
67 	struct inoinfo **inpend;
68 	struct inodesc curino;
69 	union dinode dino;
70 	int i;
71 	char pathbuf[MAXPATHLEN + 1];
72 
73 	switch (inoinfo(UFS_ROOTINO)->ino_state) {
74 
75 	case USTATE:
76 		pfatal("ROOT INODE UNALLOCATED");
77 		if (reply("ALLOCATE") == 0) {
78 			ckfini(0);
79 			exit(EEXIT);
80 		}
81 		if (allocdir(UFS_ROOTINO, UFS_ROOTINO, 0755) != UFS_ROOTINO)
82 			errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
83 		break;
84 
85 	case DCLEAR:
86 		pfatal("DUPS/BAD IN ROOT INODE");
87 		if (reply("REALLOCATE")) {
88 			freedirino(UFS_ROOTINO, UFS_ROOTINO);
89 			if (allocdir(UFS_ROOTINO, UFS_ROOTINO, 0755) !=
90 			    UFS_ROOTINO)
91 				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
92 			break;
93 		}
94 		if (reply("CONTINUE") == 0) {
95 			ckfini(0);
96 			exit(EEXIT);
97 		}
98 		break;
99 
100 	case FSTATE:
101 	case FCLEAR:
102 	case FZLINK:
103 		pfatal("ROOT INODE NOT DIRECTORY");
104 		if (reply("REALLOCATE")) {
105 			freeino(UFS_ROOTINO);
106 			if (allocdir(UFS_ROOTINO, UFS_ROOTINO, 0755) !=
107 			    UFS_ROOTINO)
108 				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
109 			break;
110 		}
111 		if (reply("FIX") == 0) {
112 			ckfini(0);
113 			exit(EEXIT);
114 		}
115 		ginode(UFS_ROOTINO, &ip);
116 		dp = ip.i_dp;
117 		DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
118 		DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
119 		inodirty(&ip);
120 		irelse(&ip);
121 		break;
122 
123 	case DSTATE:
124 	case DZLINK:
125 		break;
126 
127 	default:
128 		errx(EEXIT, "BAD STATE %d FOR ROOT INODE",
129 		    inoinfo(UFS_ROOTINO)->ino_state);
130 	}
131 	inoinfo(UFS_ROOTINO)->ino_state = DFOUND;
132 	inoinfo(UFS_WINO)->ino_state = FSTATE;
133 	inoinfo(UFS_WINO)->ino_type = DT_WHT;
134 	/*
135 	 * Sort the directory list into disk block order.
136 	 */
137 	qsort((char *)inpsort, (size_t)inplast, sizeof *inpsort, blksort);
138 	/*
139 	 * Check the integrity of each directory.
140 	 */
141 	memset(&curino, 0, sizeof(struct inodesc));
142 	curino.id_type = DATA;
143 	curino.id_func = pass2check;
144 	inpend = &inpsort[inplast];
145 	for (inpp = inpsort; inpp < inpend; inpp++) {
146 		if (got_siginfo) {
147 			printf("%s: phase 2: dir %td of %d (%d%%)\n", cdevname,
148 			    inpp - inpsort, (int)inplast,
149 			    (int)((inpp - inpsort) * 100 / inplast));
150 			got_siginfo = 0;
151 		}
152 		if (got_sigalarm) {
153 			setproctitle("%s p2 %d%%", cdevname,
154 			    (int)((inpp - inpsort) * 100 / inplast));
155 			got_sigalarm = 0;
156 		}
157 		inp = *inpp;
158 		if (inp->i_isize == 0)
159 			continue;
160 		if (inp->i_isize < MINDIRSIZE) {
161 			direrror(inp->i_number, "DIRECTORY TOO SHORT");
162 			inp->i_isize = roundup(MINDIRSIZE, DIRBLKSIZ);
163 			if (reply("FIX") == 1) {
164 				ginode(inp->i_number, &ip);
165 				DIP_SET(ip.i_dp, di_size, inp->i_isize);
166 				inodirty(&ip);
167 				irelse(&ip);
168 			}
169 		} else if ((inp->i_isize & (DIRBLKSIZ - 1)) != 0) {
170 			getpathname(pathbuf, inp->i_number, inp->i_number);
171 			if (usedsoftdep)
172 				pfatal("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
173 					"DIRECTORY", pathbuf,
174 					(intmax_t)inp->i_isize, DIRBLKSIZ);
175 			else
176 				pwarn("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
177 					"DIRECTORY", pathbuf,
178 					(intmax_t)inp->i_isize, DIRBLKSIZ);
179 			if (preen)
180 				printf(" (ADJUSTED)\n");
181 			inp->i_isize = roundup(inp->i_isize, DIRBLKSIZ);
182 			if (preen || reply("ADJUST") == 1) {
183 				ginode(inp->i_number, &ip);
184 				DIP_SET(ip.i_dp, di_size,
185 				    roundup(inp->i_isize, DIRBLKSIZ));
186 				inodirty(&ip);
187 				irelse(&ip);
188 			}
189 		}
190 		dp = &dino;
191 		memset(dp, 0, sizeof(struct ufs2_dinode));
192 		DIP_SET(dp, di_mode, IFDIR);
193 		DIP_SET(dp, di_size, inp->i_isize);
194 		for (i = 0; i < MIN(inp->i_numblks, UFS_NDADDR); i++)
195 			DIP_SET(dp, di_db[i], inp->i_blks[i]);
196 		if (inp->i_numblks > UFS_NDADDR)
197 			for (i = 0; i < UFS_NIADDR; i++)
198 				DIP_SET(dp, di_ib[i],
199 				    inp->i_blks[UFS_NDADDR + i]);
200 		curino.id_number = inp->i_number;
201 		curino.id_parent = inp->i_parent;
202 		(void)ckinode(dp, &curino);
203 	}
204 	/*
205 	 * Now that the parents of all directories have been found,
206 	 * make another pass to verify the value of `..'
207 	 */
208 	for (inpp = inpsort; inpp < inpend; inpp++) {
209 		inp = *inpp;
210 		if (inp->i_parent == 0 || inp->i_isize == 0)
211 			continue;
212 		if (inoinfo(inp->i_parent)->ino_state == DFOUND &&
213 		    INO_IS_DUNFOUND(inp->i_number))
214 			inoinfo(inp->i_number)->ino_state = DFOUND;
215 		if (inp->i_dotdot == inp->i_parent ||
216 		    inp->i_dotdot == (ino_t)-1)
217 			continue;
218 		if (inp->i_dotdot == 0) {
219 			inp->i_dotdot = inp->i_parent;
220 			if (debug)
221 				fileerror(inp->i_parent, inp->i_number,
222 				    "DEFERRED MISSING '..' FIX");
223 			(void)makeentry(inp->i_number, inp->i_parent, "..");
224 			inoinfo(inp->i_parent)->ino_linkcnt--;
225 			continue;
226 		}
227 		/*
228 		 * Here we have:
229 		 *    inp->i_number is directory with bad ".." in it.
230 		 *    inp->i_dotdot is current value of "..".
231 		 *    inp->i_parent is directory to which ".." should point.
232 		 */
233 		getpathname(pathbuf, inp->i_parent, inp->i_number);
234 		printf("BAD INODE NUMBER FOR '..' in DIR I=%ju (%s)\n",
235 		    (uintmax_t)inp->i_number, pathbuf);
236 		getpathname(pathbuf, inp->i_dotdot, inp->i_dotdot);
237 		printf("CURRENTLY POINTS TO I=%ju (%s), ",
238 		    (uintmax_t)inp->i_dotdot, pathbuf);
239 		getpathname(pathbuf, inp->i_parent, inp->i_parent);
240 		printf("SHOULD POINT TO I=%ju (%s)",
241 		    (uintmax_t)inp->i_parent, pathbuf);
242 		if (cursnapshot != 0) {
243 			/*
244 			 * We need to:
245 			 *    setcwd(inp->i_number);
246 			 *    setdotdot(inp->i_dotdot, inp->i_parent);
247 			 */
248 			cmd.value = inp->i_number;
249 			if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
250 			    &cmd, sizeof cmd) == -1) {
251 				/* kernel lacks support for these functions */
252 				printf(" (IGNORED)\n");
253 				continue;
254 			}
255 			cmd.value = inp->i_dotdot; /* verify same value */
256 			cmd.size = inp->i_parent;  /* new parent */
257 			if (sysctlbyname("vfs.ffs.setdotdot", 0, 0,
258 			    &cmd, sizeof cmd) == -1) {
259 				printf(" (FIX FAILED: %s)\n", strerror(errno));
260 				continue;
261 			}
262 			printf(" (FIXED)\n");
263 			inoinfo(inp->i_parent)->ino_linkcnt--;
264 			inp->i_dotdot = inp->i_parent;
265 			continue;
266 		}
267 		if (preen)
268 			printf(" (FIXED)\n");
269 		else if (reply("FIX") == 0)
270 			continue;
271 		inoinfo(inp->i_dotdot)->ino_linkcnt++;
272 		inoinfo(inp->i_parent)->ino_linkcnt--;
273 		inp->i_dotdot = inp->i_parent;
274 		(void)changeino(inp->i_number, "..", inp->i_parent);
275 	}
276 	/*
277 	 * Mark all the directories that can be found from the root.
278 	 */
279 	propagate();
280 }
281 
282 static int
283 pass2check(struct inodesc *idesc)
284 {
285 	struct direct *dirp = idesc->id_dirp;
286 	char dirname[MAXPATHLEN + 1];
287 	struct inoinfo *inp;
288 	int n, entrysize, ret = 0;
289 	struct inode ip;
290 	union dinode *dp;
291 	const char *errmsg;
292 	struct direct proto, *newdirp;
293 
294 	/*
295 	 * check for "."
296 	 */
297 	if (idesc->id_entryno != 0)
298 		goto chk1;
299 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
300 		if (dirp->d_ino != idesc->id_number) {
301 			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
302 			if (reply("FIX") == 1) {
303 				dirp->d_ino = idesc->id_number;
304 				ret |= ALTERED;
305 			}
306 		}
307 		if (dirp->d_type != DT_DIR) {
308 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
309 			if (reply("FIX") == 1) {
310 				dirp->d_type = DT_DIR;
311 				ret |= ALTERED;
312 			}
313 		}
314 		goto chk1;
315 	}
316 	proto.d_ino = idesc->id_number;
317 	proto.d_type = DT_DIR;
318 	proto.d_namlen = 1;
319 	(void)strcpy(proto.d_name, ".");
320 	entrysize = DIRSIZ(0, &proto);
321 	direrror(idesc->id_number, "MISSING '.'");
322 	errmsg = "ADD '.' ENTRY";
323 	if (dirp->d_reclen < entrysize + DIRSIZ(0, dirp)) {
324 		/* Not enough space to add '.', replace first entry with '.' */
325 		if (dirp->d_ino != 0) {
326 			pwarn("\nFIRST ENTRY IN DIRECTORY CONTAINS %s\n",
327 			     dirp->d_name);
328 			errmsg = "REPLACE WITH '.'";
329 		}
330 		if (reply(errmsg) == 0)
331 			goto chk1;
332 		proto.d_reclen = dirp->d_reclen;
333 		memmove(dirp, &proto, (size_t)entrysize);
334 		ret |= ALTERED;
335 	} else {
336 		/* Move over first entry and add '.' entry */
337 		if (reply(errmsg) == 0)
338 			goto chk1;
339 		newdirp = (struct direct *)((char *)(dirp) + entrysize);
340 		dirp->d_reclen -= entrysize;
341 		memmove(newdirp, dirp, dirp->d_reclen);
342 		proto.d_reclen = entrysize;
343 		memmove(dirp, &proto, (size_t)entrysize);
344 		idesc->id_entryno++;
345 		inoinfo(idesc->id_number)->ino_linkcnt--;
346 		dirp = newdirp;
347 		ret |= ALTERED;
348 	}
349 chk1:
350 	if (idesc->id_entryno > 1)
351 		goto chk2;
352 	inp = getinoinfo(idesc->id_number);
353 	proto.d_ino = inp->i_parent;
354 	proto.d_type = DT_DIR;
355 	proto.d_namlen = 2;
356 	(void)strcpy(proto.d_name, "..");
357 	entrysize = DIRSIZ(0, &proto);
358 	if (idesc->id_entryno == 0) {
359 		n = DIRSIZ(0, dirp);
360 		if (dirp->d_reclen < n + entrysize)
361 			goto chk2;
362 		proto.d_reclen = dirp->d_reclen - n;
363 		dirp->d_reclen = n;
364 		idesc->id_entryno++;
365 		inoinfo(dirp->d_ino)->ino_linkcnt--;
366 		dirp = (struct direct *)((char *)(dirp) + n);
367 		memset(dirp, 0, (size_t)proto.d_reclen);
368 		dirp->d_reclen = proto.d_reclen;
369 	}
370 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
371 		if (dirp->d_ino > maxino) {
372 			direrror(idesc->id_number, "BAD INODE NUMBER FOR '..'");
373 			/*
374 			 * If we know parent set it now, otherwise let it
375 			 * point to the root inode and it will get cleaned
376 			 * up later if that is not correct.
377 			 */
378 			if (inp->i_parent != 0)
379 				dirp->d_ino = inp->i_parent;
380 			else
381 				dirp->d_ino = UFS_ROOTINO;
382 			if (reply("FIX") == 1)
383 				ret |= ALTERED;
384 		}
385 		inp->i_dotdot = dirp->d_ino;
386 		if (dirp->d_type != DT_DIR) {
387 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
388 			dirp->d_type = DT_DIR;
389 			if (reply("FIX") == 1)
390 				ret |= ALTERED;
391 		}
392 		goto chk2;
393 	}
394 	fileerror(inp->i_parent != 0 ? inp->i_parent : idesc->id_number,
395 	    idesc->id_number, "MISSING '..'");
396 	errmsg = "ADD '..' ENTRY";
397 	if (dirp->d_reclen < entrysize + DIRSIZ(0, dirp)) {
398 		/* No space to add '..', replace second entry with '..' */
399 		if (dirp->d_ino != 0) {
400 			pfatal("SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
401 			    dirp->d_name);
402 			errmsg = "REPLACE WITH '..'";
403 		}
404 		if (reply(errmsg) == 0) {
405 			inp->i_dotdot = (ino_t)-1;
406 			goto chk2;
407 		}
408 		if (proto.d_ino == 0) {
409 			/* Defer processing until parent known */
410 			idesc->id_entryno++;
411 			if (debug)
412 				printf("(FIX DEFERRED)\n");
413 		}
414 		inp->i_dotdot = proto.d_ino;
415 		proto.d_reclen = dirp->d_reclen;
416 		memmove(dirp, &proto, (size_t)entrysize);
417 		ret |= ALTERED;
418 	} else {
419 		/* Move over second entry and add '..' entry */
420 		if (reply(errmsg) == 0) {
421 			inp->i_dotdot = (ino_t)-1;
422 			goto chk2;
423 		}
424 		if (proto.d_ino == 0) {
425 			/* Defer processing until parent known */
426 			idesc->id_entryno++;
427 			if (debug)
428 				printf("(FIX DEFERRED)\n");
429 		}
430 		inp->i_dotdot = proto.d_ino;
431 		if (dirp->d_ino == 0) {
432 			proto.d_reclen = dirp->d_reclen;
433 			memmove(dirp, &proto, (size_t)entrysize);
434 		} else {
435 			newdirp = (struct direct *)((char *)(dirp) + entrysize);
436 			dirp->d_reclen -= entrysize;
437 			memmove(newdirp, dirp, dirp->d_reclen);
438 			proto.d_reclen = entrysize;
439 			memmove(dirp, &proto, (size_t)entrysize);
440 			if (dirp->d_ino != 0) {
441 				idesc->id_entryno++;
442 				inoinfo(dirp->d_ino)->ino_linkcnt--;
443 			}
444 			dirp = newdirp;
445 		}
446 		ret |= ALTERED;
447 	}
448 chk2:
449 	if (dirp->d_ino == 0)
450 		return (ret|KEEPON);
451 	if (dirp->d_namlen <= 2 &&
452 	    dirp->d_name[0] == '.' &&
453 	    idesc->id_entryno >= 2) {
454 		if (dirp->d_namlen == 1) {
455 			direrror(idesc->id_number, "EXTRA '.' ENTRY");
456 			dirp->d_ino = 0;
457 			if (reply("FIX") == 1)
458 				ret |= ALTERED;
459 			return (KEEPON | ret);
460 		}
461 		if (dirp->d_name[1] == '.') {
462 			direrror(idesc->id_number, "EXTRA '..' ENTRY");
463 			dirp->d_ino = 0;
464 			if (reply("FIX") == 1)
465 				ret |= ALTERED;
466 			return (KEEPON | ret);
467 		}
468 	}
469 	idesc->id_entryno++;
470 	n = 0;
471 	if (dirp->d_ino > maxino) {
472 		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
473 		n = reply("REMOVE");
474 	} else if (((dirp->d_ino == UFS_WINO && dirp->d_type != DT_WHT) ||
475 		    (dirp->d_ino != UFS_WINO && dirp->d_type == DT_WHT))) {
476 		fileerror(idesc->id_number, dirp->d_ino, "BAD WHITEOUT ENTRY");
477 		dirp->d_ino = UFS_WINO;
478 		dirp->d_type = DT_WHT;
479 		if (reply("FIX") == 1)
480 			ret |= ALTERED;
481 	} else {
482 again:
483 		switch (inoinfo(dirp->d_ino)->ino_state) {
484 		case USTATE:
485 			if (idesc->id_entryno <= 2)
486 				break;
487 			fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
488 			n = reply("REMOVE");
489 			break;
490 
491 		case DCLEAR:
492 		case FCLEAR:
493 			if (idesc->id_entryno <= 2)
494 				break;
495 			if (inoinfo(dirp->d_ino)->ino_state == FCLEAR)
496 				errmsg = "DUP/BAD";
497 			else if (!preen && !usedsoftdep)
498 				errmsg = "ZERO LENGTH DIRECTORY";
499 			else if (cursnapshot == 0) {
500 				n = 1;
501 				break;
502 			} else {
503 				getpathname(dirname, idesc->id_number,
504 				    dirp->d_ino);
505 				pwarn("ZERO LENGTH DIRECTORY %s I=%ju",
506 				    dirname, (uintmax_t)dirp->d_ino);
507 				/*
508 				 * We need to:
509 				 *    setcwd(idesc->id_parent);
510 				 *    rmdir(dirp->d_name);
511 				 */
512 				cmd.value = idesc->id_number;
513 				if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
514 				    &cmd, sizeof cmd) == -1) {
515 					/* kernel lacks support */
516 					printf(" (IGNORED)\n");
517 					n = 1;
518 					break;
519 				}
520 				if (rmdir(dirp->d_name) == -1) {
521 					printf(" (REMOVAL FAILED: %s)\n",
522 					    strerror(errno));
523 					n = 1;
524 					break;
525 				}
526 				/* ".." reference to parent is removed */
527 				inoinfo(idesc->id_number)->ino_linkcnt--;
528 				printf(" (REMOVED)\n");
529 				break;
530 			}
531 			fileerror(idesc->id_number, dirp->d_ino, errmsg);
532 			if ((n = reply("REMOVE")) == 1)
533 				break;
534 			ginode(dirp->d_ino, &ip);
535 			dp = ip.i_dp;
536 			inoinfo(dirp->d_ino)->ino_state =
537 			   (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
538 			inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
539 			irelse(&ip);
540 			goto again;
541 
542 		case DSTATE:
543 		case DZLINK:
544 			if (inoinfo(idesc->id_number)->ino_state == DFOUND)
545 				inoinfo(dirp->d_ino)->ino_state = DFOUND;
546 			/* FALLTHROUGH */
547 
548 		case DFOUND:
549 			inp = getinoinfo(dirp->d_ino);
550 			if (idesc->id_entryno > 2) {
551 				if (inp->i_parent == 0)
552 					inp->i_parent = idesc->id_number;
553 				else if ((n = fix_extraneous(inp, idesc)) == 1)
554 					break;
555 			}
556 			/* FALLTHROUGH */
557 
558 		case FSTATE:
559 		case FZLINK:
560 			if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
561 				fileerror(idesc->id_number, dirp->d_ino,
562 				    "BAD TYPE VALUE");
563 				dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
564 				if (reply("FIX") == 1)
565 					ret |= ALTERED;
566 			}
567 			inoinfo(dirp->d_ino)->ino_linkcnt--;
568 			break;
569 
570 		default:
571 			errx(EEXIT, "BAD STATE %d FOR INODE I=%ju",
572 			    inoinfo(dirp->d_ino)->ino_state,
573 			    (uintmax_t)dirp->d_ino);
574 		}
575 	}
576 	if (n == 0)
577 		return (ret|KEEPON);
578 	dirp->d_ino = 0;
579 	return (ret|KEEPON|ALTERED);
580 }
581 
582 static int
583 fix_extraneous(struct inoinfo *inp, struct inodesc *idesc)
584 {
585 	char *cp;
586 	struct inode ip;
587 	struct inodesc dotdesc;
588 	char oldname[MAXPATHLEN + 1];
589 	char newname[MAXPATHLEN + 1];
590 
591 	/*
592 	 * If we have not yet found "..", look it up now so we know
593 	 * which inode the directory itself believes is its parent.
594 	 */
595 	if (inp->i_dotdot == 0) {
596 		memset(&dotdesc, 0, sizeof(struct inodesc));
597 		dotdesc.id_type = DATA;
598 		dotdesc.id_number = idesc->id_dirp->d_ino;
599 		dotdesc.id_func = findino;
600 		dotdesc.id_name = strdup("..");
601 		ginode(dotdesc.id_number, &ip);
602 		if ((ckinode(ip.i_dp, &dotdesc) & FOUND))
603 			inp->i_dotdot = dotdesc.id_parent;
604 		irelse(&ip);
605 	}
606 	/*
607 	 * We have the previously found old name (inp->i_parent) and the
608 	 * just found new name (idesc->id_number). We have five cases:
609 	 * 1)  ".." is missing - can remove either name, choose to delete
610 	 *     new one and let fsck create ".." pointing to old name.
611 	 * 2) Both new and old are in same directory, choose to delete
612 	 *    the new name and let fsck fix ".." if it is wrong.
613 	 * 3) ".." does not point to the new name, so delete it and let
614 	 *    fsck fix ".." to point to the old one if it is wrong.
615 	 * 4) ".." points to the old name only, so delete the new one.
616 	 * 5) ".." points to the new name only, so delete the old one.
617 	 *
618 	 * For cases 1-4 we eliminate the new name;
619 	 * for case 5 we eliminate the old name.
620 	 */
621 	if (inp->i_dotdot == 0 ||		    /* Case 1 */
622 	    idesc->id_number == inp->i_parent ||    /* Case 2 */
623 	    inp->i_dotdot != idesc->id_number ||    /* Case 3 */
624 	    inp->i_dotdot == inp->i_parent) {	    /* Case 4 */
625 		getpathname(newname, idesc->id_number, idesc->id_number);
626 		if (strcmp(newname, "/") != 0)
627 			strcat (newname, "/");
628 		strcat(newname, idesc->id_dirp->d_name);
629 		getpathname(oldname, inp->i_number, inp->i_number);
630 		pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s",
631 		    newname, oldname);
632 		if (cursnapshot != 0) {
633 			/*
634 			 * We need to
635 			 *    setcwd(idesc->id_number);
636 			 *    unlink(idesc->id_dirp->d_name);
637 			 */
638 			cmd.value = idesc->id_number;
639 			if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
640 			    &cmd, sizeof cmd) == -1) {
641 				printf(" (IGNORED)\n");
642 				return (0);
643 			}
644 			cmd.value = (intptr_t)idesc->id_dirp->d_name;
645 			cmd.size = inp->i_number; /* verify same name */
646 			if (sysctlbyname("vfs.ffs.unlink", 0, 0,
647 			    &cmd, sizeof cmd) == -1) {
648 				printf(" (UNLINK FAILED: %s)\n",
649 				    strerror(errno));
650 				return (0);
651 			}
652 			printf(" (REMOVED)\n");
653 			return (0);
654 		}
655 		if (preen) {
656 			printf(" (REMOVED)\n");
657 			return (1);
658 		}
659 		return (reply("REMOVE"));
660 	}
661 	/*
662 	 * None of the first four cases above, so must be case (5).
663 	 * Eliminate the old name and make the new the name the parent.
664 	 */
665 	getpathname(oldname, inp->i_parent, inp->i_number);
666 	getpathname(newname, inp->i_number, inp->i_number);
667 	pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s", oldname,
668 	    newname);
669 	if (cursnapshot != 0) {
670 		/*
671 		 * We need to
672 		 *    setcwd(inp->i_parent);
673 		 *    unlink(last component of oldname pathname);
674 		 */
675 		cmd.value = inp->i_parent;
676 		if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
677 		    &cmd, sizeof cmd) == -1) {
678 			printf(" (IGNORED)\n");
679 			return (0);
680 		}
681 		if ((cp = strchr(oldname, '/')) == NULL) {
682 			printf(" (IGNORED)\n");
683 			return (0);
684 		}
685 		cmd.value = (intptr_t)(cp + 1);
686 		cmd.size = inp->i_number; /* verify same name */
687 		if (sysctlbyname("vfs.ffs.unlink", 0, 0,
688 		    &cmd, sizeof cmd) == -1) {
689 			printf(" (UNLINK FAILED: %s)\n",
690 			    strerror(errno));
691 			return (0);
692 		}
693 		printf(" (REMOVED)\n");
694 		inp->i_parent = idesc->id_number;  /* reparent to correct dir */
695 		return (0);
696 	}
697 	if (!preen && !reply("REMOVE"))
698 		return (0);
699 	memset(&dotdesc, 0, sizeof(struct inodesc));
700 	dotdesc.id_type = DATA;
701 	dotdesc.id_number = inp->i_parent; /* directory in which name appears */
702 	dotdesc.id_parent = inp->i_number; /* inode number in entry to delete */
703 	dotdesc.id_func = deleteentry;
704 	ginode(dotdesc.id_number, &ip);
705 	if ((ckinode(ip.i_dp, &dotdesc) & FOUND) && preen)
706 		printf(" (REMOVED)\n");
707 	irelse(&ip);
708 	inp->i_parent = idesc->id_number;  /* reparent to correct directory */
709 	inoinfo(inp->i_number)->ino_linkcnt++; /* name gone, return reference */
710 	return (0);
711 }
712 
713 static int
714 deleteentry(struct inodesc *idesc)
715 {
716 	struct direct *dirp = idesc->id_dirp;
717 
718 	if (idesc->id_entryno++ < 2 || dirp->d_ino != idesc->id_parent)
719 		return (KEEPON);
720 	dirp->d_ino = 0;
721 	return (ALTERED|STOP|FOUND);
722 }
723 
724 /*
725  * Routine to sort disk blocks.
726  */
727 static int
728 blksort(const void *arg1, const void *arg2)
729 {
730 
731 	return ((*(struct inoinfo * const *)arg1)->i_blks[0] -
732 		(*(struct inoinfo * const *)arg2)->i_blks[0]);
733 }
734