rustfs/rustfs · error · AuditError

Storage not available: {0}

Error message

Storage not available: {0}

What it means

Index::find() (crates/rio/src/compress_index.rs:150) needs at least one index entry to map an offset to a block. If index.info is empty — the index was constructed but add() was never called (or the stream was too small to record any entry, since entries closer than MIN_INDEX_DIST are skipped) — find() returns UnexpectedEof 'empty index' regardless of the offset being in bounds.

Source

Thrown at crates/audit/src/error.rs:41

    #[error("Configuration error: {0}")]
    Configuration(String, #[source] Option<Box<dyn std::error::Error + Send + Sync>>),

    #[error("config not loaded")]
    ConfigNotLoaded,

    #[error("Target error: {0}")]
    Target(#[from] rustfs_targets::TargetError),

    #[error("System not initialized: {0}")]
    NotInitialized(String),

    #[error("System already initialized")]
    AlreadyInitialized,

    #[error("Audit system is paused; entry was not accepted")]
    Paused,

    #[error("Storage not available: {0}")]
    StorageNotAvailable(String),

    #[error("Failed to save configuration: {0}")]
    SaveConfig(#[source] Box<dyn std::error::Error + Send + Sync>),

    #[error("Failed to load configuration: {0}")]
    LoadConfig(#[source] Box<dyn std::error::Error + Send + Sync>),

    #[error("Serialization error: {0}")]
    Serialization(#[from] serde_json::Error),

    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Join error: {0}")]
    Join(#[from] tokio::task::JoinError),
}

View on GitHub (pinned to 35af688cd9)

Solutions

  1. Skip seek-index lookups when index.is_empty() and fall back to a linear decode from stream start.
  2. Ensure load()/load_stream() ran successfully (and its byte range actually contained the index chunk) before find().
  3. For small objects, store/stream without an index or synthesize a single (0,0) entry.
  4. Guard with is_empty() in callers: if index.is_empty() { decode_from_start() } else { index.find(off)? }.

Example fix

// before
let (c, u) = index.find(offset)?; // panics path if index empty

// after: fall back to linear read
let (c, u) = if index.is_empty() {
    (0i64, 0i64)
} else {
    index.find(offset)?
};
Defensive patterns

Strategy: fallback

Validate before calling

if index.is_empty() {
    // no seek index for this object: decode linearly from the start
    return decode_from_start(&stream);
}
let (c, u) = index.find(offset)?;

Type guard

fn index_has_entries(index: &Index) -> bool {
    !index.is_empty()
}

Try / catch

match index.find(offset) {
    Ok(pos) => pos,
    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof && e.to_string().contains("empty index") => {
        decode_from_start(&stream) // fallback: no entries recorded
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling find() on a fresh Index::new(); loading an index chunk that legitimately contains zero entries (tiny streams where every add was suppressed by the MIN_INDEX_DIST rule); forgetting to call load()/load_stream() before find().

Common situations: Small-object fast path skipping index building but still calling find(); deserialization succeeded but read the wrong byte range; race where a reader seeks before the writer finished the first index entry.

Related errors


AI-assisted analysis of rustfs/rustfs@35af688cd9 (2026-08-20). Data as JSON: /api/errors/f6ed9b6fdc972459. Report an issue: GitHub.