Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > 9383e745e23602bc45f9c92184feea59 > files > 29

gfs2-utils-0.1.62-28.el5.src.rpm

commit afc7e08118c14ab73ea58907529ed456fea3c602
Author: Bob Peterson <bob@ganesha.peterson>
Date:   Thu Jan 21 23:10:23 2010 -0600

    fsck.gfs2: fix directories that have odd number of pointers.
    
    Directory dinodes in gfs2 use the "depth" field to determine the
    number of pointers in the hash table.  The depth must be a
    factor of 2.  So 2, 4, 8, 16, etc.  That's because the hash table
    is doubled every time it gets full.  The problem is, fsck.gfs2
    previously had no checks for the depth being correct and/or
    the hash table pointers being multiples of two.  Needless to say,
    the directory code gets very confused if this condition is not
    met due to corruption.  This patch introduces new code to
    validate the depth is correct and fix the depth and/or pointers
    if they're not.
    
    rhbz#455300

diff --git a/gfs2/fsck/metawalk.c b/gfs2/fsck/metawalk.c
index 79b4bdd..4d70693 100644
--- a/gfs2/fsck/metawalk.c
+++ b/gfs2/fsck/metawalk.c
@@ -344,6 +344,61 @@ static void warn_and_patch(struct gfs2_inode *ip, uint64_t *leaf_no,
 	*leaf_no = old_leaf;
 }
 
+/*
+ * fix_leaf_pointers - fix a directory dinode that has a number of pointers
+ *                     that is not a multiple of 2.
+ * dip - the directory inode having the problem
+ * lindex - the index of the leaf right after the problem (need to back up)
+ * cur_numleafs - current (incorrect) number of instances of the leaf block
+ * correct_numleafs - the correct number instances of the leaf block
+ */
+static int fix_leaf_pointers(struct gfs2_inode *dip, int *lindex,
+			     int cur_numleafs, int correct_numleafs)
+{
+	int count;
+	char *ptrbuf;
+	int start_lindex = *lindex - cur_numleafs; /* start of bad ptrs */
+	int tot_num_ptrs = (1 << dip->i_di.di_depth) - start_lindex;
+	int bufsize = tot_num_ptrs * sizeof(uint64_t);
+	int off_by = cur_numleafs - correct_numleafs;
+
+	ptrbuf = malloc(bufsize);
+	if (!ptrbuf) {
+		log_err( _("Error: Cannot allocate memory to fix the leaf "
+			   "pointers.\n"));
+		return -1;
+	}
+	/* Read all the pointers, starting with the first bad one */
+	count = gfs2_readi(dip, ptrbuf, start_lindex * sizeof(uint64_t),
+			   bufsize);
+	if (count != bufsize) {
+		log_err( _("Error: bad read while fixing leaf pointers.\n"));
+		free(ptrbuf);
+		return -1;
+	}
+
+	bufsize -= off_by * sizeof(uint64_t); /* We need to write fewer */
+	/* Write the same pointers, but offset them so they fit within the
+	   smaller factor of 2. So if we have 12 pointers, write out only
+	   the last 8 of them.  If we have 7, write the last 4, etc.
+	   We need to write these starting at the current lindex and adjust
+	   lindex accordingly. */
+	count = gfs2_writei(dip, ptrbuf + (off_by * sizeof(uint64_t)),
+			    start_lindex * sizeof(uint64_t), bufsize);
+	if (count != bufsize) {
+		log_err( _("Error: bad read while fixing leaf pointers.\n"));
+		free(ptrbuf);
+		return -1;
+	}
+	/* Now zero out the hole left at the end */
+	memset(ptrbuf, 0, off_by * sizeof(uint64_t));
+	gfs2_writei(dip, ptrbuf, (start_lindex * sizeof(uint64_t)) +
+		    bufsize, off_by * sizeof(uint64_t));
+	free(ptrbuf);
+	*lindex -= off_by; /* adjust leaf index to account for the change */
+	return 0;
+}
+
 /* Checks exhash directory entries */
 static int check_leaf_blks(struct gfs2_inode *ip, struct metawalk_fxns *pass)
 {
@@ -394,33 +449,71 @@ static int check_leaf_blks(struct gfs2_inode *ip, struct metawalk_fxns *pass)
 			ref_count++;
 			continue;
 		}
