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