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 setting I_DIRTY_TIME even if the inode already has I_DIRTY_INODE. Also clear the I_DIRTY_TIME after ->dirty_inode() otherwise it may never get cleared. Signed-off-by: Lukas Czerner <lczerner@xxxxxxxxxx> --- fs/fs-writeback.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index 05221366a16d..174f01e6b912 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -2383,6 +2383,11 @@ void __mark_inode_dirty(struct inode *inode, int flags) /* I_DIRTY_INODE supersedes I_DIRTY_TIME. */ flags &= ~I_DIRTY_TIME; + if (inode->i_state & I_DIRTY_TIME) { + spin_lock(&inode->i_lock); + inode->i_state &= ~I_DIRTY_TIME; + spin_unlock(&inode->i_lock); + } } else { /* * Else it's either I_DIRTY_PAGES, I_DIRTY_TIME, or nothing. @@ -2399,13 +2404,20 @@ 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)) + if (dirtytime && (inode->i_state & I_DIRTY_INODE)) { + /* + * We've got a new lazytime update. Make sure it's recorded in + * i_state, because the time might have already got updated in + * ->dirty_inode() and will not get updated until next + * I_DIRTY_INODE update. + */ + inode->i_state |= I_DIRTY_TIME; goto out_unlock_inode; + } if ((inode->i_state & flags) != flags) { const int was_dirty = inode->i_state & I_DIRTY; -- 2.35.3