-		if (gfs2_check_range(ip->i_sbd, old_leaf) == 0 &&
-		    ref_count != exp_count) {
-			log_err( _("Dir #%llu (0x%llx) has an incorrect "
-				   "number of pointers to leaf #%llu "
-				   " (0x%llx)\n\tFound: %u,  Expected: "
-				   "%u\n"), (unsigned long long)
-				 ip->i_di.di_num.no_addr,
-				 (unsigned long long)
-				 ip->i_di.di_num.no_addr,
-				 (unsigned long long)old_leaf,
-				 (unsigned long long)old_leaf,
-				 ref_count, exp_count);
-			if (query( _("Attempt to fix it? (y/n) "))) {
-				int factor = 0, divisor = ref_count;
-
-				lbh = bread(sbp, old_leaf);
-				while (divisor > 1) {
-					factor++;
-					divisor /= 2;
+		if (gfs2_check_range(ip->i_sbd, old_leaf) == 0) {
+			int factor = 0, divisor = ref_count, multiple = 1;
+
+			/* Check to see if the number of pointers we found is
+			   a power of 2.  It needs to be and if it's not we
+			   need to fix it.*/
+			while (divisor > 1) {
+				factor++;
+				divisor /= 2;
+				multiple = multiple << 1;
+			}
+			if (ref_count != multiple) {
+				log_err( _("Directory #%llu (0x%llx) has an "
+					   "invalid number of pointers to "
+					   "leaf #%llu (0x%llx)\n\tFound: %u, "
+					   "which is not a factor of 2.\n"),
+					 (unsigned long long)
+					 ip->i_di.di_num.no_addr,
+					 (unsigned long long)
+					 ip->i_di.di_num.no_addr,
+					 (unsigned long long)old_leaf,
+					 (unsigned long long)old_leaf,
+					 ref_count);
+				if (!query( _("Attempt to fix it? (y/n) "))) {
+					log_err( _("Directory inode was not "
+						   "fixed.\n"));
+					return 1;
 				}
+				error = fix_leaf_pointers(ip, &lindex,
+							  ref_count, multiple);
+				if (error)
+					return error;
+				ref_count = multiple;
+				log_err( _("Directory inode was fixed.\n"));
+			}
+			/* Check to see if the counted number of leaf pointers
+			   is what we expect. */
+			if (ref_count != exp_count) {
+				log_err( _("Directory #%llu (0x%llx) has an "
+					   "incorrect number of pointers to "
+					   "leaf #%llu (0x%llx)\n\tFound: "
+					   "%u,  Expected: %u\n"),
+					 (unsigned long long)
+					 ip->i_di.di_num.no_addr,
+					 (unsigned long long)
+					 ip->i_di.di_num.no_addr,
+					 (unsigned long long)old_leaf,
+					 (unsigned long long)old_leaf,
+					 ref_count, exp_count);
+				if (!query( _("Attempt to fix it? (y/n) "))) {
+					log_err( _("Directory leaf was not "
+						   "fixed.\n"));
+					return 1;
+				}
+				lbh = bread(sbp, old_leaf);
 				gfs2_leaf_in(&oldleaf, lbh);
+				log_err( _("Leaf depth was %d, changed to "
+					   "%d\n"), oldleaf.lf_depth,
+					 ip->i_di.di_depth - factor);
 				oldleaf.lf_depth = ip->i_di.di_depth - factor;
 				gfs2_leaf_out(&oldleaf, lbh);
 				brelse(lbh);
+				exp_count = ref_count;
+				log_err( _("Directory leaf was fixed.\n"));
 			}
-			else
-				return 1;
 		}
 		ref_count = 1;