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