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