commit 52b6962e42c46f4344bd94dbd7911a73e6005246 Author: Bob Peterson <rpeterso@redhat.com> Date: Tue Aug 17 13:55:49 2010 -0500 fsck.gfs2 deletes directories if they get too big This patch fixes a problem whereby too many levels of indirection caused fsck.gfs2 to get confused and delete directories. The problem was that gfs2.fsck only understood three levels of data for directories: (1) dinode, (2) block pointers to leaf blocks, (3) leaf blocks. If a directory gets sufficiently big, it goes into four or more levels of indirection, with level 2 (and possibly more) being indirect blocks leading to the leaf blocks. rhbz#624689 diff --git a/gfs2/fsck/metawalk.c b/gfs2/fsck/metawalk.c index 09305da..330dace 100644 --- a/gfs2/fsck/metawalk.c +++ b/gfs2/fsck/metawalk.c @@ -1138,7 +1138,7 @@ static int build_and_check_metalist(struct gfs2_inode *ip, osi_list_t *mlp, uint32_t height = ip->i_di.di_height; struct gfs2_buffer_head *bh, *nbh, *metabh = ip->i_bh; osi_list_t *prev_list, *cur_list, *tmp; - int i, head_size, iblk_type; + int h, head_size, iblk_type; uint64_t *ptr, block; int error = 0, err; @@ -1149,35 +1149,34 @@ static int build_and_check_metalist(struct gfs2_inode *ip, osi_list_t *mlp, because it checks everything through the hash table using "depth" field calculations. However, we still have to check the indirect blocks, even if the height == 1. */ - if (S_ISDIR(ip->i_di.di_mode)) { + if (S_ISDIR(ip->i_di.di_mode)) height++; - iblk_type = GFS2_METATYPE_JD; - } else { - iblk_type = GFS2_METATYPE_IN; - } /* if(<there are no indirect blocks to check>) */ if (height < 2) return 0; - for (i = 1; i < height; i++) { - prev_list = &mlp[i - 1]; - cur_list = &mlp[i]; + for (h = 1; h < height; h++) { + if (h > 1) { + if (S_ISDIR(ip->i_di.di_mode) && + h == ip->i_di.di_height + 1) + iblk_type = GFS2_METATYPE_JD; + else + iblk_type = GFS2_METATYPE_IN; + head_size = sizeof(struct gfs2_meta_header); + } else { + iblk_type = GFS2_METATYPE_DI; + head_size = sizeof(struct gfs2_dinode); + } + prev_list = &mlp[h - 1]; + cur_list = &mlp[h]; for (tmp = prev_list->next; tmp != prev_list; tmp = tmp->next){ bh = osi_list_entry(tmp, struct gfs2_buffer_head, b_altlist); - if (i > 1) { - /* if this isn't really a block list skip it */ - if (gfs2_check_meta(bh, iblk_type)) - continue; - head_size = sizeof(struct gfs2_meta_header); - } else { - /* if this isn't really a dinode, skip it */ - if (gfs2_check_meta(bh, GFS2_METATYPE_DI)) - continue; - head_size = sizeof(struct gfs2_dinode); - } + if (gfs2_check_meta(bh, iblk_type)) + continue; + /* Now check the metadata itself */ for (ptr = (uint64_t *)(bh->b_data + head_size); (char *)ptr < (bh->b_data + ip->i_sbd->bsize); @@ -1190,7 +1189,7 @@ static int build_and_check_metalist(struct gfs2_inode *ip, osi_list_t *mlp, continue; block = be64_to_cpu(*ptr); - err = pass->check_metalist(ip, block, &nbh, + err = pass->check_metalist(ip, block, &nbh, h, pass->private); /* check_metalist should hold any buffers it gets with "bread". */ @@ -1472,7 +1471,7 @@ int remove_dentry_from_dir(struct gfs2_sbd *sbp, uint64_t dir, } int delete_metadata(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private) + struct gfs2_buffer_head **bh, int h, void *private) { return delete_block_if_notdup(ip, block, bh, _("metadata"), private); } @@ -1504,7 +1503,7 @@ int delete_eattr_leaf(struct gfs2_inode *ip, uint64_t block, uint64_t parent, } static int alloc_metalist(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private) + struct gfs2_buffer_head **bh, int h, void *private) { uint8_t q; const char *desc = (const char *)private; diff --git a/gfs2/fsck/metawalk.h b/gfs2/fsck/metawalk.h index 3ea2991..4489c60 100644 --- a/gfs2/fsck/metawalk.h +++ b/gfs2/fsck/metawalk.h @@ -31,7 +31,7 @@ extern int delete_block(struct gfs2_inode *ip, uint64_t block, struct gfs2_buffer_head **bh, const char *btype, void *private); extern int delete_metadata(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private); + struct gfs2_buffer_head **bh, int h, void *private); extern int delete_leaf(struct gfs2_inode *ip, uint64_t block, struct gfs2_buffer_head *bh, void *private); extern int delete_data(struct gfs2_inode *ip, uint64_t block, void *private); @@ -74,7 +74,8 @@ struct metawalk_fxns { int (*check_leaf) (struct gfs2_inode *ip, uint64_t block, struct gfs2_buffer_head *bh, void *private); int (*check_metalist) (struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private); + struct gfs2_buffer_head **bh, int h, + void *private); int (*check_data) (struct gfs2_inode *ip, uint64_t block, void *private); int (*check_eattr_indir) (struct gfs2_inode *ip, uint64_t block, diff --git a/gfs2/fsck/pass1.c b/gfs2/fsck/pass1.c index bea0ef5..5f18818 100644 --- a/gfs2/fsck/pass1.c +++ b/gfs2/fsck/pass1.c @@ -50,9 +50,10 @@ struct block_count { static int leaf(struct gfs2_inode *ip, uint64_t block, struct gfs2_buffer_head *bh, void *private); static int check_metalist(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private); + struct gfs2_buffer_head **bh, int h, void *private); static int undo_check_metalist(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private); + struct gfs2_buffer_head **bh, int h, + void *private); static int check_data(struct gfs2_inode *ip, uint64_t block, void *private); static int undo_check_data(struct gfs2_inode *ip, uint64_t block, void *private); @@ -75,7 +76,8 @@ static int check_extended_leaf_eattr(struct gfs2_inode *ip, uint64_t *data_ptr, static int finish_eattr_indir(struct gfs2_inode *ip, int leaf_pointers, int leaf_pointer_errors, void *private); static int invalidate_metadata(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private); + struct gfs2_buffer_head **bh, int h, + void *private); static int invalidate_leaf(struct gfs2_inode *ip, uint64_t block, struct gfs2_buffer_head *bh, void *private); static int invalidate_data(struct gfs2_inode *ip, uint64_t block, @@ -126,7 +128,8 @@ struct metawalk_fxns invalidate_fxns = { * deleted, do you? Or worse, reused for lost+found. */ static int resuscitate_metalist(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private) + struct gfs2_buffer_head **bh, int h, + void *private) { struct block_count *bc = (struct block_count *)private; @@ -220,7 +223,7 @@ static int leaf(struct gfs2_inode *ip, uint64_t block, } static int check_metalist(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private) + struct gfs2_buffer_head **bh, int h, void *private) { uint8_t q; int found_dup = 0, iblk_type; @@ -240,7 +243,7 @@ static int check_metalist(struct gfs2_inode *ip, uint64_t block, return 1; } - if (S_ISDIR(ip->i_di.di_mode)) { + if (S_ISDIR(ip->i_di.di_mode) && h == ip->i_di.di_height) { iblk_type = GFS2_METATYPE_JD; blktypedesc = _("a directory hash table block"); } else { @@ -263,13 +266,13 @@ static int check_metalist(struct gfs2_inode *ip, uint64_t block, nbh = bread(ip->i_sbd, block); if (gfs2_check_meta(nbh, iblk_type)){ - log_debug( _("Inode %lld (0x%llx) has a bad indirect block " - "pointer %lld (0x%llx) (points to something " - "that is not %s).\n"), - (unsigned long long)ip->i_di.di_num.no_addr, - (unsigned long long)ip->i_di.di_num.no_addr, - (unsigned long long)block, - (unsigned long long)block, blktypedesc); + log_err( _("Inode %lld (0x%llx) has a bad indirect block " + "pointer %lld (0x%llx) (points to something " + "that is not %s).\n"), + (unsigned long long)ip->i_di.di_num.no_addr, + (unsigned long long)ip->i_di.di_num.no_addr, + (unsigned long long)block, + (unsigned long long)block, blktypedesc); if(!found_dup) { fsck_blockmap_set(ip, block, _("bad indirect"), gfs2_meta_inval); @@ -291,7 +294,8 @@ static int check_metalist(struct gfs2_inode *ip, uint64_t block, } static int undo_check_metalist(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private) + struct gfs2_buffer_head **bh, int h, + void *private) { struct duptree *d; int found_dup = 0, iblk_type; @@ -305,7 +309,7 @@ static int undo_check_metalist(struct gfs2_inode *ip, uint64_t block, _("itself"), gfs2_block_free); return 1; } - if (S_ISDIR(ip->i_di.di_mode)) + if (S_ISDIR(ip->i_di.di_mode) && h == ip->i_di.di_height) iblk_type = GFS2_METATYPE_JD; else iblk_type = GFS2_METATYPE_IN; @@ -849,7 +853,8 @@ static int mark_block_invalid(struct gfs2_inode *ip, uint64_t block, } static int invalidate_metadata(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private) + struct gfs2_buffer_head **bh, int h, + void *private) { return mark_block_invalid(ip, block, ref_as_meta, _("metadata")); } @@ -930,7 +935,8 @@ static int rangecheck_block(struct gfs2_inode *ip, uint64_t block, } static int rangecheck_metadata(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private) + struct gfs2_buffer_head **bh, int h, + void *private) { return rangecheck_block(ip, block, bh, _("metadata"), private); } diff --git a/gfs2/fsck/pass1b.c b/gfs2/fsck/pass1b.c index 8b67573..a0c5c88 100644 --- a/gfs2/fsck/pass1b.c +++ b/gfs2/fsck/pass1b.c @@ -42,7 +42,7 @@ struct dup_handler { }; static int check_metalist(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private); + struct gfs2_buffer_head **bh, int h, void *private); static int check_data(struct gfs2_inode *ip, uint64_t block, void *private); static int check_eattr_indir(struct gfs2_inode *ip, uint64_t block, uint64_t parent, struct gfs2_buffer_head **bh, @@ -89,7 +89,7 @@ struct metawalk_fxns find_dirents = { }; static int check_metalist(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private) + struct gfs2_buffer_head **bh, int h, void *private) { return add_duplicate_ref(ip, block, ref_as_meta, 1, INODE_VALID); } @@ -213,7 +213,8 @@ static int find_dentry(struct gfs2_inode *ip, struct gfs2_dirent *de, } static int clear_dup_metalist(struct gfs2_inode *ip, uint64_t block, - struct gfs2_buffer_head **bh, void *private) + struct gfs2_buffer_head **bh, int h, + void *private) { struct dup_handler *dh = (struct dup_handler *) private; struct duptree *d; @@ -262,21 +263,21 @@ static int clear_dup_metalist(struct gfs2_inode *ip, uint64_t block, static int clear_dup_data(struct gfs2_inode *ip, uint64_t block, void *private) { - return clear_dup_metalist(ip, block, NULL, private); + return clear_dup_metalist(ip, block, NULL, 0, private); } static int clear_dup_eattr_indir(struct gfs2_inode *ip, uint64_t block, uint64_t parent, struct gfs2_buffer_head **bh, void *private) { - return clear_dup_metalist(ip, block, NULL, private); + return clear_dup_metalist(ip, block, NULL, 0, private); } static int clear_dup_eattr_leaf(struct gfs2_inode *ip, uint64_t block, uint64_t parent, struct gfs2_buffer_head **bh, void *private) { - return clear_dup_metalist(ip, block, NULL, private); + return clear_dup_metalist(ip, block, NULL, 0, private); } static int clear_eattr_entry (struct gfs2_inode *ip, @@ -329,7 +330,7 @@ static int clear_eattr_extentry(struct gfs2_inode *ip, uint64_t *ea_data_ptr, { uint64_t block = be64_to_cpu(*ea_data_ptr); - return clear_dup_metalist(ip, block, NULL, private); + return clear_dup_metalist(ip, block, NULL, 0, private); } /* Finds all references to duplicate blocks in the metadata */