Whamcloud - gitweb
Merge branch 'maint' into next
[tools/e2fsprogs.git] / e2fsck / recovery.c
index 34c9e59..69ed68a 100644 (file)
@@ -174,6 +174,27 @@ 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 journal_block_tail *tail;
+       __u32 provided, calculated;
+
+       if (!JFS_HAS_INCOMPAT_FEATURE(j, JFS_FEATURE_INCOMPAT_CSUM_V2))
+               return 1;
+
+       tail = (struct journal_block_tail *)(buf + j->j_blocksize -
+                       sizeof(struct journal_block_tail));
+       provided = tail->t_checksum;
+       tail->t_checksum = 0;
+       calculated = ext2fs_crc32c_le(~0, j->j_superblock->s_uuid,
+                                     sizeof(j->j_superblock->s_uuid));
+       calculated = ext2fs_crc32c_le(calculated, buf, j->j_blocksize);
+       tail->t_checksum = provided;
+
+       provided = ext2fs_be32_to_cpu(provided);
+       return provided == calculated;
+}
 
 /*
  * Count the number of in-use tags in a journal descriptor block.
@@ -186,6 +207,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 (JFS_HAS_INCOMPAT_FEATURE(journal, JFS_FEATURE_INCOMPAT_CSUM_V2))
+               size -= sizeof(struct journal_block_tail);
+
        tagp = &bh->b_data[sizeof(journal_header_t)];
 
        while ((tagp - bh->b_data + tag_bytes) <= size) {
@@ -193,10 +217,10 @@ static int count_tags(journal_t *journal, struct buffer_head *bh)
 
                nr++;
                tagp += tag_bytes;
-               if (!(tag->t_flags & cpu_to_be32(JFS_FLAG_SAME_UUID)))
+               if (!(tag->t_flags & cpu_to_be16(JFS_FLAG_SAME_UUID)))
                        tagp += 16;
 
-               if (tag->t_flags & cpu_to_be32(JFS_FLAG_LAST_TAG))
+               if (tag->t_flags & cpu_to_be16(JFS_FLAG_LAST_TAG))
                        break;
        }
 
@@ -327,7 +351,8 @@ static int calc_chksums(journal_t *journal, struct buffer_head *bh,
 
        num_blks = count_tags(journal, bh);
        /* Calculate checksum of the descriptor block. */
-       *crc32_sum = crc32_be(*crc32_sum, (void *)bh->b_data, bh->b_size);
+       *crc32_sum = ext2fs_crc32_be(*crc32_sum, (void *)bh->b_data,
+                                    bh->b_size);
 
        for (i = 0; i < num_blks; i++) {
                io_block = (*next_log_block)++;
@@ -338,14 +363,54 @@ static int calc_chksums(journal_t *journal, struct buffer_head *bh,
                                "%llu in log\n", err, io_block);
                        return 1;
                } else {
-                       *crc32_sum = crc32_be(*crc32_sum, (void *)obh->b_data,
-                                    obh->b_size);
+                       *crc32_sum = ext2fs_crc32_be(*crc32_sum,
+                                                    (void *)obh->b_data,
+                                                    obh->b_size);
                }
                brelse(obh);
        }
        return 0;
 }
 
