From 6ce083eca72e96d1390e460d5809eb49df2095ee Mon Sep 17 00:00:00 2001 From: edouardparis Date: Mon, 18 Nov 2024 09:51:37 +0100 Subject: [PATCH] liana: remove the daemon feature We expect user to use systemd service to manage a daemon or to handle their process by themself. The json-rpc server is exposed only for unix systems through unix sockets, it is moved to its own module while the implementation for windows is marked as TODO. --- Cargo.lock | 1 - liana/Cargo.toml | 7 -- liana/src/bin/daemon.rs | 7 +- liana/src/daemonize.rs | 69 ------------------- liana/src/jsonrpc/server/mod.rs | 31 +++++++++ .../src/jsonrpc/{server.rs => server/unix.rs} | 20 +++--- liana/src/lib.rs | 50 ++------------ liana/src/testutils.rs | 23 ++----- 8 files changed, 52 insertions(+), 156 deletions(-) delete mode 100644 liana/src/daemonize.rs create mode 100644 liana/src/jsonrpc/server/mod.rs rename liana/src/jsonrpc/{server.rs => server/unix.rs} (98%) diff --git a/Cargo.lock b/Cargo.lock index fef627829..a7cbd833a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2812,7 +2812,6 @@ dependencies = [ "fern", "getrandom", "jsonrpc 0.17.0", - "libc", "log", "miniscript", "rdrand", diff --git a/liana/Cargo.toml b/liana/Cargo.toml index c348d97ba..79f7745d2 100644 --- a/liana/Cargo.toml +++ b/liana/Cargo.toml @@ -12,16 +12,12 @@ exclude = [".github/", ".cirrus.yml", "tests/", "test_data/", "contrib/", "pypr [[bin]] name = "lianad" path = "src/bin/daemon.rs" -required-features = ["daemon"] [[bin]] name = "liana-cli" path = "src/bin/cli.rs" -required-features = ["daemon"] [features] -default = ["daemon"] -daemon = ["libc"] nonblocking_shutdown = [] [dependencies] @@ -59,9 +55,6 @@ rusqlite = { version = "0.30", features = ["bundled", "unlock_notify"] } # To talk to bitcoind jsonrpc = { version = "0.17", features = ["minreq_http"], default-features = false } -# Used for daemonization -libc = { version = "0.2", optional = true } - # Used for generating mnemonics getrandom = "0.2" diff --git a/liana/src/bin/daemon.rs b/liana/src/bin/daemon.rs index 654d9b9bf..0b050d920 100644 --- a/liana/src/bin/daemon.rs +++ b/liana/src/bin/daemon.rs @@ -80,12 +80,7 @@ fn main() { process::exit(1); }); - let handle = DaemonHandle::start_default( - config, - #[cfg(all(unix, feature = "daemon"))] - true, - ) - .unwrap_or_else(|e| { + let handle = DaemonHandle::start_default(config, cfg!(unix)).unwrap_or_else(|e| { log::error!("Error starting Liana daemon: {}", e); process::exit(1); }); diff --git a/liana/src/daemonize.rs b/liana/src/daemonize.rs deleted file mode 100644 index 3feeac8bd..000000000 --- a/liana/src/daemonize.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::env::set_current_dir; -use std::fs::File; -use std::fs::OpenOptions; -use std::io::prelude::*; -use std::os::unix::io::AsRawFd; -use std::path::Path; - -// This code was highly inspired from Frank Denis (@jedisct1) 'daemonize-simple' crate, -// available at https://github.com/jedisct1/rust-daemonize-simple/blob/master/src/unix.rs . -// MIT licensed according to https://github.com/jedisct1/rust-daemonize-simple/blob/master/Cargo.toml -pub unsafe fn daemonize( - chdir: &Path, - log_file: &Path, - pid_file: &Path, -) -> Result<(), &'static str> { - match libc::fork() { - -1 => return Err("fork() failed"), - 0 => {} - _ => { - libc::_exit(0); - } - } - libc::setsid(); - match libc::fork() { - -1 => return Err("Second fork() failed"), - 0 => {} - _ => { - libc::_exit(0); - } - }; - - let fd = OpenOptions::new() - .read(true) - .open("/dev/null") - .map_err(|_| "Unable to open the stdin file")?; - if libc::dup2(fd.as_raw_fd(), 0) == -1 { - return Err("dup2(stdin) failed"); - } - let fd = OpenOptions::new() - .create(true) - .append(true) - .open(log_file) - .map_err(|_| "Unable to open the stdout file")?; - if libc::dup2(fd.as_raw_fd(), 1) == -1 { - return Err("dup2(stdout) failed"); - } - let fd = OpenOptions::new() - .create(true) - .append(true) - .open(log_file) - .map_err(|_| "Unable to open the stderr file")?; - if libc::dup2(fd.as_raw_fd(), 2) == -1 { - return Err("dup2(stderr) failed"); - } - - let pid = match libc::getpid() { - -1 => return Err("getpid() failed"), - pid => pid, - }; - let pid_str = format!("{}", pid); - File::create(pid_file) - .map_err(|_| "Creating the PID file failed")? - .write_all(pid_str.as_bytes()) - .map_err(|_| "Writing to the PID file failed")?; - - set_current_dir(chdir).map_err(|_| "chdir() failed")?; - - Ok(()) -} diff --git a/liana/src/jsonrpc/server/mod.rs b/liana/src/jsonrpc/server/mod.rs new file mode 100644 index 000000000..b54268a2b --- /dev/null +++ b/liana/src/jsonrpc/server/mod.rs @@ -0,0 +1,31 @@ +#[cfg(unix)] +mod unix; + +use std::{ + io, path, + sync::{atomic::AtomicBool, Arc}, +}; + +use crate::DaemonControl; + +#[cfg(unix)] +pub fn run( + socket_path: &path::Path, + daemon_control: DaemonControl, + shutdown: Arc, +) -> Result<(), io::Error> { + let listener = unix::rpcserver_setup(socket_path)?; + log::info!("JSONRPC server started."); + let res = unix::rpcserver_loop(listener, daemon_control, shutdown); + log::info!("JSONRPC server stopped."); + res +} + +#[cfg(windows)] +pub fn run( + _socket_path: &path::Path, + _daemon_control: DaemonControl, + _shutdown: Arc, +) -> Result<(), io::Error> { + todo!("Implement a json rpc server over Named pipe"); +} diff --git a/liana/src/jsonrpc/server.rs b/liana/src/jsonrpc/server/unix.rs similarity index 98% rename from liana/src/jsonrpc/server.rs rename to liana/src/jsonrpc/server/unix.rs index 85e1261b9..b0e542310 100644 --- a/liana/src/jsonrpc/server.rs +++ b/liana/src/jsonrpc/server/unix.rs @@ -9,8 +9,8 @@ use crate::{ }; use std::{ - io, - os::unix::net, + fs, io, + os::unix::{fs::PermissionsExt, net}, path, sync::{self, atomic}, thread, time, @@ -194,17 +194,13 @@ fn bind(socket_path: &path::Path) -> Result { pub fn rpcserver_setup(socket_path: &path::Path) -> Result { log::debug!("Binding socket at {}", socket_path.display()); // Create the socket with RW permissions only for the user - #[cfg(not(test))] - let old_umask = unsafe { libc::umask(0o177) }; - #[allow(clippy::all)] - let listener = bind(socket_path); - - #[cfg(not(test))] - unsafe { - libc::umask(old_umask); - } + let listener = bind(socket_path)?; + + // Set the permissions to RW for the user only + let permissions = fs::Permissions::from_mode(0o600); + fs::set_permissions(socket_path, permissions)?; - listener + Ok(listener) } #[cfg(test)] diff --git a/liana/src/lib.rs b/liana/src/lib.rs index 61d6eae13..045111738 100644 --- a/liana/src/lib.rs +++ b/liana/src/lib.rs @@ -1,11 +1,8 @@ mod bitcoin; pub mod commands; pub mod config; -#[cfg(all(unix, feature = "daemon"))] -mod daemonize; mod database; pub mod descriptors; -#[cfg(all(unix, feature = "daemon"))] mod jsonrpc; pub mod random; pub mod signer; @@ -22,8 +19,8 @@ pub use crate::bitcoin::{ d::{BitcoinD, BitcoindError, WalletError}, electrum::{Electrum, ElectrumError}, }; -#[cfg(all(unix, feature = "daemon"))] -use crate::jsonrpc::server::{rpcserver_loop, rpcserver_setup}; + +use crate::jsonrpc::server; use crate::{ bitcoin::{poller, BitcoinInterface}, config::Config, @@ -411,7 +408,6 @@ pub enum DaemonHandle { poller_handle: thread::JoinHandle<()>, control: DaemonControl, }, - #[cfg(feature = "daemon")] Server { poller_sender: mpsc::SyncSender, poller_handle: thread::JoinHandle<()>, @@ -435,7 +431,7 @@ impl DaemonHandle { config: Config, bitcoin: Option, db: Option, - #[cfg(all(unix, feature = "daemon"))] with_rpc_server: bool, + with_rpc_server: bool, ) -> Result { #[cfg(not(test))] setup_panic_hook(); @@ -490,20 +486,6 @@ impl DaemonHandle { (None, None) => Err(StartupError::MissingBitcoinBackendConfig)?, }; - // If we are on a UNIX system and they told us to daemonize, do it now. - // NOTE: it's safe to daemonize now, as we don't carry any open DB connection - // https://www.sqlite.org/howtocorrupt.html#_carrying_an_open_database_connection_across_a_fork_ - #[cfg(all(unix, feature = "daemon"))] - if config.daemon { - log::info!("Daemonizing"); - let log_file = data_dir.as_path().join("log"); - let pid_file = data_dir.as_path().join("lianad.pid"); - unsafe { - daemonize::daemonize(&data_dir, &log_file, &pid_file) - .map_err(StartupError::Daemonization)?; - } - } - // Start the poller thread. Keep the thread handle to be able to check if it crashed. Store // an atomic to be able to stop it. let mut bitcoin_poller = @@ -525,7 +507,6 @@ impl DaemonHandle { // structure or through the JSONRPC server we may setup below. let control = DaemonControl::new(config, bit, poller_sender.clone(), db, secp); - #[cfg(all(unix, feature = "daemon"))] if with_rpc_server { let rpcserver_shutdown = sync::Arc::from(sync::atomic::AtomicBool::from(false)); let rpcserver_handle = thread::Builder::new() @@ -535,11 +516,7 @@ impl DaemonHandle { move || { let mut rpc_socket = data_dir; rpc_socket.push("lianad_rpc"); - let listener = rpcserver_setup(&rpc_socket)?; - log::info!("JSONRPC server started."); - - rpcserver_loop(listener, control, shutdown)?; - log::info!("JSONRPC server stopped."); + server::run(&rpc_socket, control, shutdown)?; Ok(()) } }) @@ -564,13 +541,12 @@ impl DaemonHandle { /// and SQLite). pub fn start_default( config: Config, - #[cfg(all(unix, feature = "daemon"))] with_rpc_server: bool, + with_rpc_server: bool, ) -> Result { Self::start( config, Option::::None, Option::::None, - #[cfg(all(unix, feature = "daemon"))] with_rpc_server, ) } @@ -583,7 +559,6 @@ impl DaemonHandle { Self::Controller { ref poller_handle, .. } => !poller_handle.is_finished(), - #[cfg(feature = "daemon")] Self::Server { ref poller_handle, ref rpcserver_handle, @@ -606,7 +581,6 @@ impl DaemonHandle { poller_handle.join().expect("Poller thread must not panic"); Ok(()) } - #[cfg(feature = "daemon")] Self::Server { poller_sender, poller_handle, @@ -867,12 +841,7 @@ mod tests { let t = thread::spawn({ let config = config.clone(); move || { - let handle = DaemonHandle::start_default( - config, - #[cfg(all(unix, feature = "daemon"))] - false, - ) - .unwrap(); + let handle = DaemonHandle::start_default(config, false).unwrap(); handle.stop().unwrap(); } }); @@ -892,12 +861,7 @@ mod tests { let t = thread::spawn({ let config = config.clone(); move || { - let handle = DaemonHandle::start_default( - config, - #[cfg(all(unix, feature = "daemon"))] - false, - ) - .unwrap(); + let handle = DaemonHandle::start_default(config, false).unwrap(); handle.stop().unwrap(); } }); diff --git a/liana/src/testutils.rs b/liana/src/testutils.rs index 50026408c..dd63ef937 100644 --- a/liana/src/testutils.rs +++ b/liana/src/testutils.rs @@ -544,7 +544,7 @@ impl DummyLiana { pub fn _new( bitcoin_interface: impl BitcoinInterface + 'static, database: impl DatabaseInterface + 'static, - #[cfg(all(unix, feature = "daemon"))] rpc_server: bool, + rpc_server: bool, ) -> DummyLiana { let tmp_dir = tmp_dir(); fs::create_dir_all(&tmp_dir).unwrap(); @@ -569,20 +569,14 @@ impl DummyLiana { bitcoin_config, bitcoin_backend: None, data_dir: Some(data_dir), - #[cfg(unix)] daemon: false, log_level: log::LevelFilter::Debug, main_descriptor: desc, }; - let handle = DaemonHandle::start( - config, - Some(bitcoin_interface), - Some(database), - #[cfg(all(unix, feature = "daemon"))] - rpc_server, - ) - .unwrap(); + let handle = + DaemonHandle::start(config, Some(bitcoin_interface), Some(database), rpc_server) + .unwrap(); DummyLiana { tmp_dir, handle } } @@ -591,16 +585,10 @@ impl DummyLiana { bitcoin_interface: impl BitcoinInterface + 'static, database: impl DatabaseInterface + 'static, ) -> DummyLiana { - Self::_new( - bitcoin_interface, - database, - #[cfg(all(unix, feature = "daemon"))] - false, - ) + Self::_new(bitcoin_interface, database, false) } /// Creates a new DummyLiana interface which also spins up an RPC server. - #[cfg(all(unix, feature = "daemon"))] pub fn new_server( bitcoin_interface: impl BitcoinInterface + 'static, database: impl DatabaseInterface + 'static, @@ -611,7 +599,6 @@ impl DummyLiana { pub fn control(&self) -> &DaemonControl { match self.handle { DaemonHandle::Controller { ref control, .. } => control, - #[cfg(feature = "daemon")] DaemonHandle::Server { .. } => unreachable!(), } }