diff --git a/file.c b/file.c index ad6838a6eb94..c03f4267f030 100644 --- a/file.c +++ b/file.c @@ -155,12 +155,17 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) if (ei->type == TYPE_FILE) ei->attr |= ATTR_ARCHIVE; - /* update the directory entry */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - inode->i_mtime = current_time(inode); -#else - inode->i_mtime = CURRENT_TIME_SEC; -#endif + /* + * update the directory entry + * + * If the directory entry is updated by mark_inode_dirty(), the + * directory entry will be written after a writeback cycle of + * updating the bitmap/FAT, which may result in clusters being + * freed but referenced by the directory entry in the event of a + * sudden power failure. + * __exfat_write_inode() is called for directory entry, bitmap + * and FAT to be written in a same writeback. + */ if (__exfat_write_inode(inode, inode_needs_sync(inode))) return -EIO; @@ -217,16 +222,6 @@ void exfat_truncate(struct inode *inode, loff_t size) if (err) goto write_size; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - inode->i_ctime = inode->i_mtime = current_time(inode); -#else - inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC; -#endif - if (IS_DIRSYNC(inode)) - exfat_sync_inode(inode); - else - mark_inode_dirty(inode); - inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; write_size: @@ -342,16 +337,12 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) attr->ia_valid &= ~ATTR_MODE; } - if (attr->ia_valid & ATTR_SIZE) { - error = exfat_block_truncate_page(inode, attr->ia_size); - if (error) - goto out; - - down_write(&EXFAT_I(inode)->truncate_lock); - truncate_setsize(inode, attr->ia_size); - exfat_truncate(inode, attr->ia_size); - up_write(&EXFAT_I(inode)->truncate_lock); - } + if (attr->ia_valid & ATTR_SIZE) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) + inode->i_mtime = inode->i_ctime = current_time(inode); +#else + inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; +#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) setattr_copy(&init_user_ns, inode, attr); @@ -359,8 +350,22 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) setattr_copy(inode, attr); #endif exfat_truncate_atime(&inode->i_atime); - mark_inode_dirty(inode); + if (attr->ia_valid & ATTR_SIZE) { + error = exfat_block_truncate_page(inode, attr->ia_size); + if (error) + goto out; + + down_write(&EXFAT_I(inode)->truncate_lock); + truncate_setsize(inode, attr->ia_size); + /* + * __exfat_write_inode() is called from exfat_truncate(), inode + * is already written by it, so mark_inode_dirty() is unneeded. + */ + exfat_truncate(inode, attr->ia_size); + up_write(&EXFAT_I(inode)->truncate_lock); + } else + mark_inode_dirty(inode); out: return error; } diff --git a/inode.c b/inode.c index 3c68a8930ad9..8cbc49835902 100644 --- a/inode.c +++ b/inode.c @@ -379,6 +379,11 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) if (to > i_size_read(inode)) { truncate_pagecache(inode, i_size_read(inode)); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) + inode->i_mtime = inode->i_ctime = current_time(inode); +#else + inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; +#endif exfat_truncate(inode, EXFAT_I(inode)->i_size_aligned); } }