daemon: implement daemonize for UNIX platforms
This commit is contained in:
parent
c095346e17
commit
7340c13142
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -237,6 +237,7 @@ dependencies = [
|
||||
"dirs",
|
||||
"fern",
|
||||
"jsonrpc",
|
||||
"libc",
|
||||
"log",
|
||||
"miniscript",
|
||||
"rusqlite",
|
||||
|
||||
@ -42,3 +42,6 @@ rusqlite = { version = "0.28", features = ["bundled", "unlock_notify"] }
|
||||
|
||||
# To talk to bitcoind
|
||||
jsonrpc = "0.12"
|
||||
|
||||
# Used for daemonization
|
||||
libc = "0.2"
|
||||
|
||||
@ -45,6 +45,11 @@ fn default_poll_interval() -> Duration {
|
||||
Duration::from_secs(30)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn default_daemon() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Everything we need to know for talking to bitcoind serenely
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct BitcoindConfig {
|
||||
@ -69,7 +74,9 @@ pub struct Config {
|
||||
/// An optional custom data directory
|
||||
pub data_dir: Option<PathBuf>,
|
||||
/// Whether to daemonize the process
|
||||
pub daemon: Option<bool>,
|
||||
#[cfg(unix)]
|
||||
#[serde(default = "default_daemon")]
|
||||
pub daemon: bool,
|
||||
/// What messages to log
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_fromstr",
|
||||
@ -244,6 +251,7 @@ mod tests {
|
||||
"#.trim_start().replace(" ", "");
|
||||
let parsed = toml::from_str::<Config>(&toml_str).expect("Deserializing toml_str");
|
||||
let serialized = toml::to_string_pretty(&parsed).expect("Serializing to toml");
|
||||
#[cfg(unix)] // On non-UNIX there is no 'daemon' member.
|
||||
assert_eq!(toml_str, serialized);
|
||||
|
||||
// Invalid desc checksum
|
||||
|
||||
69
src/daemonize.rs
Normal file
69
src/daemonize.rs
Normal file
@ -0,0 +1,69 @@
|
||||
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,
|
||||
pid_file: &Path,
|
||||
log_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(())
|
||||
}
|
||||
23
src/lib.rs
23
src/lib.rs
@ -1,5 +1,7 @@
|
||||
mod bitcoin;
|
||||
pub mod config;
|
||||
#[cfg(unix)]
|
||||
mod daemonize;
|
||||
mod database;
|
||||
|
||||
use crate::{
|
||||
@ -50,6 +52,8 @@ pub enum StartupError {
|
||||
DatadirCreation(path::PathBuf, io::Error),
|
||||
Database(SqliteDbError),
|
||||
Bitcoind(BitcoindError),
|
||||
#[cfg(unix)]
|
||||
Daemonization(&'static str),
|
||||
}
|
||||
|
||||
impl fmt::Display for StartupError {
|
||||
@ -66,6 +70,8 @@ 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),
|
||||
#[cfg(unix)]
|
||||
Self::Daemonization(e) => write!(f, "Error when daemonizing: '{}'.", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,6 +176,20 @@ impl DaemonHandle {
|
||||
bitcoind.with_retry_limit(None);
|
||||
log::info!("Connection to bitcoind established and checked.");
|
||||
|
||||
// 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(unix)]
|
||||
if config.daemon {
|
||||
log::info!("Daemonizing");
|
||||
let log_file = data_dir.as_path().join("log");
|
||||
let pid_file = data_dir.as_path().join("revaultd.pid");
|
||||
unsafe {
|
||||
daemonize::daemonize(&data_dir, &log_file, &pid_file)
|
||||
.map_err(StartupError::Daemonization)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {})
|
||||
}
|
||||
|
||||
@ -369,7 +389,8 @@ mod tests {
|
||||
let config = Config {
|
||||
bitcoind_config,
|
||||
data_dir: Some(data_dir.clone()),
|
||||
daemon: None,
|
||||
#[cfg(unix)]
|
||||
daemon: false,
|
||||
log_level: log::LevelFilter::Debug,
|
||||
main_descriptor: desc,
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user