Skip to main content

powdb_backup/
restore.rs

1use crate::manifest::BackupManifest;
2use powdb_storage::catalog::Catalog;
3use std::io;
4use std::path::Path;
5
6/// Refuse a non-empty destination: a stale wal.log left there would replay
7/// onto the restored data on `Catalog::open` and corrupt it. Restore requires
8/// a fresh or empty directory. A nonexistent or empty dest is fine.
9pub(crate) fn ensure_empty_dir(dest_data_dir: &Path) -> io::Result<()> {
10    if dest_data_dir.exists() && dest_data_dir.read_dir()?.next().is_some() {
11        return Err(io::Error::other(format!(
12            "restore destination {} is not empty; restore requires a fresh or empty directory",
13            dest_data_dir.display()
14        )));
15    }
16    std::fs::create_dir_all(dest_data_dir)?;
17    Ok(())
18}
19
20/// Verify every file in a full backup's manifest against its blake3, then copy
21/// it into `dest`. Does NOT open the catalog — callers (full restore, chain
22/// restore) decide when to validate. Assumes `dest` already exists.
23pub(crate) fn verify_and_copy_full(
24    manifest: &BackupManifest,
25    backup_dir: &Path,
26    dest_data_dir: &Path,
27) -> io::Result<()> {
28    for f in &manifest.files {
29        let bytes = std::fs::read(backup_dir.join(&f.name))?;
30        let hash = blake3::hash(&bytes).to_hex().to_string();
31        if hash != f.blake3_hex {
32            return Err(io::Error::other(format!(
33                "integrity check failed for {}: blake3 mismatch (backup is corrupt)",
34                f.name
35            )));
36        }
37        std::fs::write(dest_data_dir.join(&f.name), &bytes)?;
38    }
39    Ok(())
40}
41
42/// Rebuild a data dir from a full backup. Verifies every file's blake3 against
43/// the manifest BEFORE writing it, then opens the result through
44/// `Catalog::open` (which sets `next_lsn = max_page_lsn + 1` — the v0.4.3
45/// LSN-reset fix) to validate that the restored database actually opens.
46pub fn restore(backup_dir: &Path, dest_data_dir: &Path) -> io::Result<()> {
47    let manifest = BackupManifest::read(backup_dir)?;
48    ensure_empty_dir(dest_data_dir)?;
49    verify_and_copy_full(&manifest, backup_dir, dest_data_dir)?;
50    // Validate: opening must succeed and reset next_lsn correctly.
51    let cat = Catalog::open(dest_data_dir)?;
52    drop(cat);
53    Ok(())
54}