rustfs/rustfs · error · std::io::Error
rename destination must have a file name
Error message
rename destination must have a file name
What it means
Unix guarded rename (os.rs:2339): the destination must have a final component for renameat(2), but Path::file_name() returns None when the destination terminates in ".." or is a root/empty path. The operation fails with InvalidInput before touching the filesystem, rather than letting renameat target a directory.
Source
Thrown at crates/ecstore/src/disk/os.rs:2420
let Some(parent_guard) = parent_guard else {
let rename_started = rustfs_io_metrics::put_stage_timer();
let result = super::fs::rename_std(src_file_path, dst_file_path);
rustfs_io_metrics::record_put_object_stage_duration_from(
rustfs_io_metrics::PUT_STAGE_SET_DISK_RENAME_RENAME_SYSCALL,
rename_started,
);
return result;
};
let src_parent = src_file_path
.parent()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "rename source must have a parent directory"))?;
let src_name = src_file_path
.file_name()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "rename source must have a file name"))?;
let dst_name = dst_file_path
.file_name()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "rename destination must have a file name"))?;
let src_parent = open(
src_parent,
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC,
Mode::empty(),
)
.map_err(io::Error::from)?;
let dst_parent = parent_guard
.last()
.ok_or_else(|| io::Error::other("rename destination parent guard is empty"))?;
let rename_started = rustfs_io_metrics::put_stage_timer();
let result = renameat(&src_parent, src_name, dst_parent, dst_name).map_err(io::Error::from);
rustfs_io_metrics::record_put_object_stage_duration_from(
rustfs_io_metrics::PUT_STAGE_SET_DISK_RENAME_RENAME_SYSCALL,
rename_started,
);
result
}View on GitHub (pinned to 201c653dcd)
Solutions
- Log the dst path; trailing ".." or root form identifies the construction bug
- Validate dst.file_name().is_some() before initiating the rename
- Reject ".." components and empty names in object keys at the API boundary
Example fix
// before
rename_all(src, &dst_from_client, &base, &root, lease).await?;
// after
if dst_from_client.file_name().is_none() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "rename destination must name a file"));
}
rename_all(src, &dst_from_client, &base, &root, lease).await?; Defensive patterns
Strategy: validation
Validate before calling
if dst.file_name().is_none() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "rename destination must name a file"));
} Type guard
fn has_final_file_name(p: &Path) -> bool { p.file_name().is_some() } Try / catch
match rename_all(src, &dst, &base, &root, lease).await {
Err(e) if e.kind() == io::ErrorKind::InvalidInput
&& e.to_string().contains("destination must have a file name") => {
// dst ends in ".." or is a root: fix destination construction
}
other => other?,
} Prevention
- Validate destinations end in a real file component before rename
- Treat ".." in object keys as invalid input at the API boundary
- Build destinations via Path::join with pre-validated components only
When it happens
Trigger: Rename destination built from an object/bucket whose path collapses to a trailing ".." or to "/" while the guard branch is active (destination below base_dir required directory creation).
Common situations: Destination keys with "../" segments; empty destination strings; path join bugs producing root-form destinations on Unix hosts.
Related errors
- rename source must have a parent directory
- guarded destination file must be an immediate child of its d
- guarded destination file must have a name
- publication root must be absolute
- path must remain below its publication root
AI-assisted analysis of rustfs/rustfs@201c653dcd (2026-08-20).
Data as JSON: /api/errors/ae2af8b8f114448b.
Report an issue: GitHub.