#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
mod ffi;
extern crate libc;
use std::fmt;
use std::env::{set_current_dir};
use std::ffi::{CString};
use std::os::unix::ffi::OsStringExt;
use std::os::unix::io::RawFd;
use std::mem::{transmute};
use std::path::{Path, PathBuf};
use std::process::{exit};
pub use libc::{uid_t, gid_t, mode_t};
use libc::{LOCK_EX, LOCK_NB, c_int, fopen, write, close, fileno, fork, getpid, setsid, setuid, setgid, dup2, umask};
use self::ffi::{errno, flock, get_gid_by_name, get_uid_by_name};
macro_rules! tryret {
($expr:expr, $ret:expr, $err:expr) => (
if $expr == -1 {
return Err($err(errno()))
} else {
$ret
}
)
}
pub type Errno = c_int;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum DaemonizeError {
Fork,
DetachSession(Errno),
GroupNotFound,
GroupContainsNul,
SetGroup(Errno),
UserNotFound,
UserContainsNul,
SetUser(Errno),
ChangeDirectory,
PathContainsNul,
OpenPidfile,
LockPidfile(Errno),
ChownPidfile(Errno),
RedirectStreams(Errno),
WritePid,
#[doc(hidden)]
__Nonexhaustive,
}
impl DaemonizeError {
fn __description(&self) -> &str {
match *self {
DaemonizeError::Fork => "unable to fork",
DaemonizeError::DetachSession(_) => "unable to create new session",
DaemonizeError::GroupNotFound => "unable to resolve group name to group id",
DaemonizeError::GroupContainsNul => "group option contains NUL",
DaemonizeError::SetGroup(_) => "unable to set group",
DaemonizeError::UserNotFound => "unable to resolve user name to user id",
DaemonizeError::UserContainsNul => "user option contains NUL",
DaemonizeError::SetUser(_) => "unable to set user",
DaemonizeError::ChangeDirectory => "unable to change directory",
DaemonizeError::PathContainsNul => "pid_file option contains NUL",
DaemonizeError::OpenPidfile => "unable to open pid file",
DaemonizeError::LockPidfile(_) => "unable to lock pid file",
DaemonizeError::ChownPidfile(_) => "unable to chown pid file",
DaemonizeError::RedirectStreams(_) => "unable to redirect standard streams to /dev/null",
DaemonizeError::WritePid => "unable to write self pid to pid file",
DaemonizeError::__Nonexhaustive => unreachable!(),
}
}
}
impl std::fmt::Display for DaemonizeError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.__description().fmt(f)
}
}
impl std::error::Error for DaemonizeError {
fn description(&self) -> &str {
self.__description()
}
}
type Result<T> = std::result::Result<T, DaemonizeError>;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum User {
Name(String),
Id(uid_t),
}
impl<'a> From<&'a str> for User {
fn from(t: &'a str) -> User {
User::Name(t.to_owned())
}
}
impl From<uid_t> for User {
fn from(t: uid_t) -> User {
User::Id(t)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum Group {
Name(String),
Id(gid_t),
}
impl<'a> From<&'a str> for Group {
fn from(t: &'a str) -> Group {
Group::Name(t.to_owned())
}
}
impl From<gid_t> for Group {
fn from(t: gid_t) -> Group {
Group::Id(t)
}
}
pub struct Daemonize<T> {
directory: PathBuf,
pid_file: Option<PathBuf>,
chown_pid_file: bool,
user: Option<User>,
group: Option<Group>,
umask: mode_t,
privileged_action: Box<Fn() -> T>,
stdin_fd: Option<RawFd>,
stdout_fd: Option<RawFd>,
stderr_fd: Option<RawFd>,
}
impl<T> fmt::Debug for Daemonize<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Daemonize")
.field("directory", &self.directory)
.field("pid_file", &self.pid_file)
.field("chown_pid_file", &self.chown_pid_file)
.field("user", &self.user)
.field("group", &self.group)
.field("umask", &self.umask)
.field("stdin_fd", &self.stdin_fd)
.field("stdout_fd", &self.stdout_fd)
.field("stderr_fd", &self.stderr_fd)
.finish()
}
}
impl Daemonize<()> {
pub fn new() -> Self {
Daemonize {
directory: Path::new("/").to_owned(),
pid_file: None,
chown_pid_file: false,
user: None,
group: None,
umask: 0o027,
privileged_action: Box::new(|| ()),
stdin_fd: None,
stdout_fd: None,
stderr_fd: None,
}
}
}
impl<T> Daemonize<T> {
pub fn pid_file<F: AsRef<Path>>(mut self, path: F) -> Self {
self.pid_file = Some(path.as_ref().to_owned());
self
}
pub fn chown_pid_file(mut self, chown: bool) -> Self {
self.chown_pid_file = chown;
self
}
pub fn working_directory<F: AsRef<Path>>(mut self, path: F) -> Self {
self.directory = path.as_ref().to_owned();
self
}
pub fn user<U: Into<User>>(mut self, user: U) -> Self {
self.user = Some(user.into());
self
}
pub fn group<G: Into<Group>>(mut self, group: G) -> Self {
self.group = Some(group.into());
self
}
pub fn stdin_fd(mut self, fd: RawFd) -> Self {
self.stdin_fd = Some(fd);
self
}
pub fn stdout_fd(mut self, fd: RawFd) -> Self {
self.stdout_fd = Some(fd);
self
}
pub fn stderr_fd(mut self, fd: RawFd) -> Self {
self.stderr_fd = Some(fd);
self
}
pub fn umask(mut self, mask: mode_t) -> Self {
self.umask = mask;
self
}
pub fn privileged_action<N, F: Fn() -> N + Sized + 'static>(self, action: F) -> Daemonize<N> {
let mut new: Daemonize<N> = unsafe { transmute(self) };
new.privileged_action = Box::new(action);
new
}
pub fn start(self) -> std::result::Result<T, DaemonizeError> {
macro_rules! maptry {
($expr:expr, $f: expr) => (
match $expr {
None => None,
Some(x) => Some(try!($f(x)))
};
)
}
unsafe {
let pid_file_fd = maptry!(self.pid_file.clone(), create_pid_file);
try!(perform_fork());
try!(set_current_dir(self.directory).map_err(|_| DaemonizeError::ChangeDirectory));
try!(set_sid());
umask(self.umask);
try!(perform_fork());
try!(redirect_standard_streams(self.stdin_fd, self.stdout_fd, self.stderr_fd));
let uid = maptry!(self.user, get_user);
let gid = maptry!(self.group, get_group);
if self.chown_pid_file {
let args: Option<(PathBuf, uid_t, gid_t)> = match (self.pid_file, uid, gid) {
(Some(pid), Some(uid), Some(gid)) => Some((pid, uid, gid)),
(Some(pid), None, Some(gid)) => Some((pid, uid_t::max_value() - 1, gid)),
(Some(pid), Some(uid), None) => Some((pid, uid, gid_t::max_value() - 1)),
_ => None
};
maptry!(args, |(pid, uid, gid)| chown_pid_file(pid, uid, gid));
}
let privileged_action_result = (self.privileged_action)();
maptry!(gid, set_group);
maptry!(uid, set_user);
maptry!(pid_file_fd, write_pid_file);
Ok(privileged_action_result)
}
}
}
unsafe fn perform_fork() -> Result<()> {
let pid = fork();
if pid < 0 {
Err(DaemonizeError::Fork)
} else if pid == 0 {
Ok(())
} else {
exit(0)
}
}
unsafe fn set_sid() -> Result<()> {
tryret!(setsid(), Ok(()), DaemonizeError::DetachSession)
}
unsafe fn redirect_standard_streams(stdin_fd: Option<RawFd>,
stdout_fd: Option<RawFd>,
stderr_fd: Option<RawFd>) -> Result<()> {
macro_rules! for_every_stream {
($expr:expr) => (
for stream in &[libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO] {
tryret!($expr(*stream), (), DaemonizeError::RedirectStreams);
}
)
}
for_every_stream!(close);
let devnull_file = fopen(transmute(b"/dev/null\0"), transmute(b"w+\0"));
if devnull_file.is_null() {
return Err(DaemonizeError::RedirectStreams(errno()))
};
let devnull_fd = fileno(devnull_file);
dup2(match stdin_fd { Some(fd) => fd, None => devnull_fd }, libc::STDIN_FILENO);
dup2(match stdout_fd { Some(fd) => fd, None => devnull_fd }, libc::STDOUT_FILENO);
dup2(match stderr_fd { Some(fd) => fd, None => devnull_fd }, libc::STDERR_FILENO);
for_every_stream!(|stream| dup2(devnull_fd, stream));
tryret!(close(devnull_fd), (), DaemonizeError::RedirectStreams);
Ok(())
}
unsafe fn get_group(group: Group) -> Result<gid_t> {
match group {
Group::Id(id) => Ok(id),
Group::Name(name) => {
let s = try!(CString::new(name).map_err(|_| DaemonizeError::GroupContainsNul));
match get_gid_by_name(&s) {
Some(id) => get_group(Group::Id(id)),
None => Err(DaemonizeError::GroupNotFound)
}
}
}
}
unsafe fn set_group(group: gid_t) -> Result<()> {
tryret!(setgid(group), Ok(()), DaemonizeError::SetGroup)
}
unsafe fn get_user(user: User) -> Result<uid_t> {
match user {
User::Id(id) => Ok(id),
User::Name(name) => {
let s = try!(CString::new(name).map_err(|_| DaemonizeError::UserContainsNul));
match get_uid_by_name(&s) {
Some(id) => get_user(User::Id(id)),
None => Err(DaemonizeError::UserNotFound)
}
}
}
}
unsafe fn set_user(user: uid_t) -> Result<()> {
tryret!(setuid(user), Ok(()), DaemonizeError::SetUser)
}
unsafe fn create_pid_file(path: PathBuf) -> Result<libc::c_int> {
let path_c = try!(pathbuf_into_cstring(path));
let f = fopen(path_c.as_ptr(), b"w" as *const u8 as *const libc::c_char);
if f.is_null() {
return Err(DaemonizeError::OpenPidfile)
}
let fd = fileno(f);
tryret!(flock(fd, LOCK_EX | LOCK_NB), Ok(fd), DaemonizeError::LockPidfile)
}
unsafe fn chown_pid_file(path: PathBuf, uid: uid_t, gid: gid_t) -> Result<()> {
let path_c = try!(pathbuf_into_cstring(path));
tryret!(libc::chown(path_c.as_ptr(), uid, gid), Ok(()), DaemonizeError::ChownPidfile)
}
unsafe fn write_pid_file(fd: libc::c_int) -> Result<()> {
let pid = getpid();
let pid_buf = format!("{}", pid).into_bytes();
let pid_length = pid_buf.len();
let pid_c = CString::new(pid_buf).unwrap();
if write(fd, transmute(pid_c.as_ptr()), pid_length) < pid_length as isize {
Err(DaemonizeError::WritePid)
} else {
Ok(())
}
}
fn pathbuf_into_cstring(path: PathBuf) -> Result<CString> {
CString::new(path.into_os_string().into_vec())
.map_err(|_| DaemonizeError::PathContainsNul)
}