src/catar/decoder.rs: simplify public restore API

This commit is contained in:
Dietmar Maurer 2019-03-11 14:31:01 +01:00
parent 8e39232acc
commit d5c34d98c0
4 changed files with 96 additions and 56 deletions

View File

@ -133,7 +133,13 @@ fn download_catar(
) -> Result<BoxFut, Error> { ) -> Result<BoxFut, Error> {
let store = tools::required_string_param(&param, "store")?; let store = tools::required_string_param(&param, "store")?;
let archive_name = tools::required_string_param(&param, "archive-name")?; let mut archive_name = tools::required_string_param(&param, "archive-name")?.to_owned();
if !archive_name.ends_with(".catar") {
bail!("wrong archive extension");
} else {
archive_name.push_str(".didx");
}
let backup_type = tools::required_string_param(&param, "backup-type")?; let backup_type = tools::required_string_param(&param, "backup-type")?;
let backup_id = tools::required_string_param(&param, "backup-id")?; let backup_id = tools::required_string_param(&param, "backup-id")?;

View File

@ -405,51 +405,59 @@ fn restore(
let repo_url = tools::required_string_param(&param, "repository")?; let repo_url = tools::required_string_param(&param, "repository")?;
let repo = BackupRepository::parse(repo_url)?; let repo = BackupRepository::parse(repo_url)?;
let path = tools::required_string_param(&param, "snapshot")?; let archive_name = tools::required_string_param(&param, "archive-name")?;
let snapshot = BackupDir::parse(path)?;
let query = tools::json_object_to_query(json!({
"backup-type": snapshot.group().backup_type(),
"backup-id": snapshot.group().backup_id(),
"backup-time": snapshot.backup_time().timestamp(),
}))?;
let target_path = tools::required_string_param(&param, "target")?;
if let Err(err) = std::fs::create_dir(target_path) {
bail!("unable to create target directory - {}", err);
}
let mut client = HttpClient::new(&repo.host, &repo.user); let mut client = HttpClient::new(&repo.host, &repo.user);
let path = format!("api2/json/admin/datastore/{}/files?{}", repo.store, query); let path = tools::required_string_param(&param, "snapshot")?;
let result = client.get(&path)?;
let files = result["data"].as_array().unwrap(); let query;
for file in files { if path.matches('/').count() == 1 {
let file = file.as_str().unwrap(); let group = BackupGroup::parse(path)?;
let query = tools::json_object_to_query(json!({ let subquery = tools::json_object_to_query(json!({
"backup-type": group.backup_type(),
"backup-id": group.backup_id(),
}))?;
let path = format!("api2/json/admin/datastore/{}/snapshots?{}", repo.store, subquery);
let result = client.get(&path)?;
let list = result["data"].as_array().unwrap();
if list.len() == 0 {
bail!("backup group '{}' does not contain any snapshots:", path);
}
query = tools::json_object_to_query(json!({
"backup-type": group.backup_type(),
"backup-id": group.backup_id(),
"backup-time": list[0]["backup-time"].as_i64().unwrap(),
"archive-name": archive_name,
}))?;
} else {
let snapshot = BackupDir::parse(path)?;
query = tools::json_object_to_query(json!({
"backup-type": snapshot.group().backup_type(), "backup-type": snapshot.group().backup_type(),
"backup-id": snapshot.group().backup_id(), "backup-id": snapshot.group().backup_id(),
"backup-time": snapshot.backup_time().timestamp(), "backup-time": snapshot.backup_time().timestamp(),
"archive-name": file, "archive-name": archive_name,
}))?; }))?;
}
if file.ends_with(".catar.didx") { let target = tools::required_string_param(&param, "target")?;
let path = format!("api2/json/admin/datastore/{}/catar?{}", repo.store, query);
let mut filename = std::path::PathBuf::from(file); if archive_name.ends_with(".catar") {
filename.set_extension(""); // remove .didx let path = format!("api2/json/admin/datastore/{}/catar?{}", repo.store, query);
filename.set_extension(""); // remove .catar
println!("DOWNLOAD FILE {} to {:?}", path, filename); println!("DOWNLOAD FILE {} to {}", path, target);
let writer = CaTarBackupWriter::new(
&PathBuf::from(target_path), OsString::from(filename), true)?; let target = PathBuf::from(target);
client.download(&path, Box::new(writer))?; let writer = CaTarBackupWriter::new(&target, true)?;
} else { client.download(&path, Box::new(writer))?;
bail!("unknown file extensions - unable to download '{}'", file); } else {
} bail!("unknown file extensions - unable to download '{}'", archive_name);
} }
Ok(Value::Null) Ok(Value::Null)
@ -558,10 +566,11 @@ fn main() {
restore, restore,
ObjectSchema::new("Restore backup repository.") ObjectSchema::new("Restore backup repository.")
.required("repository", repo_url_schema.clone()) .required("repository", repo_url_schema.clone())
.required("snapshot", StringSchema::new("Snapshot path.")) .required("snapshot", StringSchema::new("Group/Snapshot path."))
.required("archive-name", StringSchema::new("Backup archive name."))
.required("target", StringSchema::new("Target directory path.")) .required("target", StringSchema::new("Target directory path."))
)) ))
.arg_param(vec!["repository", "snapshot", "target"]); .arg_param(vec!["repository", "snapshot", "archive-name", "target"]);
let prune_cmd_def = CliCommand::new( let prune_cmd_def = CliCommand::new(
ApiMethod::new( ApiMethod::new(

View File

@ -89,11 +89,20 @@ impl <'a, R: Read> CaTarDecoder<'a, R> {
bail!("filename entry not nul terminated."); bail!("filename entry not nul terminated.");
} }
if (buffer.len() == 1 && buffer[0] == b'.') || (buffer.len() == 2 && buffer[0] == b'.' && buffer[1] == b'.') {
bail!("found invalid filename with slashes.");
}
if buffer.iter().find(|b| (**b == b'/')).is_some() { if buffer.iter().find(|b| (**b == b'/')).is_some() {
bail!("found invalid filename with slashes."); bail!("found invalid filename with slashes.");
} }
Ok(std::ffi::OsString::from_vec(buffer)) let name = std::ffi::OsString::from_vec(buffer);
if name.is_empty() {
bail!("found empty filename.");
}
Ok(name)
} }
fn restore_attributes(&mut self, _entry: &CaFormatEntry) -> Result<CaFormatHeader, Error> { fn restore_attributes(&mut self, _entry: &CaFormatEntry) -> Result<CaFormatHeader, Error> {
@ -207,12 +216,29 @@ impl <'a, R: Read> CaTarDecoder<'a, R> {
Ok(()) Ok(())
} }
pub fn restore_sequential<F>( pub fn restore<F>(
&mut self,
path: &Path, // used for error reporting
callback: &F,
) -> Result<(), Error>
where F: Fn(&Path) -> Result<(), Error>
{
let _ = std::fs::create_dir(path);
let dir = match nix::dir::Dir::open(path, nix::fcntl::OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty()) {
Ok(dir) => dir,
Err(err) => bail!("unable to open target directory {:?} - {}", path, err),
};
self.restore_sequential(&mut path.to_owned(), &OsString::new(), &dir, callback)
}
fn restore_sequential<F>(
&mut self, &mut self,
path: &mut PathBuf, // used for error reporting path: &mut PathBuf, // used for error reporting
filename: &OsStr, // repeats path last component filename: &OsStr, // repeats path last component
parent: &nix::dir::Dir, parent: &nix::dir::Dir,
create_new: bool,
callback: &F, callback: &F,
) -> Result<(), Error> ) -> Result<(), Error>
where F: Fn(&Path) -> Result<(), Error> where F: Fn(&Path) -> Result<(), Error>
@ -230,10 +256,15 @@ impl <'a, R: Read> CaTarDecoder<'a, R> {
let ifmt = mode & libc::S_IFMT; let ifmt = mode & libc::S_IFMT;
if ifmt == libc::S_IFDIR { if ifmt == libc::S_IFDIR {
let dir = match dir_mkdirat(parent_fd, filename, create_new) { let dir;
Ok(dir) => dir, if filename.is_empty() {
Err(err) => bail!("unable to open directory {:?} - {}", path, err), dir = nix::dir::Dir::openat(parent_fd, ".", OFlag::O_DIRECTORY, Mode::empty())?;
}; } else {
dir = match dir_mkdirat(parent_fd, filename, true) {
Ok(dir) => dir,
Err(err) => bail!("unable to open directory {:?} - {}", path, err),
};
}
let mut head = self.restore_attributes(&entry)?; let mut head = self.restore_attributes(&entry)?;
@ -242,7 +273,7 @@ impl <'a, R: Read> CaTarDecoder<'a, R> {
path.push(&name); path.push(&name);
println!("NAME: {:?}", path); println!("NAME: {:?}", path);
self.restore_sequential(path, &name, &dir, true, callback)?; self.restore_sequential(path, &name, &dir, callback)?;
path.pop(); path.pop();
head = self.read_item()?; head = self.read_item()?;
@ -274,6 +305,10 @@ impl <'a, R: Read> CaTarDecoder<'a, R> {
return Ok(()); return Ok(());
} }
if filename.is_empty() {
bail!("got empty file name at {:?}", path)
}
if ifmt == libc::S_IFLNK { if ifmt == libc::S_IFLNK {
// fixme: create symlink // fixme: create symlink
//fixme: restore permission, acls, xattr, ... //fixme: restore permission, acls, xattr, ...

View File

@ -4,11 +4,6 @@ use std::thread;
use std::os::unix::io::FromRawFd; use std::os::unix::io::FromRawFd;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::io::Write; use std::io::Write;
use std::ffi::OsString;
//use nix::fcntl::OFlag;
//use nix::sys::stat::Mode;
//use nix::dir::Dir;
use crate::catar::decoder::*; use crate::catar::decoder::*;
@ -29,22 +24,17 @@ impl Drop for CaTarBackupWriter {
impl CaTarBackupWriter { impl CaTarBackupWriter {
pub fn new(base: &Path, subdir: OsString, verbose: bool) -> Result<Self, Error> { pub fn new(base: &Path, verbose: bool) -> Result<Self, Error> {
let (rx, tx) = nix::unistd::pipe()?; let (rx, tx) = nix::unistd::pipe()?;
let dir = match nix::dir::Dir::open(base, nix::fcntl::OFlag::O_DIRECTORY, nix::sys::stat::Mode::empty()) { let base = PathBuf::from(base);
Ok(dir) => dir,
Err(err) => bail!("unable to open target directory {:?} - {}", base, err),
};
let mut path = PathBuf::from(base);
path.push(&subdir);
let child = thread::spawn(move|| { let child = thread::spawn(move|| {
let mut reader = unsafe { std::fs::File::from_raw_fd(rx) }; let mut reader = unsafe { std::fs::File::from_raw_fd(rx) };
let mut decoder = CaTarDecoder::new(&mut reader); let mut decoder = CaTarDecoder::new(&mut reader);
if let Err(err) = decoder.restore_sequential(&mut path, &subdir, &dir, false, & |path| { if let Err(err) = decoder.restore(&base, & |path| {
println!("RESTORE: {:?}", path); println!("RESTORE: {:?}", path);
Ok(()) Ok(())
}) { }) {