t8y2/dbx · error

Restore source is invalid

Error message

Restore source is invalid

What it means

The restore() helper validates the source backup path: it must be non-empty after trimming and contain no NUL bytes. Invalid paths are rejected up front with this error instead of letting SQLite fail with a confusing message.

Source

Thrown at crates/dbx-sqlite-worker/src/runtime.rs:217

    if dest.trim().is_empty() || dest.contains('\0') {
        return WorkerBody::err("Backup destination is invalid");
    }
    if let Some(parent) = Path::new(dest).parent() {
        if !parent.as_os_str().is_empty() {
            if let Err(error) = std::fs::create_dir_all(parent) {
                return WorkerBody::err(error.to_string());
            }
        }
    }
    match conn.backup(DatabaseName::Main, dest, None) {
        Ok(()) => WorkerBody::ok(),
        Err(error) => WorkerBody::err(format!("SQLite backup failed: {error}")),
    }
}

fn restore(conn: &mut Connection, src: &str) -> WorkerBody {
    if src.trim().is_empty() || src.contains('\0') {
        return WorkerBody::err("Restore source is invalid");
    }
    match conn.restore(DatabaseName::Main, src, None::<fn(_)>) {
        Ok(()) => WorkerBody::ok(),
        Err(error) => WorkerBody::err(format!("SQLite restore failed: {error}")),
    }
}

fn value_to_json(value: rusqlite::types::ValueRef<'_>, column_decl_type: Option<&str>) -> (serde_json::Value, bool) {
    match value {
        rusqlite::types::ValueRef::Null => (json!(null), false),
        rusqlite::types::ValueRef::Integer(value) => (json!(value), false),
        rusqlite::types::ValueRef::Real(value) => (json!(value), false),
        rusqlite::types::ValueRef::Text(value) => (json!(String::from_utf8_lossy(value)), false),
        rusqlite::types::ValueRef::Blob(value) => sqlite_blob_value_to_json(value, column_decl_type),
    }
}

fn sqlite_blob_value_to_json(bytes: &[u8], column_decl_type: Option<&str>) -> (serde_json::Value, bool) {

View on GitHub (pinned to c0390bff16)

Solutions

  1. Supply a valid, non-empty path to an existing backup file
  2. Strip NUL bytes from paths derived from raw bytes
  3. Validate the source path (exists, readable) before sending the Restore op
  4. Check where the path is sourced from (arg/config/env) and add default/required checks

Example fix

// before
let src = cfg.backup_path.unwrap_or_default(); // ""
send(Restore { src });
// after
let src = cfg.backup_path.context("backup source required")?;
assert!(!src.trim().is_empty() && !src.contains('\0'));
send(Restore { src });
Defensive patterns

Strategy: validation

Validate before calling

fn validate_restore_src(src: &str) -> Result<&str, &'static str> {
    if src.trim().is_empty() { return Err("restore source is required"); }
    if src.contains('\0') { return Err("restore source contains NUL byte"); }
    let p = std::path::Path::new(src);
    if !p.is_file() { return Err("restore source does not exist"); }
    Ok(src)
}

Type guard

fn is_restorable(p: &str) -> bool {
    !p.trim().is_empty() && !p.contains('\0') && std::path::Path::new(p).is_file()
}

Try / catch

match worker.send(WorkerOp::Restore { src: src.to_string() }).await {
    Err(body) if body.error() == Some("Restore source is invalid") => {
        eprintln!("Provide an existing backup file path without NUL bytes");
    }
    other => other?,
}

Prevention

When it happens

Trigger: WorkerOp::Restore with src = "", whitespace-only, or containing '\0'; a restore job whose source path was never populated; paths converted from C strings retaining a NUL terminator.

Common situations: Missing backup-path argument in a restore script; config key not set; path built from user input that was sanitized to empty; buffer-to-path conversions including NUL.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/a906514c3025b556. Report an issue: GitHub.