Merge tag 'lazytime_for_v5.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git...
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 22 Feb 2021 21:17:39 +0000 (13:17 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 22 Feb 2021 21:17:39 +0000 (13:17 -0800)
Pull lazytime updates from Jan Kara:
 "Cleanups of the lazytime handling in the writeback code making rules
  for calling ->dirty_inode() filesystem handlers saner"

* tag 'lazytime_for_v5.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs:
  ext4: simplify i_state checks in __ext4_update_other_inode_time()
  gfs2: don't worry about I_DIRTY_TIME in gfs2_fsync()
  fs: improve comments for writeback_single_inode()
  fs: drop redundant check from __writeback_single_inode()
  fs: clean up __mark_inode_dirty() a bit
  fs: pass only I_DIRTY_INODE flags to ->dirty_inode
  fs: don't call ->dirty_inode for lazytime timestamp updates
  fat: only specify I_DIRTY_TIME when needed in fat_update_time()
  fs: only specify I_DIRTY_TIME when needed in generic_update_time()
  fs: correctly document the inode dirty flags

1  2 
Documentation/filesystems/vfs.rst
fs/ext4/inode.c
fs/f2fs/super.c
fs/gfs2/file.c
fs/inode.c
include/linux/fs.h

@@@ -112,7 -112,7 +112,7 @@@ members are defined
  
  .. code-block:: c
  
 -      struct file_system_operations {
 +      struct file_system_type {
                const char *name;
                int fs_flags;
                struct dentry *(*mount) (struct file_system_type *, int,
@@@ -270,7 -270,10 +270,10 @@@ or bottom half)
        ->alloc_inode.
  
  ``dirty_inode``
-       this method is called by the VFS to mark an inode dirty.
+       this method is called by the VFS when an inode is marked dirty.
+       This is specifically for the inode itself being marked dirty,
+       not its data.  If the update needs to be persisted by fdatasync(),
+       then I_DIRTY_DATASYNC will be set in the flags argument.
  
  ``write_inode``
        this method is called when the VFS needs to write an inode to
diff --combined fs/ext4/inode.c
@@@ -4961,15 -4961,11 +4961,11 @@@ static void __ext4_update_other_inode_t
        if (!inode)
                return;
  
-       if ((inode->i_state & (I_FREEING | I_WILL_FREE | I_NEW |
-                              I_DIRTY_INODE)) ||
-           ((inode->i_state & I_DIRTY_TIME) == 0))
+       if (!inode_is_dirtytime_only(inode))
                return;
  
        spin_lock(&inode->i_lock);
-       if (((inode->i_state & (I_FREEING | I_WILL_FREE | I_NEW |
-                               I_DIRTY_INODE)) == 0) &&
-           (inode->i_state & I_DIRTY_TIME)) {
+       if (inode_is_dirtytime_only(inode)) {
                struct ext4_inode_info  *ei = EXT4_I(inode);
  
                inode->i_state &= ~I_DIRTY_TIME;
@@@ -5150,13 -5146,9 +5146,13 @@@ static int ext4_do_update_inode(handle_
                err = ext4_journal_get_write_access(handle, EXT4_SB(sb)->s_sbh);
                if (err)
                        goto out_brelse;
 +              lock_buffer(EXT4_SB(sb)->s_sbh);
                ext4_set_feature_large_file(sb);
 +              ext4_superblock_csum_set(sb);
 +              unlock_buffer(EXT4_SB(sb)->s_sbh);
                ext4_handle_sync(handle);
 -              err = ext4_handle_dirty_super(handle, sb);
 +              err = ext4_handle_dirty_metadata(handle, NULL,
 +                                               EXT4_SB(sb)->s_sbh);
        }
        ext4_update_inode_fsync_trans(handle, inode, need_datasync);
  out_brelse:
   * If the inode is marked synchronous, we don't honour that here - doing
   * so would cause a commit on atime updates, which we don't bother doing.
   * We handle synchronous inodes at the highest possible level.
-  *
-  * If only the I_DIRTY_TIME flag is set, we can skip everything.  If
-  * I_DIRTY_TIME and I_DIRTY_SYNC is set, the only inode fields we need
-  * to copy into the on-disk inode structure are the timestamp files.
   */
  void ext4_dirty_inode(struct inode *inode, int flags)
  {
        handle_t *handle;
  
-       if (flags == I_DIRTY_TIME)
-               return;
        handle = ext4_journal_start(inode, EXT4_HT_INODE, 2);
        if (IS_ERR(handle))
-               goto out;
+               return;
        ext4_mark_inode_dirty(handle, inode);
        ext4_journal_stop(handle);
- out:
-       return;
  }
  
  int ext4_change_inode_journal_flag(struct inode *inode, int val)
diff --combined fs/f2fs/super.c
  #include <linux/quota.h>
  #include <linux/unicode.h>
  #include <linux/part_stat.h>
 +#include <linux/zstd.h>
 +#include <linux/lz4.h>
  
  #include "f2fs.h"
  #include "node.h"
  #include "segment.h"
  #include "xattr.h"
  #include "gc.h"
 -#include "trace.h"
  
  #define CREATE_TRACE_POINTS
  #include <trace/events/f2fs.h>
@@@ -46,6 -45,7 +46,6 @@@ const char *f2fs_fault_name[FAULT_MAX] 
        [FAULT_KVMALLOC]        = "kvmalloc",
        [FAULT_PAGE_ALLOC]      = "page alloc",
        [FAULT_PAGE_GET]        = "page get",
 -      [FAULT_ALLOC_BIO]       = "alloc bio",
        [FAULT_ALLOC_NID]       = "alloc nid",
        [FAULT_ORPHAN]          = "orphan",
        [FAULT_BLOCK]           = "no more block",
@@@ -143,8 -143,6 +143,8 @@@ enum 
        Opt_checkpoint_disable_cap,
        Opt_checkpoint_disable_cap_perc,
        Opt_checkpoint_enable,
 +      Opt_checkpoint_merge,
 +      Opt_nocheckpoint_merge,
        Opt_compress_algorithm,
        Opt_compress_log_size,
        Opt_compress_extension,
@@@ -215,8 -213,6 +215,8 @@@ static match_table_t f2fs_tokens = 
        {Opt_checkpoint_disable_cap, "checkpoint=disable:%u"},
        {Opt_checkpoint_disable_cap_perc, "checkpoint=disable:%u%%"},
        {Opt_checkpoint_enable, "checkpoint=enable"},
 +      {Opt_checkpoint_merge, "checkpoint_merge"},
 +      {Opt_nocheckpoint_merge, "nocheckpoint_merge"},
        {Opt_compress_algorithm, "compress_algorithm=%s"},
        {Opt_compress_log_size, "compress_log_size=%u"},
        {Opt_compress_extension, "compress_extension=%s"},
@@@ -468,74 -464,6 +468,74 @@@ static int f2fs_set_test_dummy_encrypti
        return 0;
  }
  
 +#ifdef CONFIG_F2FS_FS_COMPRESSION
 +#ifdef CONFIG_F2FS_FS_LZ4
 +static int f2fs_set_lz4hc_level(struct f2fs_sb_info *sbi, const char *str)
 +{
 +#ifdef CONFIG_F2FS_FS_LZ4HC
 +      unsigned int level;
 +#endif
 +
 +      if (strlen(str) == 3) {
 +              F2FS_OPTION(sbi).compress_level = 0;
 +              return 0;
 +      }
 +
 +#ifdef CONFIG_F2FS_FS_LZ4HC
 +      str += 3;
 +
 +      if (str[0] != ':') {
 +              f2fs_info(sbi, "wrong format, e.g. <alg_name>:<compr_level>");
 +              return -EINVAL;
 +      }
 +      if (kstrtouint(str + 1, 10, &level))
 +              return -EINVAL;
 +
 +      if (level < LZ4HC_MIN_CLEVEL || level > LZ4HC_MAX_CLEVEL) {
 +              f2fs_info(sbi, "invalid lz4hc compress level: %d", level);
 +              return -EINVAL;
 +      }
 +
 +      F2FS_OPTION(sbi).compress_level = level;
 +      return 0;
 +#else
 +      f2fs_info(sbi, "kernel doesn't support lz4hc compression");
 +      return -EINVAL;
 +#endif
 +}
 +#endif
 +
 +#ifdef CONFIG_F2FS_FS_ZSTD
 +static int f2fs_set_zstd_level(struct f2fs_sb_info *sbi, const char *str)
 +{
 +      unsigned int level;
 +      int len = 4;
 +
 +      if (strlen(str) == len) {
 +              F2FS_OPTION(sbi).compress_level = 0;
 +              return 0;
 +      }
 +
 +      str += len;
 +
 +      if (str[0] != ':') {
 +              f2fs_info(sbi, "wrong format, e.g. <alg_name>:<compr_level>");
 +              return -EINVAL;
 +      }
 +      if (kstrtouint(str + 1, 10, &level))
 +              return -EINVAL;
 +
 +      if (!level || level > ZSTD_maxCLevel()) {
 +              f2fs_info(sbi, "invalid zstd compress level: %d", level);
 +              return -EINVAL;
 +      }
 +
 +      F2FS_OPTION(sbi).compress_level = level;
 +      return 0;
 +}
 +#endif
 +#endif
 +
  static int parse_options(struct super_block *sb, char *options, bool is_remount)
  {
        struct f2fs_sb_info *sbi = F2FS_SB(sb);
                case Opt_checkpoint_enable:
                        clear_opt(sbi, DISABLE_CHECKPOINT);
                        break;
 +              case Opt_checkpoint_merge:
 +                      set_opt(sbi, MERGE_CHECKPOINT);
 +                      break;
 +              case Opt_nocheckpoint_merge:
 +                      clear_opt(sbi, MERGE_CHECKPOINT);
 +                      break;
  #ifdef CONFIG_F2FS_FS_COMPRESSION
                case Opt_compress_algorithm:
                        if (!f2fs_sb_has_compression(sbi)) {
                        if (!name)
                                return -ENOMEM;
                        if (!strcmp(name, "lzo")) {
 +#ifdef CONFIG_F2FS_FS_LZO
 +                              F2FS_OPTION(sbi).compress_level = 0;
                                F2FS_OPTION(sbi).compress_algorithm =
                                                                COMPRESS_LZO;
 -                      } else if (!strcmp(name, "lz4")) {
 +#else
 +                              f2fs_info(sbi, "kernel doesn't support lzo compression");
 +#endif
 +                      } else if (!strncmp(name, "lz4", 3)) {
 +#ifdef CONFIG_F2FS_FS_LZ4
 +                              ret = f2fs_set_lz4hc_level(sbi, name);
 +                              if (ret) {
 +                                      kfree(name);
 +                                      return -EINVAL;
 +                              }
                                F2FS_OPTION(sbi).compress_algorithm =
                                                                COMPRESS_LZ4;
 -                      } else if (!strcmp(name, "zstd")) {
 +#else
 +                              f2fs_info(sbi, "kernel doesn't support lz4 compression");
 +#endif
 +                      } else if (!strncmp(name, "zstd", 4)) {
 +#ifdef CONFIG_F2FS_FS_ZSTD
 +                              ret = f2fs_set_zstd_level(sbi, name);
 +                              if (ret) {
 +                                      kfree(name);
 +                                      return -EINVAL;
 +                              }
                                F2FS_OPTION(sbi).compress_algorithm =
                                                                COMPRESS_ZSTD;
 +#else
 +                              f2fs_info(sbi, "kernel doesn't support zstd compression");
 +#endif
                        } else if (!strcmp(name, "lzo-rle")) {
 +#ifdef CONFIG_F2FS_FS_LZORLE
 +                              F2FS_OPTION(sbi).compress_level = 0;
                                F2FS_OPTION(sbi).compress_algorithm =
                                                                COMPRESS_LZORLE;
 +#else
 +                              f2fs_info(sbi, "kernel doesn't support lzorle compression");
 +#endif
                        } else {
                                kfree(name);
                                return -EINVAL;
@@@ -1182,6 -1076,8 +1182,6 @@@ static struct inode *f2fs_alloc_inode(s
        /* Will be used by directory only */
        fi->i_dir_level = F2FS_SB(sb)->dir_level;
  
 -      fi->ra_offset = -1;
 -
        return &fi->vfs_inode;
  }
  
@@@ -1300,9 -1196,6 +1300,6 @@@ static void f2fs_dirty_inode(struct ino
                        inode->i_ino == F2FS_META_INO(sbi))
                return;
  
-       if (flags == I_DIRTY_TIME)
-               return;
        if (is_inode_flag_set(inode, FI_AUTO_RECOVER))
                clear_inode_flag(inode, FI_AUTO_RECOVER);
  
@@@ -1349,12 -1242,6 +1346,12 @@@ static void f2fs_put_super(struct super
        /* prevent remaining shrinker jobs */
        mutex_lock(&sbi->umount_mutex);
  
 +      /*
 +       * flush all issued checkpoints and stop checkpoint issue thread.
 +       * after then, all checkpoints should be done by each process context.
 +       */
 +      f2fs_stop_ckpt_thread(sbi);
 +
        /*
         * We don't need to do checkpoint when superblock is clean.
         * But, the previous checkpoint was not done by umount, it needs to do
@@@ -1453,8 -1340,16 +1450,8 @@@ int f2fs_sync_fs(struct super_block *sb
        if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING)))
                return -EAGAIN;
  
 -      if (sync) {
 -              struct cp_control cpc;
 -
 -              cpc.reason = __get_cp_reason(sbi);
 -
 -              down_write(&sbi->gc_lock);
 -              err = f2fs_write_checkpoint(sbi, &cpc);
 -              up_write(&sbi->gc_lock);
 -      }
 -      f2fs_trace_ios(NULL, 1);
 +      if (sync)
 +              err = f2fs_issue_checkpoint(sbi);
  
        return err;
  }
@@@ -1471,10 -1366,6 +1468,10 @@@ static int f2fs_freeze(struct super_blo
        /* must be clean, since sync_filesystem() was already called */
        if (is_sbi_flag_set(F2FS_SB(sb), SBI_IS_DIRTY))
                return -EINVAL;
 +
 +      /* ensure no checkpoint required */
 +      if (!llist_empty(&F2FS_SB(sb)->cprc_info.issue_list))
 +              return -EINVAL;
        return 0;
  }
  
@@@ -1645,9 -1536,6 +1642,9 @@@ static inline void f2fs_show_compress_o
        }
        seq_printf(seq, ",compress_algorithm=%s", algtype);
  
 +      if (F2FS_OPTION(sbi).compress_level)
 +              seq_printf(seq, ":%d", F2FS_OPTION(sbi).compress_level);
 +
        seq_printf(seq, ",compress_log_size=%u",
                        F2FS_OPTION(sbi).compress_log_size);
  
@@@ -1783,10 -1671,6 +1780,10 @@@ static int f2fs_show_options(struct seq
        if (test_opt(sbi, DISABLE_CHECKPOINT))
                seq_printf(seq, ",checkpoint=disable:%u",
                                F2FS_OPTION(sbi).unusable_cap);
 +      if (test_opt(sbi, MERGE_CHECKPOINT))
 +              seq_puts(seq, ",checkpoint_merge");
 +      else
 +              seq_puts(seq, ",nocheckpoint_merge");
        if (F2FS_OPTION(sbi).fsync_mode == FSYNC_MODE_POSIX)
                seq_printf(seq, ",fsync_mode=%s", "posix");
        else if (F2FS_OPTION(sbi).fsync_mode == FSYNC_MODE_STRICT)
@@@ -1909,9 -1793,6 +1906,9 @@@ restore_flag
  
  static void f2fs_enable_checkpoint(struct f2fs_sb_info *sbi)
  {
 +      /* we should flush all the data to keep data consistency */
 +      sync_inodes_sb(sbi->sb);
 +
        down_write(&sbi->gc_lock);
        f2fs_dirty_to_prefree(sbi);
  
@@@ -2070,19 -1951,6 +2067,19 @@@ static int f2fs_remount(struct super_bl
                }
        }
  
 +      if (!test_opt(sbi, DISABLE_CHECKPOINT) &&
 +                      test_opt(sbi, MERGE_CHECKPOINT)) {
 +              err = f2fs_start_ckpt_thread(sbi);
 +              if (err) {
 +                      f2fs_err(sbi,
 +                          "Failed to start F2FS issue_checkpoint_thread (%d)",
 +                          err);
 +                      goto restore_gc;
 +              }
 +      } else {
 +              f2fs_stop_ckpt_thread(sbi);
 +      }
 +
        /*
         * We stop issue flush thread if FS is mounted as RO
         * or if flush_merge is not passed in mount option.
@@@ -2767,10 -2635,10 +2764,10 @@@ static const struct export_operations f
        .get_parent = f2fs_get_parent,
  };
  
 -static loff_t max_file_blocks(void)
 +loff_t max_file_blocks(struct inode *inode)
  {
        loff_t result = 0;
 -      loff_t leaf_count = DEF_ADDRS_PER_BLOCK;
 +      loff_t leaf_count;
  
        /*
         * note: previously, result is equal to (DEF_ADDRS_PER_INODE -
         * result as zero.
         */
  
 +      if (inode && f2fs_compressed_file(inode))
 +              leaf_count = ADDRS_PER_BLOCK(inode);
 +      else
 +              leaf_count = DEF_ADDRS_PER_BLOCK;
 +
        /* two direct node blocks */
        result += (leaf_count * 2);
  
@@@ -3667,7 -3530,8 +3664,7 @@@ try_onemore
        if (err)
                goto free_options;
  
 -      sbi->max_file_blocks = max_file_blocks();
 -      sb->s_maxbytes = sbi->max_file_blocks <<
 +      sb->s_maxbytes = max_file_blocks(NULL) <<
                                le32_to_cpu(raw_super->log_blocksize);
        sb->s_max_links = F2FS_LINK_MAX;
  
  
        f2fs_init_fsync_node_info(sbi);
  
 +      /* setup checkpoint request control and start checkpoint issue thread */
 +      f2fs_init_ckpt_req_control(sbi);
 +      if (!test_opt(sbi, DISABLE_CHECKPOINT) &&
 +                      test_opt(sbi, MERGE_CHECKPOINT)) {
 +              err = f2fs_start_ckpt_thread(sbi);
 +              if (err) {
 +                      f2fs_err(sbi,
 +                          "Failed to start F2FS issue_checkpoint_thread (%d)",
 +                          err);
 +                      goto stop_ckpt_thread;
 +              }
 +      }
 +
        /* setup f2fs internal modules */
        err = f2fs_build_segment_manager(sbi);
        if (err) {
                 * previous checkpoint was not done by clean system shutdown.
                 */
                if (f2fs_hw_is_readonly(sbi)) {
 -                      if (!is_set_ckpt_flags(sbi, CP_UMOUNT_FLAG)) {
 -                              err = -EROFS;
 +                      if (!is_set_ckpt_flags(sbi, CP_UMOUNT_FLAG))
                                f2fs_err(sbi, "Need to recover fsync data, but write access unavailable");
 -                              goto free_meta;
 -                      }
 -                      f2fs_info(sbi, "write access unavailable, skipping recovery");
 +                      else
 +                              f2fs_info(sbi, "write access unavailable, skipping recovery");
                        goto reset_checkpoint;
                }
  
@@@ -4054,8 -3907,6 +4051,8 @@@ free_nm
  free_sm:
        f2fs_destroy_segment_manager(sbi);
        f2fs_destroy_post_read_wq(sbi);
 +stop_ckpt_thread:
 +      f2fs_stop_ckpt_thread(sbi);
  free_devices:
        destroy_device_list(sbi);
        kvfree(sbi->ckpt);
@@@ -4170,6 -4021,8 +4167,6 @@@ static int __init init_f2fs_fs(void
                return -EINVAL;
        }
  
 -      f2fs_build_trace_ios();
 -
        err = init_inodecache();
        if (err)
                goto fail;
@@@ -4262,6 -4115,7 +4259,6 @@@ static void __exit exit_f2fs_fs(void
        f2fs_destroy_segment_manager_caches();
        f2fs_destroy_node_manager_caches();
        destroy_inodecache();
 -      f2fs_destroy_trace_ios();
  }
  
  module_init(init_f2fs_fs)
diff --combined fs/gfs2/file.c
@@@ -749,7 -749,7 +749,7 @@@ static int gfs2_fsync(struct file *file
  {
        struct address_space *mapping = file->f_mapping;
        struct inode *inode = mapping->host;
-       int sync_state = inode->i_state & I_DIRTY_ALL;
+       int sync_state = inode->i_state & I_DIRTY;
        struct gfs2_inode *ip = GFS2_I(inode);
        int ret = 0, ret1 = 0;
  
        if (!gfs2_is_jdata(ip))
                sync_state &= ~I_DIRTY_PAGES;
        if (datasync)
-               sync_state &= ~(I_DIRTY_SYNC | I_DIRTY_TIME);
+               sync_state &= ~I_DIRTY_SYNC;
  
        if (sync_state) {
                ret = sync_inode_metadata(inode, 1);
@@@ -797,7 -797,9 +797,7 @@@ static ssize_t gfs2_file_direct_read(st
        if (ret)
                goto out_uninit;
  
 -      ret = iomap_dio_rw(iocb, to, &gfs2_iomap_ops, NULL,
 -                         is_sync_kiocb(iocb));
 -
 +      ret = iomap_dio_rw(iocb, to, &gfs2_iomap_ops, NULL, 0);
        gfs2_glock_dq(gh);
  out_uninit:
        gfs2_holder_uninit(gh);
@@@ -831,7 -833,8 +831,7 @@@ static ssize_t gfs2_file_direct_write(s
        if (offset + len > i_size_read(&ip->i_inode))
                goto out;
  
 -      ret = iomap_dio_rw(iocb, from, &gfs2_iomap_ops, NULL,
 -                         is_sync_kiocb(iocb));
 +      ret = iomap_dio_rw(iocb, from, &gfs2_iomap_ops, NULL, 0);
        if (ret == -ENOTBLK)
                ret = 0;
  out:
diff --combined fs/inode.c
@@@ -1493,7 -1493,7 +1493,7 @@@ struct inode *find_inode_rcu(struct sup
  EXPORT_SYMBOL(find_inode_rcu);
  
  /**
 - * find_inode_by_rcu - Find an inode in the inode cache
 + * find_inode_by_ino_rcu - Find an inode in the inode cache
   * @sb:               Super block of file system to search
   * @ino:      The inode number to match
   *
@@@ -1743,24 -1743,26 +1743,26 @@@ static int relatime_need_update(struct 
  
  int generic_update_time(struct inode *inode, struct timespec64 *time, int flags)
  {
-       int iflags = I_DIRTY_TIME;
-       bool dirty = false;
-       if (flags & S_ATIME)
-               inode->i_atime = *time;
-       if (flags & S_VERSION)
-               dirty = inode_maybe_inc_iversion(inode, false);
-       if (flags & S_CTIME)
-               inode->i_ctime = *time;
-       if (flags & S_MTIME)
-               inode->i_mtime = *time;
-       if ((flags & (S_ATIME | S_CTIME | S_MTIME)) &&
-           !(inode->i_sb->s_flags & SB_LAZYTIME))
-               dirty = true;
-       if (dirty)
-               iflags |= I_DIRTY_SYNC;
-       __mark_inode_dirty(inode, iflags);
+       int dirty_flags = 0;
+       if (flags & (S_ATIME | S_CTIME | S_MTIME)) {
+               if (flags & S_ATIME)
+                       inode->i_atime = *time;
+               if (flags & S_CTIME)
+                       inode->i_ctime = *time;
+               if (flags & S_MTIME)
+                       inode->i_mtime = *time;
+               if (inode->i_sb->s_flags & SB_LAZYTIME)
+                       dirty_flags |= I_DIRTY_TIME;
+               else
+                       dirty_flags |= I_DIRTY_SYNC;
+       }
+       if ((flags & S_VERSION) && inode_maybe_inc_iversion(inode, false))
+               dirty_flags |= I_DIRTY_SYNC;
+       __mark_inode_dirty(inode, dirty_flags);
        return 0;
  }
  EXPORT_SYMBOL(generic_update_time);
@@@ -1777,7 -1779,7 +1779,7 @@@ static int update_time(struct inode *in
  }
  
  /**
 - *    touch_atime     -       update the access time
 + *    atime_needs_update      -       update the access time
   *    @path: the &struct path to update
   *    @inode: inode to update
   *
diff --combined include/linux/fs.h
@@@ -2084,8 -2084,8 +2084,8 @@@ static inline void kiocb_clone(struct k
  /*
   * Inode state bits.  Protected by inode->i_lock
   *
-  * Three bits determine the dirty state of the inode, I_DIRTY_SYNC,
-  * I_DIRTY_DATASYNC and I_DIRTY_PAGES.
+  * Four bits determine the dirty state of the inode: I_DIRTY_SYNC,
+  * I_DIRTY_DATASYNC, I_DIRTY_PAGES, and I_DIRTY_TIME.
   *
   * Four bits define the lifetime of an inode.  Initially, inodes are I_NEW,
   * until that flag is cleared.  I_WILL_FREE, I_FREEING and I_CLEAR are set at
   * Two bits are used for locking and completion notification, I_NEW and I_SYNC.
   *
   * I_DIRTY_SYNC               Inode is dirty, but doesn't have to be written on
-  *                    fdatasync().  i_atime is the usual cause.
-  * I_DIRTY_DATASYNC   Data-related inode changes pending. We keep track of
+  *                    fdatasync() (unless I_DIRTY_DATASYNC is also set).
+  *                    Timestamp updates are the usual cause.
+  * I_DIRTY_DATASYNC   Data-related inode changes pending.  We keep track of
   *                    these changes separately from I_DIRTY_SYNC so that we
   *                    don't have to write inode on fdatasync() when only
-  *                    mtime has changed in it.
+  *                    e.g. the timestamps have changed.
   * I_DIRTY_PAGES      Inode has dirty pages.  Inode itself may be clean.
+  * I_DIRTY_TIME               The inode itself only has dirty timestamps, and the
+  *                    lazytime mount option is enabled.  We keep track of this
+  *                    separately from I_DIRTY_SYNC in order to implement
+  *                    lazytime.  This gets cleared if I_DIRTY_INODE
+  *                    (I_DIRTY_SYNC and/or I_DIRTY_DATASYNC) gets set.  I.e.
+  *                    either I_DIRTY_TIME *or* I_DIRTY_INODE can be set in
+  *                    i_state, but not both.  I_DIRTY_PAGES may still be set.
   * I_NEW              Serves as both a mutex and completion notification.
   *                    New inodes set I_NEW.  If two processes both create
   *                    the same inode, one of them will release its inode and
@@@ -2186,6 -2194,21 +2194,21 @@@ static inline void mark_inode_dirty_syn
        __mark_inode_dirty(inode, I_DIRTY_SYNC);
  }
  
+ /*
+  * Returns true if the given inode itself only has dirty timestamps (its pages
+  * may still be dirty) and isn't currently being allocated or freed.
+  * Filesystems should call this if when writing an inode when lazytime is
+  * enabled, they want to opportunistically write the timestamps of other inodes
+  * located very nearby on-disk, e.g. in the same inode block.  This returns true
+  * if the given inode is in need of such an opportunistic update.  Requires
+  * i_lock, or at least later re-checking under i_lock.
+  */
+ static inline bool inode_is_dirtytime_only(struct inode *inode)
+ {
+       return (inode->i_state & (I_DIRTY_TIME | I_NEW |
+                                 I_FREEING | I_WILL_FREE)) == I_DIRTY_TIME;
+ }
  extern void inc_nlink(struct inode *inode);
  extern void drop_nlink(struct inode *inode);
  extern void clear_nlink(struct inode *inode);
@@@ -3192,6 -3215,11 +3215,6 @@@ extern int generic_file_fsync(struct fi
  
  extern int generic_check_addressable(unsigned, u64);
  
 -#ifdef CONFIG_UNICODE
 -extern int generic_ci_d_hash(const struct dentry *dentry, struct qstr *str);
 -extern int generic_ci_d_compare(const struct dentry *dentry, unsigned int len,
 -                              const char *str, const struct qstr *name);
 -#endif
  extern void generic_set_encrypted_ci_d_ops(struct dentry *dentry);
  
  #ifdef CONFIG_MIGRATION