* [PATCH] ntfs: remove empty EA attribute pair
2026-07-16 2:46 [PATCH] ntfs: validate final EA attribute size Namjae Jeon
@ 2026-07-16 2:46 ` Namjae Jeon
2026-07-16 2:46 ` [PATCH] ntfs: rewrite EA stream before updating metadata Namjae Jeon
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Namjae Jeon @ 2026-07-16 2:46 UTC (permalink / raw)
To: hyc.lee; +Cc: ntfs, linux-fsdevel, Namjae Jeon
Removing the final xattr leaves an empty $EA stream. An empty $EA
attribute paired with $EA_INFORMATION is not a valid EA chain and
ntfsck reports it as corrupt.
Remove both attributes when the final EA entry is deleted.
Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/ntfs/ea.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
index 0eba3f41c7bb..88bfd6560692 100644
--- a/fs/ntfs/ea.c
+++ b/fs/ntfs/ea.c
@@ -275,6 +275,15 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
ea_info_qsize -= ea_size;
p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize);
+ if ((flags & XATTR_REPLACE) && !val_size && !ea_info_qsize) {
+ err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0);
+ if (err)
+ goto out;
+
+ err = ntfs_attr_remove(ni, AT_EA_INFORMATION, AT_UNNAMED, 0);
+ goto out;
+ }
+
err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
sizeof(struct ea_information), false);
if (err)
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH] ntfs: rewrite EA stream before updating metadata
2026-07-16 2:46 [PATCH] ntfs: validate final EA attribute size Namjae Jeon
2026-07-16 2:46 ` [PATCH] ntfs: remove empty EA attribute pair Namjae Jeon
@ 2026-07-16 2:46 ` Namjae Jeon
2026-07-16 2:46 ` [PATCH] ntfs: fix kmap_local_page() usage in compress Namjae Jeon
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Namjae Jeon @ 2026-07-16 2:46 UTC (permalink / raw)
To: hyc.lee; +Cc: ntfs, linux-fsdevel, Namjae Jeon
Updating an EA removes the old record and appends its replacement.
Build the complete $EA stream in memory and rewrite it from offset zero,
rather than committing a compacted stream followed by a separate append.
generic/642 shows that the append path can leave an invalid record layout
on disk, including when a new EA entry is added.
When removing an EA, write the compacted stream before updating
$EA_INFORMATION and restore the original pair if the metadata update
fails.
When the final EA entry is removed the $EA/$EA_INFORMATION pair is torn
down. If removing $EA_INFORMATION fails after $EA has already been
removed, the original $EA is restored so the two attributes stay
consistent.
Fixes: fc053f05ca28 ("ntfs: add reparse and ea operations")
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/ntfs/ea.c | 65 +++++++++++++++++++++++++++++++++++++---------------
1 file changed, 46 insertions(+), 19 deletions(-)
diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
index 88bfd6560692..d0044c61152a 100644
--- a/fs/ntfs/ea.c
+++ b/fs/ntfs/ea.c
@@ -196,6 +196,9 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
struct ea_attr *p_ea;
u32 ea_info_qsize = 0;
char *ea_buf = NULL;
+ char *new_ea_buf;
+ char *old_ea_buf = NULL;
+ struct ea_information old_ea_info;
size_t new_ea_size = ALIGN(struct_size(p_ea, ea_name, 1 + name_len + val_size), 4);
s64 ea_off, ea_info_size, all_ea_size, ea_size;
@@ -249,6 +252,14 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
err = -EEXIST;
goto out;
}
+ if ((flags & XATTR_REPLACE) && !val_size) {
+ old_ea_info = *p_ea_info;
+ old_ea_buf = kvmemdup(ea_buf, all_ea_size, GFP_NOFS);
+ if (!old_ea_buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+ }
/* Check the final $EA size before removing the old entry. */
if (val_size &&
@@ -281,20 +292,33 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
goto out;
err = ntfs_attr_remove(ni, AT_EA_INFORMATION, AT_UNNAMED, 0);
+ if (err) {
+ /* Restore the original $EA if $EA_INFORMATION removal failed. */
+ ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, old_ea_buf,
+ all_ea_size);
+ ea_info_qsize = le32_to_cpu(old_ea_info.ea_query_length);
+ }
goto out;
}
- err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
- sizeof(struct ea_information), false);
- if (err)
- goto out;
-
- err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, true);
- if (err)
- goto out;
-
if ((flags & XATTR_REPLACE) && !val_size) {
- /* Remove xattr. */
+ err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize,
+ true);
+ if (err) {
+ ntfs_write_ea(ni, AT_EA, old_ea_buf, 0,
+ all_ea_size, false);
+ goto out;
+ }
+
+ err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info,
+ 0, sizeof(struct ea_information), false);
+ if (err) {
+ ntfs_write_ea(ni, AT_EA, old_ea_buf, 0,
+ all_ea_size, false);
+ ntfs_write_ea(ni, AT_EA_INFORMATION,
+ (char *)&old_ea_info, 0,
+ sizeof(old_ea_info), false);
+ }
goto out;
}
} else {
@@ -309,21 +333,23 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
goto out;
}
}
- kvfree(ea_buf);
-
alloc_new_ea:
- ea_buf = kzalloc(new_ea_size, GFP_NOFS);
- if (!ea_buf) {
+ new_ea_buf = kvzalloc(ea_info_qsize + new_ea_size, GFP_NOFS);
+ if (!new_ea_buf) {
err = -ENOMEM;
goto out;
}
+ if (ea_info_qsize)
+ memcpy(new_ea_buf, ea_buf, ea_info_qsize);
+ kvfree(ea_buf);
+ ea_buf = new_ea_buf;
+ p_ea = (struct ea_attr *)(ea_buf + ea_info_qsize);
/*
* EA and REPARSE_POINT compatibility not checked any more,
* required by Windows 10, but having both may lead to
* problems with earlier versions.
*/
- p_ea = (struct ea_attr *)ea_buf;
memcpy(p_ea->ea_name, name, name_len);
p_ea->ea_name_length = name_len;
p_ea->ea_name[name_len] = 0;
@@ -344,13 +370,13 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
* no EA or EA_INFORMATION : add them
*/
if (!ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) {
- err = ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, (char *)p_ea,
- new_ea_size);
+ err = ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, ea_buf,
+ ea_info_qsize + new_ea_size);
if (err)
goto out;
} else {
- err = ntfs_write_ea(ni, AT_EA, (char *)p_ea, ea_info_qsize,
- new_ea_size, false);
+ err = ntfs_write_ea(ni, AT_EA, ea_buf, 0,
+ ea_info_qsize + new_ea_size, true);
if (err)
goto out;
}
@@ -370,6 +396,7 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
NInoClearHasEA(ni);
kvfree(ea_buf);
+ kvfree(old_ea_buf);
kvfree(p_ea_info);
return err;
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH] ntfs: fix kmap_local_page() usage in compress
2026-07-16 2:46 [PATCH] ntfs: validate final EA attribute size Namjae Jeon
2026-07-16 2:46 ` [PATCH] ntfs: remove empty EA attribute pair Namjae Jeon
2026-07-16 2:46 ` [PATCH] ntfs: rewrite EA stream before updating metadata Namjae Jeon
@ 2026-07-16 2:46 ` Namjae Jeon
2026-07-16 2:46 ` [PATCH] ntfs: file extension before write submission Namjae Jeon
2026-07-16 2:46 ` [PATCH] ntfs: use pagecache_isize_extended() on size extension Namjae Jeon
4 siblings, 0 replies; 6+ messages in thread
From: Namjae Jeon @ 2026-07-16 2:46 UTC (permalink / raw)
To: hyc.lee; +Cc: ntfs, linux-fsdevel, Namjae Jeon, Matthew Wilcox
Several compressed I/O paths discard the address returned by
kmap_local_page() and later access or unmap the page using page_address().
This is invalid for highmem pages, and local mappings must also be unmapped
using the address returned by kmap_local_page().
Map each destination page in ntfs_decompress() only while producing the
current sub-block. Use memcpy_from_page(), memcpy_to_page(), and
memzero_page() for the other page accesses. Remove unnecessary local
mappings from ntfs_write_cb(), where pages are accessed through the vmap()
mapping.
Fixes: 495e90fa3348 ("ntfs: update attrib operations")
Reported-by: Matthew Wilcox <willy@infradead.org>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/ntfs/compress.c | 38 +++++++++++++++-----------------------
1 file changed, 15 insertions(+), 23 deletions(-)
diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c
index d83d3e8e06ae..e866f43ca30b 100644
--- a/fs/ntfs/compress.c
+++ b/fs/ntfs/compress.c
@@ -177,6 +177,7 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[],
/* Variables for uncompressed data / destination. */
struct page *dp; /* Current destination page being worked on. */
+ u8 *dp_kaddr; /* Local kmap for the current destination page. */
u8 *dp_addr; /* Current pointer into dp. */
u8 *dp_sb_start; /* Start of current sub-block in dp. */
u8 *dp_sb_end; /* End of current sb in dp (dp_sb_start + NTFS_SB_SIZE). */
@@ -191,6 +192,7 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[],
/* Default error code. */
int err = -EOVERFLOW;
+ dp_kaddr = NULL;
ntfs_debug("Entering, cb_size = 0x%x.", cb_size);
do_next_sb:
ntfs_debug("Beginning sub-block at offset = 0x%zx in the cb.",
@@ -223,7 +225,6 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[],
*/
handle_bounds_compressed_page(dp, i_size,
initialized_size);
- kunmap_local(page_address(dp));
SetPageUptodate(dp);
unlock_page(dp);
if (di == xpage)
@@ -269,7 +270,8 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[],
}
/* We have a valid destination page. Setup the destination pointers. */
- dp_addr = (u8 *)page_address(dp) + do_sb_start;
+ dp_kaddr = kmap_local_page(dp);
+ dp_addr = dp_kaddr + do_sb_start;
/* Now, we are ready to process the current sub-block (sb). */
if (!(le16_to_cpup((__le16 *)cb) & NTFS_SB_IS_COMPRESSED)) {
@@ -290,6 +292,8 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[],
/* Advance destination position to next sub-block. */
*dest_ofs += NTFS_SB_SIZE;
*dest_ofs &= ~PAGE_MASK;
+ kunmap_local(dp_kaddr);
+ dp_kaddr = NULL;
if (!(*dest_ofs)) {
finalize_page:
/*
@@ -324,6 +328,8 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[],
}
/* We have finished the current sub-block. */
*dest_ofs &= ~PAGE_MASK;
+ kunmap_local(dp_kaddr);
+ dp_kaddr = NULL;
if (!(*dest_ofs))
goto finalize_page;
goto do_next_sb;
@@ -429,6 +435,8 @@ static int ntfs_decompress(struct page *dest_pages[], int completed_pages[],
goto do_next_tag;
return_overflow:
+ if (dp_kaddr)
+ kunmap_local(dp_kaddr);
ntfs_error(NULL, "Failed. Returning -EOVERFLOW.");
goto return_error;
}
@@ -557,7 +565,6 @@ int ntfs_read_compressed_block(struct folio *folio)
* least wasting our time.
*/
if (!PageDirty(page) && (!PageUptodate(page))) {
- kmap_local_page(page);
continue;
}
unlock_page(page);
@@ -643,8 +650,7 @@ int ntfs_read_compressed_block(struct folio *folio)
}
lock_page(lpage);
- memcpy(cb_pos, page_address(lpage) + page_ofs,
- vol->cluster_size);
+ memcpy_from_page(cb_pos, lpage, page_ofs, vol->cluster_size);
unlock_page(lpage);
put_page(lpage);
cb_pos += vol->cluster_size;
@@ -683,14 +689,7 @@ int ntfs_read_compressed_block(struct folio *folio)
for (; cur_page < cb_max_page; cur_page++) {
page = pages[cur_page];
if (page) {
- if (likely(!cur_ofs))
- clear_page(page_address(page));
- else
- memset(page_address(page) + cur_ofs, 0,
- PAGE_SIZE -
- cur_ofs);
- flush_dcache_page(page);
- kunmap_local(page_address(page));
+ memzero_page(page, cur_ofs, PAGE_SIZE - cur_ofs);
SetPageUptodate(page);
unlock_page(page);
if (cur_page == xpage)
@@ -708,8 +707,7 @@ int ntfs_read_compressed_block(struct folio *folio)
if (cb_max_ofs && cb_pos < cb_end) {
page = pages[cur_page];
if (page)
- memset(page_address(page) + cur_ofs, 0,
- cb_max_ofs - cur_ofs);
+ memzero_page(page, cur_ofs, cb_max_ofs - cur_ofs);
/*
* No need to update cb_pos at this stage:
* cb_pos += cb_max_ofs - cur_ofs;
@@ -730,7 +728,7 @@ int ntfs_read_compressed_block(struct folio *folio)
for (; cur_page < cb_max_page; cur_page++) {
page = pages[cur_page];
if (page)
- memcpy(page_address(page) + cur_ofs, cb_pos,
+ memcpy_to_page(page, cur_ofs, cb_pos,
PAGE_SIZE - cur_ofs);
cb_pos += PAGE_SIZE - cur_ofs;
cur_ofs = 0;
@@ -741,7 +739,7 @@ int ntfs_read_compressed_block(struct folio *folio)
if (cb_max_ofs && cb_pos < cb_end) {
page = pages[cur_page];
if (page)
- memcpy(page_address(page) + cur_ofs, cb_pos,
+ memcpy_to_page(page, cur_ofs, cb_pos,
cb_max_ofs - cur_ofs);
cb_pos += cb_max_ofs - cur_ofs;
cur_ofs = cb_max_ofs;
@@ -758,7 +756,6 @@ int ntfs_read_compressed_block(struct folio *folio)
*/
handle_bounds_compressed_page(page, i_size,
initialized_size);
- kunmap_local(page_address(page));
SetPageUptodate(page);
unlock_page(page);
if (cur2_page == xpage)
@@ -794,7 +791,6 @@ int ntfs_read_compressed_block(struct folio *folio)
page = pages[prev_cur_page];
if (page) {
flush_dcache_page(page);
- kunmap_local(page_address(page));
unlock_page(page);
if (prev_cur_page != xpage)
put_page(page);
@@ -818,7 +814,6 @@ int ntfs_read_compressed_block(struct folio *folio)
"Still have pages left! Terminating them with extreme prejudice. Inode 0x%llx, page index 0x%lx.",
ni->mft_no, folio->index);
flush_dcache_folio(folio);
- kunmap_local(page_address(page));
folio_unlock(folio);
if (cur_page != xpage)
folio_put(folio);
@@ -856,7 +851,6 @@ int ntfs_read_compressed_block(struct folio *folio)
page = pages[i];
if (page) {
flush_dcache_page(page);
- kunmap_local(page_address(page));
unlock_page(page);
if (i != xpage)
put_page(page);
@@ -1308,7 +1302,6 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
}
pages_disk[i] = pg;
lock_page(pg);
- kmap_local_page(pg);
}
outbuf = vmap(pages_disk, pages_count, VM_MAP, PAGE_KERNEL);
@@ -1443,7 +1436,6 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
for (i = 0; i < pages_count; i++) {
pg = pages_disk[i];
if (pg) {
- kunmap_local(page_address(pg));
unlock_page(pg);
put_page(pg);
}
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH] ntfs: file extension before write submission
2026-07-16 2:46 [PATCH] ntfs: validate final EA attribute size Namjae Jeon
` (2 preceding siblings ...)
2026-07-16 2:46 ` [PATCH] ntfs: fix kmap_local_page() usage in compress Namjae Jeon
@ 2026-07-16 2:46 ` Namjae Jeon
2026-07-16 2:46 ` [PATCH] ntfs: use pagecache_isize_extended() on size extension Namjae Jeon
4 siblings, 0 replies; 6+ messages in thread
From: Namjae Jeon @ 2026-07-16 2:46 UTC (permalink / raw)
To: hyc.lee; +Cc: ntfs, linux-fsdevel, Namjae Jeon
Prepare non-resident file allocation and initialized-size extension in
->write_iter() before entering the buffered or direct iomap write paths.
Previously, the iomap write callback extended initialized_size. When a
direct write started beyond initialized_size,
ntfs_extend_initialized_size() used iomap_zero_range() to zero the gap
through the page cache. This created dirty folios after iomap DIO had
invalidated its target cache range. The bsync path then had to
synchronously write back the entire zeroed gap to prevent the post-DIO
invalidation from encountering a dirty boundary folio.
Move allocation and initialized-size preparation ahead of iomap submission.
For DIO, kiocb_invalidate_pages() now sees any dirty boundary folio created
by iomap_zero_range(), writes it back when necessary, and invalidates it
before the direct I/O is issued. This removes the explicit synchronous
writeback of the zeroed gap while preserving the required boundary-folio
ordering.
Keep compressed writes out of the early initialized-size extension so their
existing write path can zero uninitialized data before compression. Move
compressed-file allocation expansion to write_iter as well, eliminating the
now-redundant expansion from ntfs_compress_write().
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/ntfs/compress.c | 10 ----------
fs/ntfs/file.c | 43 +++++++++++++++++++++++++++++++++++++++++--
fs/ntfs/inode.c | 6 +-----
fs/ntfs/inode.h | 2 +-
fs/ntfs/iomap.c | 34 +---------------------------------
5 files changed, 44 insertions(+), 51 deletions(-)
diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c
index e866f43ca30b..fe1877b86f49 100644
--- a/fs/ntfs/compress.c
+++ b/fs/ntfs/compress.c
@@ -1459,16 +1459,6 @@ int ntfs_compress_write(struct ntfs_inode *ni, loff_t pos, size_t count,
size_t written = 0;
struct address_space *mapping = VFS_I(ni)->i_mapping;
- if (NInoCompressed(ni) && pos + count > ni->allocated_size) {
- int err;
- loff_t end = pos + count;
-
- err = ntfs_attr_expand(ni, end,
- round_up(end, ni->itype.compressed.block_size));
- if (err)
- return err;
- }
-
pages = kmalloc_array(pages_per_cb, sizeof(struct page *), GFP_NOFS);
if (!pages)
return -ENOMEM;
diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
index 6a7b638e523d..9061f8f77f7e 100644
--- a/fs/ntfs/file.c
+++ b/fs/ntfs/file.c
@@ -535,6 +535,31 @@ static ssize_t ntfs_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
return ret;
}
+static int ntfs_expand_for_write(struct ntfs_inode *ni, loff_t end)
+{
+ struct ntfs_volume *vol = ni->vol;
+ loff_t prealloc_size = 0;
+ int err;
+
+ if (end <= ni->data_size)
+ return 0;
+
+ if (NInoCompressed(ni)) {
+ if (end > ni->allocated_size)
+ prealloc_size = round_up(end,
+ ni->itype.compressed.block_size);
+ } else if (end > ni->allocated_size &&
+ end < ni->allocated_size + vol->preallocated_size) {
+ prealloc_size = ni->allocated_size + vol->preallocated_size;
+ }
+
+ mutex_lock(&ni->mrec_lock);
+ err = ntfs_attr_expand(ni, end, prealloc_size);
+ mutex_unlock(&ni->mrec_lock);
+
+ return err;
+}
+
static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct file *file = iocb->ki_filp;
@@ -543,7 +568,7 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
struct ntfs_volume *vol = ni->vol;
ssize_t ret;
ssize_t count;
- loff_t pos;
+ loff_t pos, end;
int err;
loff_t old_data_size, old_init_size;
@@ -580,10 +605,24 @@ static ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
pos = iocb->ki_pos;
count = ret;
+ end = pos + count;
old_data_size = ni->data_size;
old_init_size = ni->initialized_size;
+ if (end > old_data_size) {
+ ret = ntfs_expand_for_write(ni, end);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (NInoNonResident(ni) && !NInoCompressed(ni) &&
+ end > old_init_size) {
+ ret = ntfs_extend_initialized_size(vi, pos, end);
+ if (ret < 0)
+ goto out;
+ }
+
if (NInoNonResident(ni) && NInoCompressed(ni)) {
ret = ntfs_compress_write(ni, pos, count, from);
if (ret > 0)
@@ -655,7 +694,7 @@ static int ntfs_file_mmap_prepare(struct vm_area_desc *desc)
from + desc->end - desc->start);
if (NTFS_I(inode)->initialized_size < to) {
- err = ntfs_extend_initialized_size(inode, to, to, false);
+ err = ntfs_extend_initialized_size(inode, to, to);
if (err)
return err;
}
diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 7381a18cfadd..50e244aa372e 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -2401,7 +2401,7 @@ int ntfs_show_options(struct seq_file *sf, struct dentry *root)
}
int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset,
- const loff_t new_size, bool bsync)
+ const loff_t new_size)
{
struct ntfs_inode *ni = NTFS_I(vi);
loff_t old_init_size;
@@ -2428,10 +2428,6 @@ int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset,
&ntfs_iomap_folio_ops, NULL);
if (err)
return err;
- if (bsync)
- err = filemap_write_and_wait_range(vi->i_mapping,
- old_init_size,
- offset - 1);
}
diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h
index 9aacd5787ffe..c6d065aaecd5 100644
--- a/fs/ntfs/inode.h
+++ b/fs/ntfs/inode.h
@@ -352,7 +352,7 @@ static inline void ntfs_commit_inode(struct inode *vi)
int ntfs_inode_sync_filename(struct ntfs_inode *ni);
int ntfs_extend_initialized_size(struct inode *vi, const loff_t offset,
- const loff_t new_size, bool bsync);
+ const loff_t new_size);
void ntfs_set_vfs_operations(struct inode *inode, mode_t mode, dev_t dev);
struct folio *ntfs_get_locked_folio(struct address_space *mapping,
pgoff_t index, pgoff_t end_index, struct file_ra_state *ra);
diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c
index 52eecf5cb256..26a1831a2c18 100644
--- a/fs/ntfs/iomap.c
+++ b/fs/ntfs/iomap.c
@@ -675,21 +675,7 @@ static int ntfs_write_iomap_begin_non_resident(struct inode *inode, loff_t offse
loff_t length, unsigned int flags,
struct iomap *iomap, int ntfs_iomap_flags)
{
- struct ntfs_inode *ni = NTFS_I(inode);
-
- if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) &&
- offset + length > ni->initialized_size) {
- int ret;
-
- ret = ntfs_extend_initialized_size(inode, offset,
- offset + length,
- ntfs_iomap_flags &
- NTFS_IOMAP_FLAGS_DIO);
- if (ret < 0)
- return ret;
- }
-
- mutex_lock(&ni->mrec_lock);
+ mutex_lock(&NTFS_I(inode)->mrec_lock);
if (ntfs_iomap_flags & NTFS_IOMAP_FLAGS_BEGIN)
return ntfs_write_simple_iomap_begin_non_resident(inode, offset,
length, iomap);
@@ -705,28 +691,10 @@ static int __ntfs_write_iomap_begin(struct inode *inode, loff_t offset,
struct iomap *iomap, int ntfs_iomap_flags)
{
struct ntfs_inode *ni = NTFS_I(inode);
- loff_t end = offset + length;
if (NVolShutdown(ni->vol))
return -EIO;
- if (ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_BEGIN | NTFS_IOMAP_FLAGS_DIO) &&
- end > ni->data_size) {
- struct ntfs_volume *vol = ni->vol;
- int ret;
-
- mutex_lock(&ni->mrec_lock);
- if (end > ni->allocated_size &&
- end < ni->allocated_size + vol->preallocated_size)
- ret = ntfs_attr_expand(ni, end,
- ni->allocated_size + vol->preallocated_size);
- else
- ret = ntfs_attr_expand(ni, end, 0);
- mutex_unlock(&ni->mrec_lock);
- if (ret)
- return ret;
- }
-
if (!NInoNonResident(ni)) {
mutex_lock(&ni->mrec_lock);
return ntfs_write_iomap_begin_resident(inode, offset, iomap);
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH] ntfs: use pagecache_isize_extended() on size extension
2026-07-16 2:46 [PATCH] ntfs: validate final EA attribute size Namjae Jeon
` (3 preceding siblings ...)
2026-07-16 2:46 ` [PATCH] ntfs: file extension before write submission Namjae Jeon
@ 2026-07-16 2:46 ` Namjae Jeon
4 siblings, 0 replies; 6+ messages in thread
From: Namjae Jeon @ 2026-07-16 2:46 UTC (permalink / raw)
To: hyc.lee; +Cc: ntfs, linux-fsdevel, Namjae Jeon
When extending file size, call truncate_pagecache() first, then update
i_size, and use pagecache_isize_extended() instead of manual
iomap_zero_range(). This ensures the straddling folio is properly marked
RO so page_mkwrite() is called and post-EOF area is zeroed.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
---
fs/ntfs/file.c | 28 ++++++++++------------------
1 file changed, 10 insertions(+), 18 deletions(-)
diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
index 9061f8f77f7e..c8e49f83fd92 100644
--- a/fs/ntfs/file.c
+++ b/fs/ntfs/file.c
@@ -268,23 +268,19 @@ static int ntfs_setattr_size(struct inode *vi, struct iattr *attr)
return err;
inode_dio_wait(vi);
- truncate_setsize(vi, attr->ia_size);
+ if (attr->ia_size > old_size) {
+ truncate_pagecache(vi, old_size);
+ i_size_write(vi, attr->ia_size);
+ pagecache_isize_extended(vi, old_size, attr->ia_size);
+ } else
+ truncate_setsize(vi, attr->ia_size);
+
err = ntfs_truncate_vfs(vi, attr->ia_size, old_size);
if (err) {
i_size_write(vi, old_size);
return err;
}
- if (NInoNonResident(ni) && attr->ia_size > old_size &&
- old_size % PAGE_SIZE != 0) {
- loff_t len = min_t(loff_t,
- round_up(old_size, PAGE_SIZE) - old_size,
- attr->ia_size - old_size);
- err = iomap_zero_range(vi, old_size, len,
- NULL, &ntfs_seek_iomap_ops,
- &ntfs_iomap_folio_ops, NULL);
- }
-
return err;
}
@@ -1165,13 +1161,9 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le
filemap_invalidate_unlock(vi->i_mapping);
if (!err) {
if (mode == 0 && NInoNonResident(ni) &&
- offset > old_size && old_size % PAGE_SIZE != 0) {
- loff_t len = min_t(loff_t,
- round_up(old_size, PAGE_SIZE) - old_size,
- offset - old_size);
- err = iomap_zero_range(vi, old_size, len, NULL,
- &ntfs_seek_iomap_ops,
- &ntfs_iomap_folio_ops, NULL);
+ offset > old_size) {
+ truncate_pagecache(vi, old_size);
+ pagecache_isize_extended(vi, old_size, offset);
}
NInoSetFileNameDirty(ni);
inode_set_mtime_to_ts(vi, inode_set_ctime_current(vi));
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread