diff --git a/Cargo.lock b/Cargo.lock index 5572d159..ea906aef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,36 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bech32" version = "0.8.1" @@ -87,6 +117,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + [[package]] name = "itoa" version = "1.0.2" @@ -108,10 +144,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "minisafed" version = "0.0.1" dependencies = [ + "backtrace", "dirs", "fern", "log", @@ -131,6 +174,24 @@ dependencies = [ "serde", ] +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + [[package]] name = "proc-macro2" version = "1.0.40" @@ -169,6 +230,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "ryu" version = "1.0.10" diff --git a/Cargo.toml b/Cargo.toml index 642ed265..09bf554b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,8 @@ serde_json = { version = "1.0", features = ["raw_value"] } # Logging stuff log = "0.4" fern = "0.6" + +# In order to have a backtrace on panic, because the +# stdlib does not have a programmatic interface yet +# to work with our custom panic hook. +backtrace = "0.3" diff --git a/src/bin/daemon.rs b/src/bin/daemon.rs index 26e34670..ae0b31b1 100644 --- a/src/bin/daemon.rs +++ b/src/bin/daemon.rs @@ -5,7 +5,7 @@ use std::{ process, time, }; -use minisafed::config::Config; +use minisafed::{config::Config, DaemonHandle}; fn parse_args(args: Vec) -> Option { if args.len() == 1 { @@ -58,6 +58,11 @@ fn main() { process::exit(1); }); + let _ = DaemonHandle::start(config).unwrap_or_else(|e| { + // The panic hook will log::error + panic!("Starting Minisafe daemon: {}", e); + }); + // We are always logging to stdout, should it be then piped to the log file (if self) or // not. So just make sure that all messages were actually written. io::stdout().flush().expect("Flushing stdout"); diff --git a/src/lib.rs b/src/lib.rs index ef68c369..693b74a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,116 @@ pub mod config; + +use crate::config::{config_folder_path, Config}; + +use std::{error, fmt, fs, io, panic, path, process}; + +// A panic in any thread should stop the main thread, and print the panic. +fn setup_panic_hook() { + panic::set_hook(Box::new(move |panic_info| { + let file = panic_info + .location() + .map(|l| l.file()) + .unwrap_or_else(|| "'unknown'"); + let line = panic_info + .location() + .map(|l| l.line().to_string()) + .unwrap_or_else(|| "'unknown'".to_string()); + + let bt = backtrace::Backtrace::new(); + let info = panic_info + .payload() + .downcast_ref::<&str>() + .map(|s| s.to_string()) + .or_else(|| panic_info.payload().downcast_ref::().cloned()); + log::error!( + "panic occurred at line {} of file {}: {:?}\n{:?}", + line, + file, + info, + bt + ); + + process::exit(1); + })); +} + +#[derive(Debug)] +pub enum StartupError { + Io(io::Error), + DefaultDataDirNotFound, + DatadirCreation(path::PathBuf, io::Error), +} + +impl fmt::Display for StartupError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Io(e) => write!(f, "{}", e), + Self::DefaultDataDirNotFound => write!( + f, + "Not data directory was specified and a default path could not be determined for this platform." + ), + Self::DatadirCreation(dir_path, e) => write!( + f, + "Could not create data directory at '{}': '{}'", dir_path.display(), e + ), + } + } +} + +impl error::Error for StartupError {} + +impl From for StartupError { + fn from(e: io::Error) -> Self { + Self::Io(e) + } +} + +fn create_datadir(datadir_path: &path::Path) -> Result<(), StartupError> { + #[cfg(unix)] + return { + use fs::DirBuilder; + use std::os::unix::fs::DirBuilderExt; + + let mut builder = DirBuilder::new(); + builder + .mode(0o700) + .recursive(true) + .create(datadir_path) + .map_err(|e| StartupError::DatadirCreation(datadir_path.to_path_buf(), e)) + }; + + // TODO: permissions on Windows.. + #[cfg(not(unix))] + return { + fs::create_dir_all(datadir_path) + .map_err(|e| StartupError::DatadirCreation(datadir_path.to_path_buf(), e)) + }; +} + +pub struct DaemonHandle {} + +impl DaemonHandle { + /// This starts the Minisafe daemon. Call `shutdown` to shut it down. + /// + /// **Note**: we internally use threads, and set a panic hook. A downstream application must + /// not overwrite this panic hook. + pub fn start(config: Config) -> Result { + setup_panic_hook(); + + // First, check the data directory + let mut data_dir = config + .data_dir + .unwrap_or(config_folder_path().ok_or(StartupError::DefaultDataDirNotFound)?); + data_dir.push(config.bitcoind_config.network.to_string()); + if !data_dir.as_path().exists() { + create_datadir(&data_dir)?; + log::info!("Created a new data directory at '{}'", data_dir.display()); + } + + Ok(Self {}) + } + + // NOTE: this moves out the data as it should not be reused after shutdown + /// Shut down the Minisafe daemon. + pub fn shutdown(self) {} +}