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