diff options
author | metamuffin <metamuffin@disroot.org> | 2024-06-14 03:35:24 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-06-14 03:35:24 +0200 |
commit | 956b172ddbff214d055127fb89905f0057492a26 (patch) | |
tree | 675512962d99e59ff4cae5fd6153b8468b22f541 | |
download | online-offsite-backup-956b172ddbff214d055127fb89905f0057492a26.tar online-offsite-backup-956b172ddbff214d055127fb89905f0057492a26.tar.bz2 online-offsite-backup-956b172ddbff214d055127fb89905f0057492a26.tar.zst |
server kinda works
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Cargo.lock | 421 | ||||
-rw-r--r-- | Cargo.toml | 12 | ||||
-rw-r--r-- | readme.md | 33 | ||||
-rw-r--r-- | src/main.rs | 69 | ||||
-rw-r--r-- | src/server.rs | 228 |
6 files changed, 765 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fabfb87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/config.toml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..350b44e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,421 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "clap" +version = "4.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d8b92cd8358e8d229c11df9358decae64d137c5be540952c5ca7b25aea768" + +[[package]] +name = "offsite-vault" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "env_logger", + "log", + "serde", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ad34b07 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "offsite-vault" +version = "0.1.0" +edition = "2021" + +[dependencies] +toml = "0.8.14" +serde = { version = "1.0.203", features = ["derive"] } +log = "0.4.21" +env_logger = "0.11.3" +anyhow = "1.0.86" +clap = { version = "4.5.7", features = ["derive"] } diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..b3c1929 --- /dev/null +++ b/readme.md @@ -0,0 +1,33 @@ +# online-offsite-backup (name to change) + +This tool provides a way to offer others a service for backing up important +files automatically. + +Problem: Offsite backups could be as easy as a network filesystem - the problem +is however that these usually implement deleting files aswell. In the case where +a user's machine is comprimised by an attacker, these files could just be +deleted in the same way they were created, defeating the advantages of an +**offsite** backup. + +Solution: Implement a network "filesystem" that only supports non-destructive +operations. Only implementing upload means that backups will accumulate on the +server. This tool deletes backups after N newer backups (like a ring buffer). To +prevent quick successive uploads as a means of deleting backups a cooldown for +backup uploads is implemented. + +Intended Usage: This program is developed to be deployed on a number of servers +that are not under your control like backing up data of your friends. In such a +scenario, everyone in your friend group would run this software on their server +and negotiate keys with every other one. + +Possible attacks: This software primarily protects against the exact case +mentioned above especially when been attacked by automated malware on your +machine. Your remote backups servers may still be vulnerable to social +engineering and supply-chain attacks. + +Security: This software assumes security (and reliability) of the TCP +connections it makes: You **must** implement protection on this level yourself. +Backups are stored as-is on the remote server: If your backup requires it, +encrypt it! + +## Usage diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..243c621 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,69 @@ +#![feature(iterator_try_collect)] +#![feature(never_type)] +pub mod server; + +use clap::{Parser, Subcommand}; +use serde::Deserialize; +use server::server; +use std::{fs::read_to_string, net::SocketAddr, path::PathBuf}; + +pub type Serial = u64; + +#[derive(Parser)] +struct Args { + config: PathBuf, + #[clap(subcommand)] + action: Action, +} + +#[derive(Subcommand)] +enum Action { + Daemon, + Restore { id: String, destination: PathBuf }, + Backup { path: PathBuf }, +} + +#[derive(Deserialize)] +pub struct Config { + storage: StorageConfig, + server: ServerConfig, + peer: Vec<PeerConfig>, +} + +#[derive(Deserialize)] +pub struct PeerConfig { + name: String, + address: SocketAddr, + shared_secret: String, +} + +#[derive(Deserialize)] +pub struct ServerConfig { + address: String, +} + +#[derive(Deserialize)] +pub struct StorageConfig { + root: PathBuf, + size: u64, + versions: usize, + upload_cooldown: u64, + download_cooldown: u64, + upload_speed: usize, + download_speed: usize, +} + +fn main() -> anyhow::Result<()> { + env_logger::init_from_env("LOG"); + let args = Args::parse(); + + let config = read_to_string(&args.config)?; + let config = toml::from_str::<Config>(&config)?; + + match args.action { + Action::Daemon => server(config.into())?, + Action::Restore { id, destination } => todo!(), + Action::Backup { path } => todo!(), + } + Ok(()) +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..30ed0c8 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,228 @@ +use crate::{Config, PeerConfig, Serial}; +use anyhow::{anyhow, bail}; +use log::{error, info, warn}; +use serde::{Deserialize, Serialize}; +use std::{ + fs::{create_dir_all, read_to_string, remove_file, File}, + io::{copy, BufRead, BufReader, BufWriter, Read, Write}, + net::{SocketAddr, TcpListener, TcpStream}, + os::unix::fs::MetadataExt, + sync::{Arc, Mutex}, + thread::spawn, + time::SystemTime, +}; + +pub fn server(config: Arc<Config>) -> anyhow::Result<!> { + let listener = TcpListener::bind(&config.server.address)?; + info!("listening on {}", listener.local_addr()?); + loop { + let Ok((sock, addr)) = listener.accept() else { + error!("could not accept connection"); + continue; + }; + info!("connection from {addr}"); + let config = config.clone(); + spawn( + move || match handle_connection_wrapper(config, sock, addr) { + Ok(()) => (), + Err(err) => { + warn!("client error {addr}: {err:?}") + } + }, + ); + } +} + +fn handle_connection_wrapper( + config: Arc<Config>, + sock: TcpStream, + addr: SocketAddr, +) -> anyhow::Result<()> { + let mut rsock = BufReader::new(sock.try_clone()?); + let mut wsock = BufWriter::new(sock); + match handle_connection(config, &mut rsock, &mut wsock, addr) { + Ok(()) => Ok(()), + Err(e) => { + writeln!(wsock, "error,{e}")?; + wsock.flush()?; + Err(e) + } + } +} +fn handle_connection( + config: Arc<Config>, + rsock: &mut BufReader<TcpStream>, + wsock: &mut BufWriter<TcpStream>, + addr: SocketAddr, +) -> anyhow::Result<()> { + let mut line = String::new(); + + rsock.by_ref().take(4096).read_line(&mut line)?; + + let provided_secret = line.trim(); + eprintln!("{:?}", provided_secret); + let Some(peer) = config + .peer + .iter() + .find(|p| p.shared_secret == provided_secret) + else { + bail!("invalid secret"); + }; + + let peerdir = config.storage.root.join(&peer.name); + create_dir_all(&peerdir)?; + + loop { + line.clear(); + rsock.by_ref().take(4096).read_line(&mut line)?; + let mut toks = line.trim().split(","); + let command = toks.next().ok_or(anyhow!("command missing"))?; + eprintln!("{command:?}"); + match command { + "quit" => break Ok(()), + "list" => { + let mut dir = peerdir + .read_dir()? + .map(|e| { + let e = e?; + Ok::<_, anyhow::Error>(( + e.metadata()?.mtime(), + e.metadata()?.size(), + e.file_name() + .to_str() + .unwrap() + .to_string() + .parse::<Serial>()?, + )) + }) + .try_collect::<Vec<_>>()?; + dir.sort_by_key(|(_, _, a)| *a); + + for (m, s, p) in dir { + writeln!(wsock, "{m}:{s}:{p}")?; + } + writeln!(wsock)?; + wsock.flush()?; + } + "upload" => { + let size = toks.next().ok_or(anyhow!("size missing"))?.parse::<u64>()?; + if size > config.storage.size { + bail!("maximum size exceeded") + } + + let serial = transact_user_state(&config, peer, |s| { + if s.last_upload.elapsed().unwrap().as_secs() > config.storage.upload_cooldown { + s.last_upload = SystemTime::now(); + s.serial += 1; + Some(s.serial) + } else { + None + } + })? + .ok_or(anyhow!("upload cooldown"))?; + writeln!(wsock, "ready")?; + wsock.flush()?; + + while peerdir.read_dir()?.fold(0, |a, _| a + 1) > config.storage.versions { + let mut dir = peerdir + .read_dir()? + .map(|e| { + let e = e?; + Ok::<_, anyhow::Error>( + e.file_name() + .to_str() + .unwrap() + .to_string() + .parse::<Serial>()?, + ) + }) + .try_collect::<Vec<_>>()?; + dir.sort(); + let rem = dir[0]; + info!("removing serial={rem}"); + remove_file(peerdir.join(rem.to_string()))?; + } + + info!("upload from {addr} size={size}"); + let mut upload = rsock.get_ref().take(size); + let mut target = + BufWriter::new(File::create_new(peerdir.join(serial.to_string()))?); + + copy(&mut upload, &mut target)?; // TODO speed limit + + info!("done {addr}"); + writeln!(wsock, "done")?; + wsock.flush()?; + } + "download" => { + let serial = toks + .next() + .ok_or(anyhow!("serial missing"))? + .parse::<Serial>()?; + let ok = transact_user_state(&config, peer, |s| { + if s.last_download.elapsed().unwrap().as_secs() + > config.storage.download_cooldown + { + s.last_download = SystemTime::now(); + true + } else { + false + } + })?; + if !ok { + bail!("download cooldown") + } + + let source = File::open(peerdir.join(serial.to_string()))?; + let size = source.metadata()?.size(); + let mut source = BufReader::new(source); + + writeln!(wsock, "ready,{size}")?; + wsock.flush()?; + copy(&mut source, wsock)?; // TODO speed limit + + writeln!(wsock, "done")?; + wsock.flush()?; + } + _ => bail!("unknown command"), + } + } +} + +#[derive(Serialize, Deserialize)] +struct PeerState { + last_upload: SystemTime, + last_download: SystemTime, + serial: Serial, +} + +static USER_DB: Mutex<()> = Mutex::new(()); +fn transact_user_state<T>( + config: &Config, + peer: &PeerConfig, + update: impl FnOnce(&mut PeerState) -> T, +) -> anyhow::Result<T> { + let _g = USER_DB + .lock() + .map_err(|_| anyhow!("user database locked"))?; + + let conf_path = config.storage.root.join(&peer.name).with_extension("db"); + + let mut state = read_to_string(&conf_path) + .map(|s| toml::from_str(&s)) + .unwrap_or_else(|_| { + Ok(PeerState { + last_download: SystemTime::UNIX_EPOCH, + last_upload: SystemTime::UNIX_EPOCH, + serial: 0, + }) + })?; + + let res = update(&mut state); + let ser_state = toml::to_string_pretty(&state)?; + + File::create(conf_path)?.write_all(ser_state.as_bytes())?; + + drop(_g); + Ok(res) +} |