Skip to main content

daemonize_me/
daemon.rs

1use std::any::Any;
2use std::convert::TryFrom;
3use std::ffi::{CString, OsStr, OsString};
4use std::fs::File;
5use std::io::prelude::*;
6use std::path::{Path, PathBuf};
7use std::process::exit;
8
9use nix::sys::stat::{umask, Mode};
10#[cfg(target_os = "macos")]
11use nix::unistd::{
12    chdir, chown, close, dup2, fork, getpid, setgid, setsid, setuid, ForkResult, Gid, Pid, Uid,
13};
14#[cfg(not(target_os = "macos"))]
15use nix::unistd::{
16    chdir, chown, fork, getpid, initgroups, setgid, setsid, setuid, ForkResult, Gid, Uid,
17};
18
19use crate::ffi::{set_proc_name, PasswdRecord};
20use crate::group::Group;
21use crate::stdio::{redirect_stdio, Stdio};
22use crate::user::User;
23use crate::DaemonError::{InvalidGroup, InvalidUser, StartNotCalled};
24use crate::{DaemonError, Result};
25
26/// Basic daemonization consists of:
27/// forking the process, getting a new Session ID (sid), setting the umask, changing the standard io streams
28/// to files and finally dropping privileges.
29///
30/// **NOTE:** Beware there is no escalation back if dropping privileges
31#[derive(Debug, Clone)]
32pub struct Daemon<'a> {
33    pub(crate) chdir: PathBuf,
34    pub(crate) pid_file: Option<PathBuf>,
35    pub(crate) chown_pid_file: bool,
36    pub(crate) user: Option<User>,
37    pub(crate) group: Option<Group>,
38    pub(crate) umask: u16,
39    // stdin is practically always null
40    pub(crate) stdin: Box<Stdio<'a>>,
41    pub(crate) stdout: Box<Stdio<'a>>,
42    pub(crate) stderr: Box<Stdio<'a>>,
43    pub(crate) name: Option<OsString>,
44    pub(crate) before_fork_hook: Option<fn(pid: i32)>,
45    pub(crate) after_fork_parent_hook: Option<fn(parent_pid: i32, child_pid: i32) -> !>,
46    pub(crate) after_fork_child_hook: Option<fn(parent_pid: i32, child_pid: i32) -> ()>,
47    pub(crate) after_init_hook_data: Option<&'a dyn Any>,
48    pub(crate) after_init_hook: Option<fn(Option<&'a dyn Any>)>,
49    pub(crate) child_pid: Option<i32>,
50    pub(crate) parent_pid: i32,
51    pub(crate) is_child: bool,
52    pub(crate) has_forked: bool,
53    pub(crate) dropped_privileges: bool,
54}
55
56pub struct PidPair {
57    pub child_pid: i32,
58    pub parent_pid: i32,
59}
60
61impl<'a> Daemon<'a> {
62    pub fn new() -> Self {
63        Daemon {
64            chdir: Path::new("/").to_owned(),
65            pid_file: None,
66            chown_pid_file: false,
67            user: None,
68            group: None,
69            umask: 0o027,
70            stdin: Box::new(Stdio::devnull()),
71            stdout: Box::new(Stdio::devnull()),
72            stderr: Box::new(Stdio::devnull()),
73            name: None,
74            before_fork_hook: None,
75            after_fork_parent_hook: None,
76            after_fork_child_hook: None,
77            after_init_hook_data: None,
78            after_init_hook: None,
79            child_pid: None,
80            parent_pid: getpid().as_raw(),
81            has_forked: false,
82            is_child: false,
83            dropped_privileges: false,
84        }
85    }
86
87    /// Give your daemon a pid file
88    ///
89    /// By default, no pid file is created.
90    ///
91    /// # Arguments
92    /// * `path` - path to the file suggested `/var/run/my_program_name.pid`
93    /// * `chmod` - if set a chmod of the file to the user and group passed will be attempted (**this being true makes setting an user and group mandatory**)
94    pub fn pid_file<T: AsRef<Path>>(mut self, path: T, chmod: Option<bool>) -> Self {
95        self.pid_file = Some(path.as_ref().to_owned());
96        self.chown_pid_file = chmod.unwrap_or(false);
97        self
98    }
99
100    /// As the last step the code will change the working directory to this one defaults to `/`
101    pub fn work_dir<T: AsRef<Path>>(mut self, path: T) -> Self {
102        self.chdir = path.as_ref().to_owned();
103        self
104    }
105
106    /// The code will attempt to drop privileges with `setuid` to the provided user
107    ///
108    /// **NOTE:** If you provide a user, you must also provide a group.
109    pub fn user<T: Into<User>>(mut self, user: T) -> Self {
110        self.user = Some(user.into());
111        self
112    }
113
114    /// The code will attempt to drop privileges with `setgid` to the provided group
115    ///
116    /// **NOTE:** You must provide a group if you provide an user.
117    pub fn group<T: Into<Group>>(mut self, group: T) -> Self {
118        self.group = Some(group.into());
119        self
120    }
121
122    pub fn group_copy_user(mut self) -> Result<Self> {
123        if let Some(user) = &self.user {
124            self.group = Some(Group::try_from(&user.name)?);
125            Ok(self)
126        } else {
127            Err(InvalidUser)
128        }
129    }
130
131    /// umask for the process, defaults to `0o027`
132    pub fn umask(mut self, mask: u16) -> Self {
133        self.umask = mask;
134        self
135    }
136
137    /// Set this to be able to give inputs via stdio to the child
138    pub fn stdin<T: Into<Stdio<'a>>>(mut self, stdio: T) -> Self {
139        self.stdin = Box::new(stdio.into());
140        self
141    }
142
143    /// Determines where standard output will be piped to since daemons have no console attached
144    ///
145    /// It's highly recommended to set this to a file if you want to see output.
146    pub fn stdout<T: Into<Stdio<'a>>>(mut self, stdio: T) -> Self {
147        self.stdout = Box::new(stdio.into());
148        self
149    }
150
151    /// Determines where standard error will be piped to since daemons have no console attached
152    ///
153    /// It's highly recommended to set this to a file if you want to see output.
154    pub fn stderr<T: Into<Stdio<'a>>>(mut self, stdio: T) -> Self {
155        self.stderr = Box::new(stdio.into());
156        self
157    }
158
159    /// Set the daemon process name
160    ///
161    /// For example, this is what shows up in `ps`.
162    pub fn name(mut self, name: &OsStr) -> Self {
163        self.name = Some(OsString::from(name));
164        self
165    }
166
167    /// Hook called before the fork with the current pid as argument
168    pub fn setup_pre_fork_hook(mut self, pre_fork_hook: fn(pid: i32)) -> Self {
169        self.before_fork_hook = Some(pre_fork_hook);
170        self
171    }
172
173    /// Hook called after the fork with the parent pid as argument
174    ///
175    /// Can be used to continue some work on the parent after the fork.
176    /// **NOTE:** This hook must not return! For instance, you could call `std::process::exit()`.
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// # use daemonize_me::Daemon;
182    ///
183    /// fn post_fork_parent(ppid: i32, cpid: i32) -> ! {
184    ///     println!("Parent pid: {}, Child pid {}", ppid, cpid);
185    ///     println!("Exiting parent now");
186    ///     std::process::exit(0);
187    /// }
188    ///
189    /// let daemon = Daemon::new()
190    ///     .setup_post_fork_parent_hook(post_fork_parent)
191    ///     .start();
192    /// ```
193    pub fn setup_post_fork_parent_hook(
194        mut self,
195        post_fork_parent_hook: fn(parent_pid: i32, child_pid: i32) -> !,
196    ) -> Self {
197        self.after_fork_parent_hook = Some(post_fork_parent_hook);
198        self
199    }
200
201    /// Hook called after the fork with the parent and child pid as arguments
202    ///
203    /// # Examples
204    ///
205    /// ```
206    /// # use daemonize_me::Daemon;
207    ///
208    /// fn post_fork_child(ppid: i32, cpid: i32) {
209    ///     println!("Parent pid: {}, Child pid {}", ppid, cpid);
210    ///     println!("This hook is called in the child");
211    ///     // Child hook must return
212    ///     return
213    /// }
214    ///
215    /// let daemon = Daemon::new()
216    ///     .setup_post_fork_child_hook(post_fork_child)
217    ///     .start();
218    /// ```
219    pub fn setup_post_fork_child_hook(
220        mut self,
221        post_fork_child_hook: fn(parent_pid: i32, child_pid: i32) -> (),
222    ) -> Self {
223        self.after_fork_child_hook = Some(post_fork_child_hook);
224        self
225    }
226
227    /// Hook called right before returning control to the caller, that is, right after `start()`
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// # use std::any::Any;
233    /// # use daemonize_me::Daemon;
234    ///
235    /// fn after_init(_: Option<&dyn Any>) {
236    ///     println!("Initialized the daemon!");
237    ///     return
238    /// }
239    ///
240    /// let daemon = Daemon::new()
241    ///     .setup_post_init_hook(after_init, None)
242    ///     .start();
243    /// ```
244    pub fn setup_post_init_hook(
245        mut self,
246        post_fork_child_hook: fn(ctx: Option<&'a dyn Any>),
247        data: Option<&'a dyn Any>,
248    ) -> Self {
249        self.after_init_hook = Some(post_fork_child_hook);
250        self.after_init_hook_data = data;
251        self
252    }
253
254    pub fn is_child(self) -> bool {
255        self.is_child
256    }
257
258    pub fn get_parent_pid(self) -> i32 {
259        self.parent_pid
260    }
261
262    pub fn get_child_pid(self) -> Option<i32> {
263        self.child_pid
264    }
265
266    pub fn get_pids(&self) -> Result<PidPair> {
267        if let Some(cpid) = self.child_pid {
268            Ok(PidPair {
269                child_pid: cpid,
270                parent_pid: self.parent_pid,
271            })
272        } else {
273            Err(StartNotCalled)
274        }
275    }
276
277    fn do_fork(&mut self) -> Result<()> {
278        unsafe {
279            match fork() {
280                Ok(ForkResult::Parent { child: cpid }) => {
281                    self.is_child = false;
282                    self.child_pid = Some(cpid.as_raw());
283
284                    if let Some(hook) = self.after_fork_parent_hook {
285                        hook(self.parent_pid, cpid.as_raw());
286                    } else {
287                        exit(0)
288                    }
289                }
290                Ok(ForkResult::Child) => {
291                    // Set up stream redirection as early as possible
292                    redirect_stdio(&self.stdin, &self.stdout, &self.stderr)?;
293                    let pid = getpid();
294                    self.is_child = true;
295                    self.child_pid = Some(pid.as_raw());
296
297                    if let Some(hook) = self.after_fork_child_hook {
298                        hook(self.parent_pid, pid.as_raw());
299                    }
300                    ()
301                }
302                Err(_) => return Err(DaemonError::Fork),
303            }
304            self.has_forked = true;
305            Ok(())
306        }
307    }
308
309    fn do_chdir(&self) -> Result<()> {
310        let chdir_path = self.chdir.to_owned();
311        match chdir::<Path>(chdir_path.as_ref()) {
312            Ok(_) => Ok(()),
313            Err(_) => Err(DaemonError::ChDir),
314        }
315    }
316
317    fn setup_pid_file(&self, pid_file_path: &PathBuf) -> Result<()> {
318        let pid_file = &pid_file_path;
319
320        let user = match self.user.clone() {
321            Some(user) => Uid::from_raw(user.id),
322            None => return Err(InvalidUser),
323        };
324
325        let gr = match self.group.clone() {
326            Some(grp) => Gid::from_raw(grp.id),
327            None => return Err(InvalidGroup),
328        };
329
330        match File::create(pid_file) {
331            Ok(mut fp) => {
332                if let Err(_) = fp.write_all(self.child_pid.unwrap().to_string().as_ref()) {
333                    return Err(DaemonError::WritePid);
334                }
335            }
336            Err(_) => return Err(DaemonError::WritePid),
337        }
338
339        if self.chown_pid_file && self.pid_file.is_some() {
340            match chown::<PathBuf>(pid_file_path, Some(user), Some(gr)) {
341                Ok(_) => return Ok(()),
342                Err(_) => return Err(DaemonError::ChownPid),
343            };
344        };
345        Ok(())
346    }
347
348    fn check_chown_precodnitions(&self) -> Result<()> {
349        if self.chown_pid_file && (self.user.is_none() || self.group.is_none()) {
350            return Err(DaemonError::InvalidUserGroupPair);
351        } else if (self.user.is_some() || self.group.is_some())
352            && (self.user.is_none() || self.group.is_none())
353        {
354            return Err(DaemonError::InvalidUserGroupPair);
355        } else {
356            Ok(())
357        }
358    }
359
360    fn setup_privileges(&mut self) -> Result<()> {
361        self.check_chown_precodnitions()?;
362
363        let pid_file_path = match self.pid_file.clone() {
364            Some(path) => path.clone(),
365            None => Path::new("").to_path_buf(),
366        };
367
368        if self.pid_file.is_some() {
369            self.setup_pid_file(&pid_file_path)?;
370        }
371
372        // We did the check in self.check_chown_precondition
373        Ok({
374            let user = match self.user.clone() {
375                Some(user) => Uid::from_raw(user.id),
376                None => return Err(InvalidUser),
377            };
378
379            let uname = match PasswdRecord::lookup_record_by_id(user.as_raw()) {
380                Ok(record) => record.pw_name,
381                Err(_) => return Err(DaemonError::InvalidUser),
382            };
383
384            let gr = match self.group.clone() {
385                Some(grp) => Gid::from_raw(grp.id),
386                None => return Err(InvalidGroup),
387            };
388
389            // change proc group
390            match setgid(gr) {
391                Ok(_) => (),
392                Err(_) => return Err(DaemonError::SetGid),
393            };
394            #[cfg(not(target_os = "macos"))]
395            {
396                let u_cstr = match CString::new(uname) {
397                    Ok(cstr) => cstr,
398                    Err(_) => return Err(DaemonError::SetGid),
399                };
400                match initgroups(&u_cstr, gr) {
401                    Ok(_) => (),
402                    Err(_) => return Err(DaemonError::InitGroups),
403                };
404            }
405
406            // change the proc uid
407            match setuid(user) {
408                Ok(_) => (),
409                Err(_) => return Err(DaemonError::SetUid),
410            }
411            self.dropped_privileges = true;
412        })
413    }
414
415    /// Using the parameters set, daemonize the process
416    pub fn start(&mut self) -> Result<PidPair> {
417        // self pid is set on the constructor
418
419        // If the hook is set call it with the parent pid
420        if let Some(hook) = self.before_fork_hook {
421            hook(self.parent_pid);
422        }
423
424        // Fork and if the process is the parent exit gracefully
425        // if the  process is the child just continue execution
426        // this was made unsafe by the nix upstream in between versions
427        // thus the unsafe block is required here
428        self.do_fork()?;
429
430        if let Some(proc_name) = &self.name {
431            match set_proc_name(proc_name.as_ref()) {
432                Ok(()) => (),
433                Err(e) => return Err(e),
434            }
435        }
436        // Set the umask either to 0o027 (rwxr-x---) or provided value
437        let umask_mode = match Mode::from_bits(self.umask as _) {
438            Some(mode) => mode,
439            None => return Err(DaemonError::InvalidUmaskBits),
440        };
441        umask(umask_mode);
442
443        // Set the sid so the process isn't session orphan
444        if let Err(_) = setsid() {
445            return Err(DaemonError::SetSid);
446        };
447        // Do the final chdir before dropping privileges
448        if let Err(_) = chdir::<Path>(self.chdir.as_path()) {
449            return Err(DaemonError::ChDir);
450        };
451
452        // create pid file and if configured to, chmod it
453        // Drop privileges and chown the requested files
454        self.setup_privileges()?;
455
456        // chdir
457        self.do_chdir()?;
458
459        let pid_pair = if let Ok(pair) = self.get_pids() {
460            pair
461        } else {
462            unreachable!("This call should be impossible to fail, check if do_fork is updating the pid correctly")
463        };
464
465        // Now this process should be a daemon, we run the hook and return or just return
466        if let Some(hook) = self.after_init_hook {
467            hook(self.after_init_hook_data);
468            Ok(pid_pair)
469        } else {
470            Ok(pid_pair)
471        }
472    }
473}