RyanCodrai/turbovec · error · io::Error
truncated file
Error message
truncated file
What it means
read_exact_at (Windows path) loops on FileExt::seek_read until the caller's buffer is filled; if the file returns 0 bytes before the buffer is full, it raises UnexpectedEof with 'truncated file'. This means the file ended before the expected bytes at the requested offset could be read — the on-disk file is shorter than the index structures expect.
Source
Thrown at turbovec/src/io.rs:629
#[cfg(unix)]
pub(crate) fn read_exact_at(f: &File, buf: &mut [u8], off: u64) -> io::Result<()> {
use std::os::unix::fs::FileExt;
f.read_exact_at(buf, off)
}
#[cfg(windows)]
pub(crate) fn read_exact_at(f: &File, mut buf: &mut [u8], mut off: u64) -> io::Result<()> {
use std::os::windows::fs::FileExt;
while !buf.is_empty() {
let n = f.seek_read(buf, off)?;
if n == 0 {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "truncated file"));
}
buf = &mut buf[n..];
off += n as u64;
}
Ok(())
}
#[cfg(test)]
mod rename_retry_tests {
use super::*;
// #415. The retry existed but whitelisted only ERROR_SHARING_VIOLATION
// (32), so the ERROR_ACCESS_DENIED (5) that a delete-pending
// destination returns went straight to the caller as a failed save.
// The retry loop itself is `cfg(windows)` and cannot run here; the
// decision table it consults is not, so the part that was actuallyView on GitHub (pinned to ccab9f325e)
Solutions
- Restore the index from a backup or re-export it from the source vectors
- Check the file size against the expected header/declared length before loading
- Delete the incomplete file and regenerate the index
- Use the v7 fallback-to-previous-generation load if an earlier complete commit exists
Example fix
// before
let hdr = read_exact_at(&f, &mut buf, 0)?; // Err: truncated file
// after
let len = f.metadata()?.len();
if len < HEADER_SIZE as u64 {
eprintln!("index file too small ({len} bytes), regenerating");
let idx = rebuild_index()?;
} else {
let hdr = read_exact_at(&f, &mut buf, 0)?;
} Defensive patterns
Strategy: try-catch
Validate before calling
let len = std::fs::metadata(path)?.len();
if len < MIN_INDEX_SIZE {
return Err(anyhow!("index file only {len} bytes; regenerate"));
} Type guard
fn plausibly_complete(path: &Path, min_len: u64) -> bool {
std::fs::metadata(path).map(|m| m.len() >= min_len).unwrap_or(false)
} Try / catch
match Index::load(path) {
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
eprintln!("{path:?} truncated; restoring from backup");
restore_backup(path)?;
Index::load(path)?
}
other => other?,
} Prevention
- Write index files atomically (temp file + rename)
- Avoid killing writers mid-save; use graceful shutdown
- Check file sizes against expected minimums before loading
When it happens
Trigger: Any pread-style read (v7 header, commit record, vector blocks) whose target region extends past the physical end of the file, e.g. reading a header from a 0-byte or partially written file.
Common situations: Interrupted save left a partial file; another process truncated the index; disk-full during commit; copying the file while it was being written.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- {} {detail}.
- file too large for this platform
- {path} was written and committed, but syncing its parent dir
AI-assisted analysis of RyanCodrai/turbovec@ccab9f325e (2026-09-06).
Data as JSON: /api/errors/d521c0164c81b150.
Report an issue: GitHub.