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