From 24d37c1eec746441b10ecae68305f57602cd0da3 Mon Sep 17 00:00:00 2001 From: Jeff Roberson Date: Tue, 6 Jul 2010 07:07:29 +0000 Subject: [PATCH] - Permit zero length directories as a handled inconsistency. This allows directory truncation to proceed before the link has been cleared. This is accomplished by detecting a directory with no . or .. links and clearing the named directory entry in the parent. - Add a new function ino_remref() which handles the details of removing a reference to an inode as a result of a lost directory. There were some minor errors in various subcases of this routine. --- sbin/fsck_ffs/suj.c | 161 +++++++++++++++++++++++++++++++------------- 1 file changed, 115 insertions(+), 46 deletions(-) diff --git a/sbin/fsck_ffs/suj.c b/sbin/fsck_ffs/suj.c index 967b073e29f5..279e880eaee5 100644 --- a/sbin/fsck_ffs/suj.c +++ b/sbin/fsck_ffs/suj.c @@ -807,6 +807,44 @@ blk_isat(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, int *frags) return (nblk == blk); } +/* + * Clear the directory entry at diroff that should point to child. Minimal + * checking is done and it is assumed that this path was verified with isat. + */ +static void +ino_clrat(ino_t parent, off_t diroff, ino_t child) +{ + union dinode *dip; + struct direct *dp; + ufs2_daddr_t blk; + uint8_t *block; + ufs_lbn_t lbn; + int blksize; + int frags; + int doff; + + if (debug) + printf("Clearing inode %d from parent %d at offset %jd\n", + child, parent, diroff); + + lbn = lblkno(fs, diroff); + doff = blkoff(fs, diroff); + dip = ino_read(parent); + blk = ino_blkatoff(dip, parent, lbn, &frags); + blksize = sblksize(fs, DIP(dip, di_size), lbn); + block = dblk_read(blk, blksize); + dp = (struct direct *)&block[doff]; + if (dp->d_ino != child) + errx(1, "Inode %d does not exist in %d at %jd", + child, parent, diroff); + dp->d_ino = 0; + dblk_dirty(blk); + /* + * The actual .. reference count will already have been removed + * from the parent by the .. remref record. + */ +} + /* * Determines whether a pointer to an inode exists within a directory * at a specified offset. Returns the mode of the found entry. @@ -1134,6 +1172,57 @@ ino_setskip(struct suj_ino *sino, ino_t parent) sino->si_skipparent = 1; } +static void +ino_remref(ino_t parent, ino_t child, uint64_t diroff, int isdotdot) +{ + struct suj_ino *sino; + struct suj_rec *srec; + struct jrefrec *rrec; + + /* + * Lookup this inode to see if we have a record for it. + */ + sino = ino_lookup(child, 0); + /* + * Tell any child directories we've already removed their + * parent link cnt. Don't try to adjust our link down again. + */ + if (sino != NULL && isdotdot == 0) + ino_setskip(sino, parent); + /* + * No valid record for this inode. Just drop the on-disk + * link by one. + */ + if (sino == NULL || sino->si_hasrecs == 0) { + ino_decr(child); + return; + } + /* + * Use ino_adjust() if ino_check() has already processed this + * child. If we lose the last non-dot reference to a + * directory it will be discarded. + */ + if (sino->si_linkadj) { + sino->si_nlink--; + if (isdotdot) + sino->si_dotlinks--; + ino_adjust(sino); + return; + } + /* + * If we haven't yet processed this inode we need to make + * sure we will successfully discover the lost path. If not + * use nlinkadj to remember. + */ + TAILQ_FOREACH(srec, &sino->si_recs, sr_next) { + rrec = (struct jrefrec *)srec->sr_rec; + if (rrec->jr_parent == parent && + rrec->jr_diroff == diroff) + return; + } + sino->si_nlinkadj++; +} + /* * Free the children of a directory when the directory is discarded. */ @@ -1141,13 +1230,11 @@ static void ino_free_children(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, int frags) { struct suj_ino *sino; - struct suj_rec *srec; - struct jrefrec *rrec; struct direct *dp; off_t diroff; uint8_t *block; int skipparent; - int isparent; + int isdotdot; int dpoff; int size; @@ -1165,53 +1252,15 @@ ino_free_children(ino_t ino, ufs_lbn_t lbn, ufs2_daddr_t blk, int frags) continue; if (dp->d_namlen == 1 && dp->d_name[0] == '.') continue; - isparent = dp->d_namlen == 2 && dp->d_name[0] == '.' && + isdotdot = dp->d_namlen == 2 && dp->d_name[0] == '.' && dp->d_name[1] == '.'; - if (isparent && skipparent == 1) + if (isdotdot && skipparent == 1) continue; if (debug) printf("Directory %d removing ino %d name %s\n", ino, dp->d_ino, dp->d_name); - /* - * Lookup this inode to see if we have a record for it. - * If not, we've already adjusted it assuming this path - * was valid and we have to adjust once more. - */ - sino = ino_lookup(dp->d_ino, 0); - if (sino == NULL || sino->si_hasrecs == 0) { - ino_decr(ino); - continue; - } - /* - * Use ino_adjust() so if we lose the last non-dot reference - * to a directory it can be discarded. - */ - if (sino->si_linkadj) { - sino->si_nlink--; - if (isparent) - sino->si_dotlinks--; - ino_adjust(sino); - } - /* - * Tell any child directories we've already removed their - * parent. Don't try to adjust our link down again. - */ - if (isparent == 0) - ino_setskip(sino, ino); - /* - * If we haven't yet processed this inode we need to make - * sure we will successfully discover the lost path. If not - * use nlinkadj to remember. - */ diroff = lblktosize(fs, lbn) + dpoff; - TAILQ_FOREACH(srec, &sino->si_recs, sr_next) { - rrec = (struct jrefrec *)srec->sr_rec; - if (rrec->jr_parent == ino && - rrec->jr_diroff == diroff) - break; - } - if (srec == NULL) - sino->si_nlinkadj++; + ino_remref(ino, dp->d_ino, diroff, isdotdot); } } @@ -1293,18 +1342,38 @@ ino_adjust(struct suj_ino *sino) struct suj_ino *stmp; union dinode *ip; nlink_t nlink; + int recmode; int reqlink; + int isdot; int mode; ino_t ino; nlink = sino->si_nlink; ino = sino->si_ino; + mode = sino->si_mode & IFMT; + /* + * If it's a directory with no dot links, it was truncated before + * the name was cleared. We need to clear the dirent that + * points at it. + */ + if (mode == IFDIR && nlink == 1 && sino->si_dotlinks == 0) { + sino->si_nlink = nlink = 0; + TAILQ_FOREACH(srec, &sino->si_recs, sr_next) { + rrec = (struct jrefrec *)srec->sr_rec; + if (ino_isat(rrec->jr_parent, rrec->jr_diroff, ino, + &recmode, &isdot) == 0) + continue; + ino_clrat(rrec->jr_parent, rrec->jr_diroff, ino); + break; + } + if (srec == NULL) + errx(1, "Directory %d name not found", ino); + } /* * If it's a directory with no real names pointing to it go ahead * and truncate it. This will free any children. */ - if ((sino->si_mode & IFMT) == IFDIR && - nlink - sino->si_dotlinks == 0) { + if (mode == IFDIR && nlink - sino->si_dotlinks == 0) { sino->si_nlink = nlink = 0; /* * Mark any .. links so they know not to free this inode