pass2.c revision 221110
1344957Smarcel/*
2344957Smarcel * Copyright (c) 1980, 1986, 1993
3344957Smarcel *	The Regents of the University of California.  All rights reserved.
4344957Smarcel *
5344957Smarcel * Redistribution and use in source and binary forms, with or without
6344957Smarcel * modification, are permitted provided that the following conditions
7344957Smarcel * are met:
8344957Smarcel * 1. Redistributions of source code must retain the above copyright
9344957Smarcel *    notice, this list of conditions and the following disclaimer.
10344957Smarcel * 2. Redistributions in binary form must reproduce the above copyright
11344957Smarcel *    notice, this list of conditions and the following disclaimer in the
12344957Smarcel *    documentation and/or other materials provided with the distribution.
13344957Smarcel * 4. Neither the name of the University nor the names of its contributors
14344957Smarcel *    may be used to endorse or promote products derived from this software
15344957Smarcel *    without specific prior written permission.
16344957Smarcel *
17344957Smarcel * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18344957Smarcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19344957Smarcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20344957Smarcel * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21344957Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22344957Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23344957Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24344957Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25344957Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26344957Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27344957Smarcel * SUCH DAMAGE.
28344957Smarcel */
29344957Smarcel
30344957Smarcel#if 0
31344957Smarcel#ifndef lint
32344957Smarcelstatic const char sccsid[] = "@(#)pass2.c	8.9 (Berkeley) 4/28/95";
33344957Smarcel#endif /* not lint */
34344957Smarcel#endif
35344957Smarcel#include <sys/cdefs.h>
36344957Smarcel__FBSDID("$FreeBSD: head/sbin/fsck_ffs/pass2.c 221110 2011-04-27 02:55:03Z des $");
37344957Smarcel
38344957Smarcel#include <sys/param.h>
39344957Smarcel#include <sys/sysctl.h>
40344957Smarcel
41344957Smarcel#include <ufs/ufs/dinode.h>
42344957Smarcel#include <ufs/ufs/dir.h>
43344957Smarcel#include <ufs/ffs/fs.h>
44344957Smarcel
45344957Smarcel#include <err.h>
46344957Smarcel#include <errno.h>
47344957Smarcel#include <stdint.h>
48344957Smarcel#include <string.h>
49344957Smarcel
50344957Smarcel#include "fsck.h"
51344957Smarcel
52344957Smarcel#define MINDIRSIZE	(sizeof (struct dirtemplate))
53344957Smarcel
54344957Smarcelstatic int fix_extraneous(struct inoinfo *, struct inodesc *);
55344957Smarcelstatic int deleteentry(struct inodesc *);
56344957Smarcelstatic int blksort(const void *, const void *);
57344957Smarcelstatic int pass2check(struct inodesc *);
58344957Smarcel
59344957Smarcelvoid
60344957Smarcelpass2(void)
61344957Smarcel{
62344957Smarcel	union dinode *dp;
63344957Smarcel	struct inoinfo **inpp, *inp;
64344957Smarcel	struct inoinfo **inpend;
65344957Smarcel	struct inodesc curino;
66344957Smarcel	union dinode dino;
67344957Smarcel	int i;
68344957Smarcel	char pathbuf[MAXPATHLEN + 1];
69344957Smarcel
70344957Smarcel	switch (inoinfo(ROOTINO)->ino_state) {
71344957Smarcel
72344957Smarcel	case USTATE:
73344957Smarcel		pfatal("ROOT INODE UNALLOCATED");
74344957Smarcel		if (reply("ALLOCATE") == 0) {
75344957Smarcel			ckfini(0);
76344957Smarcel			exit(EEXIT);
77344957Smarcel		}
78344957Smarcel		if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
79344957Smarcel			errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
80344957Smarcel		break;
81344957Smarcel
82344957Smarcel	case DCLEAR:
83344957Smarcel		pfatal("DUPS/BAD IN ROOT INODE");
84344957Smarcel		if (reply("REALLOCATE")) {
85344957Smarcel			freeino(ROOTINO);
86344957Smarcel			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
87344957Smarcel				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
88344957Smarcel			break;
89344957Smarcel		}
90344957Smarcel		if (reply("CONTINUE") == 0) {
91344957Smarcel			ckfini(0);
92344957Smarcel			exit(EEXIT);
93344957Smarcel		}
94344957Smarcel		break;
95344957Smarcel
96344957Smarcel	case FSTATE:
97344957Smarcel	case FCLEAR:
98344957Smarcel	case FZLINK:
99344957Smarcel		pfatal("ROOT INODE NOT DIRECTORY");
100344957Smarcel		if (reply("REALLOCATE")) {
101344957Smarcel			freeino(ROOTINO);
102344957Smarcel			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
103344957Smarcel				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
104344957Smarcel			break;
105344957Smarcel		}
106344957Smarcel		if (reply("FIX") == 0) {
107344957Smarcel			ckfini(0);
108344957Smarcel			exit(EEXIT);
109344957Smarcel		}
110344957Smarcel		dp = ginode(ROOTINO);
111344957Smarcel		DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
112344957Smarcel		DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
113344957Smarcel		inodirty();
114344957Smarcel		break;
115344957Smarcel
116344957Smarcel	case DSTATE:
117344957Smarcel	case DZLINK:
118344957Smarcel		break;
119344957Smarcel
120344957Smarcel	default:
121344957Smarcel		errx(EEXIT, "BAD STATE %d FOR ROOT INODE",
122344957Smarcel		    inoinfo(ROOTINO)->ino_state);
123344957Smarcel	}
124344957Smarcel	inoinfo(ROOTINO)->ino_state = DFOUND;
125344957Smarcel	inoinfo(WINO)->ino_state = FSTATE;
126344957Smarcel	inoinfo(WINO)->ino_type = DT_WHT;
127344957Smarcel	/*
128344957Smarcel	 * Sort the directory list into disk block order.
129344957Smarcel	 */
130344957Smarcel	qsort((char *)inpsort, (size_t)inplast, sizeof *inpsort, blksort);
131344957Smarcel	/*
132344957Smarcel	 * Check the integrity of each directory.
133344957Smarcel	 */
134344957Smarcel	memset(&curino, 0, sizeof(struct inodesc));
135344957Smarcel	curino.id_type = DATA;
136344957Smarcel	curino.id_func = pass2check;
137344957Smarcel	inpend = &inpsort[inplast];
138344957Smarcel	for (inpp = inpsort; inpp < inpend; inpp++) {
139344957Smarcel		if (got_siginfo) {
140344957Smarcel			printf("%s: phase 2: dir %td of %d (%d%%)\n", cdevname,
141344957Smarcel			    inpp - inpsort, (int)inplast,
142344957Smarcel			    (int)((inpp - inpsort) * 100 / inplast));
143344957Smarcel			got_siginfo = 0;
144344957Smarcel		}
145344957Smarcel		if (got_sigalarm) {
146344957Smarcel			setproctitle("%s p2 %d%%", cdevname,
147344957Smarcel			    (int)((inpp - inpsort) * 100 / inplast));
148344957Smarcel			got_sigalarm = 0;
149344957Smarcel		}
150344957Smarcel		inp = *inpp;
151344957Smarcel		if (inp->i_isize == 0)
152344957Smarcel			continue;
153344957Smarcel		if (inp->i_isize < MINDIRSIZE) {
154344957Smarcel			direrror(inp->i_number, "DIRECTORY TOO SHORT");
155344957Smarcel			inp->i_isize = roundup(MINDIRSIZE, DIRBLKSIZ);
156344957Smarcel			if (reply("FIX") == 1) {
157344957Smarcel				dp = ginode(inp->i_number);
158344957Smarcel				DIP_SET(dp, di_size, inp->i_isize);
159344957Smarcel				inodirty();
160344957Smarcel			}
161344957Smarcel		} else if ((inp->i_isize & (DIRBLKSIZ - 1)) != 0) {
162344957Smarcel			getpathname(pathbuf, inp->i_number, inp->i_number);
163344957Smarcel			if (usedsoftdep)
164344957Smarcel				pfatal("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
165344957Smarcel					"DIRECTORY", pathbuf,
166344957Smarcel					(intmax_t)inp->i_isize, DIRBLKSIZ);
167344957Smarcel			else
168344957Smarcel				pwarn("%s %s: LENGTH %jd NOT MULTIPLE OF %d",
169344957Smarcel					"DIRECTORY", pathbuf,
170344957Smarcel					(intmax_t)inp->i_isize, DIRBLKSIZ);
171344957Smarcel			if (preen)
172344957Smarcel				printf(" (ADJUSTED)\n");
173344957Smarcel			inp->i_isize = roundup(inp->i_isize, DIRBLKSIZ);
174344957Smarcel			if (preen || reply("ADJUST") == 1) {
175344957Smarcel				dp = ginode(inp->i_number);
176344957Smarcel				DIP_SET(dp, di_size,
177344957Smarcel				    roundup(inp->i_isize, DIRBLKSIZ));
178344957Smarcel				inodirty();
179344957Smarcel			}
180344957Smarcel		}
181344957Smarcel		dp = &dino;
182344957Smarcel		memset(dp, 0, sizeof(struct ufs2_dinode));
183344957Smarcel		DIP_SET(dp, di_mode, IFDIR);
184344957Smarcel		DIP_SET(dp, di_size, inp->i_isize);
185344957Smarcel		for (i = 0;
186344957Smarcel		     i < (inp->i_numblks<NDADDR ? inp->i_numblks : NDADDR);
187344957Smarcel		     i++)
188344957Smarcel			DIP_SET(dp, di_db[i], inp->i_blks[i]);
189344957Smarcel		if (inp->i_numblks > NDADDR)
190344957Smarcel			for (i = 0; i < NIADDR; i++)
191344957Smarcel				DIP_SET(dp, di_ib[i], inp->i_blks[NDADDR + i]);
192344957Smarcel		curino.id_number = inp->i_number;
193344957Smarcel		curino.id_parent = inp->i_parent;
194344957Smarcel		(void)ckinode(dp, &curino);
195344957Smarcel	}
196344957Smarcel	/*
197344957Smarcel	 * Now that the parents of all directories have been found,
198344957Smarcel	 * make another pass to verify the value of `..'
199344957Smarcel	 */
200344957Smarcel	for (inpp = inpsort; inpp < inpend; inpp++) {
201344957Smarcel		inp = *inpp;
202344957Smarcel		if (inp->i_parent == 0 || inp->i_isize == 0)
203344957Smarcel			continue;
204344957Smarcel		if (inoinfo(inp->i_parent)->ino_state == DFOUND &&
205344957Smarcel		    INO_IS_DUNFOUND(inp->i_number))
206344957Smarcel			inoinfo(inp->i_number)->ino_state = DFOUND;
207344957Smarcel		if (inp->i_dotdot == inp->i_parent ||
208344957Smarcel		    inp->i_dotdot == (ino_t)-1)
209344957Smarcel			continue;
210344957Smarcel		if (inp->i_dotdot == 0) {
211344957Smarcel			inp->i_dotdot = inp->i_parent;
212344957Smarcel			fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
213344957Smarcel			if (reply("FIX") == 0)
214344957Smarcel				continue;
215344957Smarcel			(void)makeentry(inp->i_number, inp->i_parent, "..");
216344957Smarcel			inoinfo(inp->i_parent)->ino_linkcnt--;
217344957Smarcel			continue;
218344957Smarcel		}
219344957Smarcel		/*
220344957Smarcel		 * Here we have:
221344957Smarcel		 *    inp->i_number is directory with bad ".." in it.
222344957Smarcel		 *    inp->i_dotdot is current value of "..".
223344957Smarcel		 *    inp->i_parent is directory to which ".." should point.
224344957Smarcel		 */
225344957Smarcel		getpathname(pathbuf, inp->i_parent, inp->i_number);
226344957Smarcel		printf("BAD INODE NUMBER FOR '..' in DIR I=%d (%s)\n",
227344957Smarcel		    inp->i_number, pathbuf);
228344957Smarcel		getpathname(pathbuf, inp->i_dotdot, inp->i_dotdot);
229344957Smarcel		printf("CURRENTLY POINTS TO I=%d (%s), ", inp->i_dotdot,
230344957Smarcel		    pathbuf);
231344957Smarcel		getpathname(pathbuf, inp->i_parent, inp->i_parent);
232344957Smarcel		printf("SHOULD POINT TO I=%d (%s)", inp->i_parent, pathbuf);
233344957Smarcel		if (cursnapshot != 0) {
234344957Smarcel			/*
235344957Smarcel			 * We need to:
236344957Smarcel			 *    setcwd(inp->i_number);
237344957Smarcel			 *    setdotdot(inp->i_dotdot, inp->i_parent);
238344957Smarcel			 */
239344957Smarcel			cmd.value = inp->i_number;
240344957Smarcel			if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
241344957Smarcel			    &cmd, sizeof cmd) == -1) {
242344957Smarcel				/* kernel lacks support for these functions */
243344957Smarcel				printf(" (IGNORED)\n");
244344957Smarcel				continue;
245344957Smarcel			}
246344957Smarcel			cmd.value = inp->i_dotdot; /* verify same value */
247344957Smarcel			cmd.size = inp->i_parent;  /* new parent */
248344957Smarcel			if (sysctlbyname("vfs.ffs.setdotdot", 0, 0,
249344957Smarcel			    &cmd, sizeof cmd) == -1) {
250344957Smarcel				printf(" (FIX FAILED: %s)\n", strerror(errno));
251344957Smarcel				continue;
252344957Smarcel			}
253344957Smarcel			printf(" (FIXED)\n");
254344957Smarcel			inoinfo(inp->i_parent)->ino_linkcnt--;
255344957Smarcel			inp->i_dotdot = inp->i_parent;
256344957Smarcel			continue;
257344957Smarcel		}
258344957Smarcel		if (preen)
259344957Smarcel			printf(" (FIXED)\n");
260344957Smarcel		else if (reply("FIX") == 0)
261344957Smarcel			continue;
262344957Smarcel		inoinfo(inp->i_dotdot)->ino_linkcnt++;
263344957Smarcel		inoinfo(inp->i_parent)->ino_linkcnt--;
264344957Smarcel		inp->i_dotdot = inp->i_parent;
265344957Smarcel		(void)changeino(inp->i_number, "..", inp->i_parent);
266344957Smarcel	}
267344957Smarcel	/*
268344957Smarcel	 * Mark all the directories that can be found from the root.
269344957Smarcel	 */
270344957Smarcel	propagate();
271344957Smarcel}
272344957Smarcel
273344957Smarcelstatic int
274344957Smarcelpass2check(struct inodesc *idesc)
275344957Smarcel{
276344957Smarcel	struct direct *dirp = idesc->id_dirp;
277344957Smarcel	char dirname[MAXPATHLEN + 1];
278344957Smarcel	struct inoinfo *inp;
279344957Smarcel	int n, entrysize, ret = 0;
280344957Smarcel	union dinode *dp;
281344957Smarcel	const char *errmsg;
282344957Smarcel	struct direct proto;
283344957Smarcel
284344957Smarcel	/*
285344957Smarcel	 * check for "."
286344957Smarcel	 */
287344957Smarcel	if (dirp->d_ino > maxino)
288344957Smarcel		goto chk2;
289344957Smarcel	if (idesc->id_entryno != 0)
290344957Smarcel		goto chk1;
291344957Smarcel	if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) {
292344957Smarcel		if (dirp->d_ino != idesc->id_number) {
293344957Smarcel			direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'");
294344957Smarcel			dirp->d_ino = idesc->id_number;
295344957Smarcel			if (reply("FIX") == 1)
296344957Smarcel				ret |= ALTERED;
297344957Smarcel		}
298344957Smarcel		if (dirp->d_type != DT_DIR) {
299344957Smarcel			direrror(idesc->id_number, "BAD TYPE VALUE FOR '.'");
300344957Smarcel			dirp->d_type = DT_DIR;
301344957Smarcel			if (reply("FIX") == 1)
302344957Smarcel				ret |= ALTERED;
303344957Smarcel		}
304344957Smarcel		goto chk1;
305344957Smarcel	}
306344957Smarcel	direrror(idesc->id_number, "MISSING '.'");
307344957Smarcel	proto.d_ino = idesc->id_number;
308344957Smarcel	proto.d_type = DT_DIR;
309344957Smarcel	proto.d_namlen = 1;
310344957Smarcel	(void)strcpy(proto.d_name, ".");
311344957Smarcel	entrysize = DIRSIZ(0, &proto);
312344957Smarcel	if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) {
313344957Smarcel		pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n",
314344957Smarcel			dirp->d_name);
315344957Smarcel	} else if (dirp->d_reclen < entrysize) {
316344957Smarcel		pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n");
317344957Smarcel	} else if (dirp->d_reclen < 2 * entrysize) {
318344957Smarcel		proto.d_reclen = dirp->d_reclen;
319344957Smarcel		memmove(dirp, &proto, (size_t)entrysize);
320344957Smarcel		if (reply("FIX") == 1)
321344957Smarcel			ret |= ALTERED;
322344957Smarcel	} 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	}
334chk1:
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);
389chk2:
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 {
423again:
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
520static int
521fix_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
646static int
647deleteentry(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 */
660static int
661blksort(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