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