pass2.c revision 202109
1228940Sdelphij/*
2228940Sdelphij * Copyright (c) 1980, 1986, 1993
3228940Sdelphij *	The Regents of the University of California.  All rights reserved.
4228940Sdelphij *
5228940Sdelphij * Redistribution and use in source and binary forms, with or without
6228940Sdelphij * modification, are permitted provided that the following conditions
7228940Sdelphij * are met:
8228940Sdelphij * 1. Redistributions of source code must retain the above copyright
9228940Sdelphij *    notice, this list of conditions and the following disclaimer.
10228940Sdelphij * 2. Redistributions in binary form must reproduce the above copyright
11228940Sdelphij *    notice, this list of conditions and the following disclaimer in the
12228940Sdelphij *    documentation and/or other materials provided with the distribution.
13228940Sdelphij * 4. Neither the name of the University nor the names of its contributors
14228940Sdelphij *    may be used to endorse or promote products derived from this software
15228940Sdelphij *    without specific prior written permission.
16228940Sdelphij *
17228940Sdelphij * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18228940Sdelphij * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19228940Sdelphij * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20228940Sdelphij * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21228940Sdelphij * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22228940Sdelphij * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23228940Sdelphij * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24228940Sdelphij * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25228940Sdelphij * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26228940Sdelphij * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27228940Sdelphij * SUCH DAMAGE.
28228940Sdelphij */
29228940Sdelphij
30228940Sdelphij#if 0
31228940Sdelphij#ifndef lint
32228940Sdelphijstatic const char sccsid[] = "@(#)pass2.c	8.9 (Berkeley) 4/28/95";
33228940Sdelphij#endif /* not lint */
34228940Sdelphij#endif
35228940Sdelphij#include <sys/cdefs.h>
36228940Sdelphij__FBSDID("$FreeBSD: head/sbin/fsck_ffs/pass2.c 202109 2010-01-11 20:05:38Z mckusick $");
37228940Sdelphij
38228940Sdelphij#include <sys/param.h>
39228940Sdelphij#include <sys/sysctl.h>
40228940Sdelphij
41228940Sdelphij#include <ufs/ufs/dinode.h>
42228940Sdelphij#include <ufs/ufs/dir.h>
43228940Sdelphij#include <ufs/ffs/fs.h>
44228940Sdelphij
45228940Sdelphij#include <err.h>
46228940Sdelphij#include <errno.h>
47228940Sdelphij#include <stdint.h>
48228940Sdelphij#include <string.h>
49228940Sdelphij
50228940Sdelphij#include "fsck.h"
51228940Sdelphij
52228940Sdelphij#define MINDIRSIZE	(sizeof (struct dirtemplate))
53228940Sdelphij
54228940Sdelphijstatic int fix_extraneous(struct inoinfo *, struct inodesc *);
55228940Sdelphijstatic int deleteentry(struct inodesc *);
56228940Sdelphijstatic int blksort(const void *, const void *);
57228940Sdelphijstatic int pass2check(struct inodesc *);
58228940Sdelphij
59228940Sdelphijvoid
60228940Sdelphijpass2(void)
61228940Sdelphij{
62228940Sdelphij	union dinode *dp;
63228940Sdelphij	struct inoinfo **inpp, *inp;
64228940Sdelphij	struct inoinfo **inpend;
65228940Sdelphij	struct inodesc curino;
66228940Sdelphij	union dinode dino;
67228940Sdelphij	int i;
68228940Sdelphij	char pathbuf[MAXPATHLEN + 1];
69228940Sdelphij
70228940Sdelphij	switch (inoinfo(ROOTINO)->ino_state) {
71228940Sdelphij
72228940Sdelphij	case USTATE:
73228940Sdelphij		pfatal("ROOT INODE UNALLOCATED");
74228940Sdelphij		if (reply("ALLOCATE") == 0) {
75228940Sdelphij			ckfini(0);
76228940Sdelphij			exit(EEXIT);
77228940Sdelphij		}
78228940Sdelphij		if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
79228940Sdelphij			errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
80228940Sdelphij		break;
81228940Sdelphij
82228940Sdelphij	case DCLEAR:
83228940Sdelphij		pfatal("DUPS/BAD IN ROOT INODE");
84228940Sdelphij		if (reply("REALLOCATE")) {
85228940Sdelphij			freeino(ROOTINO);
86228940Sdelphij			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
87228940Sdelphij				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
88228940Sdelphij			break;
89228940Sdelphij		}
90228940Sdelphij		if (reply("CONTINUE") == 0) {
91228940Sdelphij			ckfini(0);
92228940Sdelphij			exit(EEXIT);
93228940Sdelphij		}
94228940Sdelphij		break;
95228940Sdelphij
96228940Sdelphij	case FSTATE:
97228940Sdelphij	case FCLEAR:
98228940Sdelphij	case FZLINK:
99228940Sdelphij		pfatal("ROOT INODE NOT DIRECTORY");
100228940Sdelphij		if (reply("REALLOCATE")) {
101228940Sdelphij			freeino(ROOTINO);
102228940Sdelphij			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
103228940Sdelphij				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
104228940Sdelphij			break;
105228940Sdelphij		}
106228940Sdelphij		if (reply("FIX") == 0) {
107228940Sdelphij			ckfini(0);
108228940Sdelphij			exit(EEXIT);
109228940Sdelphij		}
110228940Sdelphij		dp = ginode(ROOTINO);
111228940Sdelphij		DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
112228940Sdelphij		DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
113228940Sdelphij		inodirty();
114228940Sdelphij		break;
115228940Sdelphij
116228940Sdelphij	case DSTATE:
117228940Sdelphij	case DZLINK:
118228940Sdelphij		break;
119228940Sdelphij
120228940Sdelphij	default:
121228940Sdelphij		errx(EEXIT, "BAD STATE %d FOR ROOT INODE",
122228940Sdelphij		    inoinfo(ROOTINO)->ino_state);
123228940Sdelphij	}
124228940Sdelphij	inoinfo(ROOTINO)->ino_state = DFOUND;
125228940Sdelphij	inoinfo(WINO)->ino_state = FSTATE;
126228940Sdelphij	inoinfo(WINO)->ino_type = DT_WHT;
127228940Sdelphij	/*
128228940Sdelphij	 * Sort the directory list into disk block order.
129228940Sdelphij	 */
130228940Sdelphij	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
273static int
274pass2check(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	}
333chk1:
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);
388chk2:
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 {
422again:
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
491static int
492fix_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 = (int)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 = (int)(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
617static int
618deleteentry(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 */
631static int
632blksort(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