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