Whamcloud - gitweb
Prevent i_dtime from being mistaken for an inode number post-2038 wraparound
[tools/e2fsprogs.git] / e2fsck / journal.c
index f1aa0fd..19d68b4 100644 (file)
@@ -27,9 +27,7 @@
 #include "problem.h"
 #include "uuid/uuid.h"
 
-#ifdef CONFIG_JBD_DEBUG                /* Enabled by configure --enable-jfs-debug */
 static int bh_count = 0;
-#endif
 
 /*
  * Define USE_INODE_IO to use the inode_io.c / fileio.c codepaths.
@@ -129,10 +127,8 @@ struct buffer_head *getblk(kdev_t kdev, unsigned long long blocknr,
        if (!bh)
                return NULL;
 
-#ifdef CONFIG_JBD_DEBUG
        if (journal_enable_debug >= 3)
                bh_count++;
-#endif
        jfs_debug(4, "getblk for block %llu (%d bytes)(total %d)\n",
                  blocknr, blocksize, bh_count);
 
@@ -159,7 +155,8 @@ int sync_blockdev(kdev_t kdev)
        return io_channel_flush(io) ? -EIO : 0;
 }
 
-void ll_rw_block(int rw, int op_flags, int nr, struct buffer_head *bhp[])
+void ll_rw_block(int rw, int op_flags EXT2FS_ATTR((unused)), int nr,
+                struct buffer_head *bhp[])
 {
        errcode_t retval;
        struct buffer_head *bh;
@@ -284,12 +281,12 @@ static int ext4_fc_replay_scan(journal_t *j, struct buffer_head *bh,
        e2fsck_t ctx = j->j_fs_dev->k_ctx;
        struct e2fsck_fc_replay_state *state;
        int ret = JBD2_FC_REPLAY_CONTINUE;
-       struct ext4_fc_add_range *ext;
-       struct ext4_fc_tl *tl;
-       struct ext4_fc_tail *tail;
-       __u8 *start, *end;
-       struct ext4_fc_head *head;
-       struct ext2fs_extent ext2fs_ex;
+       struct ext4_fc_add_range ext;
+       struct ext4_fc_tl tl;
+       struct ext4_fc_tail tail;
+       __u8 *start, *cur, *end, *val;
+       struct ext4_fc_head head;
+       struct ext2fs_extent ext2fs_ex = {0};
 
        state = &ctx->fc_replay_state;
 
@@ -313,18 +310,23 @@ static int ext4_fc_replay_scan(journal_t *j, struct buffer_head *bh,
        }
 
        state->fc_replay_expected_off++;
-       fc_for_each_tl(start, end, tl) {
+       for (cur = start; cur < end; cur = cur + le16_to_cpu(tl.fc_len) + sizeof(tl)) {
+               memcpy(&tl, cur, sizeof(tl));
+               val = cur + sizeof(tl);
+
                jbd_debug(3, "Scan phase, tag:%s, blk %lld\n",
-                         tag2str(le16_to_cpu(tl->fc_tag)), bh->b_blocknr);
-               switch (le16_to_cpu(tl->fc_tag)) {
+                         tag2str(le16_to_cpu(tl.fc_tag)), bh->b_blocknr);
+               switch (le16_to_cpu(tl.fc_tag)) {
                case EXT4_FC_TAG_ADD_RANGE:
-                       ext = (struct ext4_fc_add_range *)ext4_fc_tag_val(tl);
-                       ret = ext2fs_decode_extent(&ext2fs_ex, (void *)&ext->fc_ex,
-                                                  sizeof(ext->fc_ex));
+                       memcpy(&ext, val, sizeof(ext));
+                       ret = ext2fs_decode_extent(&ext2fs_ex,
+                                                  (void *)&ext.fc_ex,
+                                                  sizeof(ext.fc_ex));
                        if (ret)
                                ret = JBD2_FC_REPLAY_STOP;
                        else
                                ret = JBD2_FC_REPLAY_CONTINUE;
+                       /* fallthrough */
                case EXT4_FC_TAG_DEL_RANGE:
                case EXT4_FC_TAG_LINK:
                case EXT4_FC_TAG_UNLINK:
