1#![cfg_attr(feature = "clippy", feature(plugin))]
47#![cfg_attr(feature = "clippy", plugin(clippy))]
48
49mod ffi;
50
51extern crate libc;
52
53use std::env::set_current_dir;
54use std::ffi::CString;
55use std::fmt;
56use std::fs::File;
57use std::io;
58use std::mem::transmute;
59use std::os::unix::ffi::OsStringExt;
60use std::os::unix::io::AsRawFd;
61use std::path::{Path, PathBuf};
62use std::process::exit;
63
64use libc::{
65 c_int, close, dup2, fork, ftruncate, getpid, open, setgid, setsid, setuid, umask, write,
66 LOCK_EX, LOCK_NB,
67};
68pub use libc::{gid_t, mode_t, uid_t};
69
70use self::ffi::{chroot, flock, get_gid_by_name, get_uid_by_name};
71
72macro_rules! tryret {
73 ($expr:expr, $ret:expr, $err:expr) => {
74 if $expr == -1 {
75 return Err($err(errno()));
76 } else {
77 $ret
78 }
79 };
80}
81
82pub type Errno = c_int;
83
84#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
86pub enum DaemonizeError {
87 Fork,
89 DetachSession(Errno),
91 GroupNotFound,
93 GroupContainsNul,
95 SetGroup(Errno),
97 UserNotFound,
99 UserContainsNul,
101 SetUser(Errno),
103 ChangeDirectory,
105 PathContainsNul,
107 OpenPidfile,
109 LockPidfile(Errno),
111 ChownPidfile(Errno),
113 RedirectStreams(Errno),
115 WritePid,
117 Chroot(Errno),
119 #[doc(hidden)]
124 __Nonexhaustive,
125}
126
127impl DaemonizeError {
128 fn __description(&self) -> &str {
129 match *self {
130 DaemonizeError::Fork => "unable to fork",
131 DaemonizeError::DetachSession(_) => "unable to create new session",
132 DaemonizeError::GroupNotFound => "unable to resolve group name to group id",
133 DaemonizeError::GroupContainsNul => "group option contains NUL",
134 DaemonizeError::SetGroup(_) => "unable to set group",
135 DaemonizeError::UserNotFound => "unable to resolve user name to user id",
136 DaemonizeError::UserContainsNul => "user option contains NUL",
137 DaemonizeError::SetUser(_) => "unable to set user",
138 DaemonizeError::ChangeDirectory => "unable to change directory",
139 DaemonizeError::PathContainsNul => "pid_file option contains NUL",
140 DaemonizeError::OpenPidfile => "unable to open pid file",
141 DaemonizeError::LockPidfile(_) => "unable to lock pid file",
142 DaemonizeError::ChownPidfile(_) => "unable to chown pid file",
143 DaemonizeError::RedirectStreams(_) => {
144 "unable to redirect standard streams to /dev/null"
145 }
146 DaemonizeError::WritePid => "unable to write self pid to pid file",
147 DaemonizeError::Chroot(_) => "unable to chroot into directory",
148 DaemonizeError::__Nonexhaustive => unreachable!(),
149 }
150 }
151}
152
153impl std::fmt::Display for DaemonizeError {
154 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
155 self.__description().fmt(f)
156 }
157}
158
159impl std::error::Error for DaemonizeError {
160 fn description(&self) -> &str {
161 self.__description()
162 }
163}
164
165type Result<T> = std::result::Result<T, DaemonizeError>;
166
167#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
169pub enum User {
170 Name(String),
171 Id(uid_t),
172}
173
174impl<'a> From<&'a str> for User {
175 fn from(t: &'a str) -> User {
176 User::Name(t.to_owned())
177 }
178}
179
180impl From<uid_t> for User {
181 fn from(t: uid_t) -> User {
182 User::Id(t)
183 }
184}
185
186#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
188pub enum Group {
189 Name(String),
190 Id(gid_t),
191}
192
193impl<'a> From<&'a str> for Group {
194 fn from(t: &'a str) -> Group {
195 Group::Name(t.to_owned())
196 }
197}
198
199impl From<gid_t> for Group {
200 fn from(t: gid_t) -> Group {
201 Group::Id(t)
202 }
203}
204
205#[derive(Debug)]
206enum StdioImp {
207 Devnull,
208 RedirectToFile(File),
209}
210
211#[derive(Debug)]
213pub struct Stdio {
214 inner: StdioImp,
215}
216
217impl Stdio {
218 fn devnull() -> Self {
219 Self {
220 inner: StdioImp::Devnull,
221 }
222 }
223}
224
225impl From<File> for Stdio {
226 fn from(file: File) -> Self {
227 Self {
228 inner: StdioImp::RedirectToFile(file),
229 }
230 }
231}
232
233pub struct Daemonize<T> {
249 directory: PathBuf,
250 pid_file: Option<PathBuf>,
251 chown_pid_file: bool,
252 user: Option<User>,
253 group: Option<Group>,
254 umask: mode_t,
255 root: Option<PathBuf>,
256 privileged_action: Box<Fn() -> T>,
257 exit_action: Box<Fn()>,
258 stdin: Stdio,
259 stdout: Stdio,
260 stderr: Stdio,
261}
262
263impl<T> fmt::Debug for Daemonize<T> {
264 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
265 fmt.debug_struct("Daemonize")
266 .field("directory", &self.directory)
267 .field("pid_file", &self.pid_file)
268 .field("chown_pid_file", &self.chown_pid_file)
269 .field("user", &self.user)
270 .field("group", &self.group)
271 .field("umask", &self.umask)
272 .field("root", &self.root)
273 .field("stdin", &self.stdin)
274 .field("stdout", &self.stdout)
275 .field("stderr", &self.stderr)
276 .finish()
277 }
278}
279
280impl Daemonize<()> {
281 pub fn new() -> Self {
282 Daemonize {
283 directory: Path::new("/").to_owned(),
284 pid_file: None,
285 chown_pid_file: false,
286 user: None,
287 group: None,
288 umask: 0o027,
289 privileged_action: Box::new(|| ()),
290 exit_action: Box::new(|| ()),
291 root: None,
292 stdin: Stdio::devnull(),
293 stdout: Stdio::devnull(),
294 stderr: Stdio::devnull(),
295 }
296 }
297}
298
299impl<T> Daemonize<T> {
300 pub fn pid_file<F: AsRef<Path>>(mut self, path: F) -> Self {
302 self.pid_file = Some(path.as_ref().to_owned());
303 self
304 }
305
306 pub fn chown_pid_file(mut self, chown: bool) -> Self {
308 self.chown_pid_file = chown;
309 self
310 }
311
312 pub fn working_directory<F: AsRef<Path>>(mut self, path: F) -> Self {
314 self.directory = path.as_ref().to_owned();
315 self
316 }
317
318 pub fn user<U: Into<User>>(mut self, user: U) -> Self {
320 self.user = Some(user.into());
321 self
322 }
323
324 pub fn group<G: Into<Group>>(mut self, group: G) -> Self {
326 self.group = Some(group.into());
327 self
328 }
329
330 pub fn umask(mut self, mask: mode_t) -> Self {
332 self.umask = mask;
333 self
334 }
335
336 pub fn chroot<F: AsRef<Path>>(mut self, path: F) -> Self {
338 self.root = Some(path.as_ref().to_owned());
339 self
340 }
341
342 pub fn privileged_action<N, F: Fn() -> N + Sized + 'static>(self, action: F) -> Daemonize<N> {
345 let mut new: Daemonize<N> = unsafe { transmute(self) };
346 new.privileged_action = Box::new(action);
347 new
348 }
349
350 pub fn exit_action<F: Fn() + Sized + 'static>(self, action: F) -> Daemonize<T> {
352 let mut new: Daemonize<T> = unsafe { transmute(self) };
353 new.exit_action = Box::new(action);
354 new
355 }
356
357 pub fn stdout<S: Into<Stdio>>(mut self, stdio: S) -> Self {
359 self.stdout = stdio.into();
360 self
361 }
362
363 pub fn stderr<S: Into<Stdio>>(mut self, stdio: S) -> Self {
365 self.stderr = stdio.into();
366 self
367 }
368
369 pub fn start(self) -> std::result::Result<T, DaemonizeError> {
371 macro_rules! maptry {
374 ($expr:expr, $f: expr) => {
375 match $expr {
376 None => None,
377 Some(x) => Some(try!($f(x))),
378 };
379 };
380 }
381
382 unsafe {
383 let pid_file_fd = maptry!(self.pid_file.clone(), create_pid_file);
384
385 try!(self.perform_fork(true));
386
387 try!(set_current_dir(&self.directory).map_err(|_| DaemonizeError::ChangeDirectory));
388 try!(set_sid());
389 umask(self.umask);
390
391 try!(self.perform_fork(false));
392
393 try!(redirect_standard_streams(
394 self.stdin,
395 self.stdout,
396 self.stderr
397 ));
398
399 let uid = maptry!(self.user, get_user);
400 let gid = maptry!(self.group, get_group);
401
402 if self.chown_pid_file {
403 let args: Option<(PathBuf, uid_t, gid_t)> = match (self.pid_file, uid, gid) {
404 (Some(pid), Some(uid), Some(gid)) => Some((pid, uid, gid)),
405 (Some(pid), None, Some(gid)) => Some((pid, uid_t::max_value() - 1, gid)),
406 (Some(pid), Some(uid), None) => Some((pid, uid, gid_t::max_value() - 1)),
407 _ => None,
409 };
410
411 maptry!(args, |(pid, uid, gid)| chown_pid_file(pid, uid, gid));
412 }
413
414 let privileged_action_result = (self.privileged_action)();
415
416 maptry!(self.root, change_root);
417
418 maptry!(gid, set_group);
419 maptry!(uid, set_user);
420
421 maptry!(pid_file_fd, write_pid_file);
422
423 Ok(privileged_action_result)
424 }
425 }
426
427 unsafe fn perform_fork(&self, perform_action: bool) -> Result<()> {
428 let pid = fork();
429 if pid < 0 {
430 Err(DaemonizeError::Fork)
431 } else if pid == 0 {
432 Ok(())
433 } else {
434 if perform_action {
435 (self.exit_action)();
436 }
437 exit(0)
438 }
439 }
440}
441
442unsafe fn set_sid() -> Result<()> {
443 tryret!(setsid(), Ok(()), DaemonizeError::DetachSession)
444}
445
446unsafe fn redirect_standard_streams(stdin: Stdio, stdout: Stdio, stderr: Stdio) -> Result<()> {
447 let devnull_fd = open(transmute(b"/dev/null\0"), libc::O_RDWR);
448 if -1 == devnull_fd {
449 return Err(DaemonizeError::RedirectStreams(errno()));
450 }
451
452 let process_stdio = |fd, stdio: Stdio| {
453 tryret!(close(fd), (), DaemonizeError::RedirectStreams);
454 match stdio.inner {
455 StdioImp::Devnull => {
456 tryret!(dup2(devnull_fd, fd), (), DaemonizeError::RedirectStreams);
457 }
458 StdioImp::RedirectToFile(file) => {
459 let raw_fd = file.as_raw_fd();
460 tryret!(dup2(raw_fd, fd), (), DaemonizeError::RedirectStreams);
461 }
462 };
463 Ok(())
464 };
465
466 process_stdio(libc::STDIN_FILENO, stdin)?;
467 process_stdio(libc::STDOUT_FILENO, stdout)?;
468 process_stdio(libc::STDERR_FILENO, stderr)?;
469
470 tryret!(close(devnull_fd), (), DaemonizeError::RedirectStreams);
471
472 Ok(())
473}
474
475unsafe fn get_group(group: Group) -> Result<gid_t> {
476 match group {
477 Group::Id(id) => Ok(id),
478 Group::Name(name) => {
479 let s = try!(CString::new(name).map_err(|_| DaemonizeError::GroupContainsNul));
480 match get_gid_by_name(&s) {
481 Some(id) => get_group(Group::Id(id)),
482 None => Err(DaemonizeError::GroupNotFound),
483 }
484 }
485 }
486}
487
488unsafe fn set_group(group: gid_t) -> Result<()> {
489 tryret!(setgid(group), Ok(()), DaemonizeError::SetGroup)
490}
491
492unsafe fn get_user(user: User) -> Result<uid_t> {
493 match user {
494 User::Id(id) => Ok(id),
495 User::Name(name) => {
496 let s = try!(CString::new(name).map_err(|_| DaemonizeError::UserContainsNul));
497 match get_uid_by_name(&s) {
498 Some(id) => get_user(User::Id(id)),
499 None => Err(DaemonizeError::UserNotFound),
500 }
501 }
502 }
503}
504
505unsafe fn set_user(user: uid_t) -> Result<()> {
506 tryret!(setuid(user), Ok(()), DaemonizeError::SetUser)
507}
508
509unsafe fn create_pid_file(path: PathBuf) -> Result<libc::c_int> {
510 let path_c = try!(pathbuf_into_cstring(path));
511
512 let fd = open(path_c.as_ptr(), libc::O_WRONLY | libc::O_CREAT, 0o666);
513 if -1 == fd {
514 return Err(DaemonizeError::OpenPidfile);
515 }
516
517 tryret!(
518 flock(fd, LOCK_EX | LOCK_NB),
519 Ok(fd),
520 DaemonizeError::LockPidfile
521 )
522}
523
524unsafe fn chown_pid_file(path: PathBuf, uid: uid_t, gid: gid_t) -> Result<()> {
525 let path_c = try!(pathbuf_into_cstring(path));
526 tryret!(
527 libc::chown(path_c.as_ptr(), uid, gid),
528 Ok(()),
529 DaemonizeError::ChownPidfile
530 )
531}
532
533unsafe fn write_pid_file(fd: libc::c_int) -> Result<()> {
534 let pid = getpid();
535 let pid_buf = format!("{}", pid).into_bytes();
536 let pid_length = pid_buf.len();
537 let pid_c = CString::new(pid_buf).unwrap();
538 if -1 == ftruncate(fd, 0) {
539 return Err(DaemonizeError::WritePid);
540 }
541 if write(fd, transmute(pid_c.as_ptr()), pid_length) < pid_length as isize {
542 Err(DaemonizeError::WritePid)
543 } else {
544 Ok(())
545 }
546}
547
548unsafe fn change_root(path: PathBuf) -> Result<()> {
549 let path_c = pathbuf_into_cstring(path)?;
550
551 if chroot(path_c.as_ptr()) == 0 {
552 Ok(())
553 } else {
554 Err(DaemonizeError::Chroot(errno()))
555 }
556}
557
558fn pathbuf_into_cstring(path: PathBuf) -> Result<CString> {
559 CString::new(path.into_os_string().into_vec()).map_err(|_| DaemonizeError::PathContainsNul)
560}
561
562fn errno() -> Errno {
563 io::Error::last_os_error().raw_os_error().expect("errno")
564}