fs: record I_DIRTY_TIME even if inode already has I_DIRTY_INODE
authorLukas Czerner <lczerner@redhat.com>
Thu, 25 Aug 2022 10:06:57 +0000 (12:06 +0200)
committerTheodore Ts'o <tytso@mit.edu>
Fri, 30 Sep 2022 03:02:00 +0000 (23:02 -0400)
Currently the I_DIRTY_TIME will never get set if the inode already has
I_DIRTY_INODE with assumption that it supersedes I_DIRTY_TIME.  That's
true, however ext4 will only update the on-disk inode in
->dirty_inode(), not on actual writeback. As a result if the inode
already has I_DIRTY_INODE state by the time we get to
__mark_inode_dirty() only with I_DIRTY_TIME, the time was already filled
into on-disk inode and will not get updated until the next I_DIRTY_INODE
update, which might never come if we crash or get a power failure.

The problem can be reproduced on ext4 by running xfstest generic/622
with -o iversion mount option.

Fix it by allowing I_DIRTY_TIME to be set even if the inode already has
I_DIRTY_INODE. Also make sure that the case is properly handled in
writeback_single_inode() as well. Additionally changes in
xfs_fs_dirty_inode() was made to accommodate for I_DIRTY_TIME in flag.

Thanks Jan Kara for suggestions on how to make this work properly.

Cc: Dave Chinner <david@fromorbit.com>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: stable@kernel.org
Signed-off-by: Lukas Czerner <lczerner@redhat.com>
Suggested-by: Jan Kara <jack@suse.cz>
Reviewed-by: Jan Kara <jack@suse.cz>
Link: https://lore.kernel.org/r/20220825100657.44217-1-lczerner@redhat.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Documentation/filesystems/vfs.rst
fs/fs-writeback.c
fs/xfs/xfs_super.c
include/linux/fs.h

index 6cd6953e175b360c9ee7c70cb0a45d5b2b531516..b2ef2449aed99498839992a9ac98798138c2984b 100644 (file)
@@ -274,6 +274,9 @@ or bottom half).
        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.
+       I_DIRTY_TIME will be set in the flags in case lazytime is enabled
+       and struct inode has times updated since the last ->dirty_inode
+       call.
 
 ``write_inode``
        this method is called when the VFS needs to write an inode to
