From 6a5a9854eb90b767b668403928e2c64091929b51 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Tue, 10 Nov 2020 07:30:32 +0200 Subject: feat: restore a generation --- src/bin/obnam-backup.rs | 2 +- src/bin/obnam-restore.rs | 95 ++++++++++++++++++++++++++++++++++++++++-------- src/generation.rs | 62 ++++++++++++++++++++++++++++--- 3 files changed, 137 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/bin/obnam-backup.rs b/src/bin/obnam-backup.rs index f5d6b43..7d7e07e 100644 --- a/src/bin/obnam-backup.rs +++ b/src/bin/obnam-backup.rs @@ -17,7 +17,7 @@ fn main() -> anyhow::Result<()> { let client = BackupClient::new(&config.server_name, config.server_port)?; { - let mut gen = Generation::new(&config.dbname)?; + let mut gen = Generation::create(&config.dbname)?; gen.insert_iter(FsIterator::new(&config.root).map(|entry| match entry { Err(err) => Err(err), Ok(entry) => client.upload_filesystem_entry(entry, BUFFER_SIZE), diff --git a/src/bin/obnam-restore.rs b/src/bin/obnam-restore.rs index 21cc096..442104a 100644 --- a/src/bin/obnam-restore.rs +++ b/src/bin/obnam-restore.rs @@ -1,5 +1,7 @@ use log::{debug, info}; use obnam::client::BackupClient; +use obnam::fsentry::{FilesystemEntry, FilesystemKind}; +use obnam::generation::Generation; //use obnam::chunkmeta::ChunkMeta; use serde::Deserialize; use std::fs::File; @@ -7,19 +9,6 @@ use std::io::prelude::*; use std::path::{Path, PathBuf}; use structopt::StructOpt; -#[derive(Debug, StructOpt)] -#[structopt(name = "obnam-backup", about = "Simplistic backup client")] -struct Opt { - #[structopt(parse(from_os_str))] - config: PathBuf, - - #[structopt()] - gen_id: String, - - #[structopt(parse(from_os_str))] - dbname: PathBuf, -} - fn main() -> anyhow::Result<()> { pretty_env_logger::init(); @@ -31,19 +20,41 @@ fn main() -> anyhow::Result<()> { info!("config: {:?}", config); let client = BackupClient::new(&config.server_name, config.server_port)?; - let gen = client.fetch_generation(&opt.gen_id)?; - debug!("gen: {:?}", gen); + let gen_chunk = client.fetch_generation(&opt.gen_id)?; + debug!("gen: {:?}", gen_chunk); { let mut dbfile = File::create(&opt.dbname)?; - for id in gen.chunk_ids() { + for id in gen_chunk.chunk_ids() { let chunk = client.fetch_chunk(id)?; dbfile.write_all(chunk.data())?; } } + info!("downloaded generation to {}", opt.dbname.display()); + + let gen = Generation::open(&opt.dbname)?; + for (fileid, entry) in gen.files()? { + restore(&client, &gen, fileid, entry, &opt.to)?; + } Ok(()) } +#[derive(Debug, StructOpt)] +#[structopt(name = "obnam-backup", about = "Simplistic backup client")] +struct Opt { + #[structopt(parse(from_os_str))] + config: PathBuf, + + #[structopt()] + gen_id: String, + + #[structopt(parse(from_os_str))] + dbname: PathBuf, + + #[structopt(parse(from_os_str))] + to: PathBuf, +} + #[derive(Debug, Deserialize, Clone)] pub struct Config { pub server_name: String, @@ -57,3 +68,55 @@ impl Config { Ok(config) } } + +fn restore( + client: &BackupClient, + gen: &Generation, + fileid: u64, + entry: FilesystemEntry, + to: &Path, +) -> anyhow::Result<()> { + println!("restoring {}:{}", fileid, entry.path().display()); + + let path = if entry.path().is_absolute() { + entry.path().strip_prefix("/")? + } else { + entry.path() + }; + let to = to.join(path); + debug!(" to: {}", to.display()); + + match entry.kind() { + FilesystemKind::Regular => restore_regular(client, &gen, &to, fileid, &entry)?, + FilesystemKind::Directory => restore_directory(&to)?, + } + Ok(()) +} + +fn restore_directory(path: &Path) -> anyhow::Result<()> { + debug!("restoring directory {}", path.display()); + std::fs::create_dir_all(path)?; + Ok(()) +} + +fn restore_regular( + client: &BackupClient, + gen: &Generation, + path: &Path, + fileid: u64, + _entry: &FilesystemEntry, +) -> anyhow::Result<()> { + debug!("restoring regular {}", path.display()); + let parent = path.parent().unwrap(); + debug!(" mkdir {}", parent.display()); + std::fs::create_dir_all(parent)?; + { + let mut file = std::fs::File::create(path)?; + for chunkid in gen.chunkids(fileid)? { + let chunk = client.fetch_chunk(&chunkid)?; + file.write_all(chunk.data())?; + } + } + debug!("restored regular {}", path.display()); + Ok(()) +} diff --git a/src/generation.rs b/src/generation.rs index ca1f8d5..b9edb74 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -1,9 +1,11 @@ -use crate::fsentry::FilesystemEntry; +use crate::fsentry::{FilesystemEntry, FilesystemKind}; +use std::ffi::OsStr; +use std::os::unix::ffi::OsStrExt; //use crate::fsiter::FsIterator; use crate::chunkid::ChunkId; -use rusqlite::{params, Connection, OpenFlags, Transaction}; +use rusqlite::{params, Connection, OpenFlags, Row, Transaction}; use std::os::unix::ffi::OsStringExt; -use std::path::Path; +use std::path::{Path, PathBuf}; /// A backup generation. pub struct Generation { @@ -12,7 +14,7 @@ pub struct Generation { } impl Generation { - pub fn new

(filename: P) -> anyhow::Result + pub fn create

(filename: P) -> anyhow::Result where P: AsRef, { @@ -30,6 +32,16 @@ impl Generation { Ok(Self { conn, fileno: 0 }) } + pub fn open

(filename: P) -> anyhow::Result + where + P: AsRef, + { + let flags = OpenFlags::SQLITE_OPEN_READ_WRITE; + let conn = Connection::open_with_flags(filename, flags)?; + conn.pragma_update(None, "journal_mode", &"WAL")?; + Ok(Self { conn, fileno: 0 }) + } + pub fn insert(&mut self, e: FilesystemEntry, ids: &[ChunkId]) -> anyhow::Result<()> { let t = self.conn.transaction()?; insert_one(&t, e, self.fileno, ids)?; @@ -51,6 +63,46 @@ impl Generation { t.commit()?; Ok(()) } + + pub fn files(&self) -> anyhow::Result> { + let mut stmt = self.conn.prepare("SELECT * FROM files")?; + let iter = stmt.query_map(params![], |row| row_to_entry(row))?; + let mut files: Vec<(u64, FilesystemEntry)> = vec![]; + for x in iter { + let (fileid, entry) = x?; + files.push((fileid, entry)); + } + Ok(files) + } + + pub fn chunkids(&self, fileid: u64) -> anyhow::Result> { + let fileid = fileid as i64; + let mut stmt = self + .conn + .prepare("SELECT chunkid FROM chunks WHERE fileid = ?1")?; + let iter = stmt.query_map(params![fileid], |row| Ok(row.get(0)?))?; + let mut ids: Vec = vec![]; + for x in iter { + let fileid: String = x?; + ids.push(ChunkId::from(&fileid)); + } + Ok(ids) + } +} + +fn row_to_entry(row: &Row) -> rusqlite::Result<(u64, FilesystemEntry)> { + let fileid: i64 = row.get(row.column_index("fileid")?)?; + let fileid = fileid as u64; + let path: Vec = row.get(row.column_index("path")?)?; + let path: &OsStr = OsStrExt::from_bytes(&path); + let path: PathBuf = PathBuf::from(path); + let kind = row.get(row.column_index("kind")?)?; + let kind = FilesystemKind::from_code(kind).unwrap(); + let entry = match kind { + FilesystemKind::Regular => FilesystemEntry::regular(path, 0), + FilesystemKind::Directory => FilesystemEntry::directory(path), + }; + Ok((fileid, entry)) } fn insert_one( @@ -85,7 +137,7 @@ mod test { fn empty() { let filename = NamedTempFile::new().unwrap().path().to_path_buf(); { - let mut _gen = Generation::new(&filename).unwrap(); + let mut _gen = Generation::create(&filename).unwrap(); // _gen is dropped here; the connection is close; the file // should not be removed. } -- cgit v1.2.1