* [PATCH v2] ntfs: handle a failed size rollback in the write error path
@ 2026-10-19 6:49 Hongling Zeng
2026-10-20 2:54 ` Hyunchul Lee
0 siblings, 1 reply; 3+ messages in thread
From: Hongling Zeng @ 2026-10-19 6:49 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, zhongling0719, Hongling Zeng
ntfs_file_write_iter() extends the data and initialized sizes before
it copies the user data and restores them when the write then fails.
The restoration is best-effort: both results are discarded, even
though the extension is already recorded in the dirty MFT record.
When the restoration fails too, the extended sizes reach the disk
while the VFS inode keeps reporting the old ones: after a remount the
file appears grown again, with an uninitialized tail where the failed
write never put any data, and nothing records the failure.
Keep both undo results: on failure, set NVolSetErrors(), which the next
persistence point persists as VOLUME_IS_DIRTY, and log a message for
the errors= policy.
Fixes: 9c87959601e8 ("ntfs: update file operations")
Suggested-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn>
---
Changes in v2:
- do not roll the initialized size back on a resident data attribute,
where ntfs_attr_set_initialized_size() always fails with -EINVAL and
the data size rollback restores both coupled sizes.
---
fs/ntfs/file.c | 46 +++++++++++++++++++++++++++++++++++++---------
1 file changed, 37 insertions(+), 9 deletions(-)
diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
index d810ca5904bd..597fd70ed30b 100644
--- a/fs/ntfs/file.c
+++ b/fs/ntfs/file.c
@@ -934,22 +934,50 @@ ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
&ntfs_iomap_folio_ops, NULL);
out:
if (ret < 0 && ret != -EIOCBQUEUED) {
- if (ni->initialized_size != old_init_size) {
- struct ntfs_inode *mrec_ni =
- ntfs_base_inode(ni);
+ int undo_err = 0;
+ int init_err = 0;
+
+ /* ntfs_attr_set_initialized_size() rejects a resident one. */
+ if (ni->initialized_size != old_init_size &&
+ NInoNonResident(ni)) {
+ struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
mutex_lock(&mrec_ni->mrec_lock);
- ntfs_attr_set_initialized_size(ni, old_init_size);
+ init_err = ntfs_attr_set_initialized_size(ni,
+ old_init_size);
mutex_unlock(&mrec_ni->mrec_lock);
+ if (init_err && !undo_err)
+ undo_err = init_err;
}
if (ni->data_size != old_data_size) {
- struct ntfs_inode *mrec_ni =
- ntfs_base_inode(ni);
+ struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
+ int data_err = 0;
truncate_setsize(vi, old_data_size);
- mutex_lock(&mrec_ni->mrec_lock);
- ntfs_attr_truncate(ni, old_data_size);
- mutex_unlock(&mrec_ni->mrec_lock);
+ /*
+ * ntfs_attr_truncate() rejects a compressed one.
+ * Skip it also after a failed initialized size
+ * rollback: the shrunk data size would stay below
+ * the initialized size, which the loader rejects.
+ */
+ if (!NInoCompressed(ni) && !init_err) {
+ mutex_lock(&mrec_ni->mrec_lock);
+ data_err = ntfs_attr_truncate(ni,
+ old_data_size);
+ mutex_unlock(&mrec_ni->mrec_lock);
+ }
+ if (data_err && !undo_err)
+ undo_err = data_err;
+ }
+ if (ni->data_size != old_data_size && NInoCompressed(ni))
+ ntfs_warning(vi->i_sb,
+ "cannot roll back the compressed data size");
+ if (undo_err) {
+ /* The new sizes stay in the dirty MFT record. */
+ NVolSetErrors(vol);
+ ntfs_error(vi->i_sb,
+ "Failed to roll back the size update: %d",
+ undo_err);
}
}
out_lock:
--
2.25.1
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] ntfs: handle a failed size rollback in the write error path
2026-10-19 6:49 [PATCH v2] ntfs: handle a failed size rollback in the write error path Hongling Zeng
@ 2026-10-20 2:54 ` Hyunchul Lee
2026-10-20 3:22 ` Hongling Zeng
0 siblings, 1 reply; 3+ messages in thread
From: Hyunchul Lee @ 2026-10-20 2:54 UTC (permalink / raw)
To: Hongling Zeng; +Cc: linkinjeon, ntfs, linux-kernel, zhongling0719
Hi Hongling,
On Mon, Oct 19, 2026 at 02:49:18PM +0800, Hongling Zeng wrote:
> ntfs_file_write_iter() extends the data and initialized sizes before
> it copies the user data and restores them when the write then fails.
> The restoration is best-effort: both results are discarded, even
> though the extension is already recorded in the dirty MFT record.
> When the restoration fails too, the extended sizes reach the disk
> while the VFS inode keeps reporting the old ones: after a remount the
> file appears grown again, with an uninitialized tail where the failed
> write never put any data, and nothing records the failure.
>
> Keep both undo results: on failure, set NVolSetErrors(), which the next
> persistence point persists as VOLUME_IS_DIRTY, and log a message for
> the errors= policy.
>
> Fixes: 9c87959601e8 ("ntfs: update file operations")
> Suggested-by: Hyunchul Lee <hyc.lee@gmail.com>
> Signed-off-by: Hongling Zeng <zenghongling@kylinos.cn>
> ---
> Changes in v2:
> - do not roll the initialized size back on a resident data attribute,
> where ntfs_attr_set_initialized_size() always fails with -EINVAL and
> the data size rollback restores both coupled sizes.
> ---
> fs/ntfs/file.c | 46 +++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 37 insertions(+), 9 deletions(-)
>
> diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
> index d810ca5904bd..597fd70ed30b 100644
> --- a/fs/ntfs/file.c
> +++ b/fs/ntfs/file.c
> @@ -934,22 +934,50 @@ ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
> &ntfs_iomap_folio_ops, NULL);
> out:
> if (ret < 0 && ret != -EIOCBQUEUED) {
> - if (ni->initialized_size != old_init_size) {
> - struct ntfs_inode *mrec_ni =
> - ntfs_base_inode(ni);
> + int undo_err = 0;
> + int init_err = 0;
> +
> + /* ntfs_attr_set_initialized_size() rejects a resident one. */
> + if (ni->initialized_size != old_init_size &&
> + NInoNonResident(ni)) {
> + struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
>
> mutex_lock(&mrec_ni->mrec_lock);
> - ntfs_attr_set_initialized_size(ni, old_init_size);
> + init_err = ntfs_attr_set_initialized_size(ni,
> + old_init_size);
> mutex_unlock(&mrec_ni->mrec_lock);
> + if (init_err && !undo_err)
> + undo_err = init_err;
> }
> if (ni->data_size != old_data_size) {
> - struct ntfs_inode *mrec_ni =
> - ntfs_base_inode(ni);
> + struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
> + int data_err = 0;
>
> truncate_setsize(vi, old_data_size);
> - mutex_lock(&mrec_ni->mrec_lock);
> - ntfs_attr_truncate(ni, old_data_size);
> - mutex_unlock(&mrec_ni->mrec_lock);
> + /*
> + * ntfs_attr_truncate() rejects a compressed one.
> + * Skip it also after a failed initialized size
> + * rollback: the shrunk data size would stay below
> + * the initialized size, which the loader rejects.
> + */
> + if (!NInoCompressed(ni) && !init_err) {
> + mutex_lock(&mrec_ni->mrec_lock);
> + data_err = ntfs_attr_truncate(ni,
> + old_data_size);
> + mutex_unlock(&mrec_ni->mrec_lock);
> + }
> + if (data_err && !undo_err)
> + undo_err = data_err;
> + }
> + if (ni->data_size != old_data_size && NInoCompressed(ni))
> + ntfs_warning(vi->i_sb,
For compressed-file, the code only emits a warning, which causes the
VOLUME_IS_DIRTY persistence to be bypassed even though the MFT data size
remains expanded.
I think that we should report the compressed rollback as an error.
--
Thanks,
Hyunchul
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] ntfs: handle a failed size rollback in the write error path
2026-10-20 2:54 ` Hyunchul Lee
@ 2026-10-20 3:22 ` Hongling Zeng
0 siblings, 0 replies; 3+ messages in thread
From: Hongling Zeng @ 2026-10-20 3:22 UTC (permalink / raw)
To: Hyunchul Lee, Hongling Zeng; +Cc: linkinjeon, ntfs, linux-kernel
在 2026年10月20日 10:54, Hyunchul Lee 写道:
> Hi Hongling,
>
> On Mon, Oct 19, 2026 at 02:49:18PM +0800, Hongling Zeng wrote:
>> >ntfs_file_write_iter() extends the data and initialized sizes before
>> >it copies the user data and restores them when the write then fails.
>> >The restoration is best-effort: both results are discarded, even
>> >though the extension is already recorded in the dirty MFT record.
>> >When the restoration fails too, the extended sizes reach the disk
>> >while the VFS inode keeps reporting the old ones: after a remount the
>> >file appears grown again, with an uninitialized tail where the failed
>> >write never put any data, and nothing records the failure.
>> >
>> >Keep both undo results: on failure, set NVolSetErrors(), which the next
>> >persistence point persists as VOLUME_IS_DIRTY, and log a message for
>> >the errors= policy.
>> >
>> >Fixes: 9c87959601e8 ("ntfs: update file operations")
>> >Suggested-by: Hyunchul Lee<hyc.lee@gmail.com>
>> >Signed-off-by: Hongling Zeng<zenghongling@kylinos.cn>
>> >---
>> >Changes in v2:
>> >- do not roll the initialized size back on a resident data attribute,
>> > where ntfs_attr_set_initialized_size() always fails with -EINVAL and
>> > the data size rollback restores both coupled sizes.
>> >---
>> > fs/ntfs/file.c | 46 +++++++++++++++++++++++++++++++++++++---------
>> > 1 file changed, 37 insertions(+), 9 deletions(-)
>> >
>> >diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
>> >index d810ca5904bd..597fd70ed30b 100644
>> >--- a/fs/ntfs/file.c
>> >+++ b/fs/ntfs/file.c
>> >@@ -934,22 +934,50 @@ ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
>> > &ntfs_iomap_folio_ops, NULL);
>> > out:
>> > if (ret < 0 && ret != -EIOCBQUEUED) {
>> >- if (ni->initialized_size != old_init_size) {
>> >- struct ntfs_inode *mrec_ni =
>> >- ntfs_base_inode(ni);
>> >+ int undo_err = 0;
>> >+ int init_err = 0;
>> >+
>> >+ /* ntfs_attr_set_initialized_size() rejects a resident one. */
>> >+ if (ni->initialized_size != old_init_size &&
>> >+ NInoNonResident(ni)) {
>> >+ struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
>> >
>> > mutex_lock(&mrec_ni->mrec_lock);
>> >- ntfs_attr_set_initialized_size(ni, old_init_size);
>> >+ init_err = ntfs_attr_set_initialized_size(ni,
>> >+ old_init_size);
>> > mutex_unlock(&mrec_ni->mrec_lock);
>> >+ if (init_err && !undo_err)
>> >+ undo_err = init_err;
>> > }
>> > if (ni->data_size != old_data_size) {
>> >- struct ntfs_inode *mrec_ni =
>> >- ntfs_base_inode(ni);
>> >+ struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
>> >+ int data_err = 0;
>> >
>> > truncate_setsize(vi, old_data_size);
>> >- mutex_lock(&mrec_ni->mrec_lock);
>> >- ntfs_attr_truncate(ni, old_data_size);
>> >- mutex_unlock(&mrec_ni->mrec_lock);
>> >+ /*
>> >+ * ntfs_attr_truncate() rejects a compressed one.
>> >+ * Skip it also after a failed initialized size
>> >+ * rollback: the shrunk data size would stay below
>> >+ * the initialized size, which the loader rejects.
>> >+ */
>> >+ if (!NInoCompressed(ni) && !init_err) {
>> >+ mutex_lock(&mrec_ni->mrec_lock);
>> >+ data_err = ntfs_attr_truncate(ni,
>> >+ old_data_size);
>> >+ mutex_unlock(&mrec_ni->mrec_lock);
>> >+ }
>> >+ if (data_err && !undo_err)
>> >+ undo_err = data_err;
>> >+ }
>> >+ if (ni->data_size != old_data_size && NInoCompressed(ni))
>> >+ ntfs_warning(vi->i_sb,
> For compressed-file, the code only emits a warning, which causes the
> VOLUME_IS_DIRTY persistence to be bypassed even though the MFT data size
> remains expanded.
>
> I think that we should report the compressed rollback as an error.
>
> -- Thanks, Hyunchul
Hi Hyunchul,
You are right, the warning-only path left the expanded data size with
no error state. v3 drops the skip: the data size rollback now goes
through __ntfs_attr_truncate_vfs(), the same entry ntfs_setattr_size()
uses, which also supports compressed attributes. The compressed
rollback is performed rather than skipped, and a failure of either
undo sets NVolSetErrors() and calls ntfs_error().
Thanks,
Hongling
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-20 3:24 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-19 6:49 [PATCH v2] ntfs: handle a failed size rollback in the write error path Hongling Zeng
2026-10-20 2:54 ` Hyunchul Lee
2026-10-20 3:22 ` Hongling Zeng
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®