From 2f9a68cf72f242ae91ded47b5f605976bd3550d2 Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: May 25 2016 15:08:11 +0000 Subject: fsck.gfs2: Undo partially done metadata records This patch fixes a problem with duplicate block processing where one reference is as metadata and another is not (reference as data or ea or something). The problem is that the metadata block may have been processed as metadata (for example, an indirect block pointing to a bunch of data blocks, so all blocks it references are now marked as metadata) but the last resolved duplicate is not as metadata, so the sub-blocks are never reprocessed, or "undone". This patch makes it so that basically if the first reference is as metadata (so we know we processed its sub-blocks) and the inode referencing it as metadata is the LAST inode to reference it as metadata, the sub-blocks are sent through "undo" processing. Signed-off-by: Bob Peterson --- diff --git a/gfs2/fsck/afterpass1_common.c b/gfs2/fsck/afterpass1_common.c index bdea242..95fdbf8 100644 --- a/gfs2/fsck/afterpass1_common.c +++ b/gfs2/fsck/afterpass1_common.c @@ -22,10 +22,13 @@ * * Returns: 1 if there are any remaining references to this block, else 0. */ -int find_remove_dup(struct gfs2_inode *ip, uint64_t block, const char *btype) +static int find_remove_dup(struct gfs2_inode *ip, uint64_t block, + const char *btype, int *removed_last_meta) { struct duptree *dt; struct inode_with_dups *id; + int deleted_a_meta_ref = 0; + int meta_refs_left = 0; dt = dupfind(block); if (!dt) @@ -36,15 +39,30 @@ int find_remove_dup(struct gfs2_inode *ip, uint64_t block, const char *btype) if (!id) goto more_refs; + if (id->reftypecount[ref_as_meta]) + deleted_a_meta_ref = 1; dup_listent_delete(dt, id); if (dt->refs == 0) { log_info( _("This was the last reference: it's no longer a " "duplicate.\n")); dup_delete(dt); /* not duplicate now */ + if (deleted_a_meta_ref) { + log_debug("Removed the last reference as metadata.\n"); + *removed_last_meta = 1; + } return 0; + } else if (deleted_a_meta_ref) { + /* If we deleted a metadata reference, see if there are more + references as meta, or if it was the last one. */ + meta_refs_left = count_dup_meta_refs(dt); } more_refs: - log_info( _("%d block reference(s) remain.\n"), dt->refs); + log_info(_("%d block reference(s) remain (%d as metadata).\n"), + dt->refs, meta_refs_left); + if (deleted_a_meta_ref && meta_refs_left == 0) { + log_debug("Removed the last reference as metadata.\n"); + *removed_last_meta = 1; + } return 1; /* references still exist so do not free the block. */ } @@ -61,6 +79,7 @@ static int delete_block_if_notdup(struct gfs2_inode *ip, uint64_t block, void *private) { int q; + int removed_lastmeta; if (!valid_block(ip->i_sbd, block)) return meta_error; @@ -75,9 +94,13 @@ static int delete_block_if_notdup(struct gfs2_inode *ip, uint64_t block, (unsigned long long)ip->i_di.di_num.no_addr); return meta_is_good; } - if (find_remove_dup(ip, block, btype)) { /* a dup */ - if (was_duplicate) - *was_duplicate = 1; + if (find_remove_dup(ip, block, btype, &removed_lastmeta)) { /* a dup */ + if (was_duplicate) { + if (removed_lastmeta) + log_debug("Removed last reference as meta.\n"); + else + *was_duplicate = 1; + } log_err( _("Not clearing duplicate reference in inode " "at block #%llu (0x%llx) to block #%llu (0x%llx) " "because it's referenced by another inode.\n"), diff --git a/gfs2/fsck/afterpass1_common.h b/gfs2/fsck/afterpass1_common.h index 8b345ee..829828f 100644 --- a/gfs2/fsck/afterpass1_common.h +++ b/gfs2/fsck/afterpass1_common.h @@ -26,8 +26,6 @@ extern int delete_eattr_extentry(struct gfs2_inode *ip, int i, struct gfs2_ea_header *ea_hdr, struct gfs2_ea_header *ea_hdr_prev, void *private); -extern int find_remove_dup(struct gfs2_inode *ip, uint64_t block, - const char *btype); extern int remove_dentry_from_dir(struct gfs2_sbd *sdp, uint64_t dir, uint64_t dentryblock); #endif diff --git a/gfs2/fsck/metawalk.c b/gfs2/fsck/metawalk.c index 2d52217..8a1748b 100644 --- a/gfs2/fsck/metawalk.c +++ b/gfs2/fsck/metawalk.c @@ -1217,7 +1217,7 @@ static int build_and_check_metalist(struct gfs2_inode *ip, osi_list_t *mlp, struct gfs2_buffer_head *bh, *nbh, *metabh = ip->i_bh; osi_list_t *prev_list, *cur_list, *tmp; int h, head_size, iblk_type; - uint64_t *ptr, block; + uint64_t *ptr, block, *undoptr; int error, was_duplicate, is_valid; int maxptrs; @@ -1297,7 +1297,7 @@ static int build_and_check_metalist(struct gfs2_inode *ip, osi_list_t *mlp, "(0x%llx).\n"), (unsigned long long)block, (unsigned long long)block); - return error; + goto error_undo; } if (error == meta_skip_further) { log_info(_("\nUnrecoverable metadata " @@ -1306,18 +1306,24 @@ static int build_and_check_metalist(struct gfs2_inode *ip, osi_list_t *mlp, " will be skipped.\n"), (unsigned long long)block, (unsigned long long)block); - return error; + goto error_undo; } if (!is_valid) { log_debug( _("Skipping rejected block " "%llu (0x%llx)\n"), (unsigned long long)block, (unsigned long long)block); - if (pass->invalid_meta_is_fatal) - return meta_error; - + if (pass->invalid_meta_is_fatal) { + error = meta_error; + goto error_undo; + } continue; } + /* Note that there's a special case in which + we need to process the metadata block, even + if it was a duplicate. That's for cases + where we deleted the last reference as + metadata. */ if (was_duplicate) { log_debug( _("Skipping duplicate %llu " "(0x%llx)\n"), @@ -1330,9 +1336,10 @@ static int build_and_check_metalist(struct gfs2_inode *ip, osi_list_t *mlp, "%lld (0x%llx)\n"), (unsigned long long)block, (unsigned long long)block); - if (pass->invalid_meta_is_fatal) - return meta_error; - + if (pass->invalid_meta_is_fatal) { + error = meta_error; + goto error_undo; + } continue; } if (!nbh) @@ -1342,6 +1349,24 @@ static int build_and_check_metalist(struct gfs2_inode *ip, osi_list_t *mlp, } /* for blocks at that height */ } /* for height */ return 0; + +error_undo: /* undo what we've done so far for this block */ + if (pass->undo_check_meta == NULL) + return error; + + log_info(_("Undoing the work we did before the error on block %llu " + "(0x%llx).\n"), (unsigned long long)bh->b_blocknr, + (unsigned long long)bh->b_blocknr); + for (undoptr = (uint64_t *)(bh->b_data + head_size); undoptr < ptr && + (char *)undoptr < (bh->b_data + ip->i_sbd->bsize); + undoptr++) { + if (!*undoptr) + continue; + + block = be64_to_cpu(*undoptr); + pass->undo_check_meta(ip, block, h, pass->private); + } + return error; } /** diff --git a/gfs2/fsck/util.c b/gfs2/fsck/util.c index 2e77000..8a8220b 100644 --- a/gfs2/fsck/util.c +++ b/gfs2/fsck/util.c @@ -293,6 +293,28 @@ struct inode_with_dups *find_dup_ref_inode(struct duptree *dt, return NULL; } +/** + * count_dup_meta_refs - count the number of remaining references as metadata + */ +int count_dup_meta_refs(struct duptree *dt) +{ + osi_list_t *ref; + struct inode_with_dups *id; + int metarefs = 0; + + osi_list_foreach(ref, &dt->ref_invinode_list) { + id = osi_list_entry(ref, struct inode_with_dups, list); + if (id->reftypecount[ref_as_meta]) + metarefs++; + } + osi_list_foreach(ref, &dt->ref_inode_list) { + id = osi_list_entry(ref, struct inode_with_dups, list); + if (id->reftypecount[ref_as_meta]) + metarefs++; + } + return metarefs; +} + /* * add_duplicate_ref - Add a duplicate reference to the duplicates tree list * A new element of the tree will be created as needed diff --git a/gfs2/fsck/util.h b/gfs2/fsck/util.h index 4545594..d93b65d 100644 --- a/gfs2/fsck/util.h +++ b/gfs2/fsck/util.h @@ -20,7 +20,7 @@ int add_duplicate_ref(struct gfs2_inode *ip, uint64_t block, extern struct inode_with_dups *find_dup_ref_inode(struct duptree *dt, struct gfs2_inode *ip); extern void dup_listent_delete(struct duptree *dt, struct inode_with_dups *id); - +extern int count_dup_meta_refs(struct duptree *dt); extern const char *reftypes[ref_types + 1]; #define BLOCKMAP_SIZE1(size) ((size) >> 3)