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/contrib/lianad_config_example.toml b/contrib/lianad_config_example.toml index b0edd609c..35b4b2560 100644 --- a/contrib/lianad_config_example.toml +++ b/contrib/lianad_config_example.toml @@ -1,6 +1,3 @@ -# Whether to run the process as a UNIX daemon (double fork magic) -daemon = false - # (Optional) Path to the folder where we should store the application data. # Defaults to `.lianad` in your home folder. data_dir = "/home/wizardsardine/.lianad" 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/config.rs b/liana/src/config.rs index ab3f70bdd..2399e161b 100644 --- a/liana/src/config.rs +++ b/liana/src/config.rs @@ -80,11 +80,6 @@ fn default_poll_interval() -> Duration { Duration::from_secs(30) } -#[cfg(unix)] -fn default_daemon() -> bool { - false -} - /// Bitcoin backend config. #[derive(Debug, Clone, Deserialize, Serialize)] pub enum BitcoinBackend { @@ -153,10 +148,6 @@ pub struct BitcoinConfig { pub struct Config { /// An optional custom data directory pub data_dir: Option, - /// Whether to daemonize the process - #[cfg(unix)] - #[serde(default = "default_daemon")] - pub daemon: bool, /// What messages to log #[serde( deserialize_with = "deserialize_fromstr", @@ -303,7 +294,6 @@ mod tests { // A valid config let toml_str = r#" data_dir = "/home/wizardsardine/custom/folder/" - daemon = false log_level = "debug" main_descriptor = "wsh(andor(pk([aabbccdd]tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/<0;1>/*),older(10000),pk([aabbccdd]tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/<0;1>/*)))#dw4ulnrs" @@ -318,11 +308,9 @@ mod tests { toml::from_str::(&toml_str).expect("Deserializing toml_str"); // A valid, round-tripping, config - #[cfg(unix)] // On non-UNIX there is no 'daemon' member. { let toml_str = r#" data_dir = '/home/wizardsardine/custom/folder/' - daemon = false log_level = 'TRACE' main_descriptor = 'wsh(andor(pk([aabbccdd]tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/<0;1>/*),older(10000),pk([aabbccdd]tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/<0;1>/*)))#dw4ulnrs' @@ -340,12 +328,9 @@ mod tests { } // A valid, round-tripping, config for a Taproot descriptor. - - #[cfg(unix)] // On non-UNIX there is no 'daemon' member. { let toml_str = r#" data_dir = '/home/wizardsardine/custom/folder/' - daemon = false log_level = 'TRACE' main_descriptor = 'tr([abcdef01]xpub6Eze7yAT3Y1wGrnzedCNVYDXUqa9NmHVWck5emBaTbXtURbe1NWZbK9bsz1TiVE7Cz341PMTfYgFw1KdLWdzcM1UMFTcdQfCYhhXZ2HJvTW/<0;1>/*,and_v(v:pk([abcdef01]xpub688Hn4wScQAAiYJLPg9yH27hUpfZAUnmJejRQBCiwfP5PEDzjWMNW1wChcninxr5gyavFqbbDjdV1aK5USJz8NDVjUy7FRQaaqqXHh5SbXe/<0;1>/*),older(52560)))#0mt7e93c' @@ -363,11 +348,9 @@ mod tests { } // A valid, round-tripping, config with `auth` instead of `cookie_path` - #[cfg(unix)] // On non-UNIX there is no 'daemon' member. { let toml_str = r#" data_dir = '/home/wizardsardine/custom/folder/' - daemon = false log_level = 'TRACE' main_descriptor = 'wsh(andor(pk([aabbccdd]tpubDEN9WSToTyy9ZQfaYqSKfmVqmq1VVLNtYfj3Vkqh67et57eJ5sTKZQBkHqSwPUsoSskJeaYnPttHe2VrkCsKA27kUaN9SDc5zhqeLzKa1rr/<0;1>/*),older(10000),pk([aabbccdd]tpubD8LYfn6njiA2inCoxwM7EuN3cuLVcaHAwLYeups13dpevd3nHLRdK9NdQksWXrhLQVxcUZRpnp5CkJ1FhE61WRAsHxDNAkvGkoQkAeWDYjV/<0;1>/*)))#dw4ulnrs' @@ -386,7 +369,6 @@ mod tests { // Invalid desc checksum let toml_str = r#" - daemon = false log_level = "trace" data_dir = "/home/wizardsardine/custom/folder/" @@ -405,7 +387,6 @@ mod tests { // Not enough parameters: missing the Bitcoin network let toml_str = r#" - daemon = false log_level = "trace" data_dir = "/home/wizardsardine/custom/folder/" 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/api.rs b/liana/src/jsonrpc/api.rs index 3ff137afb..d27a7f1d9 100644 --- a/liana/src/jsonrpc/api.rs +++ b/liana/src/jsonrpc/api.rs @@ -1,6 +1,6 @@ use crate::{ commands::{CoinStatus, LabelItem}, - jsonrpc::{Error, Params, Request, Response}, + jsonrpc::rpc::{Error, Params, Request, Response}, DaemonControl, }; diff --git a/liana/src/jsonrpc/mod.rs b/liana/src/jsonrpc/mod.rs index 131a96a6b..027774111 100644 --- a/liana/src/jsonrpc/mod.rs +++ b/liana/src/jsonrpc/mod.rs @@ -1,215 +1,5 @@ +#[cfg(unix)] mod api; +#[cfg(unix)] +pub mod rpc; pub mod server; - -use crate::commands; - -use std::{error, fmt}; - -use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; - -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum Params { - Array(Vec), - Map(serde_json::Map), -} - -impl Params { - /// Get the parameter supposed to be at a given index / of a given name. - pub fn get(&self, index: usize, name: &Q) -> Option<&serde_json::Value> - where - String: std::borrow::Borrow, - Q: ?Sized + Ord + Eq + std::hash::Hash, - { - match self { - Params::Array(vec) => vec.get(index), - Params::Map(map) => map.get(name), - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -#[serde(untagged)] -pub enum ReqId { - Num(u64), - Str(String), -} - -/// A JSONRPC2 request. See https://www.jsonrpc.org/specification#request_object. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Request { - /// Version. Must be "2.0". - pub jsonrpc: String, - /// Command name. - pub method: String, - /// Command parameters. - pub params: Option, - /// Request identifier. - pub id: ReqId, -} - -/// A failure to broadcast a transaction to the P2P network. -const BROADCAST_ERROR: i64 = 1_000; - -/// JSONRPC2 error codes. See https://www.jsonrpc.org/specification#error_object. -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum ErrorCode { - /// The method does not exist / is not available. - MethodNotFound, - /// Invalid method parameter(s). - InvalidParams, - /// Internal error while handling the command. - InternalError, - /// Reserved for implementation-defined server-errors. - ServerError(i64), -} - -impl From<&ErrorCode> for i64 { - fn from(code: &ErrorCode) -> i64 { - match code { - ErrorCode::MethodNotFound => -32601, - ErrorCode::InvalidParams => -32602, - ErrorCode::InternalError => -32603, - ErrorCode::ServerError(code) => *code, - } - } -} - -impl From for ErrorCode { - fn from(code: i64) -> ErrorCode { - match code { - -32601 => ErrorCode::MethodNotFound, - -32602 => ErrorCode::InvalidParams, - -32603 => ErrorCode::InternalError, - code => ErrorCode::ServerError(code), - } - } -} - -impl<'a> Deserialize<'a> for ErrorCode { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - let code: i64 = Deserialize::deserialize(deserializer)?; - Ok(code.into()) - } -} - -impl Serialize for ErrorCode { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_i64(self.into()) - } -} - -/// JSONRPC2 error response. See https://www.jsonrpc.org/specification#error_object. -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Error { - pub code: ErrorCode, - pub message: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, -} - -impl Error { - pub fn new(code: ErrorCode, message: impl Into) -> Error { - Error { - message: message.into(), - code, - data: None, - } - } - - pub fn method_not_found() -> Error { - Error::new(ErrorCode::MethodNotFound, "Method not found") - } - - pub fn invalid_params(message: impl Into) -> Error { - Error::new( - ErrorCode::InvalidParams, - format!("Invalid params: {}", message.into()), - ) - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let code: i64 = (&self.code).into(); - write!(f, "{}: {}", code, self.message) - } -} - -impl error::Error for Error {} - -impl From for Error { - fn from(e: commands::CommandError) -> Error { - match e { - commands::CommandError::NoOutpointForSelfSend - | commands::CommandError::UnknownOutpoint(..) - | commands::CommandError::InvalidFeerate(..) - | commands::CommandError::AlreadySpent(..) - | commands::CommandError::ImmatureCoinbase(..) - | commands::CommandError::Address(..) - | commands::CommandError::SpendCreation(..) - | commands::CommandError::InsufficientFunds(..) - | commands::CommandError::UnknownSpend(..) - | commands::CommandError::SpendFinalization(..) - | commands::CommandError::InsaneRescanTimestamp(..) - | commands::CommandError::AlreadyRescanning - | commands::CommandError::InvalidDerivationIndex - | commands::CommandError::RbfError(..) - | commands::CommandError::EmptyFilterList - | commands::CommandError::RecoveryNotAvailable => { - Error::new(ErrorCode::InvalidParams, e.to_string()) - } - commands::CommandError::RescanTrigger(..) => { - Error::new(ErrorCode::InternalError, e.to_string()) - } - commands::CommandError::TxBroadcast(_) => { - Error::new(ErrorCode::ServerError(BROADCAST_ERROR), e.to_string()) - } - } - } -} - -/// JSONRPC2 response. See https://www.jsonrpc.org/specification#response_object. -#[derive(Clone, Debug, PartialEq, Eq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Response { - /// Version. Must be "2.0". - jsonrpc: String, - /// Required on success. Must not exist on error. - #[serde(skip_serializing_if = "Option::is_none")] - result: Option, - /// Required on error. Must not exist on success. - #[serde(skip_serializing_if = "Option::is_none")] - error: Option, - /// Request identifier. - id: ReqId, -} - -impl Response { - fn new(id: ReqId, result: Option, error: Option) -> Response { - Response { - jsonrpc: "2.0".to_string(), - result, - error, - id, - } - } - - pub fn success(id: ReqId, result: serde_json::Value) -> Response { - Response::new(id, Some(result), None) - } - - pub fn error(id: ReqId, error: Error) -> Response { - Response::new(id, None, Some(error)) - } -} diff --git a/liana/src/jsonrpc/rpc.rs b/liana/src/jsonrpc/rpc.rs new file mode 100644 index 000000000..dcfd19a83 --- /dev/null +++ b/liana/src/jsonrpc/rpc.rs @@ -0,0 +1,212 @@ +use crate::commands; + +use std::{error, fmt}; + +use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum Params { + Array(Vec), + Map(serde_json::Map), +} + +impl Params { + /// Get the parameter supposed to be at a given index / of a given name. + pub fn get(&self, index: usize, name: &Q) -> Option<&serde_json::Value> + where + String: std::borrow::Borrow, + Q: ?Sized + Ord + Eq + std::hash::Hash, + { + match self { + Params::Array(vec) => vec.get(index), + Params::Map(map) => map.get(name), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum ReqId { + Num(u64), + Str(String), +} + +/// A JSONRPC2 request. See https://www.jsonrpc.org/specification#request_object. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Request { + /// Version. Must be "2.0". + pub jsonrpc: String, + /// Command name. + pub method: String, + /// Command parameters. + pub params: Option, + /// Request identifier. + pub id: ReqId, +} + +/// A failure to broadcast a transaction to the P2P network. +const BROADCAST_ERROR: i64 = 1_000; + +/// JSONRPC2 error codes. See https://www.jsonrpc.org/specification#error_object. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ErrorCode { + /// The method does not exist / is not available. + MethodNotFound, + /// Invalid method parameter(s). + InvalidParams, + /// Internal error while handling the command. + InternalError, + /// Reserved for implementation-defined server-errors. + ServerError(i64), +} + +impl From<&ErrorCode> for i64 { + fn from(code: &ErrorCode) -> i64 { + match code { + ErrorCode::MethodNotFound => -32601, + ErrorCode::InvalidParams => -32602, + ErrorCode::InternalError => -32603, + ErrorCode::ServerError(code) => *code, + } + } +} + +impl From for ErrorCode { + fn from(code: i64) -> ErrorCode { + match code { + -32601 => ErrorCode::MethodNotFound, + -32602 => ErrorCode::InvalidParams, + -32603 => ErrorCode::InternalError, + code => ErrorCode::ServerError(code), + } + } +} + +impl<'a> Deserialize<'a> for ErrorCode { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + let code: i64 = Deserialize::deserialize(deserializer)?; + Ok(code.into()) + } +} + +impl Serialize for ErrorCode { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_i64(self.into()) + } +} + +/// JSONRPC2 error response. See https://www.jsonrpc.org/specification#error_object. +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Error { + pub code: ErrorCode, + pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +impl Error { + pub fn new(code: ErrorCode, message: impl Into) -> Error { + Error { + message: message.into(), + code, + data: None, + } + } + + pub fn method_not_found() -> Error { + Error::new(ErrorCode::MethodNotFound, "Method not found") + } + + pub fn invalid_params(message: impl Into) -> Error { + Error::new( + ErrorCode::InvalidParams, + format!("Invalid params: {}", message.into()), + ) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let code: i64 = (&self.code).into(); + write!(f, "{}: {}", code, self.message) + } +} + +impl error::Error for Error {} + +impl From for Error { + fn from(e: commands::CommandError) -> Error { + match e { + commands::CommandError::NoOutpointForSelfSend + | commands::CommandError::UnknownOutpoint(..) + | commands::CommandError::InvalidFeerate(..) + | commands::CommandError::AlreadySpent(..) + | commands::CommandError::ImmatureCoinbase(..) + | commands::CommandError::Address(..) + | commands::CommandError::SpendCreation(..) + | commands::CommandError::InsufficientFunds(..) + | commands::CommandError::UnknownSpend(..) + | commands::CommandError::SpendFinalization(..) + | commands::CommandError::InsaneRescanTimestamp(..) + | commands::CommandError::AlreadyRescanning + | commands::CommandError::InvalidDerivationIndex + | commands::CommandError::RbfError(..) + | commands::CommandError::EmptyFilterList + | commands::CommandError::RecoveryNotAvailable => { + Error::new(ErrorCode::InvalidParams, e.to_string()) + } + commands::CommandError::RescanTrigger(..) => { + Error::new(ErrorCode::InternalError, e.to_string()) + } + commands::CommandError::TxBroadcast(_) => { + Error::new(ErrorCode::ServerError(BROADCAST_ERROR), e.to_string()) + } + } + } +} + +/// JSONRPC2 response. See https://www.jsonrpc.org/specification#response_object. +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Response { + /// Version. Must be "2.0". + jsonrpc: String, + /// Required on success. Must not exist on error. + #[serde(skip_serializing_if = "Option::is_none")] + result: Option, + /// Required on error. Must not exist on success. + #[serde(skip_serializing_if = "Option::is_none")] + error: Option, + /// Request identifier. + id: ReqId, +} + +impl Response { + fn new(id: ReqId, result: Option, error: Option) -> Response { + Response { + jsonrpc: "2.0".to_string(), + result, + error, + id, + } + } + + pub fn success(id: ReqId, result: serde_json::Value) -> Response { + Response::new(id, Some(result), None) + } + + pub fn error(id: ReqId, error: Error) -> Response { + Response::new(id, None, Some(error)) + } +} 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 97% rename from liana/src/jsonrpc/server.rs rename to liana/src/jsonrpc/server/unix.rs index 85e1261b9..121ccd0d3 100644 --- a/liana/src/jsonrpc/server.rs +++ b/liana/src/jsonrpc/server/unix.rs @@ -4,13 +4,16 @@ //! JSONRPC2 requests on a Unix Domain Socket. use crate::{ - jsonrpc::{api, Request, Response}, + jsonrpc::{ + api, + rpc::{Request, Response}, + }, DaemonControl, }; use std::{ - io, - os::unix::net, + fs, io, + os::unix::{fs::PermissionsExt, net}, path, sync::{self, atomic}, thread, time, @@ -194,24 +197,20 @@ 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)] mod tests { use super::*; use crate::{ - jsonrpc::{Params, ReqId}, + jsonrpc::rpc::{Params, ReqId}, testutils::*, }; diff --git a/liana/src/lib.rs b/liana/src/lib.rs index 61d6eae13..23ed20cc9 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, @@ -103,8 +100,6 @@ pub enum StartupError { Database(SqliteDbError), Bitcoind(BitcoindError), Electrum(ElectrumError), - #[cfg(unix)] - Daemonization(&'static str), #[cfg(windows)] NoWatchonlyInDatadir, } @@ -140,8 +135,6 @@ impl fmt::Display for StartupError { Self::Database(e) => write!(f, "Error initializing database: '{}'.", e), Self::Bitcoind(e) => write!(f, "Error setting up bitcoind interface: '{}'.", e), Self::Electrum(e) => write!(f, "Error setting up Electrum interface: '{}'.", e), - #[cfg(unix)] - Self::Daemonization(e) => write!(f, "Error when daemonizing: '{}'.", e), #[cfg(windows)] Self::NoWatchonlyInDatadir => { write!( @@ -411,7 +404,6 @@ pub enum DaemonHandle { poller_handle: thread::JoinHandle<()>, control: DaemonControl, }, - #[cfg(feature = "daemon")] Server { poller_sender: mpsc::SyncSender, poller_handle: thread::JoinHandle<()>, @@ -435,7 +427,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 +482,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 +503,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 +512,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 +537,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 +555,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 +577,6 @@ impl DaemonHandle { poller_handle.join().expect("Poller thread must not panic"); Ok(()) } - #[cfg(feature = "daemon")] Self::Server { poller_sender, poller_handle, @@ -857,8 +827,6 @@ mod tests { bitcoin_config, bitcoin_backend: Some(config::BitcoinBackend::Bitcoind(bitcoind_config)), data_dir: Some(data_dir), - #[cfg(unix)] - daemon: false, log_level: log::LevelFilter::Debug, main_descriptor: desc, }; @@ -867,12 +835,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 +855,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..d7d7c6799 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,13 @@ 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 +584,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 +598,6 @@ impl DummyLiana { pub fn control(&self) -> &DaemonControl { match self.handle { DaemonHandle::Controller { ref control, .. } => control, - #[cfg(feature = "daemon")] DaemonHandle::Server { .. } => unreachable!(), } } diff --git a/tests/test_framework/lianad.py b/tests/test_framework/lianad.py index 4bc5a84f6..94bf23056 100644 --- a/tests/test_framework/lianad.py +++ b/tests/test_framework/lianad.py @@ -50,7 +50,6 @@ def __init__( with open(self.conf_file, "w") as f: f.write(f"data_dir = '{datadir}'\n") - f.write("daemon = false\n") f.write(f"log_level = '{LOG_LEVEL}'\n") f.write(f'main_descriptor = "{multi_desc}"\n')