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.
This commit is contained in:
Myrddin Dundragon 2017-10-11 01:04:30 -04:00
parent 04ea4aa144
commit 9d0425859b
6 changed files with 433 additions and 3 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "daemon" name = "daemon"
version = "0.1.0" version = "1.0.0"
authors = ["Jason Smith <Myrddin@CyberMagesLLC.com>"] authors = ["Jason Smith <Myrddin@CyberMagesLLC.com>"]
description = "Handles setting up and creating a Linux Daemon." description = "Handles setting up and creating a Linux Daemon."
license = "" license = ""
@ -11,3 +11,18 @@ keywords = ["Daemon", "Demon", "Service"]
[dependencies.scribe] [dependencies.scribe]
git = "ssh://git@gitlab.com/CyberMages/Core/scribe.git" 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"

View File

@ -1,5 +1,10 @@
# Daemon # # Daemon #
A Linux daemon creation library. A daemon creation library.
Its sole purpose it to make a common, simple way to create daemons 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.

37
examples/daemon.rs Normal file
View File

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

253
src/daemon.rs Normal file
View File

@ -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<F>(child_entry: F)
-> Result<Box<Terminate>, DaemonError>
where F: FnOnce(i64) -> Box<Terminate>
{
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<pid_t, DaemonError>
{
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<pid_t, DaemonError>
{
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(())
}

87
src/daemon_error.rs Normal file
View File

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

View File

@ -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] #[macro_use]
extern crate scribe; 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;