From 9d0425859b44ec6dd738e324c0083861d888a43f Mon Sep 17 00:00:00 2001 From: Jason Travis Smith Date: Wed, 11 Oct 2017 01:04:30 -0400 Subject: [PATCH] A Daemon can now be created using the Daemon::spawn function. This follows the POSIX standard way of creating a daemon process. This does not use the systemd or other init system specifics. --- Cargo.toml | 17 ++- README.md | 9 +- examples/daemon.rs | 37 +++++++ src/daemon.rs | 253 ++++++++++++++++++++++++++++++++++++++++++++ src/daemon_error.rs | 87 +++++++++++++++ src/lib.rs | 33 ++++++ 6 files changed, 433 insertions(+), 3 deletions(-) create mode 100644 examples/daemon.rs create mode 100644 src/daemon.rs create mode 100644 src/daemon_error.rs diff --git a/Cargo.toml b/Cargo.toml index 912a007..36d971f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "daemon" -version = "0.1.0" +version = "1.0.0" authors = ["Jason Smith "] description = "Handles setting up and creating a Linux Daemon." license = "" @@ -11,3 +11,18 @@ keywords = ["Daemon", "Demon", "Service"] [dependencies.scribe] git = "ssh://git@gitlab.com/CyberMages/Core/scribe.git" + +[dependencies.weave] +git = "ssh://git@gitlab.com/CyberMages/Core/weave.git" + +[dependencies.spellbook] +git = "ssh://git@gitlab.com/CyberMages/Core/spellbook.git" + +[dependencies.binding] +git = "ssh://git@gitlab.com/CyberMages/Core/binding.git" + +[dependencies.pact] +git = "ssh://git@gitlab.com/CyberMages/Core/pact.git" + +[dependencies.posix_rs] +git = "ssh://git@gitlab.com/CyberMages/FFI/posix_rs.git" diff --git a/README.md b/README.md index 07fd77d..20af29d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Daemon # -A Linux daemon creation library. +A daemon creation library. Its sole purpose it to make a common, simple way to create daemons -for CyberMages LLC's server programs. +for CyberMages LLC's server programs. This is done following the +POSIX standard for maximum portability. + +## Future ## +Currently, this is a POSIX library. In the future this may +help with setting up a Windows Service as well. diff --git a/examples/daemon.rs b/examples/daemon.rs new file mode 100644 index 0000000..a7ae6ea --- /dev/null +++ b/examples/daemon.rs @@ -0,0 +1,37 @@ +#[macro_use] +extern crate scribe; + +extern crate weave; +extern crate daemon; + + + +use weave::{ExitCode, Terminate}; +use daemon::Daemon; + + + +fn child_entry(pid: i64) -> Box +{ + println!("Child ending. ID: {}", pid); + + Box::new(ExitCode::GeneralFailure) +} + + +fn main() +{ + println!("Starting Daemon example."); + match Daemon::spawn(&child_entry) + { + Ok(exit_code) => + { + println!("Exiting with: {}", exit_code.get_code()); + } + + Err(error) => + { + error!("{}", error); + } + } +} diff --git a/src/daemon.rs b/src/daemon.rs new file mode 100644 index 0000000..2cdf318 --- /dev/null +++ b/src/daemon.rs @@ -0,0 +1,253 @@ +use weave::Terminate; + +use spellbook::components::CString; + +use binding::CInt; + +use pact::stdio::FILE; +use pact::stdio::{fileno, fopen}; + +use posix_rs::unistd::{STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; +use posix_rs::unistd::{chdir, close, dup2, fork, setsid}; +use posix_rs::sys::stat::ModeFlags; +use posix_rs::sys::stat::umask; +use posix_rs::sys::types::pid_t; +use posix_rs::PosixError; + +use daemon_error::DaemonError; + + + +/// The value that will be received from a fork for a +/// process ID if the current process is the new +/// child process. +const CHILD_PROCESS_ID: pid_t = 0; + +/// The directory to set as the working directory of the Daemon. +const STARTING_WORKING_DIR: &'static str = "/"; + + + +/// Provides the ability to spawn off a child daemon process in place +/// of the current one. +pub enum Daemon +{ +} + + + +impl Daemon +{ + /// Spawn off a child Daemon process. The current process, if successful, + /// will return so that it may exit. The child process will begin + /// execution at the child_entry function passed in. This will take + /// a process ID as an argument and return a terminate value that will + /// be sent back to the originating spawn function on exit. + /// + /// The working directory will be changed to the root directory. + /// This can be adjusted later to the logging directory once + /// logging has been setup. + pub fn spawn(child_entry: F) + -> Result, DaemonError> + where F: FnOnce(i64) -> Box + { + let _sid: pid_t; + let mut pid: pid_t; + + // Spawn off a child process. + pid = try!(spawn_child_process()); + if pid != CHILD_PROCESS_ID + { + // Terminate the parent process. + debug!("Spawned child process. ID: {}", pid); + return Ok(Box::new(::weave::ExitCode::Success)); + } + + // Create a new session. + _sid = try!(create_session()); + + // Spawn a second time so that the daemon is no longer process leader. + // This keeps it from getting a TTY assigned to it. + pid = try!(spawn_child_process()); + if pid != CHILD_PROCESS_ID + { + // Terminate the parent process. + debug!("Spawned child process. ID: {}", pid); + return Ok(Box::new(::weave::ExitCode::Success)); + } + + // Change the file mode mask to 0. + change_file_mode_mask(ModeFlags::empty()); + + // Change the program to the correct working directory. + try!(change_working_directory()); + + // Close all the file descriptors of the process. + try!(close_standard_file_descriptors()); + + // Call the child entry function for the Daemon + // to proceed with execution. + Ok(child_entry(pid as i64)) + } +} + + + +/// Change the working directory of the current process. +fn change_working_directory() -> Result<(), DaemonError> +{ + match CString::new(STARTING_WORKING_DIR) + { + Ok(path) => + { + unsafe + { + if chdir(path.as_ptr()) < 0 + { + return Err(DaemonError::ChangeDirectory + { parent: PosixError::get_current_error() }); + } + } + + Ok(()) + } + + Err(_error) => + { + Err(DaemonError::ChangeDirectory + { parent: PosixError::get_current_error() }) + } + } +} + +/// Spawn a new Child process. The returned value is the process ID. +/// If the value is -1 there was an error. This will be covered by the +/// Error result. +/// If the value is 0, then the current process is the new child process. +/// If the value is anything else, then it is the original parent process. +fn spawn_child_process() -> Result +{ + let pid: pid_t; + + unsafe + { + // Fork the process. + pid = fork(); + } + + if pid < 0 + { + // Check errno. + Err(DaemonError::Fork { parent: PosixError::get_current_error() }) + } + else + { + Ok(pid) + } +} + +/// Change the file mode mask. +fn change_file_mode_mask(mask: ModeFlags) +{ + unsafe + { + umask(mask.get_bits()); + } +} + +/// This will create a new session, turn the current process into the +/// session leader, create a new process group, and turn the current process +/// into the process group leader. +fn create_session() -> Result +{ + let sid: pid_t; + + unsafe + { + sid = setsid(); + } + + if sid < 0 + { + Err(DaemonError::SessionCreation + { parent: PosixError::get_current_error() }) + } + else + { + Ok(sid) + } +} + +/// Close the standard file descriptors and point them at "/dev/null". +fn close_standard_file_descriptors() -> Result<(), DaemonError> +{ + let dev_null_filename: CString; + let dev_null_file: *mut FILE; + let dev_null_fd: CInt; + let mode: CString; + + match CString::new("/dev/null") + { + Ok(val) => + { + dev_null_filename = val; + } + + Err(_error) => + { + return Err(DaemonError::CloseStream + { parent: PosixError::get_current_error()} ); + } + } + + match CString::new("w+") + { + Ok(val) => + { + mode = val; + } + + Err(_error) => + { + return Err(DaemonError::CloseStream + { parent: PosixError::get_current_error()} ); + } + } + + unsafe + { + // Open /dev/null file to redirect the standard streams to. + dev_null_file = fopen(dev_null_filename.as_ptr(), mode.as_ptr()); + if dev_null_file.is_null() == true + { + return Err(DaemonError::CloseStream + { parent: PosixError::get_current_error()} ); + } + dev_null_fd = fileno(dev_null_file); + + // Close each standard stream and redirect it to /dev/null. + for file in &[STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO] + { + if close(*file) < 0 + { + return Err(DaemonError::CloseStream + { parent: PosixError::get_current_error()} ); + } + + if dup2(dev_null_fd, *file) < 0 + { + return Err(DaemonError::CloseStream + { parent: PosixError::get_current_error()} ); + } + } + + // Close the handle to /dev/null. + if close(dev_null_fd) < 0 + { + return Err(DaemonError::CloseStream + { parent: PosixError::get_current_error()} ); + } + } + + Ok(()) +} diff --git a/src/daemon_error.rs b/src/daemon_error.rs new file mode 100644 index 0000000..fa597a9 --- /dev/null +++ b/src/daemon_error.rs @@ -0,0 +1,87 @@ +use weave::Error; + +use posix_rs::PosixError; + + + +/// The possible errors that may occur when +/// attempting to spawn a Daemon. +pub enum DaemonError +{ + /// There was an issue changing the working directory. + ChangeDirectory + { + parent: PosixError + }, + + /// There was an issue closing a stream. + CloseStream + { + parent: PosixError + }, + + /// There was an issue forking the current process. + Fork + { + parent: PosixError + }, + + /// There was an issue creating a new session and + /// making the current process group leader. + SessionCreation + { + parent: PosixError + } +} + + + +impl ::std::fmt::Debug for DaemonError +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result + { + ::std::fmt::Display::fmt(self, f) + } +} + +impl ::std::fmt::Display for DaemonError +{ + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result + { + write!(f, "{}", self.get_description()) + } +} + +impl Error for DaemonError +{ + fn get_description(&self) -> &str + { + match *self + { + DaemonError::ChangeDirectory { parent: ref parent_error } => + { parent_error.get_description() } + DaemonError::CloseStream { parent: ref parent_error } => + { parent_error.get_description() } + DaemonError::Fork { parent: ref parent_error } => + { parent_error.get_description() } + DaemonError::SessionCreation { parent: ref parent_error } => + { parent_error.get_description() } + } + } + + fn get_cause(&self) -> Option<&Error> + { + match *self + { + DaemonError::ChangeDirectory { parent: ref parent_error } => + { Some(parent_error) } + DaemonError::CloseStream { parent: ref parent_error } => + { Some(parent_error) } + DaemonError::Fork { parent: ref parent_error } => + { Some(parent_error) } + DaemonError::SessionCreation { parent: ref parent_error } => + { Some(parent_error) } + } + } +} + diff --git a/src/lib.rs b/src/lib.rs index 40828cb..4a885ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,35 @@ +//! A daemon creation library. +//! +//! Its sole purpose it to make a common, simple way to create daemons +//! programs. This is done following the POSIX standard for +//! maximum portability. + +#![cfg(any(target_os="linux", target_os="freebsd", target_os="dragonfly", + target_os="netbsd", target_os="openbsd"))] + + + #[macro_use] extern crate scribe; + +extern crate weave; + +extern crate spellbook; + +extern crate binding; + +extern crate pact; + +#[cfg(any(target_os="linux", target_os="freebsd", target_os="dragonfly", + target_os="netbsd", target_os="openbsd"))] +extern crate posix_rs; + + + +mod daemon; +mod daemon_error; + + + +pub use daemon::Daemon; +pub use daemon_error::DaemonError;