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.
This commit is contained in:
edouardparis 2024-11-18 09:51:37 +01:00
parent d83f34c876
commit a636cda2f7
8 changed files with 51 additions and 158 deletions

1
Cargo.lock generated
View File

@ -2812,7 +2812,6 @@ dependencies = [
"fern",
"getrandom",
"jsonrpc 0.17.0",
"libc",
"log",
"miniscript",
"rdrand",

View File

@ -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"

View File

@ -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);
});

View File

@ -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(())
}

View File

@ -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<AtomicBool>,
) -> 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<AtomicBool>,
) -> Result<(), io::Error> {
todo!("Implement a json rpc server over Named pipe");
}

View File

@ -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<net::UnixListener, io::Error> {
pub fn rpcserver_setup(socket_path: &path::Path) -> Result<net::UnixListener, io::Error> {
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);
let listener = bind(socket_path)?;
#[cfg(not(test))]
unsafe {
libc::umask(old_umask);
}
// 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)]

View File

@ -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::PollerMessage>,
poller_handle: thread::JoinHandle<()>,
@ -435,7 +427,7 @@ impl DaemonHandle {
config: Config,
bitcoin: Option<impl BitcoinInterface + 'static>,
db: Option<impl DatabaseInterface + 'static>,
#[cfg(all(unix, feature = "daemon"))] with_rpc_server: bool,
with_rpc_server: bool,
) -> Result<Self, StartupError> {
#[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<DaemonHandle, StartupError> {
Self::start(
config,
Option::<BitcoinD>::None,
Option::<SqliteDb>::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,
@ -867,12 +837,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 +857,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();
}
});

View File

@ -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();
@ -575,14 +575,9 @@ impl DummyLiana {
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 +586,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 +600,6 @@ impl DummyLiana {
pub fn control(&self) -> &DaemonControl {
match self.handle {
DaemonHandle::Controller { ref control, .. } => control,
#[cfg(feature = "daemon")]
DaemonHandle::Server { .. } => unreachable!(),
}
}