Write past the end of a file (truncate -s 1g, dd seek=N) and we physically write zeros across the whole gap instead of leaving a hole — so a "1 GB sparse file" really allocates and writes 1 GB. On native ext4 that's an instant metadata update.
The reason is lwext4's API: ext4_ftruncate can't grow a file (it's a silent no-op), and it won't seek past EOF, so the only way we can extend a file is to ext4_fwrite zeros — that's what Ext4Volume.zeroExtend does. The read side already returns zeros for unwritten/hole blocks (fblock == 0 → memset), so reading holes would just work; we have no way to create one through the public API.
A real fix means allocating unwritten extents (fallocate-style), which is either an lwext4 patch or building the extent ourselves — overlaps with the kernel-offloaded I/O work since both touch extent management. Today's behavior is at least safe (we pre-check free space and fail with ENOSPC rather than filling the disk), just slow and space-hungry.
Write past the end of a file (
truncate -s 1g,dd seek=N) and we physically write zeros across the whole gap instead of leaving a hole — so a "1 GB sparse file" really allocates and writes 1 GB. On native ext4 that's an instant metadata update.The reason is lwext4's API:
ext4_ftruncatecan't grow a file (it's a silent no-op), and it won't seek past EOF, so the only way we can extend a file is toext4_fwritezeros — that's whatExt4Volume.zeroExtenddoes. The read side already returns zeros for unwritten/hole blocks (fblock == 0→ memset), so reading holes would just work; we have no way to create one through the public API.A real fix means allocating unwritten extents (fallocate-style), which is either an lwext4 patch or building the extent ourselves — overlaps with the kernel-offloaded I/O work since both touch extent management. Today's behavior is at least safe (we pre-check free space and fail with ENOSPC rather than filling the disk), just slow and space-hungry.