Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions src/commands/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{

use abscissa_core::{Command, Runnable, Shutdown};
use anyhow::Result;
use rustic_core::{CheckOptions, repofile::SnapshotFile};
use rustic_core::{CheckOptions, Credentials, repofile::SnapshotFile};

/// `check` subcommand
#[derive(clap::Parser, Command, Debug)]
Expand All @@ -26,11 +26,35 @@ pub(crate) struct CheckCmd {

impl Runnable for CheckCmd {
fn run(&self) {
if let Err(err) = RUSTIC_APP
.config()
.repository
.run_open(|repo| self.inner_run(repo))
{
let config = RUSTIC_APP.config();
let repository = &config.repository;

// A hot repository intentionally contains only metadata and tree packs;
// data packs live in the cold repository. First preserve the usual
// hot/cold consistency checks without reading data. Then create an
// isolated cold-only repository for the pack-data check. Reuse the
// validated master key so an interactive user is not prompted twice.
let result = if self.opts.read_data && repository.be.repo_hot.is_some() {
let mut cold_repository = repository.clone();
cold_repository.be.repo_hot = None;
repository.run(|repo| {
let repo = repo.open(&repository.credential_opts)?;
let credentials = Credentials::Masterkey(repo.key());
let mut metadata_opts = self.opts;
metadata_opts.read_data = false;
self.inner_run_with_options(repo, metadata_opts)?;

let cold_repo = cold_repository
.repository(config.global.progress_options)?
.0
.open(&credentials)?;
self.inner_run(cold_repo)
})
} else {
repository.run_open(|repo| self.inner_run(repo))
};

if let Err(err) = result {
status_err!("{}", err);
RUSTIC_APP.shutdown(Shutdown::Crash);
};
Expand All @@ -39,9 +63,13 @@ impl Runnable for CheckCmd {

impl CheckCmd {
fn inner_run(&self, repo: OpenRepo) -> Result<()> {
self.inner_run_with_options(repo, self.opts)
}

fn inner_run_with_options(&self, repo: OpenRepo, opts: CheckOptions) -> Result<()> {
let snaps: Vec<SnapshotFile> = get_global_grouped_snapshots(&repo, &self.ids)?.into();
let trees = snaps.into_iter().map(|snap| snap.tree).collect();
repo.check_with_trees(self.opts, trees)?.is_ok()?;
repo.check_with_trees(opts, trees)?.is_ok()?;
Ok(())
}
}
51 changes: 51 additions & 0 deletions tests/check_hot_cold.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::path::Path;

use assert_cmd::Command;
use rustic_testing::TestResult;
use tempfile::tempdir;

fn rustic_runner(profile: &Path, cold_repo: &Path, hot_repo: &Path) -> TestResult<Command> {
let mut runner = Command::new(env!("CARGO_BIN_EXE_rustic"));
runner
.arg("--use-profile")
.arg(profile)
.arg("--repository")
.arg(cold_repo)
.arg("--repo-hot")
.arg(hot_repo)
.args(["--password", "test", "--no-progress", "--no-cache"]);
Ok(runner)
}

#[test]
fn check_read_data_uses_cold_data_packs_for_hot_cold_repositories() -> TestResult<()> {
let temp_dir = tempdir()?;
let cold_repo = temp_dir.path().join("cold-repository");
let hot_repo = temp_dir.path().join("hot-repository");
let source = temp_dir.path().join("source");
let profile = temp_dir.path().join("test.toml");
std::fs::write(&profile, "")?;
std::fs::create_dir(&source)?;
std::fs::write(source.join("payload.bin"), vec![42_u8; 256 * 1024])?;

rustic_runner(&profile, &cold_repo, &hot_repo)?
.arg("init")
.assert()
.success();

rustic_runner(&profile, &cold_repo, &hot_repo)?
.arg("backup")
.arg(&source)
.assert()
.success();

// Data packs are intentionally stored only in the cold repository. A
// successful read-data check proves it reads them there rather than trying
// the hot metadata repository.
rustic_runner(&profile, &cold_repo, &hot_repo)?
.args(["check", "--read-data"])
.assert()
.success();

Ok(())
}