cluster: STABLE3 - fsck.gfs2: reprocess lost+found and other inode metadata when blocks are added

Bob Peterson rpeterso at fedoraproject.org
Tue Jan 26 21:20:43 UTC 2010


Gitweb:        http://git.fedorahosted.org/git/cluster.git?p=cluster.git;a=commitdiff;h=9e52bf1d064e6e0fee60927b8cd5496e19917196
Commit:        9e52bf1d064e6e0fee60927b8cd5496e19917196
Parent:        b46c19c385f182f950c6df56bafd3db15459c48e
Author:        Bob Peterson <bob at ganesha.peterson>
AuthorDate:    Fri Jan 22 15:29:19 2010 -0600
Committer:     Bob Peterson <rpeterso at redhat.com>
CommitterDate: Tue Jan 26 14:39:31 2010 -0600

fsck.gfs2: reprocess lost+found and other inode metadata when blocks are added

On rare occasions, fsck.gfs2 needs to allocate blocks in the file system.
For example, this can happen when new directory leafs are added to
lost+found and when lost+found's hash table needs to be doubled.
While libgfs2 did a stellar job of updating the rgrp bitmap information,
those newly allocated blocks were unfortunately not being properly
accounted for in the fsck.gfs2 blockmap.  Therefore, pass5 would
mistakenly mark them as free blocks.  Further use of the file
system would often allocate those blocks a second time, creating
a duplicate block reference.  That means very bad file system
corruption.  This patch fixes the problem by checking for block
count changes in inodes at critical points, and subsequently fixing
up the blockmap using new "alloc_*" helper functions.

rhbz#455300
---
 gfs2/fsck/lost_n_found.c |    8 ++++
 gfs2/fsck/metawalk.c     |   98 ++++++++++++++++++++++++++++++++++++++++++++++
 gfs2/fsck/metawalk.h     |    1 +
 gfs2/fsck/pass2.c        |   16 +++++++
 gfs2/fsck/pass3.c        |   10 +++++
 5 files changed, 133 insertions(+), 0 deletions(-)

