daxfs: Read holes as zeros instead of returning short - #17
Open
congwang-mk wants to merge 1 commit into
Open
Conversation
A file extended by truncate has no overlay pages behind the new range and no base image data either. daxfs_read_iter() treated that as end of file and stopped, so read() reported EOF in the middle of a file that stat() says is larger: : > f; truncate -s 16384 f wc -c < f -> 16384 dd if=f -> 0 bytes POSIX requires a hole to read as zeros. The mmap path already does this, since daxfs_copy_page() zero-fills when there is no source, so read() and mmap() disagreed about the same range. Zero-fill the hole a page at a time and keep going, so a read spanning a hole still finds the data after it rather than stopping at the gap. This needs the caller to tell a hole apart from a failure, which daxfs_base_file_data() could not express: it returned NULL both when nothing backed the range and when daxfs_pcache_get_page() failed. Blindly zeroing would have turned a pcache -EIO or -EFBIG into silent zeros, which is worse than the short read it replaces. Return ERR_PTR for failures and keep NULL for holes. Update the other callers for the new contract. The COW paths in daxfs_write_prealloc(), daxfs_write_iter(), daxfs_dax_fault() and daxfs_dax_pfn_mkwrite() previously fed the NULL to daxfs_copy_page(), which zero-fills; with an ERR_PTR that would have memcpy'd from an error pointer. They now leave the page unpublished or fail the fault rather than COW zeros over data they could not read. Signed-off-by: Cong Wang <cwang@multikernel.io>
congwang-mk
force-pushed
the
fix-hole-reads
branch
from
August 16, 2026 03:33
50cdd4c to
e433d54
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A file extended by
truncatehas no overlay pages behind the new range and no base image data either.daxfs_read_iter()treated that as end of file and stopped, soread()reported EOF in the middle of a file thatstat()says is larger:POSIX requires a hole to read as zeros. The mmap path already does this, since
daxfs_copy_page()zero-fills when there is no source, soread()andmmap()disagreed about the same range. This is also why the fio benchmarking in #15 had to lay files out withddrather than truncating them into place.Fix
Zero-fill the hole a page at a time and continue the loop, so a read spanning a hole still finds the data after it rather than stopping at the gap.
The part that needed care: the caller could not tell a hole from a failure.
daxfs_base_file_data()returned NULL both when nothing backed the range and whendaxfs_pcache_get_page()failed with-EIO,-EFBIGor-ENOENT. Blindly zero-filling would have converted a pcache read error into silent zeros, which is a worse bug than the short read it replaces.So the function now returns
ERR_PTRfor failures and keeps NULL for holes, and the contract is documented at the definition.daxfs_read_iter()propagates the error (or returns the short count if it already copied something) and zero-fills only for a genuine NULL.The other five callers are updated for the new contract, which is not optional: they previously passed the NULL to
daxfs_copy_page(), which zero-fills, and with anERR_PTRthat would havememcpy'd from an error pointer.daxfs_write_prealloc()leaves the page unpublished so the per-page path retries and surfaces the errordaxfs_write_iter()fails the write rather than COW zeros over unreadable datadaxfs_dax_fault()anddaxfs_dax_pfn_mkwrite()fall through toVM_FAULT_SIGBUS, taking care to do it via the existing!datacheck aftersb_end_pagefault()rather than returning from inside the critical sectionTesting
New regression test in
tests/test_overlay.sh: a truncate-extended file must report the full size fromread(), the range must be zeros, and a read spanning the hole must still find data written after it.Verified it catches the bug rather than passing vacuously:
Also checked, since this touches the hot read loop:
dmesgwarningsNote
#16 is open against
mainand also adds a test aftertest_overlay_truncateintests/test_overlay.sh. Whichever merges second will need a trivial rebase for the test registration list; the C changes do not overlap.