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