xref: /freebsd/sbin/fsck_ffs/pass2.c (revision 9fd69f37d28cfd7438cac3eeb45fe9dd46b4d7dd)
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 	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 {
440 				n = 1;
441 				break;
442 			}
443 			fileerror(idesc->id_number, dirp->d_ino, errmsg);
444 			if ((n = reply("REMOVE")) == 1)
445 				break;
446 			dp = ginode(dirp->d_ino);
447 			inoinfo(dirp->d_ino)->ino_state =
448 			   (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
449 			inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
450 			goto again;
451 
452 		case DSTATE:
453 		case DZLINK:
454 			if (inoinfo(idesc->id_number)->ino_state == DFOUND)
455 				inoinfo(dirp->d_ino)->ino_state = DFOUND;
456 			/* FALLTHROUGH */
457 
458 		case DFOUND:
459 			inp = getinoinfo(dirp->d_ino);
460 			if (idesc->id_entryno > 2) {
461 				if (inp->i_parent == 0)
462 					inp->i_parent = idesc->id_number;
463 				else if ((n = fix_extraneous(inp, idesc)) == 1)
464 					break;
465 			}
466 			/* FALLTHROUGH */
467 
468 		case FSTATE:
469 		case FZLINK:
470 			if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
471 				fileerror(idesc->id_number, dirp->d_ino,
472 				    "BAD TYPE VALUE");
473 				dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
474 				if (reply("FIX") == 1)
475 					ret |= ALTERED;
476 			}
477 			inoinfo(dirp->d_ino)->ino_linkcnt--;
478 			break;
479 
480 		default:
481 			errx(EEXIT, "BAD STATE %d FOR INODE I=%d",
482 			    inoinfo(dirp->d_ino)->ino_state, dirp->d_ino);
483 		}
484 	}
485 	if (n == 0)
486 		return (ret|KEEPON);
487 	dirp->d_ino = 0;
488 	return (ret|KEEPON|ALTERED);
489 }
490 
491 static int
492 fix_extraneous(struct inoinfo *inp, struct inodesc *idesc)
493 {
494 	char *cp;
495 	struct inodesc dotdesc;
496 	char oldname[MAXPATHLEN + 1];
497 	char newname[MAXPATHLEN + 1];
498 
499 	/*
500 	 * If we have not yet found "..", look it up now so we know
501 	 * which inode the directory itself believes is its parent.
502 	 */
503 	if (inp->i_dotdot == 0) {
504 		memset(&dotdesc, 0, sizeof(struct inodesc));
505 		dotdesc.id_type = DATA;
506 		dotdesc.id_number = idesc->id_dirp->d_ino;
507 		dotdesc.id_func = findino;
508 		dotdesc.id_name = strdup("..");
509 		if ((ckinode(ginode(dotdesc.id_number), &dotdesc) & FOUND))
510 			inp->i_dotdot = dotdesc.id_parent;
511 	}
512 	/*
513 	 * We have the previously found old name (inp->i_parent) and the
514 	 * just found new name (idesc->id_number). We have five cases:
515 	 * 1)  ".." is missing - can remove either name, choose to delete
516 	 *     new one and let fsck create ".." pointing to old name.
517 	 * 2) Both new and old are in same directory, choose to delete
518 	 *    the new name and let fsck fix ".." if it is wrong.
519 	 * 3) ".." does not point to the new name, so delete it and let
520 	 *    fsck fix ".." to point to the old one if it is wrong.
521 	 * 4) ".." points to the old name only, so delete the new one.
522 	 * 5) ".." points to the new name only, so delete the old one.
523 	 *
524 	 * For cases 1-4 we eliminate the new name;
525 	 * for case 5 we eliminate the old name.
526 	 */
527 	if (inp->i_dotdot == 0 ||		    /* Case 1 */
528 	    idesc->id_number == inp->i_parent ||    /* Case 2 */
529 	    inp->i_dotdot != idesc->id_number ||    /* Case 3 */
530 	    inp->i_dotdot == inp->i_parent) {	    /* Case 4 */
531 		getpathname(newname, idesc->id_number, idesc->id_number);
532 		if (strcmp(newname, "/") != 0)
533 			strcat (newname, "/");
534 		strcat(newname, idesc->id_dirp->d_name);
535 		getpathname(oldname, inp->i_number, inp->i_number);
536 		pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s",
537 		    newname, oldname);
538 		if (cursnapshot != 0) {
539 			/*
540 			 * We need to
541 			 *    setcwd(idesc->id_number);
542 			 *    unlink(idesc->id_dirp->d_name);
543 			 */
544 			cmd.value = idesc->id_number;
545 			if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
546 			    &cmd, sizeof cmd) == -1) {
547 				printf(" (IGNORED)\n");
548 				return (0);
549 			}
550 			cmd.value = (intptr_t)idesc->id_dirp->d_name;
551 			cmd.size = inp->i_number; /* verify same name */
552 			if (sysctlbyname("vfs.ffs.unlink", 0, 0,
553 			    &cmd, sizeof cmd) == -1) {
554 				printf(" (UNLINK FAILED: %s)\n",
555 				    strerror(errno));
556 				return (0);
557 			}
558 			printf(" (REMOVED)\n");
559 			return (0);
560 		}
561 		if (preen) {
562 			printf(" (REMOVED)\n");
563 			return (1);
564 		}
565 		return (reply("REMOVE"));
566 	}
567 	/*
568 	 * None of the first four cases above, so must be case (5).
569 	 * Eliminate the old name and make the new the name the parent.
570 	 */
571 	getpathname(oldname, inp->i_parent, inp->i_number);
572 	getpathname(newname, inp->i_number, inp->i_number);
573 	pwarn("%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s", oldname,
574 	    newname);
575 	if (cursnapshot != 0) {
576 		/*
577 		 * We need to
578 		 *    setcwd(inp->i_parent);
579 		 *    unlink(last component of oldname pathname);
580 		 */
581 		cmd.value = inp->i_parent;
582 		if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
583 		    &cmd, sizeof cmd) == -1) {
584 			printf(" (IGNORED)\n");
585 			return (0);
586 		}
587 		if ((cp = rindex(oldname, '/')) == NULL) {
588 			printf(" (IGNORED)\n");
589 			return (0);
590 		}
591 		cmd.value = (intptr_t)(cp + 1);
592 		cmd.size = inp->i_number; /* verify same name */
593 		if (sysctlbyname("vfs.ffs.unlink", 0, 0,
594 		    &cmd, sizeof cmd) == -1) {
595 			printf(" (UNLINK FAILED: %s)\n",
596 			    strerror(errno));
597 			return (0);
598 		}
599 		printf(" (REMOVED)\n");
600 		inp->i_parent = idesc->id_number;  /* reparent to correct dir */
601 		return (0);
602 	}
603 	if (!preen && !reply("REMOVE"))
604 		return (0);
605 	memset(&dotdesc, 0, sizeof(struct inodesc));
606 	dotdesc.id_type = DATA;
607 	dotdesc.id_number = inp->i_parent; /* directory in which name appears */
608 	dotdesc.id_parent = inp->i_number; /* inode number in entry to delete */
609 	dotdesc.id_func = deleteentry;
610 	if ((ckinode(ginode(dotdesc.id_number), &dotdesc) & FOUND) && preen)
611 		printf(" (REMOVED)\n");
612 	inp->i_parent = idesc->id_number;  /* reparent to correct directory */
613 	inoinfo(inp->i_number)->ino_linkcnt++; /* name gone, return reference */
614 	return (0);
615 }
616 
617 static int
618 deleteentry(struct inodesc *idesc)
619 {
620 	struct direct *dirp = idesc->id_dirp;
621 
622 	if (idesc->id_entryno++ < 2 || dirp->d_ino != idesc->id_parent)
623 		return (KEEPON);
624 	dirp->d_ino = 0;
625 	return (ALTERED|STOP|FOUND);
626 }
627 
628 /*
629  * Routine to sort disk blocks.
630  */
631 static int
632 blksort(const void *arg1, const void *arg2)
633 {
634 
635 	return ((*(struct inoinfo * const *)arg1)->i_blks[0] -
636 		(*(struct inoinfo * const *)arg2)->i_blks[0]);
637 }
638