1/*
2 * Copyright (c) 1980, 1986, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 4. Neither the name of the University nor the names of its contributors
14 *    may be used to endorse or promote products derived from this software
15 *    without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#if 0
31#ifndef lint
32static const char sccsid[] = "@(#)pass2.c	8.9 (Berkeley) 4/28/95";
33#endif /* not lint */
34#endif
35#include <sys/cdefs.h>
36__FBSDID("$FreeBSD: stable/11/sbin/fsck_ffs/pass2.c 344887 2019-03-07 13:53:59Z kib $");
37
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
54static int fix_extraneous(struct inoinfo *, struct inodesc *);
55static int deleteentry(struct inodesc *);
56static int blksort(const void *, const void *);
57static int pass2check(struct inodesc *);
58
59void
60pass2(void)
61{
62	union dinode *dp;
63	struct inoinfo **inpp, *inp;
64	struct inoinfo **inpend;
65	struct inodesc curino;
66	union dinode dino;
67	int i;
68	char pathbuf[MAXPATHLEN + 1];
69
70	switch (inoinfo(ROOTINO)->ino_state) {
71
72	case USTATE:
73		pfatal("ROOT INODE UNALLOCATED");
74		if (reply("ALLOCATE") == 0) {
75			ckfini(0);
76			exit(EEXIT);
77		}
78		if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
79			errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
80		break;
81
82	case DCLEAR:
83		pfatal("DUPS/BAD IN ROOT INODE");
84		if (reply("REALLOCATE")) {
85			freeino(ROOTINO);
86			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
87				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
88			break;
89		}
90		if (reply("CONTINUE") == 0) {
91			ckfini(0);
92			exit(EEXIT);
93		}
94		break;
95
96	case FSTATE:
97	case FCLEAR:
98	case FZLINK:
99		pfatal("ROOT INODE NOT DIRECTORY");
100		if (reply("REALLOCATE")) {
101			freeino(ROOTINO);
102			if (allocdir(ROOTINO, ROOTINO, 0755) != ROOTINO)
103				errx(EEXIT, "CANNOT ALLOCATE ROOT INODE");
104			break;
105		}
106		if (reply("FIX") == 0) {
107			ckfini(0);
108			exit(EEXIT);
109		}
110		dp = ginode(ROOTINO);
111		DIP_SET(dp, di_mode, DIP(dp, di_mode) & ~IFMT);
112		DIP_SET(dp, di_mode, DIP(dp, di_mode) | IFDIR);
113		inodirty(dp);
114		break;
115
116	case DSTATE:
117	case DZLINK:
118		break;
119
120	default:
121		errx(EEXIT, "BAD STATE %d FOR ROOT INODE",
122		    inoinfo(ROOTINO)->ino_state);
123	}
124	inoinfo(ROOTINO)->ino_state = DFOUND;
125	inoinfo(WINO)->ino_state = FSTATE;
126	inoinfo(WINO)->ino_type = DT_WHT;
127	/*
128	 * Sort the directory list into disk block order.
129	 */
130	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(dp);
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(dp);
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; i < MIN(inp->i_numblks, NDADDR); i++)
186			DIP_SET(dp, di_db[i], inp->i_blks[i]);
187		if (inp->i_numblks > NDADDR)
188			for (i = 0; i < NIADDR; i++)
189				DIP_SET(dp, di_ib[i], inp->i_blks[NDADDR + i]);
190		curino.id_number = inp->i_number;
191		curino.id_parent = inp->i_parent;
192		(void)ckinode(dp, &curino);
193	}
194	/*
195	 * Now that the parents of all directories have been found,
196	 * make another pass to verify the value of `..'
197	 */
198	for (inpp = inpsort; inpp < inpend; inpp++) {
199		inp = *inpp;
200		if (inp->i_parent == 0 || inp->i_isize == 0)
201			continue;
202		if (inoinfo(inp->i_parent)->ino_state == DFOUND &&
203		    INO_IS_DUNFOUND(inp->i_number))
204			inoinfo(inp->i_number)->ino_state = DFOUND;
205		if (inp->i_dotdot == inp->i_parent ||
206		    inp->i_dotdot == (ino_t)-1)
207			continue;
208		if (inp->i_dotdot == 0) {
209			inp->i_dotdot = inp->i_parent;
210			fileerror(inp->i_parent, inp->i_number, "MISSING '..'");
211			if (reply("FIX") == 0)
212				continue;
213			(void)makeentry(inp->i_number, inp->i_parent, "..");
214			inoinfo(inp->i_parent)->ino_linkcnt--;
215			continue;
216		}
217		/*
218		 * Here we have:
219		 *    inp->i_number is directory with bad ".." in it.
220		 *    inp->i_dotdot is current value of "..".
221		 *    inp->i_parent is directory to which ".." should point.
222		 */
223		getpathname(pathbuf, inp->i_parent, inp->i_number);
224		printf("BAD INODE NUMBER FOR '..' in DIR I=%ju (%s)\n",
225		    (uintmax_t)inp->i_number, pathbuf);
226		getpathname(pathbuf, inp->i_dotdot, inp->i_dotdot);
227		printf("CURRENTLY POINTS TO I=%ju (%s), ",
228		    (uintmax_t)inp->i_dotdot, pathbuf);
229		getpathname(pathbuf, inp->i_parent, inp->i_parent);
230		printf("SHOULD POINT TO I=%ju (%s)",
231		    (uintmax_t)inp->i_parent, pathbuf);
232		if (cursnapshot != 0) {
233			/*
234			 * We need to:
235			 *    setcwd(inp->i_number);
236			 *    setdotdot(inp->i_dotdot, inp->i_parent);
237			 */
238			cmd.value = inp->i_number;
239			if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
240			    &cmd, sizeof cmd) == -1) {
241				/* kernel lacks support for these functions */
242				printf(" (IGNORED)\n");
243				continue;
244			}
245			cmd.value = inp->i_dotdot; /* verify same value */
246			cmd.size = inp->i_parent;  /* new parent */
247			if (sysctlbyname("vfs.ffs.setdotdot", 0, 0,
248			    &cmd, sizeof cmd) == -1) {
249				printf(" (FIX FAILED: %s)\n", strerror(errno));
250				continue;
251			}
252			printf(" (FIXED)\n");
253			inoinfo(inp->i_parent)->ino_linkcnt--;
254			inp->i_dotdot = inp->i_parent;
255			continue;
256		}
257		if (preen)
258			printf(" (FIXED)\n");
259		else if (reply("FIX") == 0)
260			continue;
261		inoinfo(inp->i_dotdot)->ino_linkcnt++;
262		inoinfo(inp->i_parent)->ino_linkcnt--;
263		inp->i_dotdot = inp->i_parent;
264		(void)changeino(inp->i_number, "..", inp->i_parent);
265	}
266	/*
267	 * Mark all the directories that can be found from the root.
268	 */
269	propagate();
270}
271
272static int
273pass2check(struct inodesc *idesc)
274{
275	struct direct *dirp = idesc->id_dirp;
276	char dirname[MAXPATHLEN + 1];
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 if (cursnapshot == 0) {
440				n = 1;
441				break;
442			} else {
443				getpathname(dirname, idesc->id_number,
444				    dirp->d_ino);
445				pwarn("ZERO LENGTH DIRECTORY %s I=%ju",
446				    dirname, (uintmax_t)dirp->d_ino);
447				/*
448				 * We need to:
449				 *    setcwd(idesc->id_parent);
450				 *    rmdir(dirp->d_name);
451				 */
452				cmd.value = idesc->id_number;
453				if (sysctlbyname("vfs.ffs.setcwd", 0, 0,
454				    &cmd, sizeof cmd) == -1) {
455					/* kernel lacks support */
456					printf(" (IGNORED)\n");
457					n = 1;
458					break;
459				}
460				if (rmdir(dirp->d_name) == -1) {
461					printf(" (REMOVAL FAILED: %s)\n",
462					    strerror(errno));
463					n = 1;
464					break;
465				}
466				/* ".." reference to parent is removed */
467				inoinfo(idesc->id_number)->ino_linkcnt--;
468				printf(" (REMOVED)\n");
469				break;
470			}
471			fileerror(idesc->id_number, dirp->d_ino, errmsg);
472			if ((n = reply("REMOVE")) == 1)
473				break;
474			dp = ginode(dirp->d_ino);
475			inoinfo(dirp->d_ino)->ino_state =
476			   (DIP(dp, di_mode) & IFMT) == IFDIR ? DSTATE : FSTATE;
477			inoinfo(dirp->d_ino)->ino_linkcnt = DIP(dp, di_nlink);
478			goto again;
479
480		case DSTATE:
481		case DZLINK:
482			if (inoinfo(idesc->id_number)->ino_state == DFOUND)
483				inoinfo(dirp->d_ino)->ino_state = DFOUND;
484			/* FALLTHROUGH */
485
486		case DFOUND:
487			inp = getinoinfo(dirp->d_ino);
488			if (idesc->id_entryno > 2) {
489				if (inp->i_parent == 0)
490					inp->i_parent = idesc->id_number;
491				else if ((n = fix_extraneous(inp, idesc)) == 1)
492					break;
493			}
494			/* FALLTHROUGH */
495
496		case FSTATE:
497		case FZLINK:
498			if (dirp->d_type != inoinfo(dirp->d_ino)->ino_type) {
499				fileerror(idesc->id_number, dirp->d_ino,
500				    "BAD TYPE VALUE");
501				dirp->d_type = inoinfo(dirp->d_ino)->ino_type;
502				if (reply("FIX") == 1)
503					ret |= ALTERED;
504			}
505			inoinfo(dirp->d_ino)->ino_linkcnt--;
506			break;
507
508		default:
509			errx(EEXIT, "BAD STATE %d FOR INODE I=%ju",
510			    inoinfo(dirp->d_ino)->ino_state,
511			    (uintmax_t)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 = strchr(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