diff options
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | DONE.md | 4 | ||||
-rw-r--r-- | NEWS.md | 4 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | RELEASE.md | 4 | ||||
-rw-r--r-- | code-of-conduct.md | 4 | ||||
-rw-r--r-- | deny.toml | 4 | ||||
-rw-r--r-- | src/backup_reason.rs | 3 | ||||
-rw-r--r-- | src/backup_run.rs | 15 | ||||
-rw-r--r-- | src/benchmark.rs | 38 | ||||
-rw-r--r-- | src/chunker.rs | 6 | ||||
-rw-r--r-- | src/client.rs | 111 | ||||
-rw-r--r-- | src/cmd/backup.rs | 4 | ||||
-rw-r--r-- | src/cmd/gen_info.rs | 4 | ||||
-rw-r--r-- | src/cmd/get_chunk.rs | 4 | ||||
-rw-r--r-- | src/cmd/list.rs | 4 | ||||
-rw-r--r-- | src/cmd/list_files.rs | 4 | ||||
-rw-r--r-- | src/cmd/resolve.rs | 4 | ||||
-rw-r--r-- | src/cmd/restore.rs | 8 | ||||
-rw-r--r-- | src/cmd/show_gen.rs | 4 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/policy.rs | 9 | ||||
-rw-r--r-- | tutorial.md | 4 |
23 files changed, 81 insertions, 167 deletions
@@ -7,6 +7,7 @@ description = "a backup program" license = "AGPL-3.0-or-later" homepage = "https://obnam.org/" repository = "https://gitlab.com/larswirzenius/obnam" +rust-version = "1.56.0" [dependencies] @@ -1,7 +1,3 @@ ---- -title: Definition of done -... - # Definition of done This definition is not meant to be petty bureaucracy. It's meant to be @@ -1,6 +1,4 @@ ---- -title: Release notes for Obnam2 -... +# Release notes for Obnam2 This file summarizes changes between releases of the second generation of Obnam, the backup software. The software is technically called @@ -1,7 +1,3 @@ ---- -title: Obnam — a backup system -... - # Obnam — a backup system Obnam2 is a project to develop a backup system. @@ -1,7 +1,3 @@ ---- -title: Release checklist for Obnam -... - # Release checklist for Obnam Follow these steps to make a release of Obnam. diff --git a/code-of-conduct.md b/code-of-conduct.md index 8851723..ebc4a55 100644 --- a/code-of-conduct.md +++ b/code-of-conduct.md @@ -1,7 +1,3 @@ ---- -title: Contributor Covenant Code of Conduct -... - # Contributor Covenant Code of Conduct ## Our Pledge @@ -9,8 +9,8 @@ db-path = "~/.cargo/advisory-db" db-urls = ["https://github.com/rustsec/advisory-db"] vulnerability = "deny" unmaintained = "warn" -yanked = "allow" -notice = "warn" +yanked = "deny" +notice = "deny" ignore = [ "RUSTSEC-2020-0027", "RUSTSEC-2020-0071", diff --git a/src/backup_reason.rs b/src/backup_reason.rs index 590f470..9a17d80 100644 --- a/src/backup_reason.rs +++ b/src/backup_reason.rs @@ -7,7 +7,8 @@ use std::fmt; /// Represent the reason a file is in a backup. #[derive(Debug, Copy, Clone)] pub enum Reason { - /// File was skipped for some reason, but carried over without changes. + /// File was skipped due to policy, but carried over without + /// changes. Skipped, /// File is new, compared to previous backup. IsNew, diff --git a/src/backup_run.rs b/src/backup_run.rs index ade5ee0..3787494 100644 --- a/src/backup_run.rs +++ b/src/backup_run.rs @@ -3,9 +3,9 @@ use crate::backup_progress::BackupProgress; use crate::backup_reason::Reason; use crate::chunk::{GenerationChunk, GenerationChunkError}; -use crate::chunker::{Chunker, ChunkerError}; +use crate::chunker::{ChunkerError, FileChunks}; use crate::chunkid::ChunkId; -use crate::client::{AsyncBackupClient, ClientError}; +use crate::client::{BackupClient, ClientError}; use crate::config::ClientConfig; use crate::error::ObnamError; use crate::fsentry::{FilesystemEntry, FilesystemKind}; @@ -24,7 +24,7 @@ const SQLITE_CHUNK_SIZE: usize = MIB as usize; /// A running backup. pub struct BackupRun<'a> { - client: &'a AsyncBackupClient, + client: &'a BackupClient, policy: BackupPolicy, buffer_size: usize, progress: Option<BackupProgress>, @@ -95,10 +95,7 @@ pub struct RootsBackupOutcome { impl<'a> BackupRun<'a> { /// Create a new run for an initial backup. - pub fn initial( - config: &ClientConfig, - client: &'a AsyncBackupClient, - ) -> Result<Self, BackupError> { + pub fn initial(config: &ClientConfig, client: &'a BackupClient) -> Result<Self, BackupError> { Ok(Self { client, policy: BackupPolicy::default(), @@ -110,7 +107,7 @@ impl<'a> BackupRun<'a> { /// Create a new run for an incremental backup. pub fn incremental( config: &ClientConfig, - client: &'a AsyncBackupClient, + client: &'a BackupClient, ) -> Result<Self, BackupError> { Ok(Self { client, @@ -361,7 +358,7 @@ impl<'a> BackupRun<'a> { let mut chunk_ids = vec![]; let file = std::fs::File::open(filename) .map_err(|err| ClientError::FileOpen(filename.to_path_buf(), err))?; - let chunker = Chunker::new(size, file, filename); + let chunker = FileChunks::new(size, file, filename); for item in chunker { let chunk = item?; if let Some(chunk_id) = self.client.has_chunk(chunk.meta()).await? { diff --git a/src/benchmark.rs b/src/benchmark.rs deleted file mode 100644 index d2d9003..0000000 --- a/src/benchmark.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Benchmark chunk generation. -//! -//! This is only for development. - -use crate::checksummer::Checksum; -use crate::chunk::DataChunk; -use crate::chunkid::ChunkId; -use crate::chunkmeta::ChunkMeta; - -/// Generate a desired number of empty data chunks with id and metadata. -pub struct ChunkGenerator { - goal: u32, - next: u32, -} - -impl ChunkGenerator { - /// Create a new ChunkGenerator. - pub fn new(goal: u32) -> Self { - Self { goal, next: 0 } - } -} - -impl Iterator for ChunkGenerator { - type Item = (ChunkId, Checksum, DataChunk); - - fn next(&mut self) -> Option<Self::Item> { - if self.next >= self.goal { - None - } else { - let id = ChunkId::recreate(&format!("{}", self.next)); - let checksum = id.sha256(); - let meta = ChunkMeta::new(&checksum); - let chunk = DataChunk::new(vec![], meta); - self.next += 1; - Some((id, checksum, chunk)) - } - } -} diff --git a/src/chunker.rs b/src/chunker.rs index e8e31e1..7954621 100644 --- a/src/chunker.rs +++ b/src/chunker.rs @@ -7,7 +7,7 @@ use std::io::prelude::*; use std::path::{Path, PathBuf}; /// Iterator over chunks in a file. -pub struct Chunker { +pub struct FileChunks { chunk_size: usize, buf: Vec<u8>, filename: PathBuf, @@ -22,7 +22,7 @@ pub enum ChunkerError { FileRead(PathBuf, std::io::Error), } -impl Chunker { +impl FileChunks { /// Create new iterator. pub fn new(chunk_size: usize, handle: std::fs::File, filename: &Path) -> Self { let mut buf = vec![]; @@ -61,7 +61,7 @@ impl Chunker { } } -impl Iterator for Chunker { +impl Iterator for FileChunks { type Item = Result<DataChunk, ChunkerError>; /// Return the next chunk, if any, or an error. diff --git a/src/client.rs b/src/client.rs index ed6b86b..bcc31b4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -96,81 +96,17 @@ pub enum ClientError { } /// Client for the Obnam server HTTP API. -/// -/// This is the async version. -pub struct AsyncBackupClient { - chunk_client: AsyncChunkClient, -} - -impl AsyncBackupClient { - /// Create a new backup client. - pub fn new(config: &ClientConfig) -> Result<Self, ClientError> { - info!("creating backup client with config: {:#?}", config); - Ok(Self { - chunk_client: AsyncChunkClient::new(config)?, - }) - } - - /// Does the server have a chunk? - pub async fn has_chunk(&self, meta: &ChunkMeta) -> Result<Option<ChunkId>, ClientError> { - self.chunk_client.has_chunk(meta).await - } - - /// Upload a data chunk to the srver. - pub async fn upload_chunk(&self, chunk: DataChunk) -> Result<ChunkId, ClientError> { - self.chunk_client.upload_chunk(chunk).await - } - - /// List backup generations known by the server. - pub async fn list_generations(&self) -> Result<GenerationList, ClientError> { - self.chunk_client.list_generations().await - } - - /// Fetch a data chunk from the server, given the chunk identifier. - pub async fn fetch_chunk(&self, chunk_id: &ChunkId) -> Result<DataChunk, ClientError> { - self.chunk_client.fetch_chunk(chunk_id).await - } - - async fn fetch_generation_chunk(&self, gen_id: &GenId) -> Result<GenerationChunk, ClientError> { - let chunk = self.fetch_chunk(gen_id.as_chunk_id()).await?; - let gen = GenerationChunk::from_data_chunk(&chunk)?; - Ok(gen) - } - - /// Fetch a backup generation's metadata, given it's identifier. - pub async fn fetch_generation( - &self, - gen_id: &GenId, - dbname: &Path, - ) -> Result<LocalGeneration, ClientError> { - let gen = self.fetch_generation_chunk(gen_id).await?; - - // Fetch the SQLite file, storing it in the named file. - let mut dbfile = File::create(&dbname) - .map_err(|err| ClientError::FileCreate(dbname.to_path_buf(), err))?; - for id in gen.chunk_ids() { - let chunk = self.fetch_chunk(id).await?; - dbfile - .write_all(chunk.data()) - .map_err(|err| ClientError::FileWrite(dbname.to_path_buf(), err))?; - } - info!("downloaded generation to {}", dbname.display()); - - let gen = LocalGeneration::open(dbname)?; - Ok(gen) - } -} - -/// Client for using chunk part of Obnam server HTTP API. -pub struct AsyncChunkClient { +pub struct BackupClient { client: reqwest::Client, base_url: String, cipher: CipherEngine, } -impl AsyncChunkClient { - /// Create a new chunk client. +impl BackupClient { + /// Create a new backup client. pub fn new(config: &ClientConfig) -> Result<Self, ClientError> { + info!("creating backup client with config: {:#?}", config); + let pass = config.passwords()?; let client = reqwest::Client::builder() @@ -192,7 +128,7 @@ impl AsyncChunkClient { format!("{}/chunks", self.base_url()) } - /// Does server have a chunk? + /// Does the server have a chunk? pub async fn has_chunk(&self, meta: &ChunkMeta) -> Result<Option<ChunkId>, ClientError> { let body = match self.get("", &[("sha256", meta.sha256())]).await { Ok((_, body)) => body, @@ -211,7 +147,7 @@ impl AsyncChunkClient { Ok(has) } - /// Upload a new chunk to the server. + /// Upload a data chunk to the srver. pub async fn upload_chunk(&self, chunk: DataChunk) -> Result<ChunkId, ClientError> { let enc = self.cipher.encrypt_chunk(&chunk)?; let res = self @@ -234,7 +170,7 @@ impl AsyncChunkClient { Ok(chunk_id) } - /// List all generation chunks on the server. + /// List backup generations known by the server. pub async fn list_generations(&self) -> Result<GenerationList, ClientError> { let (_, body) = self.get("", &[("generation", "true")]).await?; @@ -248,7 +184,7 @@ impl AsyncChunkClient { Ok(GenerationList::new(finished)) } - /// Fetch a chunk from the server, given its id. + /// Fetch a data chunk from the server, given the chunk identifier. pub async fn fetch_chunk(&self, chunk_id: &ChunkId) -> Result<DataChunk, ClientError> { let (headers, body) = self.get(&format!("/{}", chunk_id), &[]).await?; let meta = self.get_chunk_meta_header(chunk_id, &headers)?; @@ -259,6 +195,35 @@ impl AsyncChunkClient { Ok(chunk) } + async fn fetch_generation_chunk(&self, gen_id: &GenId) -> Result<GenerationChunk, ClientError> { + let chunk = self.fetch_chunk(gen_id.as_chunk_id()).await?; + let gen = GenerationChunk::from_data_chunk(&chunk)?; + Ok(gen) + } + + /// Fetch a backup generation's metadata, given it's identifier. + pub async fn fetch_generation( + &self, + gen_id: &GenId, + dbname: &Path, + ) -> Result<LocalGeneration, ClientError> { + let gen = self.fetch_generation_chunk(gen_id).await?; + + // Fetch the SQLite file, storing it in the named file. + let mut dbfile = File::create(&dbname) + .map_err(|err| ClientError::FileCreate(dbname.to_path_buf(), err))?; + for id in gen.chunk_ids() { + let chunk = self.fetch_chunk(id).await?; + dbfile + .write_all(chunk.data()) + .map_err(|err| ClientError::FileWrite(dbname.to_path_buf(), err))?; + } + info!("downloaded generation to {}", dbname.display()); + + let gen = LocalGeneration::open(dbname)?; + Ok(gen) + } + async fn get( &self, path: &str, diff --git a/src/cmd/backup.rs b/src/cmd/backup.rs index 6e09d37..92b0f40 100644 --- a/src/cmd/backup.rs +++ b/src/cmd/backup.rs @@ -1,7 +1,7 @@ //! The `backup` subcommand. use crate::backup_run::BackupRun; -use crate::client::AsyncBackupClient; +use crate::client::BackupClient; use crate::config::ClientConfig; use crate::error::ObnamError; use crate::generation::GenId; @@ -26,7 +26,7 @@ impl Backup { async fn run_async(&self, config: &ClientConfig) -> Result<(), ObnamError> { let runtime = SystemTime::now(); - let client = AsyncBackupClient::new(config)?; + let client = BackupClient::new(config)?; let genlist = client.list_generations().await?; let oldtemp = NamedTempFile::new()?; diff --git a/src/cmd/gen_info.rs b/src/cmd/gen_info.rs index 2663d9b..2ce1f64 100644 --- a/src/cmd/gen_info.rs +++ b/src/cmd/gen_info.rs @@ -1,6 +1,6 @@ //! The `gen-info` subcommand. -use crate::client::AsyncBackupClient; +use crate::client::BackupClient; use crate::config::ClientConfig; use crate::error::ObnamError; use log::info; @@ -26,7 +26,7 @@ impl GenInfo { async fn run_async(&self, config: &ClientConfig) -> Result<(), ObnamError> { let temp = NamedTempFile::new()?; - let client = AsyncBackupClient::new(config)?; + let client = BackupClient::new(config)?; let genlist = client.list_generations().await?; let gen_id = genlist.resolve(&self.gen_ref)?; diff --git a/src/cmd/get_chunk.rs b/src/cmd/get_chunk.rs index 905e997..0b27084 100644 --- a/src/cmd/get_chunk.rs +++ b/src/cmd/get_chunk.rs @@ -1,7 +1,7 @@ //! The `get-chunk` subcommand. use crate::chunkid::ChunkId; -use crate::client::AsyncBackupClient; +use crate::client::BackupClient; use crate::config::ClientConfig; use crate::error::ObnamError; use std::io::{stdout, Write}; @@ -24,7 +24,7 @@ impl GetChunk { } async fn run_async(&self, config: &ClientConfig) -> Result<(), ObnamError> { - let client = AsyncBackupClient::new(config)?; + let client = BackupClient::new(config)?; let chunk_id: ChunkId = self.chunk_id.parse().unwrap(); let chunk = client.fetch_chunk(&chunk_id).await?; let stdout = stdout(); diff --git a/src/cmd/list.rs b/src/cmd/list.rs index 6c58e30..f176594 100644 --- a/src/cmd/list.rs +++ b/src/cmd/list.rs @@ -1,6 +1,6 @@ //! The `list` subcommand. -use crate::client::AsyncBackupClient; +use crate::client::BackupClient; use crate::config::ClientConfig; use crate::error::ObnamError; use structopt::StructOpt; @@ -18,7 +18,7 @@ impl List { } async fn run_async(&self, config: &ClientConfig) -> Result<(), ObnamError> { - let client = AsyncBackupClient::new(config)?; + let client = BackupClient::new(config)?; let generations = client.list_generations().await?; for finished in generations.iter() { diff --git a/src/cmd/list_files.rs b/src/cmd/list_files.rs index 888943e..12d34b1 100644 --- a/src/cmd/list_files.rs +++ b/src/cmd/list_files.rs @@ -1,7 +1,7 @@ //! The `list-files` subcommand. use crate::backup_reason::Reason; -use crate::client::AsyncBackupClient; +use crate::client::BackupClient; use crate::config::ClientConfig; use crate::error::ObnamError; use crate::fsentry::{FilesystemEntry, FilesystemKind}; @@ -27,7 +27,7 @@ impl ListFiles { async fn run_async(&self, config: &ClientConfig) -> Result<(), ObnamError> { let temp = NamedTempFile::new()?; - let client = AsyncBackupClient::new(config)?; + let client = BackupClient::new(config)?; let genlist = client.list_generations().await?; let gen_id = genlist.resolve(&self.gen_id)?; diff --git a/src/cmd/resolve.rs b/src/cmd/resolve.rs index cd08908..3b9570a 100644 --- a/src/cmd/resolve.rs +++ b/src/cmd/resolve.rs @@ -1,6 +1,6 @@ //! The `resolve` subcommand. -use crate::client::AsyncBackupClient; +use crate::client::BackupClient; use crate::config::ClientConfig; use crate::error::ObnamError; use structopt::StructOpt; @@ -21,7 +21,7 @@ impl Resolve { } async fn run_async(&self, config: &ClientConfig) -> Result<(), ObnamError> { - let client = AsyncBackupClient::new(config)?; + let client = BackupClient::new(config)?; let generations = client.list_generations().await?; match generations.resolve(&self.generation) { diff --git a/src/cmd/restore.rs b/src/cmd/restore.rs index 2a36986..7b3d95e 100644 --- a/src/cmd/restore.rs +++ b/src/cmd/restore.rs @@ -1,7 +1,7 @@ //! The `restore` subcommand. use crate::backup_reason::Reason; -use crate::client::{AsyncBackupClient, ClientError}; +use crate::client::{BackupClient, ClientError}; use crate::config::ClientConfig; use crate::error::ObnamError; use crate::fsentry::{FilesystemEntry, FilesystemKind}; @@ -43,7 +43,7 @@ impl Restore { async fn run_async(&self, config: &ClientConfig) -> Result<(), ObnamError> { let temp = NamedTempFile::new()?; - let client = AsyncBackupClient::new(config)?; + let client = BackupClient::new(config)?; let genlist = client.list_generations().await?; let gen_id = genlist.resolve(&self.gen_id)?; @@ -130,7 +130,7 @@ pub enum RestoreError { } async fn restore_generation( - client: &AsyncBackupClient, + client: &BackupClient, gen: &LocalGeneration, fileid: i64, entry: &FilesystemEntry, @@ -182,7 +182,7 @@ fn restored_path(entry: &FilesystemEntry, to: &Path) -> Result<PathBuf, RestoreE } async fn restore_regular( - client: &AsyncBackupClient, + client: &BackupClient, gen: &LocalGeneration, path: &Path, fileid: i64, diff --git a/src/cmd/show_gen.rs b/src/cmd/show_gen.rs index 6ec1203..6c8ba19 100644 --- a/src/cmd/show_gen.rs +++ b/src/cmd/show_gen.rs @@ -1,6 +1,6 @@ //! The `show-generation` subcommand. -use crate::client::AsyncBackupClient; +use crate::client::BackupClient; use crate::config::ClientConfig; use crate::error::ObnamError; use crate::fsentry::FilesystemKind; @@ -26,7 +26,7 @@ impl ShowGeneration { async fn run_async(&self, config: &ClientConfig) -> Result<(), ObnamError> { let temp = NamedTempFile::new()?; - let client = AsyncBackupClient::new(config)?; + let client = BackupClient::new(config)?; let genlist = client.list_generations().await?; let gen_id = genlist.resolve(&self.gen_id)?; @@ -8,7 +8,6 @@ pub mod backup_progress; pub mod backup_reason; pub mod backup_run; -pub mod benchmark; pub mod checksummer; pub mod chunk; pub mod chunker; diff --git a/src/policy.rs b/src/policy.rs index 9b66c1d..7241f0f 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -6,6 +6,15 @@ use crate::generation::LocalGeneration; use log::{debug, warn}; /// Policy for what gets backed up. +/// +/// The policy allows two aspects to be controlled: +/// +/// * should new files )(files that didn't exist in the previous +/// backup be included in the new backup? +/// * should files that haven't been changed since the previous backup +/// be included in the new backup? +/// +/// If policy doesn't allow a file to be included, it's skipped. pub struct BackupPolicy { new: bool, old_if_changed: bool, diff --git a/tutorial.md b/tutorial.md index 3435a64..b20c84e 100644 --- a/tutorial.md +++ b/tutorial.md @@ -1,6 +1,4 @@ ---- -title: Obnam tutorial -... +# Obnam tutorial With the help of this tutorial, you're going to set up Obnam, make your first backup, and check that you can restore files from it. |