xref: /freebsd/sbin/fsck_ffs/pass2.c (revision ae1f3df43466466a21c7da0df93ecb58a3e53d74)
1 /*
2  * Copyright (c) 1980, 1986, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 4. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #if 0
31 #ifndef lint
32 static const char sccsid[] = "@(#)pass2.c	8.9 (Berkeley) 4/28/95";
33 #endif /* not lint */
34 #endif
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
37 
38 #include <sys/param.h>
39 #include <sys/sysctl.h>
40 
41 #include <ufs/ufs/dinode.h>
42 #include <ufs/ufs/dir.h>
43 #include <ufs/ffs/fs.h>
44 
45 #include <err.h>
46 #include <errno.h>
47 #include <stdint.h>
48 #include <string.h>
49 
50 #include "fsck.h"
51 
52 #define MINDIRSIZE	(sizeof (struct dirtemplate))
53 
54 static int fix_extraneous(struct inoinfo *, struct inodesc *);
55 static int deleteentry(struct inodesc *);
56 static int blksort(const void *, const void *);
57 static int pass2check(struct inodesc *);
58 
59 void
60 pass2(void)
61 {
62 	union dinode *dp;
63 	struct inoinfo **inpp, *inp;
64 	struct inoinfo **inpend;
65 	struct inodesc curino;
66 	union dinode dino;
67 	int i;
68 	char pathbuf[MAXPATHLEN + 1];
69 
70 	switch (inoinfo(ROOTINO)->ino_state) {
71 
72 	case USTATE:
73 		pfatal("ROOT INODE UNALLOCATED");
74 		if (reply("ALLOCATE") == 0) {
75 			ckfini(0);
76 			exit(EEXIT);
77 		}
78 		if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
79 			errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
80 		break;
81 
82 	case DCLEAR:
83 		pfatal("DUPS/BAD IN ROOT INODE");
84 		if (reply("REALLOCATE")) {
85 			freeino(ROOTINO);
86 			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
87 				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
88 			break;
89 		}
90 		if (reply("CONTINUE") == 0) {
91 			ckfini(0);
92 			exit(EEXIT);
93 		}
94 		break;
95 
96 	case FSTATE:
97 	case FCLEAR:
98 	case FZLINK:
99 		pfatal("ROOT INODE NOT DIRECTORY");
100 		if (reply("REALLOCATE")) {
101 			freeino(ROOTINO);
102 			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
103 				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
104 			break;
105 		}
106 		if (reply("FIX") == 0) {
107 			ckfini(0);
108 			exit(EEXIT);
109 		}
110 		dp = ginode(ROOTINO);
111 		DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
112 		DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
113 		inodirty();
114 		break;
115 
116 	case DSTATE:
117 	case DZLINK:
118 		break;
119 
120 	default:
121 		errx(EEXIT, "BAD STATE %d FOR ROOT INODE",
122 		    inoinfo(ROOTINO)->ino_state);
123 	}
124 	inoinfo(ROOTINO)->ino_state = DFOUND;
125 	inoinfo(WINO)->ino_state = FSTATE;
126 	inoinfo(WINO)->ino_type = DT_WHT;
127 	/*
128 	 * Sort the directory list into disk block order.
129 	 */
130 	qsort((char *)inpsort, (size_t)inplast, sizeof *inpsort, blksort);
131 	/*
132 	 * Check the integrity of each directory.
133 	 */
134 	memset(&curino, 0, sizeof(struct inodesc));
135 	curino.id_type = DATA;
136 	curino.id_func = pass2check;
137 	inpend = &inpsort[inplast];
138 	for (inpp = inpsort; inpp < inpend; inpp++) {
139 		if (got_siginfo) {
140 			printf("%s: phase 2: dir %td of %d (%d%%)\n", cdevname,
141 			    inpp - inpsort, (int)inplast,
142 			    (int)((inpp - inpsort) * 100 / inplast));
143 			got_siginfo = 0;
144 		}
145 		if (got_sigalarm) {
146 			setproctitle("%s p2 %d%%", cdevname,
147 			    (int)((inpp - inpsort) * 100 / inplast));
148 			got_sigalarm = 0;
149 		}
150 		inp = *inpp;
151 		if (inp->i_isize == 0)
152 			continue;
153 		if (inp->i_isize < MINDIRSIZE) {
154 			direrror(inp->i_number, "DIRECTORY TOO SHORT");
155 			inp->i_isize = roundup(MINDIRSIZE, DIRBLKSIZ);
156 			if (reply("FIX") == 1) {
157 				dp = ginode(inp->i_number);
158 				DIP_SET(dp, di_size, inp->i_isize);
159 				inodirty();
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 				dp = ginode(inp->i_number);
176 				DIP_SET(dp, di_size,
177 				    roundup(inp->i_isize, DIRBLKSIZ));
178 				inodirty();
179 			}
180 		}
181 		dp = &dino;
182 		memset(dp, 0, sizeof(struct ufs2_dinode));
183 		DIP_SET(dp, di_mode, IFDIR);
184 		DIP_SET(dp, di_size, inp->i_isize);
185 		for (i = 0;
186 		     i < (inp->i_numblks<NDADDR ? inp->i_numblks : NDADDR);
187 		     i++)
188 			DIP_SET(dp, di_db[i], inp->i_blks[i]);
189 		if (inp->i_numblks > NDADDR)
190 			for (i = 0; i < NIADDR; i++)
191 				DIP_SET(dp, di_ib[i], inp->i_blks[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 		if (inp->i_dotdot == inp->i_parent ||
208 		    inp->i_dotdot == (ino_t)-1)
209 			continue;
210 		if (inp->i_dotdot == 0) {
211 			inp->i_dotdot = inp->i_parent;
212 			fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
213 			if (reply("FIX") == 0)
214 				continue;
215 			(void)makeentry(inp->i_number, inp->i_parent, "..");
216 			inoinfo(inp->i_parent)->ino_linkcnt--;
217 			continue;
218 		}
219 		/*
220 		 * Here we have:
221 		 *    inp->i_number is directory with bad ".." in it.
222 		 *    inp->i_dotdot is current value of "..".
223 		 *    inp->i_parent is directory to which ".." should point.
224 		 */
225 		getpathname(pathbuf, inp->i_parent, inp->i_number);
226 		printf("BAD INODE NUMBER FOR '..' in DIR I=%ju (%s)\n",
227 		    (uintmax_t)inp->i_number, pathbuf);
228 		getpathname(pathbuf, inp->i_dotdot, inp->i_dotdot);
229 		printf("CURRENTLY POINTS TO I=%ju (%s), ",
230 		    (uintmax_t)inp->i_dotdot, pathbuf);
231 		getpathname(pathbuf, inp->i_parent, inp->i_parent);
232 		printf("SHOULD POINT TO I=%ju (%s)",
233 		    (uintmax_t)inp->i_parent, pathbuf);
234 		if (cursnapshot != 0) {
235 			/*
236 			 * We need to:
237 			 *    setcwd(inp->i_number);
238 			 *    setdotdot(inp->i_dotdot, inp->i_parent);
239 			 */
240 			cmd.value = inp->i_number;
241 			if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
242 			    &cmd, sizeof cmd) == -1) {
243 				/* kernel lacks support for these functions */
244 				printf(" (IGNORED)\n");
245 				continue;
246 			}
247 			cmd.value = inp->i_dotdot; /* verify same value */
248 			cmd.size = inp->i_parent;  /* new parent */
249 			if (sysctlbyname("vfs.ffs.setdotdot", 0, 0,
250 			    &cmd, sizeof cmd) == -1) {
251 				printf(" (FIX FAILED: %s)\n", strerror(errno));
252 				continue;
253 			}
254 			printf(" (FIXED)\n");
255 			inoinfo(inp->i_parent)->ino_linkcnt--;
256 			inp->i_dotdot = inp->i_parent;
257 			continue;
258 		}
259 		if (preen)
260 			printf(" (FIXED)\n");
261 		else if (reply("FIX") == 0)
262 			continue;
263 		inoinfo(inp->i_dotdot)->ino_linkcnt++;
264 		inoinfo(inp->i_parent)->ino_linkcnt--;
265 		inp->i_dotdot = inp->i_parent;
266 		(void)changeino(inp->i_number, "..", inp->i_parent);
267 	}
268 	/*
269 	 * Mark all the directories that can be found from the root.
270 	 */
271 	propagate();
272 }
273 
274 static int
275 pass2check(struct inodesc *idesc)
276 {
277 	struct direct *dirp = idesc->id_dirp;
278 	char dirname[MAXPATHLEN + 1];
279 	struct inoinfo *inp;
280 	int n, entrysize, ret = 0;
281 	union dinode *dp;
282 	const char *errmsg;
283 	struct direct proto;
284 
285 	/*
286 	 * check for "."
287 	 */
288 	if (dirp->d_ino > maxino)
289 		goto chk2;
290 	if (idesc->id_entryno != 0)
291 		goto chk1;
292 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
293 		if (dirp->d_ino != idesc->id_number) {
294 			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
295 			dirp->d_ino = idesc->id_number;
296 			if (reply("FIX") == 1)
297 				ret |= ALTERED;
298 		}
299 		if (dirp->d_type != DT_DIR) {
300 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
301 			dirp->d_type = DT_DIR;
302 			if (reply("FIX") == 1)
303 				ret |= ALTERED;
304 		}
305 		goto chk1;
306 	}
307 	direrror(idesc->id_number, "MISSING '.'");
308 	proto.d_ino = idesc->id_number;
309 	proto.d_type = DT_DIR;
310 	proto.d_namlen = 1;
311 	(void)strcpy(proto.d_name, ".");
312 	entrysize = DIRSIZ(0, &proto);
313 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
314 		pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
315 			dirp->d_name);
316 	} else if (dirp->d_reclen < entrysize) {
317 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
318 	} else if (dirp->d_reclen < 2 * entrysize) {
319 		proto.d_reclen = dirp->d_reclen;
320 		memmove(dirp, &proto, (size_t)entrysize);
321 		if (reply("FIX") == 1)
322 			ret |= ALTERED;
323 	} else {
324 		n = dirp->d_reclen - entrysize;
325 		proto.d_reclen = entrysize;
326 		memmove(dirp, &proto, (size_t)entrysize);
327 		idesc->id_entryno++;
328 		inoinfo(dirp->d_ino)->ino_linkcnt--;
329 		dirp = (struct direct *)((char *)(dirp) + entrysize);
330 		memset(dirp, 0, (size_t)n);
331 		dirp->d_reclen = n;
332 		if (reply("FIX") == 1)
333 			ret |= ALTERED;
334 	}
335 chk1:
336 	if (idesc->id_entryno > 1)
337 		goto chk2;
338 	inp = getinoinfo(idesc->id_number);
339 	proto.d_ino = inp->i_parent;
340 	proto.d_type = DT_DIR;
341 	proto.d_namlen = 2;
342 	(void)strcpy(proto.d_name, "..");
343 	entrysize = DIRSIZ(0, &proto);
344 	if (idesc->id_entryno == 0) {
345 		n = DIRSIZ(0, dirp);
346 		if (dirp->d_reclen < n + entrysize)
347 			goto chk2;
348 		proto.d_reclen = dirp->d_reclen - n;
349 		dirp->d_reclen = n;
350 		idesc->id_entryno++;
351 		inoinfo(dirp->d_ino)->ino_linkcnt--;
352 		dirp = (struct direct *)((char *)(dirp) + n);
353 		memset(dirp, 0, (size_t)proto.d_reclen);
354 		dirp->d_reclen = proto.d_reclen;
355 	}
356 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
357 		inp->i_dotdot = dirp->d_ino;
358 		if (dirp->d_type != DT_DIR) {
359 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
360 			dirp->d_type = DT_DIR;
361 			if (reply("FIX") == 1)
362 				ret |= ALTERED;
363 		}
364 		goto chk2;
365 	}
366 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0) {
367 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
368 		pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
369 			dirp->d_name);
370 		inp->i_dotdot = (ino_t)-1;
371 	} else if (dirp->d_reclen < entrysize) {
372 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
373 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
374 		inp->i_dotdot = (ino_t)-1;
375 	} else if (inp->i_parent != 0) {
376 		/*
377 		 * We know the parent, so fix now.
378 		 */
379 		inp->i_dotdot = inp->i_parent;
380 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
381 		proto.d_reclen = dirp->d_reclen;
382 		memmove(dirp, &proto, (size_t)entrysize);
383 		if (reply("FIX") == 1)
384 			ret |= ALTERED;
385 	}
386 	idesc->id_entryno++;
387 	if (dirp->d_ino != 0)
388 		inoinfo(dirp->d_ino)->ino_linkcnt--;
389 	return (ret|KEEPON);
390 chk2:
391 	if (dirp->d_ino == 0)
392 		return (ret|KEEPON);
393 	if (dirp->d_namlen <= 2 &&
394 	    dirp->d_name[0] == '.' &&
395 	    idesc->id_entryno >= 2) {
396 		if (dirp->d_namlen == 1) {
397 			direrror(idesc->id_number, "EXTRA '.' ENTRY");
398 			dirp->d_ino = 0;
399 			if (reply("FIX") == 1)
400 				ret |= ALTERED;
401 			return (KEEPON | ret);
402 		}
403 		if (dirp->d_name[1] == '.') {
404 			direrror(idesc->id_number, "EXTRA '..' ENTRY");
405 			dirp->d_ino = 0;
406 			if (reply("FIX") == 1)
407 				ret |= ALTERED;
408 			return (KEEPON | ret);
409 		}
410 	}
411 	idesc->id_entryno++;
412 	n = 0;
413 	if (dirp->d_ino > maxino) {
414 		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
415 		n = reply("REMOVE");
416 	} else if (((dirp->d_ino == WINO && dirp->d_type != DT_WHT) ||
417 		    (dirp->d_ino != WINO && dirp->d_type == DT_WHT))) {
418 		fileerror(idesc->id_number, dirp->d_ino, "BAD WHITEOUT ENTRY");
419 		dirp->d_ino = WINO;
420 		dirp->d_type = DT_WHT;
421 		if (reply("FIX") == 1)
422 			ret |= ALTERED;
423 	} else {
424 again:
425 		switch (inoinfo(dirp->d_ino)->ino_state) {
426 		case USTATE:
427 			if (idesc->id_entryno <= 2)
428 				break;
429 			fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
430 			n = reply("REMOVE");
431 			break;
432 
433 		case DCLEAR:
434 		case FCLEAR:
435 			if (idesc->id_entryno <= 2)
436 				break;
437 			if (inoinfo(dirp->d_ino)->ino_state == FCLEAR)
438 				errmsg = "DUP/BAD";
439 			else if (!preen && !usedsoftdep)
440 				errmsg = "ZERO LENGTH DIRECTORY";
441 			else if (cursnapshot == 0) {
442 				n = 1;
443 				break;
444 			} else {
445 				getpathname(dirname, idesc->id_number,
446 				    dirp->d_ino);
447 				pwarn("ZERO LENGTH DIRECTORY %s I=%ju",
448 				    dirname, (uintmax_t)dirp->d_ino);
449 				/*
450 				 * We need to:
451 				 *    setcwd(idesc->id_parent);
452 				 *    rmdir(dirp->d_name);
453 				 */
454 				cmd.value = idesc->id_number;
455 				if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
456 				    &cmd, sizeof cmd) == -1) {
457 					/* kernel lacks support */
458 					printf(" (IGNORED)\n");
459 					n = 1;
460 					break;
461 				}
462 				if (rmdir(dirp->d_name) == -1) {
463 					printf(" (REMOVAL FAILED: %s)\n",
464 					    strerror(errno));
465 					n = 1;
466 					break;
467 				}
468 				/* ".." reference to parent is removed */
469 				inoinfo(idesc->id_number)->ino_linkcnt--;
470 				printf(" (REMOVED)\n");
471 				break;
472 			}
473 			fileerror(idesc->id_number, dirp->d_ino, errmsg);
474 			if ((n = reply("REMOVE")) == 1)
475 				break;
476 			dp = ginode(dirp->d_ino);
477 			inoinfo(dirp->d_ino)->ino_state =
478 			   (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
479 			inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
480 			goto again;
481 
482 		case DSTATE:
483 		case DZLINK:
484 			if (inoinfo(idesc->id_number)->ino_state == DFOUND)
485 				inoinfo(dirp->d_ino)->ino_state = DFOUND;
486 			/* FALLTHROUGH */
487 
488 		case DFOUND:
489 			inp = getinoinfo(dirp->d_ino);
490 			if (idesc->id_entryno > 2) {
491 				if (inp->i_parent == 0)
492 					inp->i_parent = idesc->id_number;
493 				else if ((n = fix_extraneous(inp, idesc)) == 1)
494 					break;
495 			}
496 			/* FALLTHROUGH */
497 
498 		case FSTATE:
499 		case FZLINK:
500 			if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
501 				fileerror(idesc->id_number, dirp->d_ino,
502 				    "BAD TYPE VALUE");
503 				dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
504 				if (reply("FIX") == 1)
505 					ret |= ALTERED;
506 			}
507 			inoinfo(dirp->d_ino)->ino_linkcnt--;
508 			break;
509 
510 		default:
511 			errx(EEXIT, "BAD STATE %d FOR INODE I=%ju",
512 			    inoinfo(dirp->d_ino)->ino_state,
513 			    (uintmax_t)dirp->d_ino);
514 		}
515 	}
516 	if (n == 0)
517 		return (ret|KEEPON);
518 	dirp->d_ino = 0;
519 	return (ret|KEEPON|ALTERED);
520 }
521 
522 static int
523 fix_extraneous(struct inoinfo *inp, struct inodesc *idesc)
524 {
525 	char *cp;
526 	struct inodesc dotdesc;
527 	char oldname[MAXPATHLEN + 1];
528 	char newname[MAXPATHLEN + 1];
529 
530 	/*
531 	 * If we have not yet found "..", look it up now so we know
532 	 * which inode the directory itself believes is its parent.
533 	 */
534 	if (inp->i_dotdot == 0) {
535 		memset(&dotdesc, 0, sizeof(struct inodesc));
536 		dotdesc.id_type = DATA;
537 		dotdesc.id_number = idesc->id_dirp->d_ino;
538 		dotdesc.id_func = findino;
539 		dotdesc.id_name = strdup("..");
540 		if ((ckinode(ginode(dotdesc.id_number), &dotdesc) & FOUND))
541 			inp->i_dotdot = dotdesc.id_parent;
542 	}
543 	/*
544 	 * We have the previously found old name (inp->i_parent) and the
545 	 * just found new name (idesc->id_number). We have five cases:
546 	 * 1)  ".." is missing - can remove either name, choose to delete
547 	 *     new one and let fsck create ".." pointing to old name.
548 	 * 2) Both new and old are in same directory, choose to delete
549 	 *    the new name and let fsck fix ".." if it is wrong.
550 	 * 3) ".." does not point to the new name, so delete it and let
551 	 *    fsck fix ".." to point to the old one if it is wrong.
552 	 * 4) ".." points to the old name only, so delete the new one.
553 	 * 5) ".." points to the new name only, so delete the old one.
554 	 *
555 	 * For cases 1-4 we eliminate the new name;
556 	 * for case 5 we eliminate the old name.
557 	 */
558 	if (inp->i_dotdot == 0 ||		    /* Case 1 */
559 	    idesc->id_number == inp->i_parent ||    /* Case 2 */
560 	    inp->i_dotdot != idesc->id_number ||    /* Case 3 */
561 	    inp->i_dotdot == inp->i_parent) {	    /* Case 4 */
562 		getpathname(newname, idesc->id_number, idesc->id_number);
563 		if (strcmp(newname, "/") != 0)
564 			strcat (newname, "/");
565 		strcat(newname, idesc->id_dirp->d_name);
566 		getpathname(oldname, inp->i_number, inp->i_number);
567 		pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s",
568 		    newname, oldname);
569 		if (cursnapshot != 0) {
570 			/*
571 			 * We need to
572 			 *    setcwd(idesc->id_number);
573 			 *    unlink(idesc->id_dirp->d_name);
574 			 */
575 			cmd.value = idesc->id_number;
576 			if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
577 			    &cmd, sizeof cmd) == -1) {
578 				printf(" (IGNORED)\n");
579 				return (0);
580 			}
581 			cmd.value = (intptr_t)idesc->id_dirp->d_name;
582 			cmd.size = inp->i_number; /* verify same name */
583 			if (sysctlbyname("vfs.ffs.unlink", 0, 0,
584 			    &cmd, sizeof cmd) == -1) {
585 				printf(" (UNLINK FAILED: %s)\n",
586 				    strerror(errno));
587 				return (0);
588 			}
589 			printf(" (REMOVED)\n");
590 			return (0);
591 		}
592 		if (preen) {
593 			printf(" (REMOVED)\n");
594 			return (1);
595 		}
596 		return (reply("REMOVE"));
597 	}
598 	/*
599 	 * None of the first four cases above, so must be case (5).
600 	 * Eliminate the old name and make the new the name the parent.
601 	 */
602 	getpathname(oldname, inp->i_parent, inp->i_number);
603 	getpathname(newname, inp->i_number, inp->i_number);
604 	pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s", oldname,
605 	    newname);
606 	if (cursnapshot != 0) {
607 		/*
608 		 * We need to
609 		 *    setcwd(inp->i_parent);
610 		 *    unlink(last component of oldname pathname);
611 		 */
612 		cmd.value = inp->i_parent;
613 		if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
614 		    &cmd, sizeof cmd) == -1) {
615 			printf(" (IGNORED)\n");
616 			return (0);
617 		}
618 		if ((cp = strchr(oldname, '/')) == NULL) {
619 			printf(" (IGNORED)\n");
620 			return (0);
621 		}
622 		cmd.value = (intptr_t)(cp + 1);
623 		cmd.size = inp->i_number; /* verify same name */
624 		if (sysctlbyname("vfs.ffs.unlink", 0, 0,
625 		    &cmd, sizeof cmd) == -1) {
626 			printf(" (UNLINK FAILED: %s)\n",
627 			    strerror(errno));
628 			return (0);
629 		}
630 		printf(" (REMOVED)\n");
631 		inp->i_parent = idesc->id_number;  /* reparent to correct dir */
632 		return (0);
633 	}
634 	if (!preen && !reply("REMOVE"))
635 		return (0);
636 	memset(&dotdesc, 0, sizeof(struct inodesc));
637 	dotdesc.id_type = DATA;
638 	dotdesc.id_number = inp->i_parent; /* directory in which name appears */
639 	dotdesc.id_parent = inp->i_number; /* inode number in entry to delete */
640 	dotdesc.id_func = deleteentry;
641 	if ((ckinode(ginode(dotdesc.id_number), &dotdesc) & FOUND) && preen)
642 		printf(" (REMOVED)\n");
643 	inp->i_parent = idesc->id_number;  /* reparent to correct directory */
644 	inoinfo(inp->i_number)->ino_linkcnt++; /* name gone, return reference */
645 	return (0);
646 }
647 
648 static int
649 deleteentry(struct inodesc *idesc)
650 {
651 	struct direct *dirp = idesc->id_dirp;
652 
653 	if (idesc->id_entryno++ < 2 || dirp->d_ino != idesc->id_parent)
654 		return (KEEPON);
655 	dirp->d_ino = 0;
656 	return (ALTERED|STOP|FOUND);
657 }
658 
659 /*
660  * Routine to sort disk blocks.
661  */
662 static int
663 blksort(const void *arg1, const void *arg2)
664 {
665 
666 	return ((*(struct inoinfo * const *)arg1)->i_blks[0] -
667 		(*(struct inoinfo * const *)arg2)->i_blks[0]);
668 }
669