Research notes / Capacity semantics
Sparse files, preallocation and thin-pool capacity
13 October 2026 / Primary-source engineering review
A large file, an allocated file range and available thin-pool capacity are different facts. Preallocation can protect a file against filesystem allocation failure without establishing that every lower storage layer has reserved the resources needed for future writes.
Start with the layer being measured
On Linux, st_size reports a regular file's logical length in bytes. st_blocks reports its allocation in 512-byte units; st_blksize is a preferred I/O size, not the multiplier for that count. A sparse file can have a long logical address range while consuming fewer allocated blocks. Neither field directly measures allocation in a storage array below the filesystem. [3]
| Observation | Question answered | Still unknown |
|---|---|---|
| File logical length | Where is this file's end? | How much of its range has allocated storage? |
| File allocated blocks | What allocation does this filesystem report for the file? | How much backing space does a lower thin pool have? |
| Filesystem free space | What space does this filesystem report as available? | Are lower data and metadata resources available? |
| Thin-pool Data% and Meta% | How much of each pool resource is used? | Can growth or repair finish before workloads need more? |
The final row uses LVM's two distinct pool counters. A thin logical volume can advertise more capacity than its pool can physically supply. Other storage systems require their own counters and contracts; these Linux examples are not universal names for every array. [5]
A hole is a file-level zero-reading range
Seeking beyond end-of-file does not enlarge the file by itself. A later write at that offset leaves a gap that reads as zeros. This supports sparse files, but not every zero-filled range is a hole. SEEK_DATA and SEEK_HOLE are filesystem-dependent hints: an implementation may report all offsets as data and only EOF as a hole. They are not an exact physical allocation map. [2]
With hole punching, data in the selected file range is deliberately removed. Whole filesystem blocks can be deallocated; partial boundary blocks are zeroed. The logical length stays unchanged. This changes file content and is not a harmless capacity probe. [4]
Preallocation is a real, scoped guarantee
Linux documents successful fallocate(fd, 0, offset, length) as allocating the range so later writes there do not fail for lack of disk space. FALLOC_FL_KEEP_SIZE prevents extending the file's logical length. That is a meaningful allocation contract, not merely a performance hint. [1]
However, the util-linux manual describes fast file preallocation through uninitialized blocks without I/O to the data blocks. LVM documents thin-pool backing being allocated when blocks are written. Our layer-boundary inference: a filesystem reservation is not evidence that all lower thin-pool data and metadata resources have been reserved. This does not negate the filesystem contract or imply every preallocated write will fail. [4] [5]
Allocation is also not durability. Successfully reserving an unwritten range does not establish that later application data reached persistent storage. That requires a separate write and synchronization error-handling contract.
The same zeros can mean opposite allocation changes
The util-linux --zero-range operation can preallocate holes and represent zeros using unwritten extents. By contrast, --punch-hole removes whole blocks while retaining file length. Both can produce zero-filled reads. Readback alone therefore cannot tell whether space was allocated, released or physically overwritten. Check operation support and allocation at the layer of interest. [4]
Even a decrease in filesystem allocation does not prove immediate backend reclamation. Discard handling, chunk boundaries and blocks retained by thin snapshots matter. no_discard_passdown removes thin mappings without forwarding discard to the data device; ignore_discard disables discard handling instead. [5] [6] The separate discard analysis explains why reclamation and sanitization must not be conflated.
An unwritten extent is not a hole
A file extent maps a run of file blocks to storage blocks. Ext4 represents contiguous runs with a start and length instead of a separate mapping for every block. [7] An unwritten extent already has allocation but returns zeros through the filesystem; its underlying device contents are not thereby defined. [8]
FIEMAP distinguishes UNWRITTEN from DELALLOC: delayed allocation has data but no assigned physical location yet. These are different states, neither interchangeable with a hole. Extent mappings can also change between calls. Do not use reported offsets to bypass a mounted filesystem and access its block device; the FIEMAP documentation explicitly rules out that use. [8]
Allocated does not mean private: reflink and CoW
A reflink gives distinct files shared backing storage. A write to either file must remain private to that file, so a later overwrite can need new allocation even though the original range already had blocks. Linux FICLONE operates within one filesystem; it does not create an independent backup. [9]
FALLOC_FL_UNSHARE_RANGE addresses this sharing boundary by making shared extents private before later writes. Support depends on the filesystem. Do not silently replace an unsupported request with a different operation and assume the same guarantee. Unsharing is still a filesystem operation, not a reservation in every underlying pool. [1]
Research context: measure the work after cloning
Zhan and colleagues' How to Copy Files (FAST 2020, Section 5.1) tests clone creation, later writes, reads and space use separately. Its Dookubench starts with 64 files of 4 MiB, makes clones, changes 16 bytes per file, synchronizes, then clears caches before reading. This exposes work that a clone-creation timer alone misses. [10]
The authors propose copy-on-abundant-write in BetrFS: batch modifications before breaking sharing, balancing copying cost with locality. The evaluation used a 500 GB 7200 RPM SATA disk and different kernels for BetrFS and the comparison filesystems. It reports at least five runs unless stated otherwise; timing bars show observed minima and maxima, not confidence intervals. These are historical experimental results, not a ranking of current SSD filesystems or a pool-capacity guarantee. [10]
Engineering implication: evaluate the complete clone, modify, read and reclaim lifecycle. For backup independence, separately assess the recovery and failure boundary. Hesela has not reproduced the BetrFS experiments.
Data exhaustion is not metadata exhaustion
Linux 6.12's dm-thin documentation distinguishes the cases. Data-space exhaustion can queue or fail I/O according to configuration. Its metadata-update section describes metadata exhaustion or a failed metadata operation as requiring I/O errors and offline repair; an aborted metadata transaction can also require consistency checks in higher layers. Treating every full-pool incident as a routine extension misses this difference. [6, Updating on-disk metadata]
Monitor both LVM Data% and Meta%. Automatic extension still depends on monitoring, free volume-group capacity and enough time to react. A configured threshold is not proof that extension can succeed. [5]
- Identify the filesystem, backing volume, pool and relevant snapshots.
- Record logical size, filesystem allocation, data usage and metadata usage separately.
- Check the actual allocation mode, discard policy and supported operations.
- Verify growth headroom and error handling before capacity pressure becomes an incident.
This checklist is editorial synthesis. It is not a command sequence for modifying or repairing a live pool.
Method and limits
Primary-source review of Linux man-pages, util-linux 2.41.2, LVM2, Linux 6.12 dm-thin, ext4 and FIEMAP documentation, plus Zhan et al., FAST 2020 Sections 1, 4 and 5.1; layer-boundary conclusions are identified as editorial inferences.
No filesystem, device or pool-exhaustion experiment was performed. Filesystem support, discard policy, snapshots and deployment versions affect behavior; no universal physical-capacity guarantee is inferred. This is not a device experiment, capacity benchmark or claim of measured space savings. The pinned Linux and util-linux releases are reference versions, not assertions about the newest software. Recheck the implementation used by your filesystem and storage stack.
Machine-readable companion definitions: sparse file, hole punching, file preallocation and thin provisioning.
Primary sources
- Linux man-pages: fallocate(2), allocation and mode contracts
- Linux man-pages: lseek(2), holes and SEEK_DATA/SEEK_HOLE
- Linux man-pages: inode(7), file size and allocated blocks
- util-linux 2.41.2: fallocate(1), preallocation, punching and zeroing
- LVM2: lvmthin(7), physical allocation, discards and pool exhaustion
- Linux 6.12: device-mapper thin provisioning, metadata and exhaustion
- Linux 6.12: ext4 extent tree and mapping format
- Linux 6.12: FIEMAP extent states and access restrictions
- Linux man-pages: FICLONE and FICLONERANGE sharing semantics
- Zhan et al. (2020), How to Copy Files, FAST 20, pp. 75-89; Sections 4 and 5.1
Sources checked 13 October 2026, Europe/Warsaw; earlier access dates remain in the metadata.