git: f4fc3895243b - main - Properly handle the replacement of a partially allocated root directory.

From: Kirk McKusick <mckusick_at_FreeBSD.org>
Date: Sat, 03 Sep 2022 21:48:40 UTC
The branch main has been updated by mckusick:

URL: https://cgit.FreeBSD.org/src/commit/?id=f4fc3895243b9a8ae0577e731a3e450377071196

commit f4fc3895243b9a8ae0577e731a3e450377071196
Author:     Kirk McKusick <mckusick@FreeBSD.org>
AuthorDate: 2022-09-03 21:46:50 +0000
Commit:     Kirk McKusick <mckusick@FreeBSD.org>
CommitDate: 2022-09-03 21:48:34 +0000

    Properly handle the replacement of a partially allocated root directory.
    
    If the root directory exists but has a bad block number Pass1 will
    accept it and setup an inoinfo structure for it. When Pass2 runs
    and cannot read the root inode's content because of a bad (or
    duplicate) block number, it removes the bad root inode and replaces
    it. As part of creating the replacement root inode, it creates an
    inoinfo entry for it. But Pass2 did delete the inoinfo entry that
    Pass1 had set up for the root inode so ended up with two inoinfo
    structures for it. The final step of Pass2 checks that all the ".."
    entries are correct adding them if they are missing which resulted
    in a second ".." entry being added to the root directory which
    definitely did not go over well in the kernel name cache!
    
    Reported by:  Peter Holm
    Sponsored by: The FreeBSD Foundation
---
 sbin/fsck_ffs/dir.c   | 18 +++++++++++++-----
 sbin/fsck_ffs/fsck.h  |  8 +++++++-
 sbin/fsck_ffs/inode.c |  6 ++++--
 3 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/sbin/fsck_ffs/dir.c b/sbin/fsck_ffs/dir.c
index 42ecf4112253..87e3e34cc1ad 100644
--- a/sbin/fsck_ffs/dir.c
+++ b/sbin/fsck_ffs/dir.c
@@ -474,6 +474,7 @@ linkup(ino_t orphan, ino_t parentdir, char *name)
 	union dinode *dp;
 	int lostdir;
 	ino_t oldlfdir;
+	struct inoinfo *inp;
 	struct inodesc idesc;
 	char tempname[BUFSIZ];
 
@@ -581,10 +582,12 @@ linkup(ino_t orphan, ino_t parentdir, char *name)
 		inodirty(&ip);
 		inoinfo(lfdir)->ino_linkcnt++;
 		pwarn("DIR I=%lu CONNECTED. ", (u_long)orphan);
-		if (parentdir != (ino_t)-1) {
+		inp = getinoinfo(parentdir);
+		if (parentdir != (ino_t)-1 && (inp->i_flags & INFO_NEW) == 0) {
 			printf("PARENT WAS I=%lu\n", (u_long)parentdir);
 			/*
-			 * The parent directory, because of the ordering
+			 * If the parent directory did not have to
+			 * be replaced then because of the ordering
 			 * guarantees, has had the link count incremented
 			 * for the child, but no entry was made.  This
 			 * fixes the parent link count so that fsck does
@@ -823,7 +826,12 @@ allocdir(ino_t parent, ino_t request, int mode)
 	inodirty(&ip);
 	if (ino == UFS_ROOTINO) {
 		inoinfo(ino)->ino_linkcnt = DIP(dp, di_nlink);
-		cacheino(dp, ino);
+		if ((inp = getinoinfo(ino)) == NULL)
+			inp = cacheino(dp, ino);
+		else
+			inp->i_flags = INFO_NEW;
+		inp->i_parent = parent;
+		inp->i_dotdot = parent;
 		irelse(&ip);
 		return(ino);
 	}
@@ -832,8 +840,8 @@ allocdir(ino_t parent, ino_t request, int mode)
 		irelse(&ip);
 		return (0);
 	}
-	cacheino(dp, ino);
-	inp = getinoinfo(ino);
+	if ((inp = getinoinfo(ino)) == NULL)
+		inp = cacheino(dp, ino);
 	inp->i_parent = parent;
 	inp->i_dotdot = parent;
 	inoinfo(ino)->ino_state = inoinfo(parent)->ino_state;
diff --git a/sbin/fsck_ffs/fsck.h b/sbin/fsck_ffs/fsck.h
index 65c7056532be..d1b45a0850da 100644
--- a/sbin/fsck_ffs/fsck.h
+++ b/sbin/fsck_ffs/fsck.h
@@ -311,9 +311,15 @@ extern struct inoinfo {
 	ino_t	i_parent;		/* inode number of parent */
 	ino_t	i_dotdot;		/* inode number of `..' */
 	size_t	i_isize;		/* size of inode */
+	u_int	i_flags;		/* flags, see below */
 	u_int	i_numblks;		/* size of block array in bytes */
 	ufs2_daddr_t i_blks[1];		/* actually longer */
 } **inphead, **inpsort;
+/*
+ * flags for struct inoinfo
+ */
+#define INFO_NEW	0x0000001	/* replaced broken directory */
+
 extern long dirhash, inplast;
 extern unsigned long numdirs, listmax;
 extern long countdirs;		/* number of directories we actually found */
@@ -446,7 +452,7 @@ void		blwrite(int fd, char *buf, ufs2_daddr_t blk, ssize_t size);
 void		blerase(int fd, ufs2_daddr_t blk, long size);
 void		blzero(int fd, ufs2_daddr_t blk, long size);
 void		brelse(struct bufarea *);
-void		cacheino(union dinode *dp, ino_t inumber);
+struct inoinfo *cacheino(union dinode *dp, ino_t inumber);
 void		catch(int);
 void		catchquit(int);
 void		cgdirty(struct bufarea *);
diff --git a/sbin/fsck_ffs/inode.c b/sbin/fsck_ffs/inode.c
index f0699aabe349..cbdd756e5d17 100644
--- a/sbin/fsck_ffs/inode.c
+++ b/sbin/fsck_ffs/inode.c
@@ -698,12 +698,14 @@ freeinodebuf(void)
  *
  * Enter inodes into the cache.
  */
-void
+struct inoinfo *
 cacheino(union dinode *dp, ino_t inumber)
 {
 	struct inoinfo *inp, **inpp;
 	int i, blks;
 
+	if (getinoinfo(inumber) != NULL)
+		pfatal("cacheino: duplicate entry for ino %ld\n", inumber);
 	if (howmany(DIP(dp, di_size), sblock.fs_bsize) > UFS_NDADDR)
 		blks = UFS_NDADDR + UFS_NIADDR;
 	else if (DIP(dp, di_size) > 0)
@@ -735,6 +737,7 @@ cacheino(union dinode *dp, ino_t inumber)
 			errx(EEXIT, "cannot increase directory list");
 	}
 	inpsort[inplast++] = inp;
+	return (inp);
 }
 
 /*
@@ -750,7 +753,6 @@ getinoinfo(ino_t inumber)
 			continue;
 		return (inp);
 	}
-	errx(EEXIT, "cannot find inode %ju", (uintmax_t)inumber);
 	return ((struct inoinfo *)0);
 }