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