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:
parent
04ea4aa144
commit
9d0425859b
17
Cargo.toml
17
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "daemon"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
authors = ["Jason Smith <Myrddin@CyberMagesLLC.com>"]
|
||||
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"
|
||||
|
@ -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.
|
||||
|
37
examples/daemon.rs
Normal file
37
examples/daemon.rs
Normal 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
253
src/daemon.rs
Normal 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
87
src/daemon_error.rs
Normal 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
33
src/lib.rs
33
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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user