summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2020-11-10 07:30:32 +0200
committerLars Wirzenius <liw@liw.fi>2020-11-10 10:46:07 +0200
commit6a5a9854eb90b767b668403928e2c64091929b51 (patch)
treee17847eea9be6df26da86669008185b2ac2ec967 /src
parent6707f65cc6152bc1a5f54331e921d4fac5f597fc (diff)
downloadobnam2-6a5a9854eb90b767b668403928e2c64091929b51.tar.gz
feat: restore a generation
Diffstat (limited to 'src')
-rw-r--r--src/bin/obnam-backup.rs2
-rw-r--r--src/bin/obnam-restore.rs95
-rw-r--r--src/generation.rs62
3 files changed, 137 insertions, 22 deletions
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<P>(filename: P) -> anyhow::Result<Self>
+ pub fn create<P>(filename: P) -> anyhow::Result<Self>
where
P: AsRef<Path>,
{
@@ -30,6 +32,16 @@ impl Generation {
Ok(Self { conn, fileno: 0 })
}
+ pub fn open<P>(filename: P) -> anyhow::Result<Self>
+ where
+ P: AsRef<Path>,
+ {
+ 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<Vec<(u64, FilesystemEntry)>> {
+ 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<Vec<ChunkId>> {
+ 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<ChunkId> = 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<u8> = 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.
}