From 3a8d82e1aead2e8548b249dc958122547b02ee0b Mon Sep 17 00:00:00 2001 From: Hugues de Valon Date: Wed, 16 Oct 2019 19:36:46 +0100 Subject: [PATCH] Add a compile-time option for a daemon binary The "systemd-daemon" feature will compile PARSEC to be used as a systemd, socket activated daemon. Add systemd unit files and instruction on how to install the service. Signed-off-by: Hugues de Valon --- Cargo.lock | 7 +++++ Cargo.toml | 5 ++++ README.md | 34 ++++++++++++++++++++++++ src/bin/main.rs | 4 +++ src/front/domain_socket.rs | 49 +++++++++++++++++++++++++---------- systemd-daemon/parsec.service | 8 ++++++ systemd-daemon/parsec.socket | 9 +++++++ 7 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 systemd-daemon/parsec.service create mode 100644 systemd-daemon/parsec.socket diff --git a/Cargo.lock b/Cargo.lock index 55d06f47..5f86e1a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -405,6 +405,7 @@ dependencies = [ "parsec-client-test 0.1.1 (git+https://github.com/parallaxsecond/parsec-client-test?tag=0.1.2)", "parsec-interface 0.1.0 (git+https://github.com/parallaxsecond/parsec-interface-rs?tag=0.1.0)", "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "sd-notify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "std-semaphore 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -613,6 +614,11 @@ name = "rustc-demangle" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "sd-notify" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde" version = "1.0.99" @@ -906,6 +912,7 @@ dependencies = [ "checksum regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b143cceb2ca5e56d5671988ef8b15615733e7ee16cd348e064333b251b89343f" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum sd-notify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "40b76d9fb089398d0b9c5a365008b3cb3104624fe8143f1a43c9827788a22252" "checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f" "checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425" "checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" diff --git a/Cargo.toml b/Cargo.toml index c66b8938..120b005c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ uuid = "0.7.4" threadpool = "1.7.1" std-semaphore = "0.1.0" signal-hook = "0.1.10" +sd-notify = { version = "0.1.0", optional = true } [dev-dependencies] parsec-client-test = { git = "https://github.com/parallaxsecond/parsec-client-test", tag = "0.1.2" } @@ -34,3 +35,7 @@ mbed-crypto-version = "mbedcrypto-1.1.0" [features] default = ["mbed"] mbed = [] + +# Feature to compile the PARSEC binary to be executed as a systemd daemon. +# This feature is only available on Linux. +systemd-daemon = ["sd-notify"] diff --git a/README.md b/README.md index ea2db5dd..17e91b20 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ This project uses the following third party crates: * std-semaphore (MIT and Apache-2.0) * num_cpus (MIT and Apache-2.0) * signal-hook (MIT and Apache-2.0) +* sd-notify (Apache-2.0) This project uses the following third party libraries: * [Mbed Crypto](https://github.com/ARMmbed/mbed-crypto) (Apache-2.0) @@ -160,6 +161,39 @@ You can execute unit tests with `cargo test --lib`. The [test client](https://github.com/parallaxsecond/parsec-client-test) is used for integration testing. Check that repository for more details. +# **Installing the PARSEC service (Linux only)** + +PARSEC can be built and installed as a Linux daemon using systemd. The PARSEC daemon uses socket +activation which means that the daemon will be automatically started when a client request is +made on the socket. The daemon is a systemd user daemon run by the `parsec` user. + +If your Linux system uses systemd to manage daemons, you can follow these steps. + +* Create and log in to a new user named `parsec` +* In its home directory, pull and install PARSEC as a daemon +```bash +$ git pull https://github.com/parallaxsecond/parsec.git +$ cargo install --features "systemd-daemon" --path parsec +``` +* Install the systemd unit files and activate the PARSEC socket +```bash +$ mkdir -p ~/.config/systemd/user +$ cp -r systemd-daemon/parsec.service systemd-daemon/parsec.socket ~/.config/systemd/user +$ systemctl --user enable parsec.socket +$ systemctl --user start parsec.socket +``` + +Every user on the system can now use PARSEC! + +You can test it going inside the `parsec` directory and: +```bash +$ cargo test --test normal +``` +Check the logs with: +```bash +$ journalclt --user -u parsec +``` + # **Contributing** Please check the [**Contributing**](CONTRIBUTING.md) to know more about the contribution process. diff --git a/src/bin/main.rs b/src/bin/main.rs index 1f4fd9f9..993d5b8f 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -109,6 +109,10 @@ fn main() -> Result<(), Error> { let threadpool = Builder::new().build(); + #[cfg(feature = "systemd-daemon")] + // Notify systemd that the daemon is ready, the start command will block until this point. + let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]); + loop { if kill_signal.load(Ordering::Relaxed) { println!("SIGTERM signal received."); diff --git a/src/front/domain_socket.rs b/src/front/domain_socket.rs index dc8a2b7c..71e138dd 100644 --- a/src/front/domain_socket.rs +++ b/src/front/domain_socket.rs @@ -22,6 +22,7 @@ use super::listener; use listener::Listen; use listener::ReadWrite; +use std::os::unix::io::FromRawFd; static SOCKET_PATH: &str = "/tmp/security-daemon-socket"; @@ -43,23 +44,35 @@ impl DomainSocketListener { /// fails /// - if binding to the socket path fails fn init(&mut self) { - let socket = Path::new(SOCKET_PATH); + if cfg!(feature = "systemd-daemon") { + // The PARSEC service is socket activated (see parsec.socket file). + // systemd creates the PARSEC service giving it an initialised socket as the file + // descriptor number 3 (see sd_listen_fds(3) man page). + // If an instance of PARSEC compiled with the "systemd-daemon" feature is run directly + // instead of by systemd, this call will still work but the next accept call on the + // UnixListener will generate a Linux error 9 (Bad file number), as checked below. + unsafe { + self.listener = Some(UnixListener::from_raw_fd(3)); + } + } else { + let socket = Path::new(SOCKET_PATH); - if socket.exists() { - fs::remove_file(&socket).unwrap(); - } + if socket.exists() { + fs::remove_file(&socket).unwrap(); + } - let listener_val = match UnixListener::bind(SOCKET_PATH) { - Ok(listener) => listener, - Err(err) => panic!(err), - }; + let listener_val = match UnixListener::bind(SOCKET_PATH) { + Ok(listener) => listener, + Err(err) => panic!(err), + }; - // Set the socket as non-blocking. - listener_val - .set_nonblocking(true) - .expect("Could not set the socket as non-blocking"); + // Set the socket as non-blocking. + listener_val + .set_nonblocking(true) + .expect("Could not set the socket as non-blocking"); - self.listener = Some(listener_val); + self.listener = Some(listener_val); + } } } @@ -84,6 +97,16 @@ impl Listen for DomainSocketListener { } } Err(err) => { + if cfg!(feature = "systemd-daemon") { + // When run as a systemd daemon, a file descriptor mapping to the Domain Socket + // should have been passed to this process. + if let Some(os_error) = err.raw_os_error() { + // On Linux, 9 is EBADF (Bad file number) + if os_error == 9 { + panic!("The Unix Domain Socket file descriptor (number 3) should have been given to this process."); + } + } + } // Check if the error is because no connections are currently present. if err.kind() != ErrorKind::WouldBlock { // Only log the real errors. diff --git a/systemd-daemon/parsec.service b/systemd-daemon/parsec.service new file mode 100644 index 00000000..ee0e4612 --- /dev/null +++ b/systemd-daemon/parsec.service @@ -0,0 +1,8 @@ +[Unit] +Description=PARSEC Service +Documentation=https://github.com/parallaxsecond/parsec + +[Service] +Type=notify +NonBlocking=true +ExecStart=/home/parsec/.cargo/bin/parsec diff --git a/systemd-daemon/parsec.socket b/systemd-daemon/parsec.socket new file mode 100644 index 00000000..f53c159f --- /dev/null +++ b/systemd-daemon/parsec.socket @@ -0,0 +1,9 @@ +[Unit] +Description=PARSEC Socket +Documentation=https://github.com/parallaxsecond/parsec + +[Socket] +ListenStream=/home/parsec/parsec.sock + +[Install] +WantedBy=sockets.target