diff options
Diffstat (limited to 'fs/jbd2/recovery.c')
| -rw-r--r-- | fs/jbd2/recovery.c | 163 | 
1 files changed, 143 insertions, 20 deletions
diff --git a/fs/jbd2/recovery.c b/fs/jbd2/recovery.c index 2bc4d5f116f..3b6bb19d60b 100644 --- a/fs/jbd2/recovery.c +++ b/fs/jbd2/recovery.c @@ -21,6 +21,7 @@  #include <linux/jbd2.h>  #include <linux/errno.h>  #include <linux/crc32.h> +#include <linux/blkdev.h>  #endif  /* @@ -89,7 +90,7 @@ static int do_readahead(journal_t *journal, unsigned int start)  		err = jbd2_journal_bmap(journal, next, &blocknr);  		if (err) { -			printk (KERN_ERR "JBD: bad block at offset %u\n", +			printk(KERN_ERR "JBD2: bad block at offset %u\n",  				next);  			goto failed;  		} @@ -138,14 +139,14 @@ static int jread(struct buffer_head **bhp, journal_t *journal,  	*bhp = NULL;  	if (offset >= journal->j_maxlen) { -		printk(KERN_ERR "JBD: corrupted journal superblock\n"); +		printk(KERN_ERR "JBD2: corrupted journal superblock\n");  		return -EIO;  	}  	err = jbd2_journal_bmap(journal, offset, &blocknr);  	if (err) { -		printk (KERN_ERR "JBD: bad block at offset %u\n", +		printk(KERN_ERR "JBD2: bad block at offset %u\n",  			offset);  		return err;  	} @@ -163,7 +164,7 @@ static int jread(struct buffer_head **bhp, journal_t *journal,  	}  	if (!buffer_uptodate(bh)) { -		printk (KERN_ERR "JBD: Failed to read block at offset %u\n", +		printk(KERN_ERR "JBD2: Failed to read block at offset %u\n",  			offset);  		brelse(bh);  		return -EIO; @@ -173,6 +174,25 @@ static int jread(struct buffer_head **bhp, journal_t *journal,  	return 0;  } +static int jbd2_descr_block_csum_verify(journal_t *j, +					void *buf) +{ +	struct jbd2_journal_block_tail *tail; +	__be32 provided; +	__u32 calculated; + +	if (!JBD2_HAS_INCOMPAT_FEATURE(j, JBD2_FEATURE_INCOMPAT_CSUM_V2)) +		return 1; + +	tail = (struct jbd2_journal_block_tail *)(buf + j->j_blocksize - +			sizeof(struct jbd2_journal_block_tail)); +	provided = tail->t_checksum; +	tail->t_checksum = 0; +	calculated = jbd2_chksum(j, j->j_csum_seed, buf, j->j_blocksize); +	tail->t_checksum = provided; + +	return provided == cpu_to_be32(calculated); +}  /*   * Count the number of in-use tags in a journal descriptor block. @@ -185,6 +205,9 @@ static int count_tags(journal_t *journal, struct buffer_head *bh)  	int			nr = 0, size = journal->j_blocksize;  	int			tag_bytes = journal_tag_bytes(journal); +	if (JBD2_HAS_INCOMPAT_FEATURE(journal, JBD2_FEATURE_INCOMPAT_CSUM_V2)) +		size -= sizeof(struct jbd2_journal_block_tail); +  	tagp = &bh->b_data[sizeof(journal_header_t)];  	while ((tagp - bh->b_data + tag_bytes) <= size) { @@ -192,10 +215,10 @@ static int count_tags(journal_t *journal, struct buffer_head *bh)  		nr++;  		tagp += tag_bytes; -		if (!(tag->t_flags & cpu_to_be32(JBD2_FLAG_SAME_UUID))) +		if (!(tag->t_flags & cpu_to_be16(JBD2_FLAG_SAME_UUID)))  			tagp += 16; -		if (tag->t_flags & cpu_to_be32(JBD2_FLAG_LAST_TAG)) +		if (tag->t_flags & cpu_to_be16(JBD2_FLAG_LAST_TAG))  			break;  	} @@ -251,10 +274,10 @@ int jbd2_journal_recover(journal_t *journal)  	if (!err)  		err = do_one_pass(journal, &info, PASS_REPLAY); -	jbd_debug(1, "JBD: recovery, exit status %d, " +	jbd_debug(1, "JBD2: recovery, exit status %d, "  		  "recovered transactions %u to %u\n",  		  err, info.start_transaction, info.end_transaction); -	jbd_debug(1, "JBD: Replayed %d and revoked %d/%d blocks\n", +	jbd_debug(1, "JBD2: Replayed %d and revoked %d/%d blocks\n",  		  info.nr_replays, info.nr_revoke_hits, info.nr_revokes);  	/* Restart the log at the next transaction ID, thus invalidating @@ -265,7 +288,12 @@ int jbd2_journal_recover(journal_t *journal)  	err2 = sync_blockdev(journal->j_fs_dev);  	if (!err)  		err = err2; - +	/* Make sure all replayed data is on permanent storage */ +	if (journal->j_flags & JBD2_BARRIER) { +		err2 = blkdev_issue_flush(journal->j_fs_dev, GFP_KERNEL, NULL); +		if (!err) +			err = err2; +	}  	return err;  } @@ -293,16 +321,16 @@ int jbd2_journal_skip_recovery(journal_t *journal)  	err = do_one_pass(journal, &info, PASS_SCAN);  	if (err) { -		printk(KERN_ERR "JBD: error %d scanning journal\n", err); +		printk(KERN_ERR "JBD2: error %d scanning journal\n", err);  		++journal->j_transaction_sequence;  	} else {  #ifdef CONFIG_JBD2_DEBUG  		int dropped = info.end_transaction -   			be32_to_cpu(journal->j_superblock->s_sequence); -#endif  		jbd_debug(1, -			  "JBD: ignoring %d transaction%s from the journal.\n", +			  "JBD2: ignoring %d transaction%s from the journal.\n",  			  dropped, (dropped == 1) ? "" : "s"); +#endif  		journal->j_transaction_sequence = ++info.end_transaction;  	} @@ -338,7 +366,7 @@ static int calc_chksums(journal_t *journal, struct buffer_head *bh,  		wrap(journal, *next_log_block);  		err = jread(&obh, journal, io_block);  		if (err) { -			printk(KERN_ERR "JBD: IO error %d recovering block " +			printk(KERN_ERR "JBD2: IO error %d recovering block "  				"%lu in log\n", err, io_block);  			return 1;  		} else { @@ -350,6 +378,40 @@ static int calc_chksums(journal_t *journal, struct buffer_head *bh,  	return 0;  } +static int jbd2_commit_block_csum_verify(journal_t *j, void *buf) +{ +	struct commit_header *h; +	__be32 provided; +	__u32 calculated; + +	if (!JBD2_HAS_INCOMPAT_FEATURE(j, JBD2_FEATURE_INCOMPAT_CSUM_V2)) +		return 1; + +	h = buf; +	provided = h->h_chksum[0]; +	h->h_chksum[0] = 0; +	calculated = jbd2_chksum(j, j->j_csum_seed, buf, j->j_blocksize); +	h->h_chksum[0] = provided; + +	return provided == cpu_to_be32(calculated); +} + +static int jbd2_block_tag_csum_verify(journal_t *j, journal_block_tag_t *tag, +				      void *buf, __u32 sequence) +{ +	__u32 csum32; +	__be32 seq; + +	if (!JBD2_HAS_INCOMPAT_FEATURE(j, JBD2_FEATURE_INCOMPAT_CSUM_V2)) +		return 1; + +	seq = cpu_to_be32(sequence); +	csum32 = jbd2_chksum(j, j->j_csum_seed, (__u8 *)&seq, sizeof(seq)); +	csum32 = jbd2_chksum(j, csum32, buf, j->j_blocksize); + +	return tag->t_checksum == cpu_to_be16(csum32); +} +  static int do_one_pass(journal_t *journal,  			struct recovery_info *info, enum passtype pass)  { @@ -363,6 +425,7 @@ static int do_one_pass(journal_t *journal,  	int			blocktype;  	int			tag_bytes = journal_tag_bytes(journal);  	__u32			crc32_sum = ~0; /* Transactional Checksums */ +	int			descr_csum_size = 0;  	/*  	 * First thing is to establish what we expect to find in the log @@ -411,7 +474,7 @@ static int do_one_pass(journal_t *journal,  		 * either the next descriptor block or the final commit  		 * record. */ -		jbd_debug(3, "JBD: checking block %ld\n", next_log_block); +		jbd_debug(3, "JBD2: checking block %ld\n", next_log_block);  		err = jread(&bh, journal, next_log_block);  		if (err)  			goto failed; @@ -448,6 +511,18 @@ static int do_one_pass(journal_t *journal,  		switch(blocktype) {  		case JBD2_DESCRIPTOR_BLOCK: +			/* Verify checksum first */ +			if (JBD2_HAS_INCOMPAT_FEATURE(journal, +					JBD2_FEATURE_INCOMPAT_CSUM_V2)) +				descr_csum_size = +					sizeof(struct jbd2_journal_block_tail); +			if (descr_csum_size > 0 && +			    !jbd2_descr_block_csum_verify(journal, +							  bh->b_data)) { +				err = -EIO; +				goto failed; +			} +  			/* If it is a valid descriptor block, replay it  			 * in pass REPLAY; if journal_checksums enabled, then  			 * calculate checksums in PASS_SCAN, otherwise, @@ -478,11 +553,11 @@ static int do_one_pass(journal_t *journal,  			tagp = &bh->b_data[sizeof(journal_header_t)];  			while ((tagp - bh->b_data + tag_bytes) -			       <= journal->j_blocksize) { +			       <= journal->j_blocksize - descr_csum_size) {  				unsigned long io_block;  				tag = (journal_block_tag_t *) tagp; -				flags = be32_to_cpu(tag->t_flags); +				flags = be16_to_cpu(tag->t_flags);  				io_block = next_log_block++;  				wrap(journal, next_log_block); @@ -491,8 +566,8 @@ static int do_one_pass(journal_t *journal,  					/* Recover what we can, but  					 * report failure at the end. */  					success = err; -					printk (KERN_ERR -						"JBD: IO error %d recovering " +					printk(KERN_ERR +						"JBD2: IO error %d recovering "  						"block %ld in log\n",  						err, io_block);  				} else { @@ -513,6 +588,19 @@ static int do_one_pass(journal_t *journal,  						goto skip_write;  					} +					/* Look for block corruption */ +					if (!jbd2_block_tag_csum_verify( +						journal, tag, obh->b_data, +						be32_to_cpu(tmp->h_sequence))) { +						brelse(obh); +						success = -EIO; +						printk(KERN_ERR "JBD2: Invalid " +						       "checksum recovering " +						       "block %llu in log\n", +						       blocknr); +						continue; +					} +  					/* Find a buffer for the new  					 * data being restored */  					nbh = __getblk(journal->j_fs_dev, @@ -520,7 +608,7 @@ static int do_one_pass(journal_t *journal,  							journal->j_blocksize);  					if (nbh == NULL) {  						printk(KERN_ERR -						       "JBD: Out of memory " +						       "JBD2: Out of memory "  						       "during recovery.\n");  						err = -ENOMEM;  						brelse(bh); @@ -647,6 +735,19 @@ static int do_one_pass(journal_t *journal,  				}  				crc32_sum = ~0;  			} +			if (pass == PASS_SCAN && +			    !jbd2_commit_block_csum_verify(journal, +							   bh->b_data)) { +				info->end_transaction = next_commit_ID; + +				if (!JBD2_HAS_INCOMPAT_FEATURE(journal, +				     JBD2_FEATURE_INCOMPAT_ASYNC_COMMIT)) { +					journal->j_failed_commit = +						next_commit_ID; +					brelse(bh); +					break; +				} +			}  			brelse(bh);  			next_commit_ID++;  			continue; @@ -689,7 +790,7 @@ static int do_one_pass(journal_t *journal,  		/* It's really bad news if different passes end up at  		 * different places (but possible due to IO errors). */  		if (info->end_transaction != next_commit_ID) { -			printk (KERN_ERR "JBD: recovery pass %d ended at " +			printk(KERN_ERR "JBD2: recovery pass %d ended at "  				"transaction %u, expected %u\n",  				pass, next_commit_ID, info->end_transaction);  			if (!success) @@ -703,6 +804,25 @@ static int do_one_pass(journal_t *journal,  	return err;  } +static int jbd2_revoke_block_csum_verify(journal_t *j, +					 void *buf) +{ +	struct jbd2_journal_revoke_tail *tail; +	__be32 provided; +	__u32 calculated; + +	if (!JBD2_HAS_INCOMPAT_FEATURE(j, JBD2_FEATURE_INCOMPAT_CSUM_V2)) +		return 1; + +	tail = (struct jbd2_journal_revoke_tail *)(buf + j->j_blocksize - +			sizeof(struct jbd2_journal_revoke_tail)); +	provided = tail->r_checksum; +	tail->r_checksum = 0; +	calculated = jbd2_chksum(j, j->j_csum_seed, buf, j->j_blocksize); +	tail->r_checksum = provided; + +	return provided == cpu_to_be32(calculated); +}  /* Scan a revoke record, marking all blocks mentioned as revoked. */ @@ -717,6 +837,9 @@ static int scan_revoke_records(journal_t *journal, struct buffer_head *bh,  	offset = sizeof(jbd2_journal_revoke_header_t);  	max = be32_to_cpu(header->r_count); +	if (!jbd2_revoke_block_csum_verify(journal, header)) +		return -EINVAL; +  	if (JBD2_HAS_INCOMPAT_FEATURE(journal, JBD2_FEATURE_INCOMPAT_64BIT))  		record_len = 8;  | 