@@ -332,21 +334,20 @@ static int ext4_fc_replay_scan(journal_t *j, struct buffer_head *bh,
                case EXT4_FC_TAG_INODE:
                case EXT4_FC_TAG_PAD:
                        state->fc_cur_tag++;
-                       state->fc_crc = jbd2_chksum(j, state->fc_crc, tl,
-                                       sizeof(*tl) + ext4_fc_tag_len(tl));
+                       state->fc_crc = jbd2_chksum(j, state->fc_crc, cur,
+                                       sizeof(tl) + ext4_fc_tag_len(&tl));
                        break;
                case EXT4_FC_TAG_TAIL:
                        state->fc_cur_tag++;
-                       tail = (struct ext4_fc_tail *)ext4_fc_tag_val(tl);
-                       state->fc_crc = jbd2_chksum(j, state->fc_crc, tl,
-                                               sizeof(*tl) +
+                       memcpy(&tail, val, sizeof(tail));
+                       state->fc_crc = jbd2_chksum(j, state->fc_crc, cur,
+                                               sizeof(tl) +
                                                offsetof(struct ext4_fc_tail,
                                                fc_crc));
                        jbd_debug(1, "tail tid %d, expected %d\n",
-                                       le32_to_cpu(tail->fc_tid),
-                                       expected_tid);
-                       if (le32_to_cpu(tail->fc_tid) == expected_tid &&
-                               le32_to_cpu(tail->fc_crc) == state->fc_crc) {
+                                 le32_to_cpu(tail.fc_tid), expected_tid);
+                       if (le32_to_cpu(tail.fc_tid) == expected_tid &&
+                           le32_to_cpu(tail.fc_crc) == state->fc_crc) {
                                state->fc_replay_num_tags = state->fc_cur_tag;
                        } else {
                                ret = state->fc_replay_num_tags ?
@@ -355,19 +356,19 @@ static int ext4_fc_replay_scan(journal_t *j, struct buffer_head *bh,
                        state->fc_crc = 0;
                        break;
                case EXT4_FC_TAG_HEAD:
-                       head = (struct ext4_fc_head *)ext4_fc_tag_val(tl);
-                       if (le32_to_cpu(head->fc_features) &
-                               ~EXT4_FC_SUPPORTED_FEATURES) {
+                       memcpy(&head, val, sizeof(head));
+                       if (le32_to_cpu(head.fc_features) &
+                           ~EXT4_FC_SUPPORTED_FEATURES) {
                                ret = -EOPNOTSUPP;
                                break;
                        }
-                       if (le32_to_cpu(head->fc_tid) != expected_tid) {
+                       if (le32_to_cpu(head.fc_tid) != expected_tid) {
                                ret = -EINVAL;
                                break;
                        }
                        state->fc_cur_tag++;
-                       state->fc_crc = jbd2_chksum(j, state->fc_crc, tl,
-                                       sizeof(*tl) + ext4_fc_tag_len(tl));
+                       state->fc_crc = jbd2_chksum(j, state->fc_crc, cur,
+                                       sizeof(tl) + ext4_fc_tag_len(&tl));
                        break;
                default:
                        ret = state->fc_replay_num_tags ?
@@ -380,6 +381,477 @@ static int ext4_fc_replay_scan(journal_t *j, struct buffer_head *bh,
 out_err:
        return ret;
 }
+
+static int __errcode_to_errno(errcode_t err, const char *func, int line)
+{
+       if (err == 0)
+               return 0;
+       fprintf(stderr, "Error \"%s\" encountered in function %s at line %d\n",
+               error_message(err), func, line);
+       if (err <= 256)
+               return -err;
+       return -EFAULT;
+}
+
+#define errcode_to_errno(err)  __errcode_to_errno(err, __func__, __LINE__)
+
+#define ex_end(__ex) ((__ex)->e_lblk + (__ex)->e_len - 1)
+#define ex_pend(__ex) ((__ex)->e_pblk + (__ex)->e_len - 1)
+
+static int make_room(struct extent_list *list, int i)
+{
+       int ret;
+
+       if (list->count == list->size) {
+               unsigned int new_size = (list->size + 341) *
+                                       sizeof(struct ext2fs_extent);
+               ret = errcode_to_errno(ext2fs_resize_mem(0, new_size, &list->extents));
+               if (ret)
+                       return ret;
+               list->size += 341;
+       }
+
+       memmove(&list->extents[i + 1], &list->extents[i],
+                       sizeof(list->extents[0]) * (list->count - i));
+       list->count++;
+       return 0;
+}
+
+static int ex_compar(const void *arg1, const void *arg2)
+{
+       const struct ext2fs_extent *ex1 = (const struct ext2fs_extent *)arg1;
+       const struct ext2fs_extent *ex2 = (const struct ext2fs_extent *)arg2;
+
+       if (ex1->e_lblk < ex2->e_lblk)
+               return -1;
+       if (ex1->e_lblk > ex2->e_lblk)
+               return 1;
+       return ex1->e_len - ex2->e_len;
+}
+
+static int ex_len_compar(const void *arg1, const void *arg2)
+{
+       const struct ext2fs_extent *ex1 = (const struct ext2fs_extent *)arg1;
+       const struct ext2fs_extent *ex2 = (const struct ext2fs_extent *)arg2;
+
+       if (ex1->e_len < ex2->e_len)
+               return 1;
+
+       if (ex1->e_lblk > ex2->e_lblk)
+               return -1;
+
+       return 0;
+}
+
+static void ex_sort_and_merge(struct extent_list *list)
+{
+       unsigned int i, j;
+
+       if (list->count < 2)
+               return;
+
+       /*
+        * Reverse sort by length, that way we strip off all the 0 length
+        * extents
+        */
+       qsort(list->extents, list->count, sizeof(struct ext2fs_extent),
+               ex_len_compar);
+
+       for (i = 0; i < list->count; i++) {
+               if (list->extents[i].e_len == 0) {
+                       list->count = i;
+                       break;
+               }
+       }
+
+       if (list->count == 0)
+               return;
+
+       /* Now sort by logical offset */
+       qsort(list->extents, list->count, sizeof(list->extents[0]),
+               ex_compar);
+
+       /* Merge adjacent extents if they are logically and physically contiguous */
+       i = 0;
+       while (i < list->count - 1) {
+               if (ex_end(&list->extents[i]) + 1 != list->extents[i + 1].e_lblk ||
+                       ex_pend(&list->extents[i]) + 1 != list->extents[i + 1].e_pblk ||
+                       (list->extents[i].e_flags & EXT2_EXTENT_FLAGS_UNINIT) !=
+                               (list->extents[i + 1].e_flags & EXT2_EXTENT_FLAGS_UNINIT)) {
+                       i++;
+                       continue;
+               }
+
+               list->extents[i].e_len += list->extents[i + 1].e_len;
+               for (j = i + 1; j < list->count - 1; j++)
+                       list->extents[j] = list->extents[j + 1];
+               list->count--;
+       }
+}
+
+/* must free blocks that are released */
+static int ext4_modify_extent_list(e2fsck_t ctx, struct extent_list *list,
+                                       struct ext2fs_extent *ex, int del)
+{
+       int ret, offset;
+       unsigned int i;
+       struct ext2fs_extent add_ex = *ex;
+
+       /* First let's create a hole from ex->e_lblk of length ex->e_len */
+       for (i = 0; i < list->count; i++) {
+               if (ex_end(&list->extents[i]) < add_ex.e_lblk)
+                       continue;
+
+               /* Case 1: No overlap */
+               if (list->extents[i].e_lblk > ex_end(&add_ex))
+                       break;
+               /*
+                * Unmark all the blocks in bb now. All the blocks get marked
+                * before we exit this function.
+                */
+               ext2fs_unmark_block_bitmap_range2(ctx->fs->block_map,
+                       list->extents[i].e_pblk, list->extents[i].e_len);
+               /* Case 2: Split */
+               if (list->extents[i].e_lblk < add_ex.e_lblk &&
+                       ex_end(&list->extents[i]) > ex_end(&add_ex)) {
+                       ret = make_room(list, i + 1);
+                       if (ret)
+                               return ret;
+                       list->extents[i + 1] = list->extents[i];
+                       offset = ex_end(&add_ex) + 1 - list->extents[i].e_lblk;
+                       list->extents[i + 1].e_lblk += offset;
+                       list->extents[i + 1].e_pblk += offset;
+                       list->extents[i + 1].e_len -= offset;
+                       list->extents[i].e_len =
+                               add_ex.e_lblk - list->extents[i].e_lblk;
+                       break;
+               }
+
+               /* Case 3: Exact overlap */
+               if (add_ex.e_lblk <= list->extents[i].e_lblk  &&
+                       ex_end(&list->extents[i]) <= ex_end(&add_ex)) {
+
+                       list->extents[i].e_len = 0;
+                       continue;
+               }
+
+               /* Case 4: Partial overlap */
+               if (ex_end(&list->extents[i]) > ex_end(&add_ex)) {
+                       offset = ex_end(&add_ex) + 1 - list->extents[i].e_lblk;
+                       list->extents[i].e_lblk += offset;
+                       list->extents[i].e_pblk += offset;
+                       list->extents[i].e_len -= offset;
+                       break;
+               }
+
+               if (ex_end(&add_ex) >= ex_end(&list->extents[i]))
+                       list->extents[i].e_len =
+                               add_ex.e_lblk > list->extents[i].e_lblk ?
+                               add_ex.e_lblk - list->extents[i].e_lblk : 0;
+       }
+
+       if (add_ex.e_len && !del) {
+               make_room(list, list->count);
+               list->extents[list->count - 1] = add_ex;
+       }
+
+       ex_sort_and_merge(list);
+
+       /* Mark all occupied blocks allocated */
+       for (i = 0; i < list->count; i++)
+               ext2fs_mark_block_bitmap_range2(ctx->fs->block_map,
+                       list->extents[i].e_pblk, list->extents[i].e_len);
+       ext2fs_mark_bb_dirty(ctx->fs);
+
+       return 0;
+}
+
+static int ext4_add_extent_to_list(e2fsck_t ctx, struct extent_list *list,
+                                       struct ext2fs_extent *ex)
+{
+       return ext4_modify_extent_list(ctx, list, ex, 0 /* add */);
+}
+
+static int ext4_del_extent_from_list(e2fsck_t ctx, struct extent_list *list,
+                                       struct ext2fs_extent *ex)
+{
+       return ext4_modify_extent_list(ctx, list, ex, 1 /* delete */);
+}
+
+static int ext4_fc_read_extents(e2fsck_t ctx, ext2_ino_t ino)
+{
+       struct extent_list *extent_list = &ctx->fc_replay_state.fc_extent_list;
+
+       if (extent_list->ino == ino)
+               return 0;
+
+       extent_list->ino = ino;
+       return errcode_to_errno(e2fsck_read_extents(ctx, extent_list));
+}
+
+/*
+ * Flush extents in replay state on disk. @ino is the inode that is going
+ * to be processed next. So, we hold back flushing of the extent list
+ * if the next inode that's going to be processed is same as the one with
+ * cached extents in our replay state. That allows us to gather multiple extents
+ * for the inode so that we can flush all of them at once and it also saves us
+ * from continuously growing and shrinking the extent tree.
+ */
+static void ext4_fc_flush_extents(e2fsck_t ctx, ext2_ino_t ino)
+{
+       struct extent_list *extent_list = &ctx->fc_replay_state.fc_extent_list;
+
+       if (extent_list->ino == ino || extent_list->ino == 0)
+               return;
+       e2fsck_rewrite_extent_tree(ctx, extent_list);
+       ext2fs_free_mem(&extent_list->extents);
+       memset(extent_list, 0, sizeof(*extent_list));
+}
+
+/* Helper struct for dentry replay routines */
+struct dentry_info_args {
+       ext2_ino_t      parent_ino;
+       ext2_ino_t      ino;
+       int             dname_len;
+       char            *dname;
+};
+
+static inline int tl_to_darg(struct dentry_info_args *darg,
+                            struct  ext4_fc_tl *tl, __u8 *val)
+{
+       struct ext4_fc_dentry_info fcd;
+
+       memcpy(&fcd, val, sizeof(fcd));
+
+       darg->parent_ino = le32_to_cpu(fcd.fc_parent_ino);
+       darg->ino = le32_to_cpu(fcd.fc_ino);
+       darg->dname_len = ext4_fc_tag_len(tl) -
+                       sizeof(struct ext4_fc_dentry_info);
+       darg->dname = malloc(darg->dname_len + 1);
+       if (!darg->dname)
+               return -ENOMEM;
+       memcpy(darg->dname,
+              val + sizeof(struct ext4_fc_dentry_info),
+              darg->dname_len);
+       darg->dname[darg->dname_len] = 0;
+       jbd_debug(1, "%s: %s, ino %u, parent %u\n",
+                 le16_to_cpu(tl->fc_tag) == EXT4_FC_TAG_CREAT ? "create" :
+                 (le16_to_cpu(tl->fc_tag) == EXT4_FC_TAG_LINK ? "link" :
+                  (le16_to_cpu(tl->fc_tag) == EXT4_FC_TAG_UNLINK ? "unlink" :
+                   "error")), darg->dname, darg->ino, darg->parent_ino);
+       return 0;
+}
+
+static int ext4_fc_handle_unlink(e2fsck_t ctx, struct ext4_fc_tl *tl, __u8 *val)
+{
+       struct dentry_info_args darg;
+       int ret;
+
+       ret = tl_to_darg(&darg, tl, val);
+       if (ret)
+               return ret;
+       ext4_fc_flush_extents(ctx, darg.ino);
+       ret = errcode_to_errno(ext2fs_unlink(ctx->fs, darg.parent_ino,
+                                            darg.dname, darg.ino, 0));
+       /* It's okay if the above call fails */
+       free(darg.dname);
+
+       return ret;
+}
+
+static int ext4_fc_handle_link_and_create(e2fsck_t ctx, struct ext4_fc_tl *tl, __u8 *val)
+{
+       struct dentry_info_args darg;
+       ext2_filsys fs = ctx->fs;
+       struct ext2_inode_large inode_large;
+       int ret, filetype, mode;
+
+       ret = tl_to_darg(&darg, tl, val);
+       if (ret)
+               return ret;
+       ext4_fc_flush_extents(ctx, 0);
+       ret = errcode_to_errno(ext2fs_read_inode(fs, darg.ino,
+                                                (struct ext2_inode *)&inode_large));
+       if (ret)
+               goto out;
+
+       mode = inode_large.i_mode;
+
+       if (LINUX_S_ISREG(mode))
+               filetype = EXT2_FT_REG_FILE;
+       else if (LINUX_S_ISDIR(mode))
+               filetype = EXT2_FT_DIR;
+       else if (LINUX_S_ISCHR(mode))
+               filetype = EXT2_FT_CHRDEV;
+       else if (LINUX_S_ISBLK(mode))
+               filetype = EXT2_FT_BLKDEV;
+       else if (LINUX_S_ISLNK(mode))
+               return EXT2_FT_SYMLINK;
+       else if (LINUX_S_ISFIFO(mode))
+               filetype = EXT2_FT_FIFO;
+       else if (LINUX_S_ISSOCK(mode))
+               filetype = EXT2_FT_SOCK;
+       else {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /*
+        * Forcefully unlink if the same name is present and ignore the error
+        * if any, since this dirent might not exist
+        */
+       ext2fs_unlink(fs, darg.parent_ino, darg.dname, darg.ino,
+                       EXT2FS_UNLINK_FORCE);
+
+       ret = errcode_to_errno(
+                      ext2fs_link(fs, darg.parent_ino, darg.dname, darg.ino,
+                                  filetype));
+out:
+       free(darg.dname);
+       return ret;
+
+}
+
+/* This function fixes the i_blocks field in the replayed indoe */
+static void ext4_fc_replay_fixup_iblocks(struct ext2_inode_large *ondisk_inode,
+       struct ext2_inode_large *fc_inode)
+{
+       if (ondisk_inode->i_flags & EXT4_EXTENTS_FL) {
+               struct ext3_extent_header *eh;
+
+               eh = (struct ext3_extent_header *)(&ondisk_inode->i_block[0]);
+               if (le16_to_cpu(eh->eh_magic) != EXT3_EXT_MAGIC) {
+                       memset(eh, 0, sizeof(*eh));
+                       eh->eh_magic = cpu_to_le16(EXT3_EXT_MAGIC);
+                       eh->eh_max = cpu_to_le16(
+                               (sizeof(ondisk_inode->i_block) -
+                                       sizeof(struct ext3_extent_header)) /
+                               sizeof(struct ext3_extent));
+               }
+       } else if (ondisk_inode->i_flags & EXT4_INLINE_DATA_FL) {
+               memcpy(ondisk_inode->i_block, fc_inode->i_block,
+                       sizeof(fc_inode->i_block));
+       }
+}
+
+static int ext4_fc_handle_inode(e2fsck_t ctx, __u8 *val)
+{
+       int ino, inode_len = EXT2_GOOD_OLD_INODE_SIZE;
+       struct ext2_inode_large *inode = NULL, *fc_inode = NULL;
+       __le32 fc_ino;
+       __u8 *fc_raw_inode;
+       errcode_t err;
+       blk64_t blks;
+
+       memcpy(&fc_ino, val, sizeof(fc_ino));
+       fc_raw_inode = val + sizeof(fc_ino);
+       ino = le32_to_cpu(fc_ino);
+
+       if (EXT2_INODE_SIZE(ctx->fs->super) > EXT2_GOOD_OLD_INODE_SIZE) {
+               __u16 extra_isize = ext2fs_le16_to_cpu(
+                       ((struct ext2_inode_large *)fc_raw_inode)->i_extra_isize);
+
+               if ((extra_isize < (sizeof(inode->i_extra_isize) +
+                                   sizeof(inode->i_checksum_hi))) ||
+                   (extra_isize > (EXT2_INODE_SIZE(ctx->fs->super) -
+                                   EXT2_GOOD_OLD_INODE_SIZE))) {
+                       err = EFSCORRUPTED;
+                       goto out;
+               }
+               inode_len += extra_isize;
+       }
+       err = ext2fs_get_mem(inode_len, &inode);
+       if (err)
+               goto out;
+       err = ext2fs_get_mem(inode_len, &fc_inode);
+       if (err)
+               goto out;
+       ext4_fc_flush_extents(ctx, ino);
+
+       err = ext2fs_read_inode_full(ctx->fs, ino, (struct ext2_inode *)inode,
+                                       inode_len);
+       if (err)
+               goto out;
+       memcpy(fc_inode, fc_raw_inode, inode_len);
+#ifdef WORDS_BIGENDIAN
+       ext2fs_swap_inode_full(ctx->fs, fc_inode, fc_inode, 0, inode_len);
+#endif
+       memcpy(inode, fc_inode, offsetof(struct ext2_inode_large, i_block));
+       memcpy(&inode->i_generation, &fc_inode->i_generation,
+               inode_len - offsetof(struct ext2_inode_large, i_generation));
+       ext4_fc_replay_fixup_iblocks(inode, fc_inode);
+       err = ext2fs_count_blocks(ctx->fs, ino, EXT2_INODE(inode), &blks);
+       if (err)
+               goto out;
+       ext2fs_iblk_set(ctx->fs, EXT2_INODE(inode), blks);
+       ext2fs_inode_csum_set(ctx->fs, ino, inode);
+
+       err = ext2fs_write_inode_full(ctx->fs, ino, (struct ext2_inode *)inode,
+                                       inode_len);
+       if (err)
+               goto out;
+       if (inode->i_links_count)
+               ext2fs_mark_inode_bitmap2(ctx->fs->inode_map, ino);
+       else
+               ext2fs_unmark_inode_bitmap2(ctx->fs->inode_map, ino);
+       ext2fs_mark_ib_dirty(ctx->fs);
+
+out:
+       ext2fs_free_mem(&inode);
+       ext2fs_free_mem(&fc_inode);
+       return errcode_to_errno(err);
+}
+
+/*
+ * Handle add extent replay tag.
+ */
+static int ext4_fc_handle_add_extent(e2fsck_t ctx, __u8 *val)
+{
+       struct ext2fs_extent extent;
+       struct ext4_fc_add_range add_range;
+       ext2_ino_t ino;
+       int ret = 0;
+
+       memcpy(&add_range, val, sizeof(add_range));
+       ino = le32_to_cpu(add_range.fc_ino);
+       ext4_fc_flush_extents(ctx, ino);
+
+       ret = ext4_fc_read_extents(ctx, ino);
+       if (ret)
+               return ret;
+       memset(&extent, 0, sizeof(extent));
+       ret = errcode_to_errno(ext2fs_decode_extent(
+                       &extent, (void *)add_range.fc_ex,
+                       sizeof(add_range.fc_ex)));
+       if (ret)
+               return ret;
+       return ext4_add_extent_to_list(ctx,
+               &ctx->fc_replay_state.fc_extent_list, &extent);
+}
+
+/*
+ * Handle delete logical range replay tag.
+ */
+static int ext4_fc_handle_del_range(e2fsck_t ctx, __u8 *val)
+{
+       struct ext2fs_extent extent;
+       struct ext4_fc_del_range del_range;
+       int ret, ino;
+
+       memcpy(&del_range, val, sizeof(del_range));
+       ino = le32_to_cpu(del_range.fc_ino);
+       ext4_fc_flush_extents(ctx, ino);
+
+       memset(&extent, 0, sizeof(extent));
+       extent.e_lblk = le32_to_cpu(del_range.fc_lblk);
+       extent.e_len = le32_to_cpu(del_range.fc_len);
+       ret = ext4_fc_read_extents(ctx, ino);
+       if (ret)
+               return ret;
+       return ext4_del_extent_from_list(ctx,
+               &ctx->fc_replay_state.fc_extent_list, &extent);
+}
+
 /*
  * Main recovery path entry point. This function returns JBD2_FC_REPLAY_CONTINUE
  * to indicate that it is expecting more fast commit blocks. It returns
@@ -390,11 +862,97 @@ static int ext4_fc_replay(journal_t *journal, struct buffer_head *bh,
 {
        e2fsck_t ctx = journal->j_fs_dev->k_ctx;
        struct e2fsck_fc_replay_state *state = &ctx->fc_replay_state;
+       int ret = JBD2_FC_REPLAY_CONTINUE;
+       struct ext4_fc_tl tl;
+       __u8 *start, *end, *cur, *val;
 
        if (pass == PASS_SCAN) {
                state->fc_current_pass = PASS_SCAN;
                return ext4_fc_replay_scan(journal, bh, off, expected_tid);
        }
+
+       if (state->fc_replay_num_tags == 0)
+               goto replay_done;
+
+       if (state->fc_current_pass != pass) {
+               /* Starting replay phase */
+               state->fc_current_pass = pass;
+               /* We will reset checksums */
+               ctx->fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
+               ret = errcode_to_errno(ext2fs_read_bitmaps(ctx->fs));
+               if (ret) {
+                       jbd_debug(1, "Error %d while reading bitmaps\n", ret);
+                       return ret;
+               }
+               state->fc_super_state = ctx->fs->super->s_state;
+               /*
+                * Mark the file system to indicate it contains errors. That's
+                * because the updates performed by fast commit replay code are
+                * not atomic and may result in inconsistent file system if it
+                * crashes before the replay is complete.
+                */
+               ctx->fs->super->s_state |= EXT2_ERROR_FS;
+               ctx->fs->super->s_state |= EXT4_FC_REPLAY;
+               ext2fs_mark_super_dirty(ctx->fs);
+               ext2fs_flush(ctx->fs);
+       }
+
+       start = (__u8 *)bh->b_data;
+       end = (__u8 *)bh->b_data + journal->j_blocksize - 1;
+
+       for (cur = start; cur < end; cur = cur + le16_to_cpu(tl.fc_len) + sizeof(tl)) {
+               memcpy(&tl, cur, sizeof(tl));
+               val = cur + sizeof(tl);
+
+               if (state->fc_replay_num_tags == 0)
+                       goto replay_done;
+               jbd_debug(3, "Replay phase processing %s tag\n",
+                               tag2str(le16_to_cpu(tl.fc_tag)));
+               state->fc_replay_num_tags--;
+               switch (le16_to_cpu(tl.fc_tag)) {
+               case EXT4_FC_TAG_CREAT:
+               case EXT4_FC_TAG_LINK:
+                       ret = ext4_fc_handle_link_and_create(ctx, &tl, val);
+                       break;
+               case EXT4_FC_TAG_UNLINK:
+                       ret = ext4_fc_handle_unlink(ctx, &tl, val);
+                       break;
+               case EXT4_FC_TAG_ADD_RANGE:
+                       ret = ext4_fc_handle_add_extent(ctx, val);
+                       break;
+               case EXT4_FC_TAG_DEL_RANGE:
+                       ret = ext4_fc_handle_del_range(ctx, val);
+                       break;
+               case EXT4_FC_TAG_INODE:
+                       ret = ext4_fc_handle_inode(ctx, val);
+                       break;
+               case EXT4_FC_TAG_TAIL:
+                       ext4_fc_flush_extents(ctx, 0);
+               case EXT4_FC_TAG_PAD:
+               case EXT4_FC_TAG_HEAD:
+                       break;
+               default:
+                       ret = -ECANCELED;
+                       break;
+               }
+               if (ret < 0)
+                       break;
+               ret = JBD2_FC_REPLAY_CONTINUE;
+       }
+       return ret;
+replay_done:
+       jbd_debug(1, "End of fast commit replay\n");
+       if (state->fc_current_pass != pass)
+               return JBD2_FC_REPLAY_STOP;
+
+       ext2fs_calculate_summary_stats(ctx->fs, 0 /* update bg also */);
+       ext2fs_write_block_bitmap(ctx->fs);
+       ext2fs_write_inode_bitmap(ctx->fs);
+       ext2fs_mark_super_dirty(ctx->fs);
+       ext2fs_set_gdt_csum(ctx->fs);
+       ctx->fs->super->s_state = state->fc_super_state;
+       ext2fs_flush(ctx->fs);
+
        return JBD2_FC_REPLAY_STOP;
 }
 
@@ -440,7 +998,14 @@ static errcode_t e2fsck_get_journal(e2fsck_t ctx, journal_t **ret_journal)
        journal->j_blocksize = ctx->fs->blocksize;
 
        if (uuid_is_null(sb->s_journal_uuid)) {
-               if (!sb->s_journal_inum) {
+               /*
+                * The full set of superblock sanity checks haven't
+                * been performed yet, so we need to do some basic
+                * checks here to avoid potential array overruns.
+                */
+               if (!sb->s_journal_inum ||
+                   (sb->s_journal_inum >
+                    (ctx->fs->group_desc_count * sb->s_inodes_per_group))) {
                        retval = EXT2_ET_BAD_INODE_NUM;
                        goto errout;
                }
@@ -474,7 +1039,8 @@ static errcode_t e2fsck_get_journal(e2fsck_t ctx, journal_t **ret_journal)
                        tried_backup_jnl++;
                }
                if (!j_inode->i_ext2.i_links_count ||
-                   !LINUX_S_ISREG(j_inode->i_ext2.i_mode)) {
+                   !LINUX_S_ISREG(j_inode->i_ext2.i_mode) ||
+                   (j_inode->i_ext2.i_flags & EXT4_ENCRYPT_FL)) {
                        retval = EXT2_ET_NO_JOURNAL;
                        goto try_backup_journal;
                }
@@ -1117,6 +1683,7 @@ errcode_t e2fsck_run_ext3_journal(e2fsck_t ctx)
        errcode_t       retval, recover_retval;
        io_stats        stats = 0;
        unsigned long long kbytes_written = 0;
+       __u16 s_error_state;
 
        printf(_("%s: recovering journal\n"), ctx->device_name);
        if (ctx->options & E2F_OPT_READONLY) {
@@ -1139,6 +1706,7 @@ errcode_t e2fsck_run_ext3_journal(e2fsck_t ctx)
                ctx->fs->io->manager->get_stats(ctx->fs->io, &stats);
        if (stats && stats->bytes_written)
                kbytes_written = stats->bytes_written >> 10;
+       s_error_state = ctx->fs->super->s_state & EXT2_ERROR_FS;
 
        ext2fs_mmp_stop(ctx->fs);
        ext2fs_free(ctx->fs);
@@ -1155,6 +1723,7 @@ errcode_t e2fsck_run_ext3_journal(e2fsck_t ctx)
        ctx->fs->now = ctx->now;
        ctx->fs->flags |= EXT2_FLAG_MASTER_SB_ONLY;
        ctx->fs->super->s_kbytes_written += kbytes_written;
+       ctx->fs->super->s_state |= s_error_state;
 
        /* Set the superblock flags */
        e2fsck_clear_recover(ctx, recover_retval != 0);
@@ -1275,7 +1844,7 @@ void e2fsck_move_ext3_journal(e2fsck_t ctx)
        ext2fs_mark_super_dirty(fs);
        fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY;
        inode.i_links_count = 0;
-       inode.i_dtime = ctx->now;
+       ext2fs_set_dtime(fs, &inode);
        if ((retval = ext2fs_write_inode(fs, ino, &inode)) != 0)
                goto err_out;