index 08a1993ab7fd3943f586e2c08ade7c1d5431de63..443f83382b9bdda308451ab285dccec25b4e9edc 100644 (file)
@@ -1718,9 +1718,14 @@ static int writeback_single_inode(struct inode *inode,
         */
        if (!(inode->i_state & I_DIRTY_ALL))
                inode_cgwb_move_to_attached(inode, wb);
-       else if (!(inode->i_state & I_SYNC_QUEUED) &&
-                (inode->i_state & I_DIRTY))
-               redirty_tail_locked(inode, wb);
+       else if (!(inode->i_state & I_SYNC_QUEUED)) {
+               if ((inode->i_state & I_DIRTY))
+                       redirty_tail_locked(inode, wb);
+               else if (inode->i_state & I_DIRTY_TIME) {
+                       inode->dirtied_when = jiffies;
+                       inode_io_list_move_locked(inode, wb, &wb->b_dirty_time);
+               }
+       }
 
        spin_unlock(&wb->list_lock);
        inode_sync_complete(inode);
@@ -2369,6 +2374,20 @@ void __mark_inode_dirty(struct inode *inode, int flags)
        trace_writeback_mark_inode_dirty(inode, flags);
 
        if (flags & I_DIRTY_INODE) {
+               /*
+                * Inode timestamp update will piggback on this dirtying.
+                * We tell ->dirty_inode callback that timestamps need to
+                * be updated by setting I_DIRTY_TIME in flags.
+                */
+               if (inode->i_state & I_DIRTY_TIME) {
+                       spin_lock(&inode->i_lock);
+                       if (inode->i_state & I_DIRTY_TIME) {
+                               inode->i_state &= ~I_DIRTY_TIME;
+                               flags |= I_DIRTY_TIME;
+                       }
+                       spin_unlock(&inode->i_lock);
+               }
+
                /*
                 * Notify the filesystem about the inode being dirtied, so that
                 * (if needed) it can update on-disk fields and journal the
@@ -2378,7 +2397,8 @@ void __mark_inode_dirty(struct inode *inode, int flags)
                 */
                trace_writeback_dirty_inode_start(inode, flags);
                if (sb->s_op->dirty_inode)
-                       sb->s_op->dirty_inode(inode, flags & I_DIRTY_INODE);
+                       sb->s_op->dirty_inode(inode,
+                               flags & (I_DIRTY_INODE | I_DIRTY_TIME));
                trace_writeback_dirty_inode(inode, flags);
 
                /* I_DIRTY_INODE supersedes I_DIRTY_TIME. */
@@ -2399,21 +2419,15 @@ void __mark_inode_dirty(struct inode *inode, int flags)
         */
        smp_mb();
 
-       if (((inode->i_state & flags) == flags) ||
-           (dirtytime && (inode->i_state & I_DIRTY_INODE)))
+       if ((inode->i_state & flags) == flags)
                return;
 
        spin_lock(&inode->i_lock);
-       if (dirtytime && (inode->i_state & I_DIRTY_INODE))
-               goto out_unlock_inode;
        if ((inode->i_state & flags) != flags) {
                const int was_dirty = inode->i_state & I_DIRTY;
 
                inode_attach_wb(inode, NULL);
 
-               /* I_DIRTY_INODE supersedes I_DIRTY_TIME. */
-               if (flags & I_DIRTY_INODE)
-                       inode->i_state &= ~I_DIRTY_TIME;
                inode->i_state |= flags;
 
                /*
@@ -2486,7 +2500,6 @@ void __mark_inode_dirty(struct inode *inode, int flags)
 out_unlock:
        if (wb)
                spin_unlock(&wb->list_lock);
-out_unlock_inode:
        spin_unlock(&inode->i_lock);
 }
 EXPORT_SYMBOL(__mark_inode_dirty);
index 9ac59814bbb6c69a2bffd2c2e129728afc467584..f029c6702dda149c025fcc8231a2fc13ac864df9 100644 (file)
@@ -653,7 +653,7 @@ xfs_fs_destroy_inode(
 static void
 xfs_fs_dirty_inode(
        struct inode                    *inode,
-       int                             flag)
+       int                             flags)
 {
        struct xfs_inode                *ip = XFS_I(inode);
        struct xfs_mount                *mp = ip->i_mount;
@@ -661,7 +661,13 @@ xfs_fs_dirty_inode(
 
        if (!(inode->i_sb->s_flags & SB_LAZYTIME))
                return;
-       if (flag != I_DIRTY_SYNC || !(inode->i_state & I_DIRTY_TIME))
+
+       /*
+        * Only do the timestamp update if the inode is dirty (I_DIRTY_SYNC)
+        * and has dirty timestamp (I_DIRTY_TIME). I_DIRTY_TIME can be passed
+        * in flags possibly together with I_DIRTY_SYNC.
+        */
+       if ((flags & ~I_DIRTY_TIME) != I_DIRTY_SYNC || !(flags & I_DIRTY_TIME))
                return;
 
        if (xfs_trans_alloc(mp, &M_RES(mp)->tr_fsyncts, 0, 0, 0, &tp))
index 9eced4cc286ee18ad996c337be92ba6fc9f57111..56a4b4b02477db4ad75d4b33a2a94aa5aa99b2cb 100644 (file)
@@ -2371,13 +2371,14 @@ static inline void kiocb_clone(struct kiocb *kiocb, struct kiocb *kiocb_src,
  *                     don't have to write inode on fdatasync() when only
  *                     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
+ * I_DIRTY_TIME                The inode itself 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_DIRTY_SYNC and/or I_DIRTY_DATASYNC) gets set. But
+ *                     I_DIRTY_TIME can still be set if I_DIRTY_SYNC is already
+ *                     in place because writeback might already be in progress
+ *                     and we don't want to lose the time update
  * 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