{"record":{"id":"ae2af8b8f114448b","repo":"rustfs/rustfs","slug":"rename-destination-must-have-a-file-name","errorCode":null,"errorMessage":"rename destination must have a file name","messagePattern":"rename destination must have a file name","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/ecstore/src/disk/os.rs","lineNumber":2420,"sourceCode":"\n    let Some(parent_guard) = parent_guard else {\n        let rename_started = rustfs_io_metrics::put_stage_timer();\n        let result = super::fs::rename_std(src_file_path, dst_file_path);\n        rustfs_io_metrics::record_put_object_stage_duration_from(\n            rustfs_io_metrics::PUT_STAGE_SET_DISK_RENAME_RENAME_SYSCALL,\n            rename_started,\n        );\n        return result;\n    };\n    let src_parent = src_file_path\n        .parent()\n        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, \"rename source must have a parent directory\"))?;\n    let src_name = src_file_path\n        .file_name()\n        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, \"rename source must have a file name\"))?;\n    let dst_name = dst_file_path\n        .file_name()\n        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, \"rename destination must have a file name\"))?;\n    let src_parent = open(\n        src_parent,\n        OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC,\n        Mode::empty(),\n    )\n    .map_err(io::Error::from)?;\n    let dst_parent = parent_guard\n        .last()\n        .ok_or_else(|| io::Error::other(\"rename destination parent guard is empty\"))?;\n\n    let rename_started = rustfs_io_metrics::put_stage_timer();\n    let result = renameat(&src_parent, src_name, dst_parent, dst_name).map_err(io::Error::from);\n    rustfs_io_metrics::record_put_object_stage_duration_from(\n        rustfs_io_metrics::PUT_STAGE_SET_DISK_RENAME_RENAME_SYSCALL,\n        rename_started,\n    );\n    result\n}","sourceCodeStart":2402,"sourceCodeEnd":2438,"githubUrl":"https://github.com/rustfs/rustfs/blob/201c653dcd34c2a01b9aec5991ed76176b342118/crates/ecstore/src/disk/os.rs#L2402-L2438","documentation":"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.","triggerScenarios":"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).","commonSituations":"Destination keys with \"../\" segments; empty destination strings; path join bugs producing root-form destinations on Unix hosts.","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"],"exampleFix":"// before\nrename_all(src, &dst_from_client, &base, &root, lease).await?;\n\n// after\nif dst_from_client.file_name().is_none() {\n    return Err(io::Error::new(io::ErrorKind::InvalidInput, \"rename destination must name a file\"));\n}\nrename_all(src, &dst_from_client, &base, &root, lease).await?;","handlingStrategy":"validation","validationCode":"if dst.file_name().is_none() {\n    return Err(io::Error::new(io::ErrorKind::InvalidInput, \"rename destination must name a file\"));\n}","typeGuard":"fn has_final_file_name(p: &Path) -> bool { p.file_name().is_some() }","tryCatchPattern":"match rename_all(src, &dst, &base, &root, lease).await {\n    Err(e) if e.kind() == io::ErrorKind::InvalidInput\n        && e.to_string().contains(\"destination must have a file name\") => {\n        // dst ends in \"..\" or is a root: fix destination construction\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["unix","rename","path-validation","ecstore","filesystem"],"backgroundTag":"path-has-no-filename","analyzedSha":"201c653dcd34c2a01b9aec5991ed76176b342118","analyzedAt":"2026-08-20T21:57:04.799Z","contentChangedAt":"2026-08-20T21:57:04.799Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}