From f4fc3895243b9a8ae0577e731a3e450377071196 Mon Sep 17 00:00:00 2001 From: Kirk McKusick Date: Sat, 3 Sep 2022 14:46:50 -0700 Subject: [PATCH] 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); }