rustfs/rustfs · error · std::io::Error

InvalidInput

InvalidInput

Error message

missing object metadata parent

What it means

rollback_committed_rename_std undoes a committed xl.meta rename by moving the backup from the old data-dir back over the destination; it needs dst_file_path.parent() to locate that directory. Path::parent() returned None, meaning the metadata path was a bare relative filename with no directory component - something valid bucket/object-derived absolute paths never produce. This is a defensive InvalidInput guarding path construction.

Source

Thrown at crates/ecstore/src/disk/local.rs:187

        Ok(metadata) if metadata.is_file() => {
            remove_file_if_exists(restore)?;
            std::fs::hard_link(backup, restore)?;
            std::fs::rename(restore, current)
        }
        Ok(_) => Err(std::io::Error::new(ErrorKind::InvalidData, "multipart transaction backup is not a file")),
        Err(err) if err.kind() == ErrorKind::NotFound && absent.is_file() => remove_file_if_exists(current),
        Err(err) => Err(err),
    }
}

fn rollback_committed_rename_std(
    dst_file_path: &Path,
    new_data_path: Option<&Path>,
    rollback_data_dir: Option<Uuid>,
) -> std::io::Result<()> {
    if let Some(old_data_dir) = rollback_data_dir {
        let Some(dst_parent) = dst_file_path.parent() else {
            return Err(std::io::Error::new(ErrorKind::InvalidInput, "missing object metadata parent"));
        };
        let backup_path = dst_parent.join(old_data_dir.to_string()).join(STORAGE_FORMAT_FILE_BACKUP);
        std::fs::rename(backup_path, dst_file_path)?;
    } else {
        remove_file_if_exists(dst_file_path)?;
    }

    if let Some(new_data_path) = new_data_path {
        remove_dir_all_if_exists(new_data_path)?;
    }

    Ok(())
}

fn rollback_inline_metadata_commit_std(
    dst_file_path: &Path,
    rollback_data_dir: Option<Uuid>,
    local_rollback_path: Option<&Path>,

View on GitHub (pinned to 9e6e02ea09)

Solutions

  1. Validate bucket and object names are non-empty before calling disk-level rename/rollback APIs
  2. Fix the path-join logic that produced a parentless relative metadata path
  3. Log the final constructed path to confirm it is absolute with a parent directory
  4. Add a unit test covering empty volume/bucket inputs to the path builder

Example fix

// before
let path = build_meta_path(bucket, object); // can yield bare xl.meta when bucket is empty
// after
if bucket.is_empty() || object.is_empty() {
    return Err(invalid_argument("bucket and object must be non-empty"));
}
let path = build_meta_path(bucket, object);
debug_assert!(path.parent().is_some(), "metadata path must have a parent");
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_metadata_path(volume: &str, bucket: &str, path: &Path) -> std::io::Result<()> {
    if volume.is_empty() || bucket.is_empty() || path.parent().is_none() {
        return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "metadata path must live under bucket/object directories"));
    }
    Ok(())
}

Prevention

When it happens

Trigger: The rollback path is invoked with a metadata path built from empty volume/bucket components so that path joining collapsed to a parentless relative name like 'xl.meta'; or a caller passed a hand-built path in tests.

Common situations: Empty bucket or volume strings reaching the disk layer; a path-join refactor dropping the prefix component; unit tests constructing raw paths instead of using the API path builders.

Related errors


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