diff --git a/gfs2/fsck/lost_n_found.c b/gfs2/fsck/lost_n_found.c
index 6d26096..5b9be67 100644
--- a/gfs2/fsck/lost_n_found.c
+++ b/gfs2/fsck/lost_n_found.c
@@ -27,6 +27,7 @@
 int add_inode_to_lf(struct gfs2_inode *ip){
 	char tmp_name[256];
 	__be32 inode_type;
+	uint64_t lf_blocks;
 	struct dir_info *di;
 
 	if(!lf_dip) {
@@ -81,6 +82,8 @@ int add_inode_to_lf(struct gfs2_inode *ip){
 		log_err( _("Trying to add lost+found to itself...skipping"));
 		return 0;
 	}
+	lf_blocks = lf_dip->i_di.di_blocks;
+
 	switch(ip->i_di.di_mode & S_IFMT){
 	case S_IFDIR:
 		log_info( _("Adding .. entry pointing to lost+found for "
@@ -163,6 +166,11 @@ int add_inode_to_lf(struct gfs2_inode *ip){
 
 	dir_add(lf_dip, tmp_name, strlen(tmp_name), &(ip->i_di.di_num),
 		inode_type);
+	/* If the lf directory had new blocks added we have to mark them
+	   properly in the bitmap so they're not freed. */
+	if (lf_dip->i_di.di_blocks != lf_blocks)
+		reprocess_inode(lf_dip, "lost+found");
+
 	/* This inode is linked from lost+found */
   	increment_link(ip->i_di.di_num.no_addr, lf_dip->i_di.di_num.no_addr,
 		       _("from lost+found"));
diff --git a/gfs2/fsck/metawalk.c b/gfs2/fsck/metawalk.c
index 2ad47fe..3ccb0bc 100644
--- a/gfs2/fsck/metawalk.c
+++ b/gfs2/fsck/metawalk.c
@@ -1430,3 +1430,101 @@ int delete_eattr_leaf(struct gfs2_inode *ip, uint64_t block, uint64_t parent,
 	return delete_blocks(ip, block, NULL, _("extended attribute"),
 			     private);
 }
+
+static int alloc_metalist(struct gfs2_inode *ip, uint64_t block,
+			  struct gfs2_buffer_head **bh, void *private)
+{
+	uint8_t q;
+	const char *desc = (const char *)private;
+
+	/* No need to range_check here--if it was added, it's in range. */
+	/* We can't check the bitmap here because this function is called
+	   after the bitmap has been set but before the blockmap has. */
+	*bh = bread(ip->i_sbd, block);
+	q = block_type(block);
+	if (blockmap_to_bitmap(q) == GFS2_BLKST_FREE) { /* If not marked yet */
+		log_debug(_("%s reference to new metadata block "
+			    "%lld (0x%llx) is now marked as indirect.\n"),
+			  desc, (unsigned long long)block,
+			  (unsigned long long)block);
+		gfs2_blockmap_set(bl, block, gfs2_indir_blk);
+	}
+	return 0;
+}
+
+static int alloc_data(struct gfs2_inode *ip, uint64_t block, void *private)
+{
+	uint8_t q;
+	const char *desc = (const char *)private;
+
+	/* No need to range_check here--if it was added, it's in range. */
+	/* We can't check the bitmap here because this function is called
+	   after the bitmap has been set but before the blockmap has. */
+	q = block_type(block);
+	if (blockmap_to_bitmap(q) == GFS2_BLKST_FREE) { /* If not marked yet */
+		log_debug(_("%s reference to new data block "
+			    "%lld (0x%llx) is now marked as data.\n"),
+			  desc, (unsigned long long)block,
+			  (unsigned long long)block);
+		gfs2_blockmap_set(bl, block, gfs2_block_used);
+	}
+	return 0;
+}
+
+static int alloc_leaf(struct gfs2_inode *ip, uint64_t block,
+		      struct gfs2_buffer_head *bh, void *private)
+{
+	uint8_t q;
+
+	/* No need to range_check here--if it was added, it's in range. */
+	/* We can't check the bitmap here because this function is called
+	   after the bitmap has been set but before the blockmap has. */
+	q = block_type(block);
+	if (blockmap_to_bitmap(q) == GFS2_BLKST_FREE) /* If not marked yet */
+		fsck_blockmap_set(ip, block, _("newly allocated leaf"),
+				  gfs2_leaf_blk);
+	return 0;
+}
+
+struct metawalk_fxns alloc_fxns = {
+	.private = NULL,
+	.check_leaf = alloc_leaf,
+	.check_metalist = alloc_metalist,
+	.check_data = alloc_data,
+	.check_eattr_indir = NULL,
+	.check_eattr_leaf = NULL,
+	.check_dentry = NULL,
+	.check_eattr_entry = NULL,
+	.check_eattr_extentry = NULL,
+	.finish_eattr_indir = NULL,
+};
+
+/*
+ * reprocess_inode - fixes the blockmap to match the bitmap due to an
+ *                   unexpected block allocation via libgfs2.
+ *
+ * The problem we're trying to overcome here is when a new block must be
+ * added to a dinode because of a write.  This will happen when lost+found
+ * needs a new indirect block for its hash table.  In that case, the write
+ * causes a new block to be assigned in the bitmap but that block is not yet
+ * accurately reflected in the fsck blockmap.  We need to compensate here.
+ *
+ * We can't really use fsck_blockmap_set here because the new block
+ * was already allocated by libgfs2 and therefore it took care of
+ * the rgrp free space variable.  fsck_blockmap_set adjusts the free space
+ * in the rgrp according to the change, which has already been done.
+ * So it's only our blockmap that now disagrees with the rgrp bitmap, so we
+ * need to fix only that.
+ */
+void reprocess_inode(struct gfs2_inode *ip, const char *desc)
+{
+	int error;
+
+	alloc_fxns.private = (void *)desc;
+	log_info( _("%s had blocks added; reprocessing its metadata tree "
+		    "at height=%d.\n"), desc, ip->i_di.di_height);
+	error = check_metatree(ip, &alloc_fxns);
+	if (error)
+		log_err( _("Error %d reprocessing the %s metadata tree.\n"),
+			 error, desc);
+}
diff --git a/gfs2/fsck/metawalk.h b/gfs2/fsck/metawalk.h
index 72c282a..653b512 100644
--- a/gfs2/fsck/metawalk.h
+++ b/gfs2/fsck/metawalk.h
@@ -32,6 +32,7 @@ extern int _fsck_blockmap_set(struct gfs2_inode *ip, uint64_t bblock,
 		       const char *caller, int line);
 extern int check_n_fix_bitmap(struct gfs2_sbd *sdp, uint64_t blk,
 		       enum gfs2_mark_block new_blockmap_state);
+extern void reprocess_inode(struct gfs2_inode *ip, const char *desc);
 extern struct duptree *dupfind(uint64_t block);
 
 #define is_duplicate(dblock) ((dupfind(dblock)) ? 1 : 0)
diff --git a/gfs2/fsck/pass2.c b/gfs2/fsck/pass2.c
index 0650966..583bc09 100644
--- a/gfs2/fsck/pass2.c
+++ b/gfs2/fsck/pass2.c
@@ -549,6 +549,8 @@ static int check_system_dir(struct gfs2_inode *sysinode, const char *dirname,
 	if(!ds.dotdir) {
 		log_err( _("No '.' entry found for %s directory.\n"), dirname);
 		if (query( _("Is it okay to add '.' entry? (y/n) "))) {
+			uint64_t cur_blks = sysinode->i_di.di_blocks;
+
 			sprintf(tmp_name, ".");
 			filename_len = strlen(tmp_name); /* no trailing NULL */
 			if(!(filename = malloc(sizeof(char) * filename_len))) {
@@ -566,6 +568,8 @@ static int check_system_dir(struct gfs2_inode *sysinode, const char *dirname,
 			log_warn( _("Adding '.' entry\n"));
 			dir_add(sysinode, filename, filename_len,
 				&(sysinode->i_di.di_num), DT_DIR);
+			if (cur_blks != sysinode->i_di.di_blocks)
+				reprocess_inode(sysinode, dirname);
 			/* This system inode is linked to itself via '.' */
 			increment_link(sysinode->i_di.di_num.no_addr,
 				       sysinode->i_di.di_num.no_addr,
@@ -731,6 +735,8 @@ int pass2(struct gfs2_sbd *sbp)
 				dirblk, dirblk);
 
 			if (query( _("Is it okay to add '.' entry? (y/n) "))) {
+				uint64_t cur_blks;
+
 				sprintf(tmp_name, ".");
 				filename_len = strlen(tmp_name); /* no trailing
 								    NULL */
@@ -748,8 +754,18 @@ int pass2(struct gfs2_sbd *sbp)
 				}
 				memcpy(filename, tmp_name, filename_len);
 
+				cur_blks = ip->i_di.di_blocks;
 				dir_add(ip, filename, filename_len,
 					&(ip->i_di.di_num), DT_DIR);
+				if (cur_blks != ip->i_di.di_blocks) {
+					char dirname[80];
+
+					sprintf(dirname, _("Directory at %lld "
+							   "(0x%llx)"),
+						(unsigned long long)dirblk,
+						(unsigned long long)dirblk);
+					reprocess_inode(ip, dirname);
+				}
 				/* directory links to itself via '.' */
 				increment_link(ip->i_di.di_num.no_addr,
 					       ip->i_di.di_num.no_addr,
diff --git a/gfs2/fsck/pass3.c b/gfs2/fsck/pass3.c
index 2ff4fbf..260f4fe 100644
--- a/gfs2/fsck/pass3.c
+++ b/gfs2/fsck/pass3.c
@@ -20,6 +20,7 @@ static int attach_dotdot_to(struct gfs2_sbd *sbp, uint64_t newdotdot,
 	char *filename;
 	int filename_len;
 	struct gfs2_inode *ip, *pip;
+	uint64_t cur_blks;
 
 	ip = fsck_load_inode(sbp, block);
 	pip = fsck_load_inode(sbp, newdotdot);
@@ -50,7 +51,16 @@ static int attach_dotdot_to(struct gfs2_sbd *sbp, uint64_t newdotdot,
 		log_warn( _("Unable to remove \"..\" directory entry.\n"));
 	else
 		decrement_link(olddotdot, block, _("old \"..\""));
+	cur_blks = ip->i_di.di_blocks;
 	dir_add(ip, filename, filename_len, &pip->i_di.di_num, DT_DIR);
+	if (cur_blks != ip->i_di.di_blocks) {
+		char dirname[80];
+
+		sprintf(dirname, _("Directory at %lld (0x%llx)"),
+			(unsigned long long)ip->i_di.di_num.no_addr,
+			(unsigned long long)ip->i_di.di_num.no_addr);
+		reprocess_inode(ip, dirname);
+	}
 	increment_link(newdotdot, block, _("new \"..\""));
 	bmodified(ip->i_bh);
 	fsck_inode_put(&ip);


More information about the cluster-commits mailing list