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]
|
[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"
|
||||||
|
@ -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
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]
|
#[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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user