+/*
+ * This is a helper function to check the validity of the journal.
+ */
+struct process_block_struct {
+ e2_blkcnt_t last_block;
+};
+
+static int process_journal_block(ext2_filsys fs,
+ blk64_t *block_nr,
+ e2_blkcnt_t blockcnt,
+ blk64_t ref_block EXT2FS_ATTR((unused)),
+ int ref_offset EXT2FS_ATTR((unused)),
+ void *priv_data)
+{
+ struct process_block_struct *p;
+ blk64_t blk = *block_nr;
+
+ p = (struct process_block_struct *) priv_data;
+
+ if (!blk || blk < fs->super->s_first_data_block ||
+ blk >= ext2fs_blocks_count(fs->super))
+ return BLOCK_ABORT;
+
+ if (blockcnt >= 0)
+ p->last_block = blockcnt;
+ return 0;
+}
+
+static int ext4_fc_replay_scan(journal_t *j, struct buffer_head *bh,
+ int off, tid_t expected_tid)
+{
+ 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 = {0};
+
+ state = &ctx->fc_replay_state;
+
+ start = (__u8 *)bh->b_data;
+ end = (__u8 *)bh->b_data + j->j_blocksize - 1;
+
+ jbd_debug(1, "Scan phase starting, expected %d", expected_tid);
+ if (state->fc_replay_expected_off == 0) {
+ memset(state, 0, sizeof(*state));
+ /* Check if we can stop early */
+ if (le16_to_cpu(((struct ext4_fc_tl *)start)->fc_tag)
+ != EXT4_FC_TAG_HEAD) {
+ jbd_debug(1, "Ending early!, not a head tag");
+ return 0;
+ }
+ }
+
+ if (off != state->fc_replay_expected_off) {
+ ret = -EFSCORRUPTED;
+ goto out_err;
+ }
+
+ state->fc_replay_expected_off++;
+ fc_for_each_tl(start, end, 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)) {
+ 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));
+ 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:
+ case EXT4_FC_TAG_CREAT:
+ 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));
+ 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) +
+ 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) {
+ state->fc_replay_num_tags = state->fc_cur_tag;
+ } else {
+ ret = state->fc_replay_num_tags ?
+ JBD2_FC_REPLAY_STOP : -EFSBADCRC;
+ }
+ 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) {
+ ret = -EOPNOTSUPP;
+ break;
+ }
+ 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));
+ break;
+ default:
+ ret = state->fc_replay_num_tags ?
+ JBD2_FC_REPLAY_STOP : -ECANCELED;
+ }
+ if (ret < 0 || ret == JBD2_FC_REPLAY_STOP)
+ break;
+ }
+
+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)
+{
+ struct ext2fs_extent *ex1 = (struct ext2fs_extent *)arg1;
+ struct ext2fs_extent *ex2 = (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)
+{
+ struct ext2fs_extent *ex1 = (struct ext2fs_extent *)arg1;
+ struct ext2fs_extent *ex2 = (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(e2fsck_t ctx, struct extent_list *list)
+{
+ blk64_t ex_end;
+ 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;
+ }
+ }
+
+ /* 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;
+ int i, offset;
+ struct ext2fs_extent add_ex = *ex, add_ex2;
+
+ /* 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(ctx, 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, int 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, int 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 {
+ int parent_ino, dname_len, ino, inode_len;
+ char *dname;
+};
+
+static inline void tl_to_darg(struct dentry_info_args *darg,
+ struct ext4_fc_tl *tl)
+{
+ struct ext4_fc_dentry_info *fcd;
+ int tag = le16_to_cpu(tl->fc_tag);
+
+ fcd = (struct ext4_fc_dentry_info *)ext4_fc_tag_val(tl);
+
+ darg->parent_ino = le32_to_cpu(fcd->fc_parent_ino);
+ darg->ino = le32_to_cpu(fcd->fc_ino);
+ darg->dname = (char *) fcd->fc_dname;
+ darg->dname_len = ext4_fc_tag_len(tl) -
+ sizeof(struct ext4_fc_dentry_info);
+ darg->dname = malloc(darg->dname_len + 1);
+ memcpy(darg->dname, fcd->fc_dname, darg->dname_len);
+ darg->dname[darg->dname_len] = 0;
+ jbd_debug(1, "%s: %s, ino %d, parent %d\n",
+ tag == EXT4_FC_TAG_CREAT ? "create" :
+ (tag == EXT4_FC_TAG_LINK ? "link" :
+ (tag == EXT4_FC_TAG_UNLINK ? "unlink" : "error")),
+ darg->dname, darg->ino, darg->parent_ino);
+}
+
+static int ext4_fc_handle_unlink(e2fsck_t ctx, struct ext4_fc_tl *tl)
+{
+ struct ext2_inode inode;
+ struct dentry_info_args darg;
+ ext2_filsys fs = ctx->fs;
+ int ret;
+
+ tl_to_darg(&darg, tl);
+ 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)
+{
+ struct dentry_info_args darg;
+ ext2_filsys fs = ctx->fs;
+ struct ext2_inode_large inode_large;
+ int ret, filetype, mode;
+
+ tl_to_darg(&darg, tl);
+ 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, struct ext4_fc_tl *tl)
+{
+ struct e2fsck_fc_replay_state *state = &ctx->fc_replay_state;
+ int ino, inode_len = EXT2_GOOD_OLD_INODE_SIZE;
+ struct ext2_inode_large *inode = NULL, *fc_inode = NULL;
+ struct ext4_fc_inode *fc_inode_val;
+ errcode_t err;
+ blk64_t blks;
+
+ fc_inode_val = (struct ext4_fc_inode *)ext4_fc_tag_val(tl);
+ ino = le32_to_cpu(fc_inode_val->fc_ino);
+
+ if (EXT2_INODE_SIZE(ctx->fs->super) > EXT2_GOOD_OLD_INODE_SIZE)
+ inode_len += ext2fs_le16_to_cpu(
+ ((struct ext2_inode_large *)fc_inode_val->fc_raw_inode)
+ ->i_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;
+#ifdef WORDS_BIGENDIAN
+ ext2fs_swap_inode_full(ctx->fs, fc_inode,
+ (struct ext2_inode_large *)fc_inode_val->fc_raw_inode,
+ 0, sizeof(*inode));
+#else
+ memcpy(fc_inode, fc_inode_val->fc_raw_inode, 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, struct ext4_fc_tl *tl)
+{
+ struct ext2fs_extent extent;
+ struct ext4_fc_add_range *add_range;
+ struct ext4_fc_del_range *del_range;
+ int ret = 0, ino;
+
+ add_range = (struct ext4_fc_add_range *)ext4_fc_tag_val(tl);
+ 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, struct ext4_fc_tl *tl)
+{
+ struct ext2fs_extent extent;
+ struct ext4_fc_del_range *del_range;
+ int ret, ino;
+
+ del_range = (struct ext4_fc_del_range *)ext4_fc_tag_val(tl);
+ ino = le32_to_cpu(del_range->fc_ino);
+ ext4_fc_flush_extents(ctx, ino);
+
+ memset(&extent, 0, sizeof(extent));
+ extent.e_lblk = ext2fs_le32_to_cpu(del_range->fc_lblk);
+ extent.e_len = ext2fs_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
+ * JBD2_FC_REPLAY_STOP to indicate that replay is done.
+ */
+static int ext4_fc_replay(journal_t *journal, struct buffer_head *bh,
+ enum passtype pass, int off, tid_t expected_tid)
+{
+ 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;
+
+ 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 incosistent 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;
+
+ fc_for_each_tl(start, end, 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);
+ break;
+ case EXT4_FC_TAG_UNLINK:
+ ret = ext4_fc_handle_unlink(ctx, tl);
+ break;
+ case EXT4_FC_TAG_ADD_RANGE:
+ ret = ext4_fc_handle_add_extent(ctx, tl);
+ break;
+ case EXT4_FC_TAG_DEL_RANGE:
+ ret = ext4_fc_handle_del_range(ctx, tl);
+ break;
+ case EXT4_FC_TAG_INODE:
+ ret = ext4_fc_handle_inode(ctx, tl);
+ 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;
+}
+