use std::collections::HashSet;
use std::fs;
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;
use clap;
use rust_crypto::digest::Digest;
use rust_crypto::sha1::Sha1;
use output::Output;
use rustc_serialize::hex::ToHex;
use convert::utils::*;
use misc::*;
use zbackup::data::*;
use zbackup::repository::*;
pub fn check_backups (
output: & Output,
arguments: & CheckBackupsArguments,
) -> Result <bool, String> {
let repository =
string_result_with_prefix (
|| format! (
"Error opening repository {}: ",
arguments.repository_path.to_string_lossy ()),
Repository::open (
& output,
Repository::default_config (),
& arguments.repository_path,
arguments.password_file_path.clone ()),
) ?;
let _atomic_file_writer =
AtomicFileWriter::new (
output,
& arguments.repository_path,
None,
) ?;
repository.load_indexes (
output) ?;
let backup_names: Vec <PathBuf> =
scan_backup_files (
& arguments.repository_path,
) ?.into_iter ().filter (
|ref backup_name|
arguments.backup_name_hash_prefix.is_none () || {
let mut sha1_digest =
Sha1::new ();
sha1_digest.input (
backup_name.as_os_str ().as_bytes ());
let mut sha1_sum = [0u8; 20];
sha1_digest.result (
& mut sha1_sum);
sha1_sum.to_hex ().starts_with (
arguments.backup_name_hash_prefix.as_ref ().unwrap ())
}
).collect ();
if arguments.backup_name_hash_prefix.is_some () {
output.message_format (
format_args! (
"Found {} backup files matching filter",
backup_names.len ()));
} else {
output.message_format (
format_args! (
"Found {} backup files",
backup_names.len ()));
}
let output_job =
output_job_start! (
output,
"Checking backups");
let mut checked_backup_count: u64 = 0;
let mut error_backup_count: u64 = 0;
for backup_name in backup_names.iter () {
output_job.progress (
checked_backup_count,
backup_names.len () as u64);
let backup_path =
repository.path ()
.join ("backups")
.join (backup_name);
let mut backup_chunks: HashSet <ChunkId> =
HashSet::new ();
let backup_expand_error =
collect_chunks_from_backup (
& repository,
& mut backup_chunks,
& backup_name,
).err ();
let missing_chunks: Vec <ChunkId> =
backup_chunks.iter ().filter (
|chunk_id|
! repository.has_chunk (
chunk_id)
).map (|c| * c).collect ();
if let Some (ref error) = backup_expand_error {
output.message_format (
format_args! (
"Backup {} could not be expanded: {}",
backup_name.to_string_lossy (),
error));
} else if ! missing_chunks.is_empty () {
output.message_format (
format_args! (
"Backup {} is missing {} out of {} chunks",
backup_name.to_string_lossy (),
missing_chunks.len (),
backup_chunks.len ()));
}
if backup_expand_error.is_some () || ! missing_chunks.is_empty () {
if arguments.move_broken {
let backups_broken_path =
repository.path ()
.join ("backups-broken");
let backup_broken_path =
backups_broken_path.join (
backup_name);
io_result (
fs::create_dir_all (
backup_broken_path.parent ().unwrap ()),
) ?;
io_result (
fs::rename (
backup_path,
backup_broken_path),
) ?;
}
error_backup_count += 1;
}
checked_backup_count += 1;
}
if error_backup_count > 0 {
output_job_replace! (
output_job,
"{} {} backups with errors out of {} checked",
if arguments.move_broken { "Moved" } else { "Found" },
error_backup_count,
backup_names.len ());
if ! arguments.move_broken {
output_message! (
output,
"Run with --move-broken to move these to backups-broken \
directory");
}
} else {
output_job_replace! (
output_job,
"All chunks present for {} backups checked",
backup_names.len ());
}
repository.close (
output);
Ok (error_backup_count == 0)
}
command! (
name = check_backups,
export = check_backups_command,
arguments = CheckBackupsArguments {
repository_path: PathBuf,
password_file_path: Option <PathBuf>,
backup_name_hash_prefix: Option <String>,
move_broken: bool,
},
clap_subcommand = {
clap::SubCommand::with_name ("check-backups")
.about ("Checks backups for missing chunks")
.arg (
clap::Arg::with_name ("repository")
.long ("repository")
.value_name ("REPOSITORY")
.required (true)
.help ("Path to the repository")
)
.arg (
clap::Arg::with_name ("password-file")
.long ("password-file")
.value_name ("PASSWORD-FILE")
.required (false)
.help ("Path to the password file")
)
.arg (
clap::Arg::with_name ("move-broken")
.long ("move-broken")
.help ("Move broken backups to backups-broken directory")
)
.arg (
clap::Arg::with_name ("backup-name-hash-prefix")
.long ("backup-name-hash-prefix")
.value_name ("BACKUP-NAME-HASH-PREFIX")
.required (false)
.help ("Only check backups whose name's SHA1 hash start with \
this")
)
},
clap_arguments_parse = |clap_matches| {
CheckBackupsArguments {
repository_path:
args::path_required (
& clap_matches,
"repository"),
password_file_path:
args::path_optional (
& clap_matches,
"password-file"),
move_broken:
args::bool_flag (
& clap_matches,
"move-broken"),
backup_name_hash_prefix:
args::string_optional (
& clap_matches,
"backup-name-hash-prefix"),
}
},
action = |output, arguments| {
check_backups (output, arguments)
},
);