xref: /freebsd/sbin/fsck_ffs/pass2.c (revision 19fae0f66023a97a9b464b3beeeabb2081f575b3)
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 			check_dirdepth(inp);
216 		}
217 		if (inp->i_dotdot == inp->i_parent ||
218 		    inp->i_dotdot == (ino_t)-1)
219 			continue;
220 		if (inp->i_dotdot == 0) {
221 			inp->i_dotdot = inp->i_parent;
222 			if (debug)
223 				fileerror(inp->i_parent, inp->i_number,
224 				    "DEFERRED MISSING '..' FIX");
225 			(void)makeentry(inp->i_number, inp->i_parent, "..");
226 			inoinfo(inp->i_parent)->ino_linkcnt--;
227 			continue;
228 		}
229 		/*
230 		 * Here we have:
231 		 *    inp->i_number is directory with bad ".." in it.
232 		 *    inp->i_dotdot is current value of "..".
233 		 *    inp->i_parent is directory to which ".." should point.
234 		 */
235 		getpathname(pathbuf, inp->i_parent, inp->i_number);
236 		printf("BAD INODE NUMBER FOR '..' in DIR I=%ju (%s)\n",
237 		    (uintmax_t)inp->i_number, pathbuf);
238 		getpathname(pathbuf, inp->i_dotdot, inp->i_dotdot);
239 		printf("CURRENTLY POINTS TO I=%ju (%s), ",
240 		    (uintmax_t)inp->i_dotdot, pathbuf);
241 		getpathname(pathbuf, inp->i_parent, inp->i_parent);
242 		printf("SHOULD POINT TO I=%ju (%s)",
243 		    (uintmax_t)inp->i_parent, pathbuf);
244 		if (cursnapshot != 0) {
245 			/*
246 			 * We need to:
247 			 *    setcwd(inp->i_number);
248 			 *    setdotdot(inp->i_dotdot, inp->i_parent);
249 			 */
250 			cmd.value = inp->i_number;
251 			if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
252 			    &cmd, sizeof cmd) == -1) {
253 				/* kernel lacks support for these functions */
254 				printf(" (IGNORED)\n");
255 				continue;
256 			}
257 			cmd.value = inp->i_dotdot; /* verify same value */
258 			cmd.size = inp->i_parent;  /* new parent */
259 			if (sysctlbyname("vfs.ffs.setdotdot", 0, 0,
260 			    &cmd, sizeof cmd) == -1) {
261 				printf(" (FIX FAILED: %s)\n", strerror(errno));
262 				continue;
263 			}
264 			printf(" (FIXED)\n");
265 			inoinfo(inp->i_parent)->ino_linkcnt--;
266 			inp->i_dotdot = inp->i_parent;
267 			continue;
268 		}
269 		if (preen)
270 			printf(" (FIXED)\n");
271 		else if (reply("FIX") == 0)
272 			continue;
273 		inoinfo(inp->i_dotdot)->ino_linkcnt++;
274 		inoinfo(inp->i_parent)->ino_linkcnt--;
275 		inp->i_dotdot = inp->i_parent;
276 		(void)changeino(inp->i_number, "..", inp->i_parent,
277 		    getinoinfo(inp->i_parent)->i_depth  + 1);
278 	}
279 	/*
280 	 * Mark all the directories that can be found from the root.
281 	 */
282 	propagate();
283 }
284 
285 static int
286 pass2check(struct inodesc *idesc)
287 {
288 	struct direct *dirp = idesc->id_dirp;
289 	char dirname[MAXPATHLEN + 1];
290 	struct inoinfo *inp;
291 	int n, entrysize, ret = 0;
292 	struct inode ip;
293 	union dinode *dp;
294 	const char *errmsg;
295 	struct direct proto, *newdirp;
296 
297 	/*
298 	 * check for "."
299 	 */
300 	if (idesc->id_entryno != 0)
301 		goto chk1;
302 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
303 		if (dirp->d_ino != idesc->id_number) {
304 			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
305 			if (reply("FIX") == 1) {
306 				dirp->d_ino = idesc->id_number;
307 				ret |= ALTERED;
308 			}
309 		}
310 		if (dirp->d_type != DT_DIR) {
311 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
312 			if (reply("FIX") == 1) {
313 				dirp->d_type = DT_DIR;
314 				ret |= ALTERED;
315 			}
316 		}
317 		goto chk1;
318 	}
319 	proto.d_ino = idesc->id_number;
320 	proto.d_type = DT_DIR;
321 	proto.d_namlen = 1;
322 	(void)strcpy(proto.d_name, ".");
323 	entrysize = DIRSIZ(0, &proto);
324 	direrror(idesc->id_number, "MISSING '.'");
325 	errmsg = "ADD '.' ENTRY";
326 	if (dirp->d_reclen < entrysize + DIRSIZ(0, dirp)) {
327 		/* Not enough space to add '.', replace first entry with '.' */
328 		if (dirp->d_ino != 0) {
329 			pwarn("\nFIRST ENTRY IN DIRECTORY CONTAINS %s\n",
330 			     dirp->d_name);
331 			errmsg = "REPLACE WITH '.'";
332 		}
333 		if (reply(errmsg) == 0)
334 			goto chk1;
335 		proto.d_reclen = dirp->d_reclen;
336 		memmove(dirp, &proto, (size_t)entrysize);
337 		ret |= ALTERED;
338 	} else {
339 		/* Move over first entry and add '.' entry */
340 		if (reply(errmsg) == 0)
341 			goto chk1;
342 		newdirp = (struct direct *)((char *)(dirp) + entrysize);
343 		dirp->d_reclen -= entrysize;
344 		memmove(newdirp, dirp, dirp->d_reclen);
345 		proto.d_reclen = entrysize;
346 		memmove(dirp, &proto, (size_t)entrysize);
347 		idesc->id_entryno++;
348 		inoinfo(idesc->id_number)->ino_linkcnt--;
349 		dirp = newdirp;
350 		ret |= ALTERED;
351 	}
352 chk1:
353 	if (idesc->id_entryno > 1)
354 		goto chk2;
355 	inp = getinoinfo(idesc->id_number);
356 	proto.d_ino = inp->i_parent;
357 	proto.d_type = DT_DIR;
358 	proto.d_namlen = 2;
359 	(void)strcpy(proto.d_name, "..");
360 	entrysize = DIRSIZ(0, &proto);
361 	if (idesc->id_entryno == 0) {
362 		n = DIRSIZ(0, dirp);
363 		if (dirp->d_reclen < n + entrysize)
364 			goto chk2;
365 		proto.d_reclen = dirp->d_reclen - n;
366 		dirp->d_reclen = n;
367 		idesc->id_entryno++;
368 		inoinfo(dirp->d_ino)->ino_linkcnt--;
369 		dirp = (struct direct *)((char *)(dirp) + n);
370 		memset(dirp, 0, (size_t)proto.d_reclen);
371 		dirp->d_reclen = proto.d_reclen;
372 	}
373 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
374 		if (dirp->d_ino >= maxino) {
375 			direrror(idesc->id_number, "BAD INODE NUMBER FOR '..'");
376 			/*
377 			 * If we know parent set it now, otherwise let it
378 			 * point to the root inode and it will get cleaned
379 			 * up later if that is not correct.
380 			 */
381 			if (inp->i_parent != 0)
382 				dirp->d_ino = inp->i_parent;
383 			else
384 				dirp->d_ino = UFS_ROOTINO;
385 			if (reply("FIX") == 1)
386 				ret |= ALTERED;
387 		}
388 		inp->i_dotdot = dirp->d_ino;
389 		if (dirp->d_type != DT_DIR) {
390 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
391 			dirp->d_type = DT_DIR;
392 			if (reply("FIX") == 1)
393 				ret |= ALTERED;
394 		}
395 		goto chk2;
396 	}
397 	fileerror(inp->i_parent != 0 ? inp->i_parent : idesc->id_number,
398 	    idesc->id_number, "MISSING '..'");
399 	errmsg = "ADD '..' ENTRY";
400 	if (dirp->d_reclen < entrysize + DIRSIZ(0, dirp)) {
401 		/* No space to add '..', replace second entry with '..' */
402 		if (dirp->d_ino != 0) {
403 			pfatal("SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
404 			    dirp->d_name);
405 			errmsg = "REPLACE WITH '..'";
406 		}
407 		if (reply(errmsg) == 0) {
408 			inp->i_dotdot = (ino_t)-1;
409 			goto chk2;
410 		}
411 		if (proto.d_ino == 0) {
412 			/* Defer processing until parent known */
413 			idesc->id_entryno++;
414 			if (debug)
415 				printf("(FIX DEFERRED)\n");
416 		}
417 		inp->i_dotdot = proto.d_ino;
418 		proto.d_reclen = dirp->d_reclen;
419 		memmove(dirp, &proto, (size_t)entrysize);
420 		ret |= ALTERED;
421 	} else {
422 		/* Move over second entry and add '..' entry */
423 		if (reply(errmsg) == 0) {
424 			inp->i_dotdot = (ino_t)-1;
425 			goto chk2;
426 		}
427 		if (proto.d_ino == 0) {
428 			/* Defer processing until parent known */
429 			idesc->id_entryno++;
430 			if (debug)
431 				printf("(FIX DEFERRED)\n");
432 		}
433 		inp->i_dotdot = proto.d_ino;
434 		if (dirp->d_ino == 0) {
435 			proto.d_reclen = dirp->d_reclen;
436 			memmove(dirp, &proto, (size_t)entrysize);
437 		} else {
438 			newdirp = (struct direct *)((char *)(dirp) + entrysize);
439 			dirp->d_reclen -= entrysize;
440 			memmove(newdirp, dirp, dirp->d_reclen);
441 			proto.d_reclen = entrysize;
442 			memmove(dirp, &proto, (size_t)entrysize);
443 			if (dirp->d_ino != 0) {
444 				idesc->id_entryno++;
445 				inoinfo(dirp->d_ino)->ino_linkcnt--;
446 			}
447 			dirp = newdirp;
448 		}
449 		ret |= ALTERED;
450 	}
451 chk2:
452 	if (dirp->d_ino == 0)
453 		return (ret|KEEPON);
454 	if (dirp->d_namlen <= 2 &&
455 	    dirp->d_name[0] == '.' &&
456 	    idesc->id_entryno >= 2) {
457 		if (dirp->d_namlen == 1) {
458 			direrror(idesc->id_number, "EXTRA '.' ENTRY");
459 			dirp->d_ino = 0;
460 			if (reply("FIX") == 1)
461 				ret |= ALTERED;
462 			return (KEEPON | ret);
463 		}
464 		if (dirp->d_name[1] == '.') {
465 			direrror(idesc->id_number, "EXTRA '..' ENTRY");
466 			dirp->d_ino = 0;
467 			if (reply("FIX") == 1)
468 				ret |= ALTERED;
469 			return (KEEPON | ret);
470 		}
471 	}
472 	idesc->id_entryno++;
473 	n = 0;
474 	if (dirp->d_ino >= maxino) {
475 		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
476 		n = reply("REMOVE");
477 	} else if (((dirp->d_ino == UFS_WINO && dirp->d_type != DT_WHT) ||
478 		    (dirp->d_ino != UFS_WINO && dirp->d_type == DT_WHT))) {
479 		fileerror(idesc->id_number, dirp->d_ino, "BAD WHITEOUT ENTRY");
480 		dirp->d_ino = UFS_WINO;
481 		dirp->d_type = DT_WHT;
482 		if (reply("FIX") == 1)
483 			ret |= ALTERED;
484 	} else {
485 again:
486 		switch (inoinfo(dirp->d_ino)->ino_state) {
487 		case USTATE:
488 			if (idesc->id_entryno <= 2)
489 				break;
490 			fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
491 			n = reply("REMOVE");
492 			break;
493 
494 		case DCLEAR:
495 		case FCLEAR:
496 			if (idesc->id_entryno <= 2)
497 				break;
498 			if (inoinfo(dirp->d_ino)->ino_state == FCLEAR)
499 				errmsg = "DUP/BAD";
500 			else if (!preen && !usedsoftdep)
501 				errmsg = "ZERO LENGTH DIRECTORY";
502 			else if (cursnapshot == 0) {
503 				n = 1;
504 				break;
505 			} else {
506 				getpathname(dirname, idesc->id_number,
507 				    dirp->d_ino);
508 				pwarn("ZERO LENGTH DIRECTORY %s I=%ju",
509 				    dirname, (uintmax_t)dirp->d_ino);
510 				/*
511 				 * We need to:
512 				 *    setcwd(idesc->id_parent);
513 				 *    rmdir(dirp->d_name);
514 				 */
515 				cmd.value = idesc->id_number;
516 				if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
517 				    &cmd, sizeof cmd) == -1) {
518 					/* kernel lacks support */
519 					printf(" (IGNORED)\n");
520 					n = 1;
521 					break;
522 				}
523 				if (rmdir(dirp->d_name) == -1) {
524 					printf(" (REMOVAL FAILED: %s)\n",
525 					    strerror(errno));
526 					n = 1;
527 					break;
528 				}
529 				/* ".." reference to parent is removed */
530 				inoinfo(idesc->id_number)->ino_linkcnt--;
531 				printf(" (REMOVED)\n");
532 				break;
533 			}
534 			fileerror(idesc->id_number, dirp->d_ino, errmsg);
535 			if ((n = reply("REMOVE")) == 1)
536 				break;
537 			ginode(dirp->d_ino, &ip);
538 			dp = ip.i_dp;
539 			inoinfo(dirp->d_ino)->ino_state =
540 			   (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
541 			inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
542 			irelse(&ip);
543 			goto again;
544 
545 		case DSTATE:
546 		case DZLINK:
547 			if (inoinfo(idesc->id_number)->ino_state == DFOUND)
548 				inoinfo(dirp->d_ino)->ino_state = DFOUND;
549 			/* FALLTHROUGH */
550 
551 		case DFOUND:
552 			inp = getinoinfo(dirp->d_ino);
553 			if (idesc->id_entryno > 2) {
554 				if (inp->i_parent == 0) {
555 					inp->i_parent = idesc->id_number;
556 					check_dirdepth(inp);
557 				} else if ((n = fix_extraneous(inp, idesc))) {
558 					break;
559 				}
560 			}
561 			/* FALLTHROUGH */
562 
563 		case FSTATE:
564 		case FZLINK:
565 			if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
566 				fileerror(idesc->id_number, dirp->d_ino,
567 				    "BAD TYPE VALUE");
568 				dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
569 				if (reply("FIX") == 1)
570 					ret |= ALTERED;
571 			}
572 			inoinfo(dirp->d_ino)->ino_linkcnt--;
573 			break;
574 
575 		default:
576 			errx(EEXIT, "BAD STATE %d FOR INODE I=%ju",
577 			    inoinfo(dirp->d_ino)->ino_state,
578 			    (uintmax_t)dirp->d_ino);
579 		}
580 	}
581 	if (n == 0)
582 		return (ret|KEEPON);
583 	dirp->d_ino = 0;
584 	return (ret|KEEPON|ALTERED);
585 }
586 
587 static int
588 fix_extraneous(struct inoinfo *inp, struct inodesc *idesc)
589 {
590 	char *cp;
591 	struct inode ip;
592 	struct inodesc dotdesc;
593 	char oldname[MAXPATHLEN + 1];
594 	char newname[MAXPATHLEN + 1];
595 
596 	/*
597 	 * If we have not yet found "..", look it up now so we know
598 	 * which inode the directory itself believes is its parent.
599 	 */
600 	if (inp->i_dotdot == 0) {
601 		memset(&dotdesc, 0, sizeof(struct inodesc));
602 		dotdesc.id_type = DATA;
603 		dotdesc.id_number = idesc->id_dirp->d_ino;
604 		dotdesc.id_func = findino;
605 		dotdesc.id_name = strdup("..");
606 		ginode(dotdesc.id_number, &ip);
607 		if ((ckinode(ip.i_dp, &dotdesc) & FOUND))
608 			inp->i_dotdot = dotdesc.id_parent;
609 		irelse(&ip);
610 		free(dotdesc.id_name);
611 	}
612 	/*
613 	 * We have the previously found old name (inp->i_parent) and the
614 	 * just found new name (idesc->id_number). We have five cases:
615 	 * 1)  ".." is missing - can remove either name, choose to delete
616 	 *     new one and let fsck create ".." pointing to old name.
617 	 * 2) Both new and old are in same directory, choose to delete
618 	 *    the new name and let fsck fix ".." if it is wrong.
619 	 * 3) ".." does not point to the new name, so delete it and let
620 	 *    fsck fix ".." to point to the old one if it is wrong.
621 	 * 4) ".." points to the old name only, so delete the new one.
622 	 * 5) ".." points to the new name only, so delete the old one.
623 	 *
624 	 * For cases 1-4 we eliminate the new name;
625 	 * for case 5 we eliminate the old name.
626 	 */
627 	if (inp->i_dotdot == 0 ||		    /* Case 1 */
628 	    idesc->id_number == inp->i_parent ||    /* Case 2 */
629 	    inp->i_dotdot != idesc->id_number ||    /* Case 3 */
630 	    inp->i_dotdot == inp->i_parent) {	    /* Case 4 */
631 		getpathname(newname, idesc->id_number, idesc->id_number);
632 		if (strcmp(newname, "/") != 0)
633 			strcat (newname, "/");
634 		strcat(newname, idesc->id_dirp->d_name);
635 		getpathname(oldname, inp->i_number, inp->i_number);
636 		pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s",
637 		    newname, oldname);
638 		if (cursnapshot != 0) {
639 			/*
640 			 * We need to
641 			 *    setcwd(idesc->id_number);
642 			 *    unlink(idesc->id_dirp->d_name);
643 			 */
644 			cmd.value = idesc->id_number;
645 			if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
646 			    &cmd, sizeof cmd) == -1) {
647 				printf(" (IGNORED)\n");
648 				return (0);
649 			}
650 			cmd.value = (intptr_t)idesc->id_dirp->d_name;
651 			cmd.size = inp->i_number; /* verify same name */
652 			if (sysctlbyname("vfs.ffs.unlink", 0, 0,
653 			    &cmd, sizeof cmd) == -1) {
654 				printf(" (UNLINK FAILED: %s)\n",
655 				    strerror(errno));
656 				return (0);
657 			}
658 			printf(" (REMOVED)\n");
659 			return (0);
660 		}
661 		if (preen) {
662 			printf(" (REMOVED)\n");
663 			return (1);
664 		}
665 		return (reply("REMOVE"));
666 	}
667 	/*
668 	 * None of the first four cases above, so must be case (5).
669 	 * Eliminate the old name and make the new the name the parent.
670 	 */
671 	getpathname(oldname, inp->i_parent, inp->i_number);
672 	getpathname(newname, inp->i_number, inp->i_number);
673 	pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s", oldname,
674 	    newname);
675 	if (cursnapshot != 0) {
676 		/*
677 		 * We need to
678 		 *    setcwd(inp->i_parent);
679 		 *    unlink(last component of oldname pathname);
680 		 */
681 		cmd.value = inp->i_parent;
682 		if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
683 		    &cmd, sizeof cmd) == -1) {
684 			printf(" (IGNORED)\n");
685 			return (0);
686 		}
687 		if ((cp = strchr(oldname, '/')) == NULL) {
688 			printf(" (IGNORED)\n");
689 			return (0);
690 		}
691 		cmd.value = (intptr_t)(cp + 1);
692 		cmd.size = inp->i_number; /* verify same name */
693 		if (sysctlbyname("vfs.ffs.unlink", 0, 0,
694 		    &cmd, sizeof cmd) == -1) {
695 			printf(" (UNLINK FAILED: %s)\n",
696 			    strerror(errno));
697 			return (0);
698 		}
699 		printf(" (REMOVED)\n");
700 		inp->i_parent = idesc->id_number;  /* reparent to correct dir */
701 		return (0);
702 	}
703 	if (!preen && !reply("REMOVE"))
704 		return (0);
705 	memset(&dotdesc, 0, sizeof(struct inodesc));
706 	dotdesc.id_type = DATA;
707 	dotdesc.id_number = inp->i_parent; /* directory in which name appears */
708 	dotdesc.id_parent = inp->i_number; /* inode number in entry to delete */
709 	dotdesc.id_func = deleteentry;
710 	ginode(dotdesc.id_number, &ip);
711 	if ((ckinode(ip.i_dp, &dotdesc) & FOUND) && preen)
712 		printf(" (REMOVED)\n");
713 	irelse(&ip);
714 	inp->i_parent = idesc->id_number;  /* reparent to correct directory */
715 	inoinfo(inp->i_number)->ino_linkcnt++; /* name gone, return reference */
716 	return (0);
717 }
718 
719 static int
720 deleteentry(struct inodesc *idesc)
721 {
722 	struct direct *dirp = idesc->id_dirp;
723 
724 	if (idesc->id_entryno++ < 2 || dirp->d_ino != idesc->id_parent)
725 		return (KEEPON);
726 	dirp->d_ino = 0;
727 	return (ALTERED|STOP|FOUND);
728 }
729 
730 /*
731  * Routine to sort disk blocks.
732  */
733 static int
734 blksort(const void *arg1, const void *arg2)
735 {
736 
737 	return ((*(struct inoinfo * const *)arg1)->i_blks[0] -
738 		(*(struct inoinfo * const *)arg2)->i_blks[0]);
739 }
740