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