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