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