ftruncate
Shortens or extends an open file to an exact size in bytes.
ftruncate(resource $stream, int $size): bool- Added in PHP 4
Parameters
| Parameter | Type | Description |
|---|---|---|
$stream | resource | An open stream handle that must be opened for writing. |
$size | int | The size in bytes to truncate to. If larger than the current file, the file is extended and the gap filled with null bytes. |
Return value
booltrue on success or false on failure.
Errors
Throws a ValueError if $size is negative and a TypeError if $stream is not an open stream resource; emits E_WARNING and returns false when the stream is not open for writing.
ftruncate sets a file's length to an exact number of bytes through an open handle. Shrinking discards everything past that point; growing pads the file with null bytes. The inode is kept, so permissions, ownership and any locks you hold survive.
When to reach for it
The dominant use is emptying a file you already hold open and locked. Reopening with mode w truncates too, but it drops your flock and creates a race window; fopen($f, 'c+') plus flock plus ftruncate($fp, 0) is the safe pattern for a lock file, a counter or a cache entry that several processes touch.
It is also how you trim a log or a queue file in place: seek to the offset of the last record you want to keep, then truncate there. And ftruncate($fp, $n) on a fresh file is a cheap way to preallocate space.
What trips people up
Truncation does not move the file pointer. If you read 500 bytes and then truncate to 0, the pointer is still at 500, and your next write lands there, silently prepending 500 null bytes to a file you thought was empty. Always rewind($fp) after truncating to 0.
Also note that mode a gives you a writable handle that ftruncate accepts, but subsequent writes still append, so use c+ or r+ for rewrite workflows.
Example
When to use it
- Empty a lock or counter file while still holding its flock, with no reopen race.
- Trim a log file back to the last complete record after a partial write.
- Preallocate a fixed-size file before writing fixed-width records into it.
Frequently asked questions
What is the difference between ftruncate and reopening a file with mode w?
Mode w truncates at open time but requires closing and reopening the file, which releases any flock you hold and opens a race window. ftruncate empties the file through the handle you already have, keeping the lock and the inode.
Why does my file contain null bytes after ftruncate?
ftruncate does not reset the file pointer, so a write after truncating to 0 lands at the old offset and the operating system fills everything before it with null bytes. Call rewind($fp) immediately after ftruncate.
Can ftruncate make a file bigger?
Yes. If size exceeds the current length, the file is extended and the new region reads as null bytes, usually allocated sparsely so it costs almost no disk space.
More examples
Safely emptying a locked file
Mode c+ opens for read/write without truncating, so the lock is taken before any content is destroyed. The rewind between ftruncate and fwrite is what stops the new value being written at offset 3 behind three null bytes. The output is 1000.
<?php
$file = "counter.txt";
file_put_contents($file, "999");
$fp = fopen($file, "c+b");
flock($fp, LOCK_EX);
$old = (int) stream_get_contents($fp);
ftruncate($fp, 0);
rewind($fp); // essential: pointer was at 3
fwrite($fp, (string) ($old + 1));
flock($fp, LOCK_UN);
fclose($fp);
echo file_get_contents($file), "\n";
unlink($file);Forgetting rewind produces null bytes
The stream ends up 13 bytes long: ten 00 bytes followed by "new" (hex 6e6577). This is the single most common ftruncate bug and it usually shows up later as corrupt JSON or a config file with a leading NUL.
<?php
$fp = fopen("php://memory", "r+b");
fwrite($fp, "0123456789");
ftruncate($fp, 0); // pointer still at 10
fwrite($fp, "new");
rewind($fp);
$data = stream_get_contents($fp);
echo strlen($data), " bytes\n";
echo bin2hex($data), "\n";
fclose($fp);Growing a file pads with nulls
Truncating to a size larger than the file extends it, so the result is 6 bytes: 4142 followed by four 00 bytes. On most filesystems this creates a sparse region, which is why it is a cheap way to reserve space.
<?php
$file = "grow.bin";
file_put_contents($file, "AB");
$fp = fopen($file, "r+b");
ftruncate($fp, 6);
fclose($fp);
clearstatcache();
echo filesize($file), " bytes\n";
echo bin2hex(file_get_contents($file)), "\n";
unlink($file);
Discussion