1 // Copyright (c) 2016 Fedor Gogolev <knsd@knsd.net>
2 //
3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6 // option. This file may not be copied, modified, or distributed
7 // except according to those terms.
8 
9 //!
10 //! daemonize is a library for writing system daemons. Inspired by the Python library [thesharp/daemonize](https://github.com/thesharp/daemonize).
11 //!
12 //! The respository is located at https://github.com/knsd/daemonize/.
13 //!
14 //! Usage example:
15 //!
16 //! ```
17 //! extern crate daemonize;
18 //!
19 //! use std::fs::File;
20 //!
21 //! use daemonize::Daemonize;
22 //!
23 //! fn main() {
24 //!     let stdout = File::create("/tmp/daemon.out").unwrap();
25 //!     let stderr = File::create("/tmp/daemon.err").unwrap();
26 //!
27 //!     let daemonize = Daemonize::new()
28 //!         .pid_file("/tmp/test.pid") // Every method except `new` and `start`
29 //!         .chown_pid_file(true)      // is optional, see `Daemonize` documentation
30 //!         .working_directory("/tmp") // for default behaviour.
31 //!         .user("nobody")
32 //!         .group("daemon") // Group name
33 //!         .group(2)        // or group id.
34 //!         .umask(0o777)    // Set umask, `0o027` by default.
35 //!         .stdout(stdout)  // Redirect stdout to `/tmp/daemon.out`.
36 //!         .stderr(stderr)  // Redirect stderr to `/tmp/daemon.err`.
37 //!         .exit_action(|| println!("Executed before master process exits"))
38 //!         .privileged_action(|| "Executed before drop privileges");
39 //!
40 //!     match daemonize.start() {
41 //!         Ok(_) => println!("Success, daemonized"),
42 //!         Err(e) => eprintln!("Error, {}", e),
43 //!     }
44 //! }
45 //! ```
46 
47 mod ffi;
48 
49 extern crate boxfnonce;
50 extern crate libc;
51 
52 use std::env::set_current_dir;
53 use std::ffi::CString;
54 use std::fmt;
55 use std::fs::File;
56 use std::io;
57 use std::mem::transmute;
58 use std::os::unix::ffi::OsStringExt;
59 use std::os::unix::io::AsRawFd;
60 use std::path::{Path, PathBuf};
61 use std::process::exit;
62 
63 use boxfnonce::BoxFnOnce;
64 use libc::{
65     c_int, close, dup2, fork, ftruncate, getpid, open, setgid, setsid, setuid, umask, write,
66     LOCK_EX, LOCK_NB,
67 };
68 pub use libc::{gid_t, mode_t, uid_t};
69 
70 use self::ffi::{chroot, flock, get_gid_by_name, get_uid_by_name};
71 
72 macro_rules! tryret {
73     ($expr:expr, $ret:expr, $err:expr) => {
74         if $expr == -1 {
75             return Err($err(errno()));
76         } else {
77             #[allow(clippy::unused_unit)]
78             {
79                 $ret
80             }
81         }
82     };
83 }
84 
85 pub type Errno = c_int;
86 
87 /// This error type for `Daemonize` `start` method.
88 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
89 pub enum DaemonizeError {
90     /// Unable to fork
91     Fork,
92     /// Unable to create new session
93     DetachSession(Errno),
94     /// Unable to resolve group name to group id
95     GroupNotFound,
96     /// Group option contains NUL
97     GroupContainsNul,
98     /// Unable to set group
99     SetGroup(Errno),
100     /// Unable to resolve user name to user id
101     UserNotFound,
102     /// User option contains NUL
103     UserContainsNul,
104     /// Unable to set user
105     SetUser(Errno),
106     /// Unable to change directory
107     ChangeDirectory,
108     /// pid_file option contains NUL
109     PathContainsNul,
110     /// Unable to open pid file
111     OpenPidfile,
112     /// Unable to lock pid file
113     LockPidfile(Errno),
114     /// Unable to chown pid file
115     ChownPidfile(Errno),
116     /// Unable to redirect standard streams to /dev/null
117     RedirectStreams(Errno),
118     /// Unable to write self pid to pid file
119     WritePid,
120     /// Unable to chroot
121     Chroot(Errno),
122     // Hints that destructuring should not be exhaustive.
123     // This enum may grow additional variants, so this makes sure clients
124     // don't count on exhaustive matching. Otherwise, adding a new variant
125     // could break existing code.
126     #[doc(hidden)]
127     __Nonexhaustive,
128 }
129 
130 impl DaemonizeError {
__description(&self) -> &str131     fn __description(&self) -> &str {
132         match *self {
133             DaemonizeError::Fork => "unable to fork",
134             DaemonizeError::DetachSession(_) => "unable to create new session",
135             DaemonizeError::GroupNotFound => "unable to resolve group name to group id",
136             DaemonizeError::GroupContainsNul => "group option contains NUL",
137             DaemonizeError::SetGroup(_) => "unable to set group",
138             DaemonizeError::UserNotFound => "unable to resolve user name to user id",
139             DaemonizeError::UserContainsNul => "user option contains NUL",
140             DaemonizeError::SetUser(_) => "unable to set user",
141             DaemonizeError::ChangeDirectory => "unable to change directory",
142             DaemonizeError::PathContainsNul => "pid_file option contains NUL",
143             DaemonizeError::OpenPidfile => "unable to open pid file",
144             DaemonizeError::LockPidfile(_) => "unable to lock pid file",
145             DaemonizeError::ChownPidfile(_) => "unable to chown pid file",
146             DaemonizeError::RedirectStreams(_) => {
147                 "unable to redirect standard streams to /dev/null"
148             }
149             DaemonizeError::WritePid => "unable to write self pid to pid file",
150             DaemonizeError::Chroot(_) => "unable to chroot into directory",
151             DaemonizeError::__Nonexhaustive => unreachable!(),
152         }
153     }
154 }
155 
156 impl std::fmt::Display for DaemonizeError {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result157     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158         self.__description().fmt(f)
159     }
160 }
161 
162 impl std::error::Error for DaemonizeError {
description(&self) -> &str163     fn description(&self) -> &str {
164         self.__description()
165     }
166 }
167 
168 type Result<T> = std::result::Result<T, DaemonizeError>;
169 
170 /// Expects system user id or name. If name is provided it will be resolved to id later.
171 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
172 pub enum User {
173     Name(String),
174     Id(uid_t),
175 }
176 
177 impl<'a> From<&'a str> for User {
from(t: &'a str) -> User178     fn from(t: &'a str) -> User {
179         User::Name(t.to_owned())
180     }
181 }
182 
183 impl From<uid_t> for User {
from(t: uid_t) -> User184     fn from(t: uid_t) -> User {
185         User::Id(t)
186     }
187 }
188 
189 /// Expects system group id or name. If name is provided it will be resolved to id later.
190 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
191 pub enum Group {
192     Name(String),
193     Id(gid_t),
194 }
195 
196 impl<'a> From<&'a str> for Group {
from(t: &'a str) -> Group197     fn from(t: &'a str) -> Group {
198         Group::Name(t.to_owned())
199     }
200 }
201 
202 impl From<gid_t> for Group {
from(t: gid_t) -> Group203     fn from(t: gid_t) -> Group {
204         Group::Id(t)
205     }
206 }
207 
208 #[derive(Debug)]
209 enum StdioImp {
210     Devnull,
211     RedirectToFile(File),
212 }
213 
214 /// Describes what to do with a standard I/O stream for a child process.
215 #[derive(Debug)]
216 pub struct Stdio {
217     inner: StdioImp,
218 }
219 
220 impl Stdio {
devnull() -> Self221     fn devnull() -> Self {
222         Self {
223             inner: StdioImp::Devnull,
224         }
225     }
226 }
227 
228 impl From<File> for Stdio {
from(file: File) -> Self229     fn from(file: File) -> Self {
230         Self {
231             inner: StdioImp::RedirectToFile(file),
232         }
233     }
234 }
235 
236 /// Daemonization options.
237 ///
238 /// Fork the process in the background, disassociate from its process group and the control terminal.
239 /// Change umask value to `0o027`, redirect all standard streams to `/dev/null`. Change working
240 /// directory to `/` or provided value.
241 ///
242 /// Optionally:
243 ///
244 ///   * maintain and lock the pid-file;
245 ///   * drop user privileges;
246 ///   * drop group privileges;
247 ///   * change root directory;
248 ///   * change the pid-file ownership to provided user (and/or) group;
249 ///   * execute any provided action just before dropping privileges.
250 ///
251 pub struct Daemonize<T> {
252     directory: PathBuf,
253     pid_file: Option<PathBuf>,
254     chown_pid_file: bool,
255     user: Option<User>,
256     group: Option<Group>,
257     umask: mode_t,
258     root: Option<PathBuf>,
259     privileged_action: BoxFnOnce<'static, (), T>,
260     exit_action: BoxFnOnce<'static, (), ()>,
261     stdin: Stdio,
262     stdout: Stdio,
263     stderr: Stdio,
264 }
265 
266 impl<T> fmt::Debug for Daemonize<T> {
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result267     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
268         fmt.debug_struct("Daemonize")
269             .field("directory", &self.directory)
270             .field("pid_file", &self.pid_file)
271             .field("chown_pid_file", &self.chown_pid_file)
272             .field("user", &self.user)
273             .field("group", &self.group)
274             .field("umask", &self.umask)
275             .field("root", &self.root)
276             .field("stdin", &self.stdin)
277             .field("stdout", &self.stdout)
278             .field("stderr", &self.stderr)
279             .finish()
280     }
281 }
282 
283 impl Daemonize<()> {
284     #[allow(clippy::new_without_default)]
new() -> Self285     pub fn new() -> Self {
286         Daemonize {
287             directory: Path::new("/").to_owned(),
288             pid_file: None,
289             chown_pid_file: false,
290             user: None,
291             group: None,
292             umask: 0o027,
293             privileged_action: BoxFnOnce::new(|| ()),
294             exit_action: BoxFnOnce::new(|| ()),
295             root: None,
296             stdin: Stdio::devnull(),
297             stdout: Stdio::devnull(),
298             stderr: Stdio::devnull(),
299         }
300     }
301 }
302 
303 impl<T> Daemonize<T> {
304     /// Create pid-file at `path`, lock it exclusive and write daemon pid.
pid_file<F: AsRef<Path>>(mut self, path: F) -> Self305     pub fn pid_file<F: AsRef<Path>>(mut self, path: F) -> Self {
306         self.pid_file = Some(path.as_ref().to_owned());
307         self
308     }
309 
310     /// If `chown` is true, daemonize will change the pid-file ownership, if user or group are provided
chown_pid_file(mut self, chown: bool) -> Self311     pub fn chown_pid_file(mut self, chown: bool) -> Self {
312         self.chown_pid_file = chown;
313         self
314     }
315 
316     /// Change working directory to `path` or `/` by default.
working_directory<F: AsRef<Path>>(mut self, path: F) -> Self317     pub fn working_directory<F: AsRef<Path>>(mut self, path: F) -> Self {
318         self.directory = path.as_ref().to_owned();
319         self
320     }
321 
322     /// Drop privileges to `user`.
user<U: Into<User>>(mut self, user: U) -> Self323     pub fn user<U: Into<User>>(mut self, user: U) -> Self {
324         self.user = Some(user.into());
325         self
326     }
327 
328     /// Drop privileges to `group`.
group<G: Into<Group>>(mut self, group: G) -> Self329     pub fn group<G: Into<Group>>(mut self, group: G) -> Self {
330         self.group = Some(group.into());
331         self
332     }
333 
334     /// Change umask to `mask` or `0o027` by default.
umask(mut self, mask: mode_t) -> Self335     pub fn umask(mut self, mask: mode_t) -> Self {
336         self.umask = mask;
337         self
338     }
339 
340     /// Change root to `path`
chroot<F: AsRef<Path>>(mut self, path: F) -> Self341     pub fn chroot<F: AsRef<Path>>(mut self, path: F) -> Self {
342         self.root = Some(path.as_ref().to_owned());
343         self
344     }
345 
346     /// Execute `action` just before dropping privileges. Most common usecase is to open listening socket.
347     /// Result of `action` execution will be returned by `start` method.
privileged_action<N, F: FnOnce() -> N + 'static>(self, action: F) -> Daemonize<N>348     pub fn privileged_action<N, F: FnOnce() -> N + 'static>(self, action: F) -> Daemonize<N> {
349         let mut new: Daemonize<N> = unsafe { transmute(self) };
350         new.privileged_action = BoxFnOnce::new(action);
351         new
352     }
353 
354     /// Execute `action` just before exiting the parent process. Most common usecase is to synchronize with
355     /// forked processes.
exit_action<F: FnOnce() + 'static>(mut self, action: F) -> Daemonize<T>356     pub fn exit_action<F: FnOnce() + 'static>(mut self, action: F) -> Daemonize<T> {
357         self.exit_action = BoxFnOnce::new(action);
358         self
359     }
360 
361     /// Configuration for the child process's standard output stream.
stdout<S: Into<Stdio>>(mut self, stdio: S) -> Self362     pub fn stdout<S: Into<Stdio>>(mut self, stdio: S) -> Self {
363         self.stdout = stdio.into();
364         self
365     }
366 
367     /// Configuration for the child process's standard error stream.
stderr<S: Into<Stdio>>(mut self, stdio: S) -> Self368     pub fn stderr<S: Into<Stdio>>(mut self, stdio: S) -> Self {
369         self.stderr = stdio.into();
370         self
371     }
372 
373     /// Start daemonization process.
start(self) -> std::result::Result<T, DaemonizeError>374     pub fn start(self) -> std::result::Result<T, DaemonizeError> {
375         // Maps an Option<T> to Option<U> by applying a function Fn(T) -> Result<U, DaemonizeError>
376         // to a contained value and try! it's result
377         macro_rules! maptry {
378             ($expr:expr, $f: expr) => {
379                 match $expr {
380                     None => None,
381                     Some(x) => Some(try!($f(x))),
382                 };
383             };
384         }
385 
386         unsafe {
387             let pid_file_fd = maptry!(self.pid_file.clone(), create_pid_file);
388 
389             perform_fork(Some(self.exit_action))?;
390 
391             set_current_dir(&self.directory).map_err(|_| DaemonizeError::ChangeDirectory)?;
392             set_sid()?;
393             umask(self.umask);
394 
395             perform_fork(None)?;
396 
397             redirect_standard_streams(self.stdin, self.stdout, self.stderr)?;
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                     // Or pid file is not provided, or both user and group
408                     _ => 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.call();
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 
perform_fork(exit_action: Option<BoxFnOnce<'static, (), ()>>) -> Result<()>428 unsafe fn perform_fork(exit_action: Option<BoxFnOnce<'static, (), ()>>) -> Result<()> {
429     let pid = fork();
430     if pid < 0 {
431         Err(DaemonizeError::Fork)
432     } else if pid == 0 {
433         Ok(())
434     } else {
435         if let Some(exit_action) = exit_action {
436             exit_action.call()
437         }
438         exit(0)
439     }
440 }
441 
set_sid() -> Result<()>442 unsafe fn set_sid() -> Result<()> {
443     tryret!(setsid(), Ok(()), DaemonizeError::DetachSession)
444 }
445 
redirect_standard_streams(stdin: Stdio, stdout: Stdio, stderr: Stdio) -> Result<()>446 unsafe fn redirect_standard_streams(stdin: Stdio, stdout: Stdio, stderr: Stdio) -> Result<()> {
447     let devnull_fd = open(b"/dev/null\0" as *const [u8; 10] as _, 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 
get_group(group: Group) -> Result<gid_t>475 unsafe fn get_group(group: Group) -> Result<gid_t> {
476     match group {
477         Group::Id(id) => Ok(id),
478         Group::Name(name) => {
479             let s = 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 
set_group(group: gid_t) -> Result<()>488 unsafe fn set_group(group: gid_t) -> Result<()> {
489     tryret!(setgid(group), Ok(()), DaemonizeError::SetGroup)
490 }
491 
get_user(user: User) -> Result<uid_t>492 unsafe fn get_user(user: User) -> Result<uid_t> {
493     match user {
494         User::Id(id) => Ok(id),
495         User::Name(name) => {
496             let s = 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 
set_user(user: uid_t) -> Result<()>505 unsafe fn set_user(user: uid_t) -> Result<()> {
506     tryret!(setuid(user), Ok(()), DaemonizeError::SetUser)
507 }
508 
create_pid_file(path: PathBuf) -> Result<libc::c_int>509 unsafe fn create_pid_file(path: PathBuf) -> Result<libc::c_int> {
510     let path_c = 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 
chown_pid_file(path: PathBuf, uid: uid_t, gid: gid_t) -> Result<()>524 unsafe fn chown_pid_file(path: PathBuf, uid: uid_t, gid: gid_t) -> Result<()> {
525     let path_c = pathbuf_into_cstring(path)?;
526     tryret!(
527         libc::chown(path_c.as_ptr(), uid, gid),
528         Ok(()),
529         DaemonizeError::ChownPidfile
530     )
531 }
532 
write_pid_file(fd: libc::c_int) -> Result<()>533 unsafe 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, pid_c.as_ptr() as *const libc::c_void, pid_length) < pid_length as isize {
542         Err(DaemonizeError::WritePid)
543     } else {
544         Ok(())
545     }
546 }
547 
change_root(path: PathBuf) -> Result<()>548 unsafe 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 
pathbuf_into_cstring(path: PathBuf) -> Result<CString>558 fn pathbuf_into_cstring(path: PathBuf) -> Result<CString> {
559     CString::new(path.into_os_string().into_vec()).map_err(|_| DaemonizeError::PathContainsNul)
560 }
561 
errno() -> Errno562 fn errno() -> Errno {
563     io::Error::last_os_error().raw_os_error().expect("errno")
564 }
565