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