From 99d0f1c2a22c7d733deacc45a5261d4217e8ea3c Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Mon, 13 Feb 2017 00:02:46 -0500 Subject: [PATCH] Sync up latest jmap patches --- add-ext4-journal-lazy-mount-option | 217 ++++++++++ add-journal-no-cleanup-option | 22 +- add-support-for-log-metadata-block-tracking-in-log | 14 +- cleaner | 441 +++++++++++++++++++-- dont-wipe-log-on-unmount | 60 ++- jbd2-suppress-extra-newline-in-jbd2_debug | 20 + series | 6 +- timestamps | 22 +- 8 files changed, 724 insertions(+), 78 deletions(-) create mode 100644 add-ext4-journal-lazy-mount-option create mode 100644 jbd2-suppress-extra-newline-in-jbd2_debug diff --git a/add-ext4-journal-lazy-mount-option b/add-ext4-journal-lazy-mount-option new file mode 100644 index 00000000..625b4c17 --- /dev/null +++ b/add-ext4-journal-lazy-mount-option @@ -0,0 +1,217 @@ +ext4: add journal_lazy mount option + +This option turns out the lazy journalling option, as described in the +FAST 2017 paper, "Evolving Ext4 for Shingled Disks"[1]. + +[1] https://www.usenix.org/conference/fast17/technical-sessions/presentation/aghayev + +Signed-off-by: Theodore Ts'o +--- + fs/ext4/ext4.h | 1 + + fs/ext4/inode.c | 2 +- + fs/ext4/ioctl.c | 48 +++++++++++++++++++++++++++++++++--------------- + fs/ext4/super.c | 32 +++++++++++++++++++++----------- + 4 files changed, 56 insertions(+), 27 deletions(-) + +diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h +index 63a4be5d5b17..07942ade7596 100644 +--- a/fs/ext4/ext4.h ++++ b/fs/ext4/ext4.h +@@ -1145,6 +1145,7 @@ struct ext4_inode_info { + #define EXT4_MOUNT_JOURNAL_CHECKSUM 0x800000 /* Journal checksums */ + #define EXT4_MOUNT_JOURNAL_ASYNC_COMMIT 0x1000000 /* Journal Async Commit */ + #define EXT4_MOUNT_JOURNAL_NO_CLEANUP 0x2000000 /* Preserve the journal on unmount */ ++#define EXT4_MOUNT_JOURNAL_LAZY 0x4000000 /* Do lazy writeback of journalled metadata */ + #define EXT4_MOUNT_DELALLOC 0x8000000 /* Delalloc support */ + #define EXT4_MOUNT_DATA_ERR_ABORT 0x10000000 /* Abort on file data write */ + #define EXT4_MOUNT_BLOCK_VALIDITY 0x20000000 /* Block validity checking */ +diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c +index 831d025e59ad..00bed25d05b2 100644 +--- a/fs/ext4/inode.c ++++ b/fs/ext4/inode.c +@@ -3188,7 +3188,7 @@ static sector_t ext4_bmap(struct address_space *mapping, sector_t block) + filemap_write_and_wait(mapping); + } + +- if (EXT4_JOURNAL(inode) && ++ if (EXT4_JOURNAL(inode) && !test_opt(inode->i_sb, JOURNAL_LAZY) && + ext4_test_inode_state(inode, EXT4_STATE_JDATA)) { + /* + * This is a REALLY heavyweight approach, but the use of +diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c +index b383ebf4020c..ba234eb5a1bd 100644 +--- a/fs/ext4/ioctl.c ++++ b/fs/ext4/ioctl.c +@@ -242,6 +242,20 @@ static int ext4_ioctl_setflags(struct inode *inode, + if (!capable(CAP_SYS_RESOURCE)) + goto flags_out; + } ++ ++ /* ++ * Clearing the JOURNAL_DATA flag is *hard* with lazy ++ * journalling. We can't use jbd2_journal_flush(); instead, ++ * we would have to make sure all blocks belonging to the file ++ * are evacuated from the journal and saved to their final ++ * location on disk. Punt for now. ++ */ ++ if ((oldflags & EXT4_JOURNAL_DATA_FL) && !jflag && ++ test_opt(inode->i_sb, JOURNAL_LAZY)) { ++ err = -EOPNOTSUPP; ++ goto flags_out; ++ } ++ + if ((flags ^ oldflags) & EXT4_EXTENTS_FL) + migrate = 1; + +@@ -489,6 +503,22 @@ int ext4_goingdown(struct super_block *sb, unsigned long arg) + return 0; + } + ++/* ++ * If we are using journalling (excepting JBD2 lazy mode), make sure ++ * the block group descriptors are written out immediately ++ */ ++static int flush_fs_group_descriptors(struct super_block *sb) ++{ ++ int err = 0; ++ ++ if (EXT4_SB(sb)->s_journal && !test_opt(sb, JOURNAL_LAZY)) { ++ jbd2_journal_lock_updates(EXT4_SB(sb)->s_journal); ++ err = jbd2_journal_flush(EXT4_SB(sb)->s_journal); ++ jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal); ++ } ++ return err; ++} ++ + long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + { + struct inode *inode = file_inode(filp); +@@ -606,11 +636,7 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + goto group_extend_out; + + err = ext4_group_extend(sb, EXT4_SB(sb)->s_es, n_blocks_count); +- if (EXT4_SB(sb)->s_journal) { +- jbd2_journal_lock_updates(EXT4_SB(sb)->s_journal); +- err2 = jbd2_journal_flush(EXT4_SB(sb)->s_journal); +- jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal); +- } ++ err2 = flush_fs_group_descriptors(sb); + if (err == 0) + err = err2; + mnt_drop_write_file(filp); +@@ -696,11 +722,7 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + goto group_add_out; + + err = ext4_group_add(sb, &input); +- if (EXT4_SB(sb)->s_journal) { +- jbd2_journal_lock_updates(EXT4_SB(sb)->s_journal); +- err2 = jbd2_journal_flush(EXT4_SB(sb)->s_journal); +- jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal); +- } ++ err2 = flush_fs_group_descriptors(sb); + if (err == 0) + err = err2; + mnt_drop_write_file(filp); +@@ -786,11 +808,7 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) + goto resizefs_out; + + err = ext4_resize_fs(sb, n_blocks_count); +- if (EXT4_SB(sb)->s_journal) { +- jbd2_journal_lock_updates(EXT4_SB(sb)->s_journal); +- err2 = jbd2_journal_flush(EXT4_SB(sb)->s_journal); +- jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal); +- } ++ err2 = flush_fs_group_descriptors(sb); + if (err == 0) + err = err2; + mnt_drop_write_file(filp); +diff --git a/fs/ext4/super.c b/fs/ext4/super.c +index 233fccf28025..b2bd2a31a931 100644 +--- a/fs/ext4/super.c ++++ b/fs/ext4/super.c +@@ -1309,6 +1309,7 @@ enum { + Opt_inode_readahead_blks, Opt_journal_ioprio, + Opt_dioread_nolock, Opt_dioread_lock, + Opt_journal_nocleanup, Opt_journal_cleanup, ++ Opt_journal_nolazy, Opt_journal_lazy, + Opt_discard, Opt_nodiscard, Opt_init_itable, Opt_noinit_itable, + Opt_max_dir_size_kb, Opt_nojournal_checksum, + }; +@@ -1395,6 +1396,8 @@ static const match_table_t tokens = { + {Opt_test_dummy_encryption, "test_dummy_encryption"}, + {Opt_journal_nocleanup, "journal_nocleanup"}, + {Opt_journal_cleanup, "journal_cleanup"}, ++ {Opt_journal_lazy, "journal_lazy"}, ++ {Opt_journal_nolazy, "journal_nolazy"}, + {Opt_removed, "check=none"}, /* mount option from ext2/3 */ + {Opt_removed, "nocheck"}, /* mount option from ext2/3 */ + {Opt_removed, "reservation"}, /* mount option from ext2/3 */ +@@ -1603,6 +1606,8 @@ static const struct mount_opts { + {Opt_test_dummy_encryption, 0, MOPT_GTE0}, + {Opt_journal_nocleanup, EXT4_MOUNT_JOURNAL_NO_CLEANUP, MOPT_SET}, + {Opt_journal_cleanup, EXT4_MOUNT_JOURNAL_NO_CLEANUP, MOPT_CLEAR}, ++ {Opt_journal_lazy, EXT4_MOUNT_JOURNAL_LAZY, MOPT_SET}, ++ {Opt_journal_nolazy, EXT4_MOUNT_JOURNAL_LAZY, MOPT_CLEAR}, + {Opt_err, 0, 0} + }; + +@@ -4337,6 +4342,10 @@ static void ext4_init_journal_params(struct super_block *sb, journal_t *journal) + journal->j_flags |= JBD2_NO_CLEANUP; + else + journal->j_flags &= ~JBD2_NO_CLEANUP; ++ if (test_opt(sb, JOURNAL_LAZY)) ++ journal->j_flags |= JBD2_LAZY; ++ else ++ journal->j_flags &= ~JBD2_LAZY; + write_unlock(&journal->j_state_lock); + } + +@@ -4801,21 +4810,20 @@ static int ext4_freeze(struct super_block *sb) + journal = EXT4_SB(sb)->s_journal; + + if (journal) { +- /* Now we set up the journal barrier. */ +- jbd2_journal_lock_updates(journal); +- + /* +- * Don't clear the needs_recovery flag if we failed to +- * flush the journal. ++ * Set the journal barrier, then flush the journal and ++ * clear the needs_recovery flag if we are not in ++ * JBD2_LAZY mode. + */ +- error = jbd2_journal_flush(journal); +- if (error < 0) +- goto out; ++ jbd2_journal_lock_updates(journal); + +- /* Journal blocked and flushed, clear needs_recovery flag. */ ++ if (!test_opt(sb, JOURNAL_LAZY)) { ++ error = jbd2_journal_flush(journal); ++ if (error < 0) ++ goto out; ++ } + ext4_clear_feature_journal_needs_recovery(sb); + } +- + error = ext4_commit_super(sb, 1); + out: + if (journal) +@@ -4833,7 +4841,7 @@ static int ext4_unfreeze(struct super_block *sb) + if ((sb->s_flags & MS_RDONLY) || ext4_forced_shutdown(EXT4_SB(sb))) + return 0; + +- if (EXT4_SB(sb)->s_journal) { ++ if (EXT4_SB(sb)->s_journal && !test_opt(sb, JOURNAL_LAZY)) { + /* Reset the needs_recovery flag before the fs is unlocked. */ + ext4_set_feature_journal_needs_recovery(sb); + } +@@ -5349,6 +5357,8 @@ static int ext4_quota_on(struct super_block *sb, int type, int format_id, + * We don't need to lock updates but journal_flush() could + * otherwise be livelocked... + */ ++ if (test_opt(sb, JOURNAL_LAZY)) ++ return -EOPNOTSUPP; + jbd2_journal_lock_updates(EXT4_SB(sb)->s_journal); + err = jbd2_journal_flush(EXT4_SB(sb)->s_journal); + jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal); diff --git a/add-journal-no-cleanup-option b/add-journal-no-cleanup-option index 25e9df56..11de50f3 100644 --- a/add-journal-no-cleanup-option +++ b/add-journal-no-cleanup-option @@ -1,3 +1,16 @@ +ext4, jbd2: add the journal_nocleanup mount option + +This debugging option is useful for generating test cases for the +journal replay code. + +Signed-off-by: Theodore Ts'o +--- + fs/ext4/ext4.h | 1 + + fs/ext4/super.c | 9 +++++++++ + fs/jbd2/journal.c | 12 +++++++++--- + include/linux/jbd2.h | 1 + + 4 files changed, 20 insertions(+), 3 deletions(-) + diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 55b7a77a0444..63a4be5d5b17 100644 --- a/fs/ext4/ext4.h @@ -11,7 +24,7 @@ index 55b7a77a0444..63a4be5d5b17 100644 #define EXT4_MOUNT_DATA_ERR_ABORT 0x10000000 /* Abort on file data write */ #define EXT4_MOUNT_BLOCK_VALIDITY 0x20000000 /* Block validity checking */ diff --git a/fs/ext4/super.c b/fs/ext4/super.c -index dde14a7ac6d7..62b770229a99 100644 +index dde14a7ac6d7..233fccf28025 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -1308,6 +1308,7 @@ enum { @@ -22,15 +35,16 @@ index dde14a7ac6d7..62b770229a99 100644 Opt_discard, Opt_nodiscard, Opt_init_itable, Opt_noinit_itable, Opt_max_dir_size_kb, Opt_nojournal_checksum, }; -@@ -1392,6 +1393,7 @@ static const match_table_t tokens = { +@@ -1392,6 +1393,8 @@ static const match_table_t tokens = { {Opt_noinit_itable, "noinit_itable"}, {Opt_max_dir_size_kb, "max_dir_size_kb=%u"}, {Opt_test_dummy_encryption, "test_dummy_encryption"}, + {Opt_journal_nocleanup, "journal_nocleanup"}, ++ {Opt_journal_cleanup, "journal_cleanup"}, {Opt_removed, "check=none"}, /* mount option from ext2/3 */ {Opt_removed, "nocheck"}, /* mount option from ext2/3 */ {Opt_removed, "reservation"}, /* mount option from ext2/3 */ -@@ -1598,6 +1600,8 @@ static const struct mount_opts { +@@ -1598,6 +1601,8 @@ static const struct mount_opts { {Opt_jqfmt_vfsv1, QFMT_VFS_V1, MOPT_QFMT}, {Opt_max_dir_size_kb, 0, MOPT_GTE0}, {Opt_test_dummy_encryption, 0, MOPT_GTE0}, @@ -39,7 +53,7 @@ index dde14a7ac6d7..62b770229a99 100644 {Opt_err, 0, 0} }; -@@ -4328,6 +4332,10 @@ static void ext4_init_journal_params(struct super_block *sb, journal_t *journal) +@@ -4328,6 +4333,10 @@ static void ext4_init_journal_params(struct super_block *sb, journal_t *journal) journal->j_flags |= JBD2_ABORT_ON_SYNCDATA_ERR; else journal->j_flags &= ~JBD2_ABORT_ON_SYNCDATA_ERR; diff --git a/add-support-for-log-metadata-block-tracking-in-log b/add-support-for-log-metadata-block-tracking-in-log index 40e5895a..aa8fb559 100644 --- a/add-support-for-log-metadata-block-tracking-in-log +++ b/add-support-for-log-metadata-block-tracking-in-log @@ -20,12 +20,12 @@ Signed-off-by: Theodore Ts'o --- fs/jbd2/Makefile | 3 +- fs/jbd2/commit.c | 23 +++++ - fs/jbd2/jmap.c | 457 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + fs/jbd2/jmap.c | 455 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/jbd2/journal.c | 17 +++- include/linux/jbd2.h | 14 +++ include/linux/jmap.h | 131 +++++++++++++++++++++++++ include/trace/events/jbd2.h | 169 ++++++++++++++++++++++++++++++++ - 7 files changed, 809 insertions(+), 5 deletions(-) + 7 files changed, 807 insertions(+), 5 deletions(-) diff --git a/fs/jbd2/Makefile b/fs/jbd2/Makefile index 802a3413872a..a54f50b3a06e 100644 @@ -95,10 +95,10 @@ index 8c514367ba5a..50e1a0b375c5 100644 J_ASSERT(list_empty(&commit_transaction->t_inode_list)); diff --git a/fs/jbd2/jmap.c b/fs/jbd2/jmap.c new file mode 100644 -index 000000000000..66a7efd807e1 +index 000000000000..ddaa7d5f5634 --- /dev/null +++ b/fs/jbd2/jmap.c -@@ -0,0 +1,457 @@ +@@ -0,0 +1,455 @@ +#include +#include +#include @@ -536,8 +536,6 @@ index 000000000000..66a7efd807e1 +{ + journal->j_jmap = RB_ROOT; + rwlock_init(&journal->j_jmap_lock); -+ if (!(journal->j_flags & JBD2_LAZY)) -+ return 0; + return jbd2_init_transaction_infos(journal); +} + @@ -618,7 +616,7 @@ index f7fd801be997..0fb2cd419ee3 100644 } diff --git a/include/linux/jbd2.h b/include/linux/jbd2.h -index 9a07b0485784..841b54a34779 100644 +index 9a07b0485784..771588026353 100644 --- a/include/linux/jbd2.h +++ b/include/linux/jbd2.h @@ -25,6 +25,7 @@ @@ -659,7 +657,7 @@ index 9a07b0485784..841b54a34779 100644 * mode */ #define JBD2_REC_ERR 0x080 /* The errno in the sb has been recorded */ #define JBD2_NO_CLEANUP 0x100 /* Don't flush empty the journal on shutdown */ -+#define JBD2_LAZY 0x100 /* Do lazy journalling */ ++#define JBD2_LAZY 0x200 /* Do lazy journalling */ /* * Function declarations for the journaling transaction and buffer diff --git a/cleaner b/cleaner index 67e0e2e2..efdc02d2 100644 --- a/cleaner +++ b/cleaner @@ -11,11 +11,12 @@ Signed-off-by: Abutalib Aghayev --- fs/jbd2/Makefile | 2 +- - fs/jbd2/jmap.c | 43 ++++++++++++++++++++++++++++++----- - fs/jbd2/journal.c | 12 +++++++++- - include/linux/jbd2.h | 6 ++++- - include/linux/jmap.h | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ - 5 files changed, 151 insertions(+), 23 deletions(-) + fs/jbd2/cleaner.c | 358 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + fs/jbd2/jmap.c | 42 +++++++++-- + fs/jbd2/journal.c | 12 ++++ + include/linux/jbd2.h | 6 +- + include/linux/jmap.h | 111 +++++++++++++++++++++++++---- + 6 files changed, 510 insertions(+), 21 deletions(-) diff --git a/fs/jbd2/Makefile b/fs/jbd2/Makefile index a54f50b3a06e..b6a2dddcc0a7 100644 @@ -27,11 +28,375 @@ index a54f50b3a06e..b6a2dddcc0a7 100644 jbd2-objs := transaction.o commit.o recovery.o checkpoint.o revoke.o journal.o \ - jmap.o + jmap.o cleaner.o +diff --git a/fs/jbd2/cleaner.c b/fs/jbd2/cleaner.c +new file mode 100644 +index 000000000000..6d02aafcded9 +--- /dev/null ++++ b/fs/jbd2/cleaner.c +@@ -0,0 +1,358 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++inline int low_on_space(journal_t *journal) ++{ ++ int x = atomic_read(&journal->j_cleaner_ctx->nr_txns_committed); ++ if (x > 10) { ++ trace_jbd2_jmap_printf1("low on space", x); ++ return true; ++ } ++ trace_jbd2_jmap_printf1("not low on space", x); ++ return false; ++} ++ ++inline int high_on_space(journal_t *journal) ++{ ++ if (atomic_read(&journal->j_cleaner_ctx->nr_txns_cleaned) < 2) { ++ trace_jbd2_jmap_printf("not enough cleaned"); ++ return false; ++ } ++ trace_jbd2_jmap_printf("enough cleaned"); ++ atomic_set(&journal->j_cleaner_ctx->nr_txns_cleaned, 0); ++ atomic_set(&journal->j_cleaner_ctx->nr_txns_committed, 0); ++ return true; ++} ++ ++inline bool cleaning(journal_t *journal) ++{ ++ return atomic_read(&journal->j_cleaner_ctx->cleaning); ++} ++ ++inline void stop_cleaning(journal_t *journal) ++{ ++ trace_jbd2_jmap_printf("stopped cleaning"); ++ atomic_set(&journal->j_cleaner_ctx->cleaning, 0); ++} ++ ++inline void start_cleaning(journal_t *journal) ++{ ++ trace_jbd2_jmap_printf("started cleaning"); ++ atomic_set(&journal->j_cleaner_ctx->cleaning, 1); ++ clean_next_batch(journal); ++} ++ ++inline bool cleaning_batch_complete(journal_t *journal) ++{ ++ return cleaning(journal) && ++ atomic_read(&journal->j_cleaner_ctx->batch_in_progress) == 0; ++} ++ ++/* ++ * Tries to move the tail forward (hence free space) as long as the transaction ++ * at the tail has only stale blocks. Returns true if manages to free a ++ * transaction, false otherwise. ++ */ ++bool try_to_move_tail(journal_t *journal) ++{ ++ struct transaction_infos *tis = journal->j_transaction_infos; ++ struct transaction_info *ti, *ti1; ++ ++ /* ++ * Advance the tail as far as possible by skipping over transactions ++ * with no live blocks. ++ */ ++ write_lock(&journal->j_jmap_lock); ++ ti = ti1 = &tis->buf[tis->tail]; ++ ++ for ( ; list_empty(&ti->live_blks); ti = &tis->buf[tis->tail]) { ++ trace_jbd2_jmap_printf2("cleaned a transaction", ++ tis->tail, ti->tid); ++ tis->tail = (tis->tail + 1) & (MAX_LIVE_TRANSACTIONS - 1); ++ atomic_inc(&journal->j_cleaner_ctx->nr_txns_cleaned); ++ } ++ write_unlock(&journal->j_jmap_lock); ++ ++ if (ti == ti1) ++ return false; ++ /* ++ * In the worst case, this will end up updating the journal superblock ++ * after cleaning up every transaction. Should we avoid it? ++ */ ++ write_unlock(&journal->j_state_lock); ++ jbd2_update_log_tail(journal, ti->tid, ti->offset); ++ write_lock(&journal->j_state_lock); ++ ++ return true; ++} ++ ++/* ++ * Finds the live blocks at the tail transaction and copies the corresponding ++ * mappings to |ctx->mappings|. Returns the number of live block mappings ++ * copied. Should be called with a read lock on |j_jmap_lock|. ++ */ ++int find_live_blocks(struct cleaner_ctx *ctx) ++{ ++ journal_t *journal = ctx->journal; ++ struct transaction_infos *tis = journal->j_transaction_infos; ++ struct transaction_info *ti = &tis->buf[tis->tail]; ++ struct jmap_entry *je = NULL; ++ int i, nr_live = 0; ++ ++ if (unlikely(list_empty(&ti->live_blks))) ++ goto done; ++ ++ spin_lock(&ctx->pos_lock); ++ if (!ctx->pos) ++ ctx->pos = list_first_entry(&ti->live_blks, typeof(*je), list); ++ je = ctx->pos; ++ spin_unlock(&ctx->pos_lock); ++ ++ list_for_each_entry_from(je, &ti->live_blks, list) { ++ if (je->revoked) ++ continue; ++ ctx->mappings[nr_live++] = je->mapping; ++ if (nr_live == CLEANER_BATCH_SIZE) ++ break; ++ } ++ ++done: ++ trace_jbd2_jmap_printf1("found live blocks", nr_live); ++ for (i = 0; i < nr_live; ++i) ++ trace_jbd2_jmap_printf2("m", ++ ctx->mappings[i].fsblk, ++ ctx->mappings[i].logblk); ++ return nr_live; ++} ++ ++void live_block_read_end_io(struct buffer_head *bh, int uptodate) ++{ ++ struct cleaner_ctx *ctx = bh->b_private; ++ ++ if (uptodate) { ++ set_buffer_uptodate(bh); ++ if (atomic_dec_and_test(&ctx->nr_pending_reads)) ++ complete(&ctx->live_block_reads); ++ } else { ++ WARN_ON(1); ++ clear_buffer_uptodate(bh); ++ } ++ ++ unlock_buffer(bh); ++ put_bh(bh); ++} ++ ++/* ++ * Reads live blocks in |ctx->mappings| populated by find_live_blocks into ++ * buffer heads in |ctx->bhs|. Returns true if at least one of the reads goes ++ * out to disk and false otherwise. If this function returns true then the ++ * client should sleep on the condition variable |ctx->live_block_reads|. The ++ * client will be woken up when all reads are complete, through the end_io ++ * handler attached to buffer heads read from disk. ++ */ ++bool read_live_blocks(struct cleaner_ctx *ctx, int nr_live) ++{ ++ journal_t *journal = ctx->journal; ++ bool slow = false; ++ struct blk_plug plug; ++ bool plugged = false; ++ int i, rc; ++ ++ for (i = 0; i < nr_live; ++i) { ++ ctx->bhs[i] = __getblk(journal->j_dev, ctx->mappings[i].fsblk, ++ journal->j_blocksize); ++ if (unlikely(!ctx->bhs[i])) ++ goto out_err; ++ if (buffer_uptodate(ctx->bhs[i])) ++ continue; ++ plugged = true; ++ blk_start_plug(&plug); ++ lock_buffer(ctx->bhs[i]); ++ ctx->bhs[i]->b_private = ctx; ++ ctx->bhs[i]->b_end_io = live_block_read_end_io; ++ atomic_inc(&ctx->nr_pending_reads); ++ get_bh(ctx->bhs[i]); ++ rc = read_block_from_log(ctx->journal, ctx->bhs[i], ++ REQ_RAHEAD, ctx->mappings[i].logblk); ++ if (unlikely(rc < 0)) ++ goto out_err; ++ if (rc) { ++ slow = true; ++ trace_jbd2_jmap_printf2("reading from disk", ++ ctx->mappings[i].fsblk, ++ ctx->mappings[i].logblk); ++ } else { ++ trace_jbd2_jmap_printf2("cached", ++ ctx->mappings[i].fsblk, ++ ctx->mappings[i].logblk); ++ } ++ } ++ if (plugged) ++ blk_finish_plug(&plug); ++ return slow; ++ ++out_err: ++ jbd2_journal_abort(ctx->journal, -ENOMEM); ++ return false; ++} ++ ++/* ++ * This function finds the live blocks that became stale between the call to ++ * find_live_blocks and now, and discards them. It returns true if there are no ++ * more live blocks left at the tail transaction. ++ */ ++bool discard_stale_blocks(struct cleaner_ctx *ctx, int nr_live) ++{ ++ journal_t *journal = ctx->journal; ++ struct transaction_infos *tis = journal->j_transaction_infos; ++ struct transaction_info *ti = &tis->buf[tis->tail]; ++ struct jmap_entry *je = NULL; ++ int i = 0, j = 0, next = 0; ++ ++ trace_jbd2_jmap_printf(__func__); ++ spin_lock(&ctx->pos_lock); ++ BUG_ON(!ctx->pos); ++ je = ctx->pos; ++ list_for_each_entry_from(je, &ti->live_blks, list) { ++ for (j = next; j < nr_live; ++j) { ++ if (je->mapping.fsblk == ctx->mappings[j].fsblk) { ++ next = j+1; ++ ctx->pos = list_next_entry(je, list); ++ if (je->revoked) { ++ brelse(ctx->bhs[j]); ++ ctx->bhs[j] = NULL; ++ trace_jbd2_jmap_printf2( ++ "revoked", ++ ctx->mappings[i].fsblk, ++ ctx->mappings[i].logblk); ++ } ++ break; ++ } else { ++ trace_jbd2_jmap_printf2( ++ "moved to another list", ++ ctx->mappings[i].fsblk, ++ ctx->mappings[i].logblk); ++ brelse(ctx->bhs[j]); ++ ctx->bhs[j] = NULL; ++ } ++ } ++ if (++i == nr_live || j == nr_live) ++ break; ++ } ++ spin_unlock(&ctx->pos_lock); ++ ++ /* ++ * We have exited the loop. If we haven't processed all the entries in ++ * |ctx->mappings|, that is if (j < nr_live) at the exit, and we have ++ * not processed |nr_live| entries from the live blocks list at the ++ * tail, that is if (i < nr_live) at the exit, then the live blocks list ++ * has shrunk and the tail transaction has no live blocks left. ++ */ ++ return j < nr_live && i < nr_live; ++} ++ ++void attach_live_blocks(struct cleaner_ctx *ctx, handle_t *handle, int nr_live) ++{ ++ int err, i; ++ ++ trace_jbd2_jmap_printf(__func__); ++ for (i = 0; i < nr_live; ++i) { ++ if (!ctx->bhs[i]) ++ continue; ++ trace_jbd2_jmap_printf2("attaching", ++ ctx->mappings[i].fsblk, ++ ctx->mappings[i].logblk); ++ err = jbd2_journal_get_write_access(handle, ctx->bhs[i]); ++ if (!err) ++ err = jbd2_journal_dirty_metadata(handle, ctx->bhs[i]); ++ if (err) { ++ jbd2_journal_abort(ctx->journal, err); ++ return; ++ } ++ } ++} ++ ++/* ++ * Read the live blocks from the tail transaction and attach them to the current ++ * transaction. ++ */ ++static void do_clean_batch(struct work_struct *work) ++{ ++ struct cleaner_ctx *ctx = container_of(work, struct cleaner_ctx, work); ++ bool wake_up_commit_thread = true; ++ handle_t *handle = NULL; ++ int nr_live, err; ++ ++ read_lock(&ctx->journal->j_jmap_lock); ++ nr_live = find_live_blocks(ctx); ++ read_unlock(&ctx->journal->j_jmap_lock); ++ ++ if (nr_live < CLEANER_BATCH_SIZE) ++ wake_up_commit_thread = false; ++ if (nr_live == 0) ++ goto done; ++ ++ reinit_completion(&ctx->live_block_reads); ++ if (read_live_blocks(ctx, nr_live)) { ++ trace_jbd2_jmap_printf("waiting for completion"); ++ wait_for_completion(&ctx->live_block_reads); ++ } else { ++ trace_jbd2_jmap_printf("not waiting for completion"); ++ } ++ ++ handle = jbd2_journal_start(ctx->journal, nr_live); ++ if (IS_ERR(handle)) { ++ jbd2_journal_abort(ctx->journal, PTR_ERR(handle)); ++ return; ++ } ++ ++ read_lock(&ctx->journal->j_jmap_lock); ++ if (discard_stale_blocks(ctx, nr_live)) ++ wake_up_commit_thread = false; ++ attach_live_blocks(ctx, handle, nr_live); ++ read_unlock(&ctx->journal->j_jmap_lock); ++ ++ err = jbd2_journal_stop(handle); ++ if (err) { ++ jbd2_journal_abort(ctx->journal, err); ++ return; ++ } ++ ++done: ++ atomic_set(&ctx->batch_in_progress, 0); ++ atomic_inc(&ctx->nr_txns_cleaned); ++ if (wake_up_commit_thread) { ++ trace_jbd2_jmap_printf("waking up commit thread"); ++ wake_up(&ctx->journal->j_wait_commit); ++ } else { ++ trace_jbd2_jmap_printf("not waking up commit thread"); ++ spin_lock(&ctx->pos_lock); ++ ctx->pos = NULL; ++ spin_unlock(&ctx->pos_lock); ++ } ++} ++ ++/* ++ * Schedules the next batch of cleaning. ++ */ ++void clean_next_batch(journal_t *journal) ++{ ++ struct cleaner_ctx *ctx = journal->j_cleaner_ctx; ++ ++ if (!cleaning_batch_complete(journal)) { ++ trace_jbd2_jmap_printf("not scheduling a new batch"); ++ return; ++ } ++ ++ trace_jbd2_jmap_printf("scheduling a batch"); ++ BUG_ON(atomic_read(&ctx->nr_pending_reads)); ++ ++ atomic_set(&ctx->batch_in_progress, 1); ++ INIT_WORK(&ctx->work, do_clean_batch); ++ schedule_work(&ctx->work); ++} diff --git a/fs/jbd2/jmap.c b/fs/jbd2/jmap.c -index 8c844f65eeaa..693b3e8d736c 100644 +index ddaa7d5f5634..08bfbd6f3587 100644 --- a/fs/jbd2/jmap.c +++ b/fs/jbd2/jmap.c -@@ -38,7 +38,7 @@ int jbd2_init_transaction_infos(journal_t *journal) +@@ -39,7 +39,7 @@ int jbd2_init_transaction_infos(journal_t *journal) } for (i = 0; i < MAX_LIVE_TRANSACTIONS; ++i) @@ -40,7 +405,7 @@ index 8c844f65eeaa..693b3e8d736c 100644 journal->j_transaction_infos = tis; return 0; -@@ -91,15 +91,26 @@ static int process_existing_mappings(journal_t *journal, +@@ -92,15 +92,26 @@ static int process_existing_mappings(journal_t *journal, * We are either deleting the entry because it was revoked, or * we are moving it to the live blocks list of this transaction. * In either case, we remove it from its existing list. @@ -49,14 +414,13 @@ index 8c844f65eeaa..693b3e8d736c 100644 + * pointer to whom is cached by the cleaner and update the + * cached pointer if so. */ -- list_del(&je->list); + spin_lock(&journal->j_cleaner_ctx->pos_lock); + if (je == journal->j_cleaner_ctx->pos) { + journal->j_cleaner_ctx->pos = list_next_entry(je, list); + trace_jbd2_jmap_printf1("updating pos to", + (unsigned long long) journal->j_cleaner_ctx->pos); + } -+ list_del(&je->list); + list_del(&je->list); + spin_unlock(&journal->j_cleaner_ctx->pos_lock); if (je->revoked) { @@ -69,7 +433,7 @@ index 8c844f65eeaa..693b3e8d736c 100644 } } return nr_new; -@@ -161,8 +172,7 @@ static void add_new_mappings(journal_t *journal, struct transaction_info *ti, +@@ -162,8 +173,7 @@ static void add_new_mappings(journal_t *journal, struct transaction_info *ti, else BUG_ON(1); } @@ -79,7 +443,7 @@ index 8c844f65eeaa..693b3e8d736c 100644 rb_link_node(&new_entries[i]->rb_node, parent, p); rb_insert_color(&new_entries[i]->rb_node, &journal->j_jmap); trace_jbd2_jmap_insert(&mappings[i], t_idx); -@@ -189,7 +199,9 @@ int jbd2_transaction_infos_add(journal_t *journal, transaction_t *transaction, +@@ -190,7 +200,9 @@ int jbd2_transaction_infos_add(journal_t *journal, transaction_t *transaction, * We are possibly reusing space of an old transaction_info. The old * transaction should not have any live blocks in it. */ @@ -90,12 +454,12 @@ index 8c844f65eeaa..693b3e8d736c 100644 write_lock(&journal->j_jmap_lock); nr_new = process_existing_mappings(journal, ti, t_idx, mappings, -@@ -432,12 +444,31 @@ int jbd2_bh_submit_read(journal_t *journal, struct buffer_head *bh, - - int jbd2_smr_journal_init(journal_t *journal) +@@ -435,11 +447,31 @@ int jbd2_smr_journal_init(journal_t *journal) { + journal->j_jmap = RB_ROOT; + rwlock_init(&journal->j_jmap_lock); + journal->j_cleaner_ctx = kzalloc(sizeof(struct cleaner_ctx), -+ GFP_KERNEL); ++ GFP_KERNEL); + if (!journal->j_cleaner_ctx) + return -ENOMEM; + @@ -108,39 +472,31 @@ index 8c844f65eeaa..693b3e8d736c 100644 + atomic_set(&journal->j_cleaner_ctx->nr_txns_committed, 0); + atomic_set(&journal->j_cleaner_ctx->nr_txns_cleaned, 0); + init_completion(&journal->j_cleaner_ctx->live_block_reads); -+ - journal->j_jmap = RB_ROOT; - rwlock_init(&journal->j_jmap_lock); -+ return jbd2_init_transaction_infos(journal); } void jbd2_smr_journal_exit(journal_t *journal) { -+ atomic_set(&journal->j_cleaner_ctx->cleaning, 0); -+ flush_work(&journal->j_cleaner_ctx->work); -+ kfree(journal->j_cleaner_ctx); ++ if (journal->j_cleaner_ctx) { ++ atomic_set(&journal->j_cleaner_ctx->cleaning, 0); ++ flush_work(&journal->j_cleaner_ctx->work); ++ kfree(journal->j_cleaner_ctx); ++ journal->j_cleaner_ctx = NULL; ++ } jbd2_free_transaction_infos(journal); } + diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c -index 0cbfb7fdc45d..8e305aacef48 100644 +index 0fb2cd419ee3..efe53b83e2e2 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c -@@ -51,7 +51,7 @@ - #include - - #ifdef CONFIG_JBD2_DEBUG --ushort jbd2_journal_enable_debug __read_mostly; -+ushort jbd2_journal_enable_debug __read_mostly = 1; - EXPORT_SYMBOL(jbd2_journal_enable_debug); - - module_param_named(jbd2_debug, jbd2_journal_enable_debug, ushort, 0644); -@@ -227,6 +227,14 @@ static int kjournald2(void *arg) +@@ -227,6 +227,15 @@ static int kjournald2(void *arg) } wake_up(&journal->j_wait_done_commit); + -+ if (cleaning(journal) || low_on_space(journal)) { ++ if ((journal->j_flags & JBD2_LAZY) && ++ (cleaning(journal) || low_on_space(journal))) { + if (try_to_move_tail(journal) && high_on_space(journal)) + stop_cleaning(journal); + else @@ -150,17 +506,18 @@ index 0cbfb7fdc45d..8e305aacef48 100644 if (freezing(current)) { /* * The simpler the better. Flushing journal isn't a -@@ -255,6 +263,8 @@ static int kjournald2(void *arg) +@@ -255,6 +264,9 @@ static int kjournald2(void *arg) should_sleep = 0; if (journal->j_flags & JBD2_UNMOUNT) should_sleep = 0; -+ if (cleaning_batch_complete(journal)) ++ if ((journal->j_flags & JBD2_LAZY) && ++ cleaning_batch_complete(journal)) + should_sleep = 0; if (should_sleep) { write_unlock(&journal->j_state_lock); schedule(); diff --git a/include/linux/jbd2.h b/include/linux/jbd2.h -index 317efb491569..350d5d229b68 100644 +index 771588026353..3112fba26598 100644 --- a/include/linux/jbd2.h +++ b/include/linux/jbd2.h @@ -735,7 +735,8 @@ jbd2_time_diff(unsigned long start, unsigned long end) @@ -184,7 +541,7 @@ index 317efb491569..350d5d229b68 100644 int j_format_version; diff --git a/include/linux/jmap.h b/include/linux/jmap.h -index d068358380b0..b734551ddb67 100644 +index a602ece7cc89..acd588b4c68b 100644 --- a/include/linux/jmap.h +++ b/include/linux/jmap.h @@ -5,6 +5,14 @@ @@ -238,9 +595,9 @@ index d068358380b0..b734551ddb67 100644 }; /* -@@ -126,4 +127,86 @@ extern void jbd2_ll_rw_block(journal_t *journal, int rw, int op_flags, int nr, - extern int jbd2_bh_submit_read(journal_t *journal, struct buffer_head *bh, - const char *func); +@@ -128,4 +129,86 @@ extern int jbd2_bh_submit_read(journal_t *journal, struct buffer_head *bh, + extern void jbd2_sb_breadahead(journal_t *journal, struct super_block *sb, + sector_t block); +/* + * Cleaner stuff is below. diff --git a/dont-wipe-log-on-unmount b/dont-wipe-log-on-unmount index 3ee6198b..ad490ac6 100644 --- a/dont-wipe-log-on-unmount +++ b/dont-wipe-log-on-unmount @@ -16,18 +16,23 @@ changes to journal.c and recovery.c is for finding the actual tail after mount (since now we are doing "recovery" on every mount) and updating in-memory journal superblock, so that new updates are written at the actual tail of the journal. + +Note: this patch is still a work in progress. In particular, the +jbd2_metamap_insert() function is no longer present. We need to +insert things via the new jbd2_transaction_infos_add. -TYT + --- fs/ext4/super.c | 4 ++-- fs/jbd2/commit.c | 4 ++-- fs/jbd2/journal.c | 11 ++++++----- - fs/jbd2/recovery.c | 10 +++++++--- - 4 files changed, 17 insertions(+), 12 deletions(-) + fs/jbd2/recovery.c | 25 +++++++++++++++++++++---- + 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/fs/ext4/super.c b/fs/ext4/super.c -index dde14a7ac6d7..ff77c84ef089 100644 +index b2bd2a31a931..e8e2bbd1e898 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c -@@ -4539,8 +4539,8 @@ static int ext4_load_journal(struct super_block *sb, +@@ -4557,8 +4557,8 @@ static int ext4_load_journal(struct super_block *sb, if (!(journal->j_flags & JBD2_BARRIER)) ext4_msg(sb, KERN_INFO, "barriers disabled"); @@ -39,10 +44,10 @@ index dde14a7ac6d7..ff77c84ef089 100644 char *save = kmalloc(EXT4_S_ERR_LEN, GFP_KERNEL); if (save) diff --git a/fs/jbd2/commit.c b/fs/jbd2/commit.c -index cf091b79ac99..6cfa74b32ad5 100644 +index 50e1a0b375c5..bf61fe5f1048 100644 --- a/fs/jbd2/commit.c +++ b/fs/jbd2/commit.c -@@ -898,8 +898,8 @@ void jbd2_journal_commit_transaction(journal_t *journal) +@@ -902,8 +902,8 @@ void jbd2_journal_commit_transaction(journal_t *journal) * erase checkpointed transactions from the log by updating journal * superblock. */ @@ -54,10 +59,10 @@ index cf091b79ac99..6cfa74b32ad5 100644 /* End of a transaction! Finally, we can do checkpoint processing: any buffers committed as a result of this diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c -index 3d44dd35c9f0..23e8b2aaf9c3 100644 +index 2d2743297810..e4e3f94e7a93 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c -@@ -1294,9 +1294,6 @@ static int journal_reset(journal_t *journal) +@@ -1296,9 +1296,6 @@ static int journal_reset(journal_t *journal) journal->j_first = first; journal->j_last = last; @@ -67,7 +72,7 @@ index 3d44dd35c9f0..23e8b2aaf9c3 100644 journal->j_free = last - first; journal->j_tail_sequence = journal->j_transaction_sequence; -@@ -1312,12 +1309,15 @@ static int journal_reset(journal_t *journal) +@@ -1314,12 +1311,15 @@ static int journal_reset(journal_t *journal) * attempting a write to a potential-readonly device. */ if (sb->s_start == 0) { @@ -83,7 +88,7 @@ index 3d44dd35c9f0..23e8b2aaf9c3 100644 /* Lock here to make assertions happy... */ mutex_lock(&journal->j_checkpoint_mutex); /* -@@ -1331,6 +1331,7 @@ static int journal_reset(journal_t *journal) +@@ -1333,6 +1333,7 @@ static int journal_reset(journal_t *journal) journal->j_tail, REQ_FUA); mutex_unlock(&journal->j_checkpoint_mutex); @@ -91,7 +96,7 @@ index 3d44dd35c9f0..23e8b2aaf9c3 100644 } return jbd2_journal_start_thread(journal); } -@@ -1439,8 +1440,8 @@ static void jbd2_mark_journal_empty(journal_t *journal, int write_op) +@@ -1441,8 +1442,8 @@ static void jbd2_mark_journal_empty(journal_t *journal, int write_op) jbd_debug(1, "JBD2: Marking journal as empty (seq %d)\n", journal->j_tail_sequence); @@ -103,7 +108,7 @@ index 3d44dd35c9f0..23e8b2aaf9c3 100644 jbd2_write_superblock(journal, write_op); diff --git a/fs/jbd2/recovery.c b/fs/jbd2/recovery.c -index da100044566c..d5c308ec541d 100644 +index da100044566c..30dc632ccf0a 100644 --- a/fs/jbd2/recovery.c +++ b/fs/jbd2/recovery.c @@ -32,6 +32,7 @@ struct recovery_info @@ -143,7 +148,36 @@ index da100044566c..d5c308ec541d 100644 switch(blocktype) { case JBD2_DESCRIPTOR_BLOCK: /* Verify checksum first */ -@@ -791,6 +794,7 @@ static int do_one_pass(journal_t *journal, +@@ -578,7 +581,7 @@ static int do_one_pass(journal_t *journal, + "block %ld in log\n", + err, io_block); + } else { +- unsigned long long blocknr; ++ unsigned long long blocknr, log_block; + + J_ASSERT(obh != NULL); + blocknr = read_tag_block(journal, +@@ -609,6 +612,19 @@ static int do_one_pass(journal_t *journal, + goto skip_write; + } + ++ err = jbd2_journal_bmap(journal, ++ io_block, ++ &log_block); ++ J_ASSERT(!err); ++ ++#if 0 ++ if (!jbd2_metamap_insert( ++ journal, ++ blocknr, ++ log_block)) ++ goto failed; ++#endif ++ + /* Find a buffer for the new + * data being restored */ + nbh = __getblk(journal->j_fs_dev, +@@ -791,6 +807,7 @@ static int do_one_pass(journal_t *journal, if (pass == PASS_SCAN) { if (!info->end_transaction) info->end_transaction = next_commit_ID; diff --git a/jbd2-suppress-extra-newline-in-jbd2_debug b/jbd2-suppress-extra-newline-in-jbd2_debug new file mode 100644 index 00000000..602841ed --- /dev/null +++ b/jbd2-suppress-extra-newline-in-jbd2_debug @@ -0,0 +1,20 @@ +jbd2: suppress extra newline in jbd2_debug + +Signed-off-by: Theodore Ts'o +--- + fs/jbd2/journal.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c +index 85d1483939b2..38ccd9070f72 100644 +--- a/fs/jbd2/journal.c ++++ b/fs/jbd2/journal.c +@@ -116,7 +116,7 @@ void __jbd2_debug(int level, const char *file, const char *func, + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; +- printk(KERN_DEBUG "%s: (%s, %u): %pV\n", file, func, line, &vaf); ++ printk(KERN_DEBUG "%s: (%s, %u): %pV", file, func, line, &vaf); + va_end(args); + } + EXPORT_SYMBOL(__jbd2_debug); diff --git a/series b/series index d27d75a2..bfe5770f 100644 --- a/series +++ b/series @@ -36,13 +36,17 @@ dont-crash-when-truncating-encrypted-on-the-orphan-list stable-boundary stable-boundary-undo.patch +# Lazy journalling patches +jbd2-suppress-extra-newline-in-jbd2_debug jbd2-dont-double-bump-transaction-number add-journal-no-cleanup-option add-support-for-log-metadata-block-tracking-in-log +add-ext4-journal-lazy-mount-option add-indirection-to-metadata-block-read-paths cleaner -dont-wipe-log-on-unmount disable-writeback +dont-wipe-log-on-unmount +#end lazy journal patches only-call-ext4_truncate-if-there-is-data-to-truncate diff --git a/timestamps b/timestamps index 4381a9d4..fa042d2f 100755 --- a/timestamps +++ b/timestamps @@ -65,13 +65,15 @@ touch -d @1486706169 do-not-use-stripe-width-if-it-is-not-set touch -d @1486783145 dont-crash-when-truncating-encrypted-on-the-orphan-list touch -d @1486783205 stable-boundary touch -d @1486783265 stable-boundary-undo.patch -touch -d @1486788983 jbd2-dont-double-bump-transaction-number -touch -d @1486826097 add-indirection-to-metadata-block-read-paths -touch -d @1486826099 cleaner -touch -d @1486826101 dont-wipe-log-on-unmount -touch -d @1486826103 disable-writeback -touch -d @1486874091 add-journal-no-cleanup-option -touch -d @1486874091 series -touch -d @1486874096 add-support-for-log-metadata-block-tracking-in-log -touch -d @1486874096 status -touch -d @1486875382 timestamps +touch -d @1486959791 series +touch -d @1486959815 jbd2-suppress-extra-newline-in-jbd2_debug +touch -d @1486959825 jbd2-dont-double-bump-transaction-number +touch -d @1486959827 add-journal-no-cleanup-option +touch -d @1486959830 add-support-for-log-metadata-block-tracking-in-log +touch -d @1486959834 add-ext4-journal-lazy-mount-option +touch -d @1486959836 add-indirection-to-metadata-block-read-paths +touch -d @1486959840 cleaner +touch -d @1486959856 disable-writeback +touch -d @1486961745 dont-wipe-log-on-unmount +touch -d @1486961746 status +touch -d @1486961768 timestamps -- 2.11.4.GIT