rustfs/rustfs · error · io::Error

rename base directory contains an invalid path component

Error message

rename base directory contains an invalid path component

What it means

Windows branch of mkdir_all_below_existing_base_std: while opening each component of base_dir relative to the publication root, any component that is not Normal (and not CurDir, which is skipped) — i.e. '..' or a root marker — is rejected with ErrorKind::InvalidInput. The base directory must be reachable purely through normal name components below the publication root so the chain of open directory handles cannot walk outside it.

Source

Thrown at crates/ecstore/src/disk/os.rs:2720

    {
        use windows_sys::Wdk::Storage::FileSystem::{FILE_OPEN, FILE_OPEN_IF};
        use windows_sys::Win32::Storage::FileSystem::{FILE_SHARE_READ, FILE_SHARE_WRITE};

        let base_relative = publication_root.relative_path(base_dir)?;
        let capacity = base_relative
            .components()
            .count()
            .saturating_add(relative.components().count())
            .saturating_add(1);
        let mut handles = Vec::with_capacity(capacity);
        handles.push(publication_root.directory.clone());
        let mut guard = ExistingBaseDirectoryGuard::new(handles);
        for component in base_relative.components() {
            let Component::Normal(component) = component else {
                if matches!(component, Component::CurDir) {
                    continue;
                }
                return Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "rename base directory contains an invalid path component",
                ));
            };
            let parent = guard
                .handles
                .last()
                .ok_or_else(|| io::Error::other("Windows publication root guard is empty"))?;
            let child = open_windows_directory_component(parent, component, FILE_OPEN)?;
            guard.handles.push(child);
        }
        for component in relative.components() {
            let Component::Normal(component) = component else {
                continue;
            };
            let parent = guard
                .handles
                .last()

View on GitHub (pinned to 9e6e02ea09)

Solutions

  1. Normalize base_dir before the call and assert it starts with the publication root prefix
  2. Audit how publication_root and the bucket base directories are joined at startup; fix the mismatched join
  3. Add a Windows unit test pinning the exact path shapes for base vs publication root

Example fix

// before
let base = Path::new(r"C:\rustfs\bucket"); // contains a root component vs publication root

// after
let base = publication_root.join(volume); // purely Normal components below the root
Defensive patterns

Strategy: validation

Validate before calling

// before renaming on Windows, base must be purely Normal components below the root
let ok = base_dir.strip_prefix(publication_root).map(|rel| {
    rel.components().all(|c| matches!(c, std::path::Component::Normal(_) | std::path::Component::CurDir))
}).unwrap_or(false);
if !ok { return Err(/* fix base construction */); }

Try / catch

Match ErrorKind::InvalidInput with the 'base directory contains an invalid path component' message; normalize base_dir and fix the join against publication_root — not retryable.

Prevention

When it happens

Trigger: Windows rename publication where base_dir contains '..' or an absolute/root component relative to publication_root: volume roots like 'C:\' leaking into the joined path, unnormalized '\\?\' style paths, or a publication root configured at a different location than the base assumes.

Common situations: Deployments where the publication root and bucket directories are joined inconsistently at startup, path normalization changes during ports to Windows, or custom volume layouts.

Related errors


AI-assisted analysis of rustfs/rustfs@9e6e02ea09 (2026-08-16). Data as JSON: /api/errors/d22c15733b0a2fca. Report an issue: GitHub.