xref: /freebsd/sbin/fsck_ffs/pass2.c (revision 273c26a3c3bea87a241d6879abd4f991db180bf0)
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; i < MIN(inp->i_numblks, NDADDR); i++)
186 			DIP_SET(dp, di_db[i], inp->i_blks[i]);
187 		if (inp->i_numblks > NDADDR)
188 			for (i = 0; i < NIADDR; i++)
189 				DIP_SET(dp, di_ib[i], inp->i_blks[NDADDR + i]);
190 		curino.id_number = inp->i_number;
191 		curino.id_parent = inp->i_parent;
192 		(void)ckinode(dp, &curino);
193 	}
194 	/*
195 	 * Now that the parents of all directories have been found,
196 	 * make another pass to verify the value of `..'
197 	 */
198 	for (inpp = inpsort; inpp < inpend; inpp++) {
199 		inp = *inpp;
200 		if (inp->i_parent == 0 || inp->i_isize == 0)
201 			continue;
202 		if (inoinfo(inp->i_parent)->ino_state == DFOUND &&
203 		    INO_IS_DUNFOUND(inp->i_number))
204 			inoinfo(inp->i_number)->ino_state = DFOUND;
205 		if (inp->i_dotdot == inp->i_parent ||
206 		    inp->i_dotdot == (ino_t)-1)
207 			continue;
208 		if (inp->i_dotdot == 0) {
209 			inp->i_dotdot = inp->i_parent;
210 			fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
211 			if (reply("FIX") == 0)
212 				continue;
213 			(void)makeentry(inp->i_number, inp->i_parent, "..");
214 			inoinfo(inp->i_parent)->ino_linkcnt--;
215 			continue;
216 		}
217 		/*
218 		 * Here we have:
219 		 *    inp->i_number is directory with bad ".." in it.
220 		 *    inp->i_dotdot is current value of "..".
221 		 *    inp->i_parent is directory to which ".." should point.
222 		 */
223 		getpathname(pathbuf, inp->i_parent, inp->i_number);
224 		printf("BAD INODE NUMBER FOR '..' in DIR I=%ju (%s)\n",
225 		    (uintmax_t)inp->i_number, pathbuf);
226 		getpathname(pathbuf, inp->i_dotdot, inp->i_dotdot);
227 		printf("CURRENTLY POINTS TO I=%ju (%s), ",
228 		    (uintmax_t)inp->i_dotdot, pathbuf);
229 		getpathname(pathbuf, inp->i_parent, inp->i_parent);
230 		printf("SHOULD POINT TO I=%ju (%s)",
231 		    (uintmax_t)inp->i_parent, pathbuf);
232 		if (cursnapshot != 0) {
233 			/*
234 			 * We need to:
235 			 *    setcwd(inp->i_number);
236 			 *    setdotdot(inp->i_dotdot, inp->i_parent);
237 			 */
238 			cmd.value = inp->i_number;
239 			if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
240 			    &cmd, sizeof cmd) == -1) {
241 				/* kernel lacks support for these functions */
242 				printf(" (IGNORED)\n");
243 				continue;
244 			}
245 			cmd.value = inp->i_dotdot; /* verify same value */
246 			cmd.size = inp->i_parent;  /* new parent */
247 			if (sysctlbyname("vfs.ffs.setdotdot", 0, 0,
248 			    &cmd, sizeof cmd) == -1) {
249 				printf(" (FIX FAILED: %s)\n", strerror(errno));
250 				continue;
251 			}
252 			printf(" (FIXED)\n");
253 			inoinfo(inp->i_parent)->ino_linkcnt--;
254 			inp->i_dotdot = inp->i_parent;
255 			continue;
256 		}
257 		if (preen)
258 			printf(" (FIXED)\n");
259 		else if (reply("FIX") == 0)
260 			continue;
261 		inoinfo(inp->i_dotdot)->ino_linkcnt++;
262 		inoinfo(inp->i_parent)->ino_linkcnt--;
263 		inp->i_dotdot = inp->i_parent;
264 		(void)changeino(inp->i_number, "..", inp->i_parent);
265 	}
266 	/*
267 	 * Mark all the directories that can be found from the root.
268 	 */
269 	propagate();
270 }
271 
272 static int
273 pass2check(struct inodesc *idesc)
274 {
275 	struct direct *dirp = idesc->id_dirp;
276 	char dirname[MAXPATHLEN + 1];
277 	struct inoinfo *inp;
278 	int n, entrysize, ret = 0;
279 	union dinode *dp;
280 	const char *errmsg;
281 	struct direct proto;
282 
283 	/*
284 	 * check for "."
285 	 */
286 	if (dirp->d_ino > maxino)
287 		goto chk2;
288 	if (idesc->id_entryno != 0)
289 		goto chk1;
290 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
291 		if (dirp->d_ino != idesc->id_number) {
292 			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
293 			dirp->d_ino = idesc->id_number;
294 			if (reply("FIX") == 1)
295 				ret |= ALTERED;
296 		}
297 		if (dirp->d_type != DT_DIR) {
298 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
299 			dirp->d_type = DT_DIR;
300 			if (reply("FIX") == 1)
301 				ret |= ALTERED;
302 		}
303 		goto chk1;
304 	}
305 	direrror(idesc->id_number, "MISSING '.'");
306 	proto.d_ino = idesc->id_number;
307 	proto.d_type = DT_DIR;
308 	proto.d_namlen = 1;
309 	(void)strcpy(proto.d_name, ".");
310 	entrysize = DIRSIZ(0, &proto);
311 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
312 		pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
313 			dirp->d_name);
314 	} else if (dirp->d_reclen < entrysize) {
315 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
316 	} else if (dirp->d_reclen < 2 * entrysize) {
317 		proto.d_reclen = dirp->d_reclen;
318 		memmove(dirp, &proto, (size_t)entrysize);
319 		if (reply("FIX") == 1)
320 			ret |= ALTERED;
321 	} else {
322 		n = dirp->d_reclen - entrysize;
323 		proto.d_reclen = entrysize;
324 		memmove(dirp, &proto, (size_t)entrysize);
325 		idesc->id_entryno++;
326 		inoinfo(dirp->d_ino)->ino_linkcnt--;
327 		dirp = (struct direct *)((char *)(dirp) + entrysize);
328 		memset(dirp, 0, (size_t)n);
329 		dirp->d_reclen = n;
330 		if (reply("FIX") == 1)
331 			ret |= ALTERED;
332 	}
333 chk1:
334 	if (idesc->id_entryno > 1)
335 		goto chk2;
336 	inp = getinoinfo(idesc->id_number);
337 	proto.d_ino = inp->i_parent;
338 	proto.d_type = DT_DIR;
339 	proto.d_namlen = 2;
340 	(void)strcpy(proto.d_name, "..");
341 	entrysize = DIRSIZ(0, &proto);
342 	if (idesc->id_entryno == 0) {
343 		n = DIRSIZ(0, dirp);
344 		if (dirp->d_reclen < n + entrysize)
345 			goto chk2;
346 		proto.d_reclen = dirp->d_reclen - n;
347 		dirp->d_reclen = n;
348 		idesc->id_entryno++;
349 		inoinfo(dirp->d_ino)->ino_linkcnt--;
350 		dirp = (struct direct *)((char *)(dirp) + n);
351 		memset(dirp, 0, (size_t)proto.d_reclen);
352 		dirp->d_reclen = proto.d_reclen;
353 	}
354 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") == 0) {
355 		inp->i_dotdot = dirp->d_ino;
356 		if (dirp->d_type != DT_DIR) {
357 			direrror(idesc->id_number, "BAD TYPE VALUE FOR '..'");
358 			dirp->d_type = DT_DIR;
359 			if (reply("FIX") == 1)
360 				ret |= ALTERED;
361 		}
362 		goto chk2;
363 	}
364 	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0) {
365 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
366 		pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n",
367 			dirp->d_name);
368 		inp->i_dotdot = (ino_t)-1;
369 	} else if (dirp->d_reclen < entrysize) {
370 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
371 		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n");
372 		inp->i_dotdot = (ino_t)-1;
373 	} else if (inp->i_parent != 0) {
374 		/*
375 		 * We know the parent, so fix now.
376 		 */
377 		inp->i_dotdot = inp->i_parent;
378 		fileerror(inp->i_parent, idesc->id_number, "MISSING '..'");
379 		proto.d_reclen = dirp->d_reclen;
380 		memmove(dirp, &proto, (size_t)entrysize);
381 		if (reply("FIX") == 1)
382 			ret |= ALTERED;
383 	}
384 	idesc->id_entryno++;
385 	if (dirp->d_ino != 0)
386 		inoinfo(dirp->d_ino)->ino_linkcnt--;
387 	return (ret|KEEPON);
388 chk2:
389 	if (dirp->d_ino == 0)
390 		return (ret|KEEPON);
391 	if (dirp->d_namlen <= 2 &&
392 	    dirp->d_name[0] == '.' &&
393 	    idesc->id_entryno >= 2) {
394 		if (dirp->d_namlen == 1) {
395 			direrror(idesc->id_number, "EXTRA '.' ENTRY");
396 			dirp->d_ino = 0;
397 			if (reply("FIX") == 1)
398 				ret |= ALTERED;
399 			return (KEEPON | ret);
400 		}
401 		if (dirp->d_name[1] == '.') {
402 			direrror(idesc->id_number, "EXTRA '..' ENTRY");
403 			dirp->d_ino = 0;
404 			if (reply("FIX") == 1)
405 				ret |= ALTERED;
406 			return (KEEPON | ret);
407 		}
408 	}
409 	idesc->id_entryno++;
410 	n = 0;
411 	if (dirp->d_ino > maxino) {
412 		fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE");
413 		n = reply("REMOVE");
414 	} else if (((dirp->d_ino == WINO && dirp->d_type != DT_WHT) ||
415 		    (dirp->d_ino != WINO && dirp->d_type == DT_WHT))) {
416 		fileerror(idesc->id_number, dirp->d_ino, "BAD WHITEOUT ENTRY");
417 		dirp->d_ino = WINO;
418 		dirp->d_type = DT_WHT;
419 		if (reply("FIX") == 1)
420 			ret |= ALTERED;
421 	} else {
422 again:
423 		switch (inoinfo(dirp->d_ino)->ino_state) {
424 		case USTATE:
425 			if (idesc->id_entryno <= 2)
426 				break;
427 			fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED");
428 			n = reply("REMOVE");
429 			break;
430 
431 		case DCLEAR:
432 		case FCLEAR:
433 			if (idesc->id_entryno <= 2)
434 				break;
435 			if (inoinfo(dirp->d_ino)->ino_state == FCLEAR)
436 				errmsg = "DUP/BAD";
437 			else if (!preen && !usedsoftdep)
438 				errmsg = "ZERO LENGTH DIRECTORY";
439 			else if (cursnapshot == 0) {
440 				n = 1;
441 				break;
442 			} else {
443 				getpathname(dirname, idesc->id_number,
444 				    dirp->d_ino);
445 				pwarn("ZERO LENGTH DIRECTORY %s I=%ju",
446 				    dirname, (uintmax_t)dirp->d_ino);
447 				/*
448 				 * We need to:
449 				 *    setcwd(idesc->id_parent);
450 				 *    rmdir(dirp->d_name);
451 				 */
452 				cmd.value = idesc->id_number;
453 				if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
454 				    &cmd, sizeof cmd) == -1) {
455 					/* kernel lacks support */
456 					printf(" (IGNORED)\n");
457 					n = 1;
458 					break;
459 				}
460 				if (rmdir(dirp->d_name) == -1) {
461 					printf(" (REMOVAL FAILED: %s)\n",
462 					    strerror(errno));
463 					n = 1;
464 					break;
465 				}
466 				/* ".." reference to parent is removed */
467 				inoinfo(idesc->id_number)->ino_linkcnt--;
468 				printf(" (REMOVED)\n");
469 				break;
470 			}
471 			fileerror(idesc->id_number, dirp->d_ino, errmsg);
472 			if ((n = reply("REMOVE")) == 1)
473 				break;
474 			dp = ginode(dirp->d_ino);
475 			inoinfo(dirp->d_ino)->ino_state =
476 			   (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
477 			inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
478 			goto again;
479 
480 		case DSTATE:
481 		case DZLINK:
482 			if (inoinfo(idesc->id_number)->ino_state == DFOUND)
483 				inoinfo(dirp->d_ino)->ino_state = DFOUND;
484 			/* FALLTHROUGH */
485 
486 		case DFOUND:
487 			inp = getinoinfo(dirp->d_ino);
488 			if (idesc->id_entryno > 2) {
489 				if (inp->i_parent == 0)
490 					inp->i_parent = idesc->id_number;
491 				else if ((n = fix_extraneous(inp, idesc)) == 1)
492 					break;
493 			}
494 			/* FALLTHROUGH */
495 
496 		case FSTATE:
497 		case FZLINK:
498 			if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
499 				fileerror(idesc->id_number, dirp->d_ino,
500 				    "BAD TYPE VALUE");
501 				dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
502 				if (reply("FIX") == 1)
503 					ret |= ALTERED;
504 			}
505 			inoinfo(dirp->d_ino)->ino_linkcnt--;
506 			break;
507 
508 		default:
509 			errx(EEXIT, "BAD STATE %d FOR INODE I=%ju",
510 			    inoinfo(dirp->d_ino)->ino_state,
511 			    (uintmax_t)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 = strchr(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