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;
194    ///
195    /// fn post_fork_parent(ppid: i32, cpid: i32) -> ! {
196    ///     println!("Parent pid: {}, Child pid {}", ppid, cpid);
197    ///     println!("Exiting parent now");
198    ///     std::process::exit(0);
199    /// }
200    ///
201    /// let daemon = Daemon::new()
202    ///     .setup_post_fork_parent_hook(post_fork_parent)
203    ///     .start();
204    /// ```
205    pub fn setup_post_fork_parent_hook(mut self, post_fork_parent_hook: HookFnFinal) -> Self {
206        self.after_fork_parent_hook = Some(post_fork_parent_hook);
207        self
208    }
209
210    /// Hook called after the fork with the parent and child pid as arguments
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// # use daemonize_me::Daemon;
216    ///
217    /// fn post_fork_child(ppid: i32, cpid: i32) {
218    ///     println!("Parent pid: {}, Child pid {}", ppid, cpid);
219    ///     println!("This hook is called in the child");
220    ///     // Child hook must return
221    ///     return
222    /// }
223    ///
224    /// let daemon = Daemon::new()
225    ///     .setup_post_fork_child_hook(post_fork_child)
226    ///     .start();
227    /// ```
228    pub fn setup_post_fork_child_hook(mut self, post_fork_child_hook: HookFnReturning) -> Self {
229        self.after_fork_child_hook = Some(post_fork_child_hook);
230        self
231    }
232
233    /// Hook called right before returning control to the caller, that is, right after `start()`
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// # use std::any::Any;
239    /// # use daemonize_me::Daemon;
240    ///
241    /// fn after_init(_: Option<&dyn Any>) {
242    ///     println!("Initialized the daemon!");
243    ///     return
244    /// }
245    ///
246    /// let daemon = Daemon::new()
247    ///     .setup_post_init_hook(after_init, None)
248    ///     .start();
249    /// ```
250    pub fn setup_post_init_hook(
251        mut self,
252        post_fork_child_hook: HookfFnPostInit<'a>,
253        data: Option<&'a dyn Any>,
254    ) -> Self {
255        self.after_init_hook = Some(post_fork_child_hook);
256        self.after_init_hook_data = data;
257        self
258    }
259
260    pub fn is_child(self) -> bool {
261        self.is_child
262    }
263
264    pub fn get_parent_pid(self) -> i32 {
265        self.parent_pid
266    }
267
268    pub fn get_child_pid(self) -> Option<i32> {
269        self.child_pid
270    }
271
272    pub fn get_pids(&self) -> Result<PidPair> {
273        if let Some(cpid) = self.child_pid {
274            Ok(PidPair {
275                child_pid: cpid,
276                parent_pid: self.parent_pid,
277            })
278        } else {
279            Err(StartNotCalled)
280        }
281    }
282
283    fn do_fork(&mut self) -> Result<()> {
284        unsafe {
285            match fork() {
286                Ok(ForkResult::Parent { child: cpid }) => {
287                    self.is_child = false;
288                    self.child_pid = Some(cpid.as_raw());
289
290                    if let Some(hook) = self.after_fork_parent_hook {
291                        hook(self.get_status());
292                    } else {
293                        exit(0)
294                    }
295                }
296                Ok(ForkResult::Child) => {
297                    // Set up stream redirection as early as possible
298                    redirect_stdio(&self.stdin, &self.stdout, &self.stderr)?;
299                    let pid = getpid();
300                    self.is_child = true;
301                    self.child_pid = Some(pid.as_raw());
302
303                    if let Some(hook) = self.after_fork_child_hook {
304                        hook(self.get_status());
305                    }
306                    ()
307                }
308                Err(_) => return Err(DaemonError::Fork),
309            }
310        }
311        self.has_forked = true;
312        Ok(())
313    }
314
315    fn do_chdir(&self) -> Result<()> {
316        let chdir_path = self.chdir.to_owned();
317        match chdir::<Path>(chdir_path.as_ref()) {
318            Ok(_) => Ok(()),
319            Err(_) => Err(DaemonError::ChDir),
320        }
321    }
322
323    fn setup_pid_file(&self, pid_file_path: &PathBuf) -> Result<()> {
324        let pid_file = &pid_file_path;
325
326        match File::create(pid_file) {
327            Ok(mut fp) => {
328                if let Err(_) = fp.write_all(self.child_pid.unwrap().to_string().as_ref()) {
329                    return Err(DaemonError::WritePid);
330                }
331            }
332            Err(_) => return Err(DaemonError::WritePid),
333        }
334
335        if self.user.is_some() && self.group.is_some() {
336            let user = match self.user.clone() {
337                Some(user) => Uid::from_raw(user.id),
338                None => return Err(InvalidUser),
339            };
340
341            let gr = match self.group.clone() {
342                Some(grp) => Gid::from_raw(grp.id),
343                None => return Err(InvalidGroup),
344            };
345            if self.chown_pid_file && self.pid_file.is_some() {
346                match chown::<PathBuf>(pid_file_path, Some(user), Some(gr)) {
347                    Ok(_) => return Ok(()),
348                    Err(_) => return Err(DaemonError::ChownPid),
349                };
350            }
351        };
352        Ok(())
353    }
354
355    fn valid_usr_gr_pair(&self) -> bool {
356        (self.user.is_some() && self.group.is_none())
357            || (self.user.is_none() && self.group.is_some())
358    }
359
360    fn setup_privileges(&mut self) -> Result<()> {
361        if self.user.is_none() && self.group.is_none() {
362            return Ok(());
363        } else if self.valid_usr_gr_pair() {
364            return Err(InvalidUserGroupPair);
365        }
366
367        let pid_file_path = match self.pid_file.clone() {
368            Some(path) => path.clone(),
369            None => Path::new("").to_path_buf(),
370        };
371
372        if self.pid_file.is_some() {
373            self.setup_pid_file(&pid_file_path)?;
374        }
375
376        // We did the check in self.check_chown_precondition
377        Ok({
378            let user = match self.user.clone() {
379                Some(user) => Uid::from_raw(user.id),
380                None => return Err(InvalidUser),
381            };
382
383            let uname = match PasswdRecord::lookup_record_by_id(user.as_raw()) {
384                Ok(record) => record.pw_name,
385                Err(_) => return Err(DaemonError::InvalidUser),
386            };
387
388            let gr = match self.group.clone() {
389                Some(grp) => Gid::from_raw(grp.id),
390                None => return Err(InvalidGroup),
391            };
392
393            // change proc group
394            match setgid(gr) {
395                Ok(_) => (),
396                Err(_) => return Err(DaemonError::SetGid),
397            };
398            #[cfg(not(target_os = "macos"))]
399            {
400                let u_cstr = match CString::new(uname) {
401                    Ok(cstr) => cstr,
402                    Err(_) => return Err(DaemonError::SetGid),
403                };
404                match initgroups(&u_cstr, gr) {
405                    Ok(_) => (),
406                    Err(_) => return Err(DaemonError::InitGroups),
407                };
408            }
409
410            // change the proc uid
411            match setuid(user) {
412                Ok(_) => (),
413                Err(_) => return Err(DaemonError::SetUid),
414            }
415            self.dropped_privileges = true;
416        })
417    }
418
419    pub fn get_status(&self) -> DaemonStatus {
420        let pids = if let Ok(pair) = self.get_pids() {
421            Some(pair)
422        } else {
423            None
424        };
425        let self_pid = if self.is_child {
426            self.child_pid.unwrap()
427        } else {
428            self.parent_pid
429        };
430
431        DaemonStatus {
432            pids: pids,
433            is_child: self.is_child,
434            dropped_privileges: self.dropped_privileges,
435            has_forked: self.has_forked,
436            self_pid: self_pid,
437        }
438    }
439
440    /// Using the parameters set, daemonize the process
441    pub fn start(&mut self) -> Result<PidPair> {
442        // self pid is set on the constructor
443
444        // If the hook is set call it with the parent pid
445        if let Some(hook) = self.before_fork_hook {
446            hook(self.parent_pid);
447        }
448
449        // Execute the fork, what happens next is dependent on if this is the parent or child process
450        self.do_fork()?;
451
452        if let Some(proc_name) = &self.name {
453            match set_proc_name(proc_name.as_ref()) {
454                Ok(()) => (),
455                Err(e) => return Err(e),
456            }
457        }
458        // Set the umask either to 0o027 (rwxr-x---) or provided value
459        let umask_mode = match Mode::from_bits(self.umask as _) {
460            Some(mode) => mode,
461            None => return Err(DaemonError::InvalidUmaskBits),
462        };
463        umask(umask_mode);
464
465        // Set the sid so the process isn't session orphan
466        if let Err(_) = setsid() {
467            return Err(DaemonError::SetSid);
468        };
469        // Do the final chdir before dropping privileges
470        if let Err(_) = chdir::<Path>(self.chdir.as_path()) {
471            return Err(DaemonError::ChDir);
472        };
473
474        // create pid file and if configured to, chmod it
475        // Drop privileges and chown the requested files
476        self.setup_privileges()?;
477
478        // chdir
479        self.do_chdir()?;
480
481        let pid_pair = if let Ok(pair) = self.get_pids() {
482            pair
483        } else {
484            unreachable!("This call should be impossible to fail, check if do_fork is updating the child_pid correctly")
485        };
486
487        // Now this process should be a daemon, we run the hook and return or just return
488        if let Some(hook) = self.after_init_hook {
489            hook(self.after_init_hook_data, self.get_status());
490            Ok(pid_pair)
491        } else {
492            Ok(pid_pair)
493        }
494    }
495}