Preallocate and Reclaim File Space with fallocate
A disk image that grows one write at a time gets fragmented, and fallocate fixes that by reserving space up front without writing a byte. You will also learn to punch holes back out of a file and check exactly what changed on disk. The examples use fallocate from util-linux 2.41.3 installed on this machine; the installed manual page identifies util-linux 2.39.3, so the command's own --help output is the better authority for the newer options.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
You need a Linux filesystem that implements the operation you choose, a writable target path, and enough free space for any allocation. Most ordinary files need no elevated privileges. Use sudo only when the target is owned by another account or sits in a protected directory, and check the path carefully before doing so.
Allow about five minutes for the examples. The operations themselves are usually quick, but support and filesystem block alignment can vary. Keep a backup before changing a file that contains data. fallocate has no general undo command.
1. Check the local command
- Confirm the version and options you actually have.
fallocate --version
fallocate --help
You should see version 2.41.3 here. The normal allocation form needs -l for a length and then a filename. Lengths and offsets are bytes by default. Binary suffixes such as MiB and decimal suffixes such as MB are accepted; K means the same as KiB.
2. Reserve space for a file
- Create or extend a file with one mebibyte of allocated space.
fallocate -l 1MiB /path/to/work/cache.bin
The command creates the file if the default allocation operation can do so, or extends an existing file. It allocates filesystem blocks without first filling the data blocks with a stream of zero bytes, which is normally much faster than using a shell loop or dd for the same reservation.
Check both the apparent length and the allocated blocks:
stat -c 'size=%s bytes, blocks=%b' /path/to/work/cache.bin
du -h /path/to/work/cache.bin
For a one mebibyte file, stat should report size=1048576. The block count depends on the filesystem, so do not treat a particular number as portable. du reports space charged to the file, while stat's size reports its apparent length.
Reserve space without changing the apparent length
Use --keep-size when you need to allocate beyond the current end of file without making the file appear longer:
fallocate --keep-size --offset 0 --length 1MiB /path/to/work/existing.bin
This can allocate blocks past EOF. The manual page says those blocks can later be removed with truncate. Do not confuse this with writing data: reads beyond EOF still report end of file.
3. Create a hole in an existing range
Punching a hole deallocates whole filesystem blocks in a range and makes later reads from that range return zeroes. Partial blocks are zeroed as needed, and the file's apparent length stays the same.
- Make a disposable test file, then punch a range from it.
printf 'abcdefghij' > /tmp/fallocate-example.bin
fallocate --punch-hole --offset 2 --length 4 /tmp/fallocate-example.bin
od -An -tx1 -v /tmp/fallocate-example.bin
stat -c 'size=%s bytes, blocks=%b' /tmp/fallocate-example.bin
The bytes shown by od should be 61 62 00 00 00 00 67 68 69 6a. The file remains ten bytes long. On a small file, the filesystem may not reclaim a whole block, so a lower block count is not guaranteed. The operation is still destructive to the original bytes in the selected range. Restore the file from a backup if the range was wrong; there is no inverse fallocate operation that reconstructs the old contents.
The manual lists hole punching support for XFS, ext4, Btrfs, tmpfs and gfs2, with filesystem and kernel version qualifications. Other filesystems can reject it. A failure returns exit status 1; test that status in scripts rather than assuming the request was accepted.
Other range operations and their boundaries
These options change file layout or contents and deserve a backup first:
--zero-rangemakes a range read as zeroes and ensures it is allocated. Filesystem metadata may represent most of the range as unwritten extents, so it need not physically write every zero.--dig-holesscans for zeroes and replaces suitable ranges with holes. It implies--keep-sizeand, without an offset and length, analyses the whole file.--collapse-rangeremoves bytes and shifts later data left, making the file shorter. Filesystems commonly require offset and length to match their logical block size.--insert-rangeinserts a hole and shifts existing data, increasing the file's apparent length. This option is present in the installed 2.41.3 command help even though it is absent from the older local manual page.
--collapse-range, --dig-holes, --punch-hole and --zero-range are mutually exclusive. Do not combine them to express a multi-stage plan. Run separate, verified commands and stop after each one if the data matters.
When allocation fails
A successful exit status is 0; failure is 1. The most common causes are insufficient free space, a read-only filesystem, a filesystem that does not support the requested operation, invalid ranges, and alignment requirements. Check the target filesystem and free space before changing the command:
df -h /path/to/work/cache.bin
findmnt -T /path/to/work/cache.bin
For a range operation, verify that the offset and length are non-negative, that the range fits the intended file, and that the filesystem's block-size rules are satisfied. --posix uses posix_fallocate(3) instead of the Linux fallocate(2) call. It can take longer, but is useful when the fast filesystem-specific operation is unavailable. It does not make destructive range operations portable.
Done means
- You checked the local version and help output.
- You used
-lwith a deliberately chosen size and target. - You verified apparent size and allocated space with
statordu. - You treated punching, collapsing, inserting and zeroing ranges as data changes, with a backup available.
- You checked the filesystem when an operation failed instead of repeatedly retrying an unsupported request.