+static int jbd2_commit_block_csum_verify(journal_t *j, void *buf)
+{
+       struct commit_header *h;
+       __u32 provided, calculated;
+
+       if (!JFS_HAS_INCOMPAT_FEATURE(j, JFS_FEATURE_INCOMPAT_CSUM_V2))
+               return 1;
+
+       h = buf;
+       provided = h->h_chksum[0];
+       h->h_chksum[0] = 0;
+       calculated = ext2fs_crc32c_le(~0, j->j_superblock->s_uuid,
+                                     sizeof(j->j_superblock->s_uuid));
+       calculated = ext2fs_crc32c_le(calculated, buf, j->j_blocksize);
+       h->h_chksum[0] = provided;
+
+       provided = ext2fs_be32_to_cpu(provided);
+       return provided == calculated;
+}
+
+static int jbd2_block_tag_csum_verify(journal_t *j, journal_block_tag_t *tag,
+                                     void *buf, __u32 sequence)
+{
+       __u32 provided, calculated;
+
+       if (!JFS_HAS_INCOMPAT_FEATURE(j, JFS_FEATURE_INCOMPAT_CSUM_V2))
+               return 1;
+
+       sequence = ext2fs_cpu_to_be32(sequence);
+       calculated = ext2fs_crc32c_le(~0, j->j_superblock->s_uuid,
+                                     sizeof(j->j_superblock->s_uuid));
+       calculated = ext2fs_crc32c_le(calculated, (__u8 *)&sequence,
+                                     sizeof(sequence));
+       calculated = ext2fs_crc32c_le(calculated, buf, j->j_blocksize) & 0xffff;
+       provided = ext2fs_be16_to_cpu(tag->t_checksum);
+
+       return provided == ext2fs_cpu_to_be32(calculated);
+}
+
 static int do_one_pass(journal_t *journal,
                        struct recovery_info *info, enum passtype pass)
 {
@@ -359,6 +424,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
@@ -444,6 +510,18 @@ static int do_one_pass(journal_t *journal,
 
                switch(blocktype) {
                case JFS_DESCRIPTOR_BLOCK:
+                       /* Verify checksum first */
+                       if (JFS_HAS_INCOMPAT_FEATURE(journal,
+                                       JFS_FEATURE_INCOMPAT_CSUM_V2))
+                               descr_csum_size =
+                                       sizeof(struct 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,
@@ -474,11 +552,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 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);
@@ -509,6 +587,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 "JBD: Invalid "
+                                                      "checksum recovering "
+                                                      "block %ld in log\n",
+                                                      blocknr);
+                                               continue;
+                                       }
+
                                        /* Find a buffer for the new
                                         * data being restored */
                                        nbh = __getblk(journal->j_fs_dev,
@@ -650,6 +741,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 (!JFS_HAS_INCOMPAT_FEATURE(journal,
+                                    JFS_FEATURE_INCOMPAT_ASYNC_COMMIT)) {
+                                       journal->j_failed_commit =
+                                               next_commit_ID;
+                                       brelse(bh);
+                                       break;
+                               }
+                       }
                        brelse(bh);
                        next_commit_ID++;
                        continue;
@@ -706,6 +810,27 @@ static int do_one_pass(journal_t *journal,
        return err;
 }
 
+static int jbd2_revoke_block_csum_verify(journal_t *j,
+                                        void *buf)
+{
+       struct journal_revoke_tail *tail;
+       __u32 provided, calculated;
+
+       if (!JFS_HAS_INCOMPAT_FEATURE(j, JFS_FEATURE_INCOMPAT_CSUM_V2))
+               return 1;
+
+       tail = (struct journal_revoke_tail *)(buf + j->j_blocksize -
+                       sizeof(struct journal_revoke_tail));
+       provided = tail->r_checksum;
+       tail->r_checksum = 0;
+       calculated = ext2fs_crc32c_le(~0, j->j_superblock->s_uuid,
+                                     sizeof(j->j_superblock->s_uuid));
+       calculated = ext2fs_crc32c_le(calculated, buf, j->j_blocksize);
+       tail->r_checksum = provided;
+
+       provided = ext2fs_be32_to_cpu(provided);
+       return provided == calculated;
+}
 
 /* Scan a revoke record, marking all blocks mentioned as revoked. */
 
@@ -720,6 +845,9 @@ static int scan_revoke_records(journal_t *journal, struct buffer_head *bh,
        offset = sizeof(journal_revoke_header_t);
        max = be32_to_cpu(header->r_count);
 
+       if (!jbd2_revoke_block_csum_verify(journal, header))
+               return -EINVAL;
+
        if (JFS_HAS_INCOMPAT_FEATURE(journal, JFS_FEATURE_INCOMPAT_64BIT))
                record_len = 8;