[PATCH] fs/ntfs3: fix SEEK_DATA/SEEK_HOLE for WOF compressed and dedup files
From: Marcin Mennemann
Date: Mon Oct 05 2026 - 18:33:48 EST
Since commit c61326967728 ("fs/ntfs3: implement llseek
SEEK_DATA/SEEK_HOLE by scanning data runs"), ntfs_llseek() finds data
and holes by scanning the runs of the unnamed $DATA attribute.
This breaks WOF compressed files. Their data is stored in the
WofCompressedData stream and the unnamed stream is completely sparse,
so SEEK_DATA returns -ENXIO and the whole file looks like a hole.
Deduplicated files keep their data outside the unnamed stream as well.
cp from coreutils uses SEEK_DATA for these files and silently creates a
file full of zeros.
Fix this by using generic_file_llseek_size() for files with
NI_FLAG_COMPRESSED_MASK or NI_FLAG_DEDUPLICATED set, like before the
commit above.
Fixes: c61326967728 ("fs/ntfs3: implement llseek SEEK_DATA/SEEK_HOLE by scanning data runs")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Marcin Mennemann <marcin@xxxxxxxxxxxxx>
---
Notes:
I found this when cp of bootmgfw.efi from my Windows partition gave me
a file of zeros.
Tested on 7.2.8 with the Windows 11 partition mounted read-only: all
WOF compressed files I sampled from C:\Windows were reported as one hole
before the patch and as one data extent after it, and cp of bootmgfw.efi
went from a zero-filled copy to an identical one. Sparse and LZNT1
compressed files behave the same with and without the patch.
Not tested: deduplicated files (I have none).
fs/ntfs3/file.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c
index 2abf334bfa0c..bbabeea7248e 100644
--- a/fs/ntfs3/file.c
+++ b/fs/ntfs3/file.c
@@ -1554,7 +1554,9 @@ static loff_t ntfs_llseek(struct file *file, loff_t offset, int whence)
loff_t maxbytes = ntfs_get_maxbytes(ni);
loff_t ret;
- if (whence != SEEK_DATA && whence != SEEK_HOLE) {
+ if ((whence != SEEK_DATA && whence != SEEK_HOLE) ||
+ /* Data of WOF and dedup files is not in the unnamed stream. */
+ (ni->ni_flags & (NI_FLAG_COMPRESSED_MASK | NI_FLAG_DEDUPLICATED))) {
ret = generic_file_llseek_size(file, offset, whence, maxbytes,
i_size_read(inode));
} else if ((unsigned long long)offset >= i_size_read(inode)) {
base-commit: 67f0943b394d920b6c142aad8c6af94340342ae7
--
2.56.0