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::{Mode, umask};
10#[cfg(not(target_os = "macos"))]
11use nix::unistd::{
12    chdir, chown, fork, ForkResult, getpid, Gid, initgroups, Pid, setgid, setsid,
13    setuid, Uid,
14};
15#[cfg(target_os = "macos")]
16use nix::unistd::{
17    chdir, chown, close, dup2, fork, ForkResult, getpid, Gid, Pid, setgid, setsid, setuid, Uid,
18};
19
20use crate::{DaemonError, Result};
21use crate::DaemonError::{InvalidGroup, InvalidUser};
22use crate::ffi::{PasswdRecord, set_proc_name};
23use crate::group::Group;
24use crate::stdio::{redirect_stdio, Stdio};
25use crate::user::User;
26
27/// Basic daemonization consists of:
28/// forking the process, getting a new sid, setting the umask, changing the standard io streams
29/// to files and finally dropping privileges.
30///
31/// Options:
32/// * user [optional], if set will drop privileges to the specified user **NOTE**: This library is strict and makes no assumptions if you provide a user you must provide a group
33/// * group [optional(**see note on user**)], if set will drop privileges to specified group
34/// * umask [optional], umask for the process defaults to 0o027
35/// * pid_file [optional], if set a pid file will be created default is that no file is created *
36/// * stdio [optional][**recommended**], this determines where standard output will be piped to since daemons have no console it's highly recommended to set this
37/// * stderr [optional][**recommended**], same as above but for standard error
38/// * chdir [optional], default is "/"
39/// * name [optional], set the daemon process name eg what shows in `ps` default is to not set a process name
40/// * before_fork_hook [optional], called before the fork with the current pid as argument
41/// * after_fork_parent_hook [optional], called after the fork with the parent pid as argument, can be used to continue some work on the parent after the fork (do not return)
42/// * after_fork_child_hook [optional], called after the fork with the parent and child pid as arguments
43///
44/// * See the setter function documentation for more details
45///
46/// **Beware there is no escalation back if dropping privileges**
47pub struct Daemon<'a> {
48    pub(crate) chdir: PathBuf,
49    pub(crate) pid_file: Option<PathBuf>,
50    pub(crate) chown_pid_file: bool,
51    pub(crate) user: Option<User>,
52    pub(crate) group: Option<Group>,
53    pub(crate) umask: u16,
54    // stdin is practically always null
55    pub(crate) stdin: Stdio,
56    pub(crate) stdout: Stdio,
57    pub(crate) stderr: Stdio,
58    pub(crate) name: Option<OsString>,
59    pub(crate) before_fork_hook: Option<fn(pid: i32)>,
60    pub(crate) after_fork_parent_hook: Option<fn(parent_pid: i32, child_pid: i32) -> !>,
61    pub(crate) after_fork_child_hook: Option<fn(parent_pid: i32, child_pid: i32) -> ()>,
62    pub(crate) after_init_hook_data: Option<&'a dyn Any>,
63    pub(crate) after_init_hook: Option<fn(Option<&'a dyn Any>)>,
64}
65
66impl<'a> Daemon<'a> {
67    pub fn new() -> Self {
68        Daemon {
69            chdir: Path::new("/").to_owned(),
70            pid_file: None,
71            chown_pid_file: false,
72            user: None,
73            group: None,
74            umask: 0o027,
75            stdin: Stdio::devnull(),
76            stdout: Stdio::devnull(),
77            stderr: Stdio::devnull(),
78            name: None,
79            before_fork_hook: None,
80            after_fork_parent_hook: None,
81            after_fork_child_hook: None,
82            after_init_hook_data: None,
83            after_init_hook: None,
84        }
85    }
86
87    /// This is a setter to give your daemon a pid file
88    /// # Arguments
89    /// * `path` - path to the file suggested `/var/run/my_program_name.pid`
90    /// * `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**)
91    pub fn pid_file<T: AsRef<Path>>(mut self, path: T, chmod: Option<bool>) -> Self {
92        self.pid_file = Some(path.as_ref().to_owned());
93        self.chown_pid_file = chmod.unwrap_or(false);
94        self
95    }
96
97    /// As the last step the code will change the working directory to this one defaults to `/`
98    pub fn work_dir<T: AsRef<Path>>(mut self, path: T) -> Self {
99        self.chdir = path.as_ref().to_owned();
100        self
101    }
102
103    /// The code will attempt to drop privileges with `setuid` to the provided user
104    pub fn user<T: Into<User>>(mut self, user: T) -> Self {
105        self.user = Some(user.into());
106        self
107    }
108
109    /// The code will attempt to drop privileges with `setgid` to the provided group, you mut provide a group if you provide an user
110    pub fn group<T: Into<Group>>(mut self, group: T) -> Self {
111        self.group = Some(group.into());
112        self
113    }
114
115    pub fn group_copy_user(mut self) -> Result<Self> {
116        if let Some(user) = &self.user {
117            self.group = Some(Group::try_from(&user.name)?);
118            Ok(self)
119        } else {
120            Err(InvalidUser)
121        }
122    }
123
124    pub fn umask(mut self, mask: u16) -> Self {
125        self.umask = mask;
126        self
127    }
128
129    pub fn stdin<T: Into<Stdio>>(mut self, stdio: T) -> Self {
130        self.stdin = stdio.into();
131        self
132    }
133
134    pub fn stdout<T: Into<Stdio>>(mut self, stdio: T) -> Self {
135        self.stdout = stdio.into();
136        self
137    }
138
139    pub fn stderr<T: Into<Stdio>>(mut self, stdio: T) -> Self {
140        self.stderr = stdio.into();
141        self
142    }
143
144    pub fn name(mut self, name: &OsStr) -> Self {
145        self.name = Some(OsString::from(name));
146        self
147    }
148
149    pub fn setup_pre_fork_hook(mut self, pre_fork_hook: fn(pid: i32)) -> Self {
150        self.before_fork_hook = Some(pre_fork_hook);
151        self
152    }
153
154    pub fn setup_post_fork_parent_hook(mut self, post_fork_parent_hook: fn(parent_pid: i32, child_pid: i32) -> !) -> Self {
155        self.after_fork_parent_hook = Some(post_fork_parent_hook);
156        self
157    }
158
159    pub fn setup_post_fork_child_hook(mut self, post_fork_child_hook: fn(parent_pid: i32, child_pid: i32) -> ()) -> Self {
160        self.after_fork_child_hook = Some(post_fork_child_hook);
161        self
162    }
163
164    pub fn setup_post_init_hook(mut self, post_fork_child_hook: fn(ctx: Option<&'a dyn Any>),
165                                data: Option<&'a dyn Any>) -> Self {
166        self.after_init_hook = Some(post_fork_child_hook);
167        self.after_init_hook_data = data;
168        self
169    }
170
171    /// Using the parameters set, daemonize the process
172    pub fn start(self) -> Result<()> {
173        let mut pid: Pid;
174        let parent_pid = getpid();
175        // resolve options to concrete values to please the borrow checker
176        let has_pid_file = self.pid_file.is_some();
177        let pid_file_path = match self.pid_file {
178            Some(path) => path.clone(),
179            None => Path::new("").to_path_buf(),
180        };
181
182        // If the hook is set call it with the parent pid
183        if let Some(hook) = self.before_fork_hook {
184            hook(parent_pid.as_raw());
185        }
186
187        // Fork and if the process is the parent exit gracefully
188        // if the  process is the child just continue execution
189        // this was made unsafe by the nix upstream in between versions
190        // thus the unsafe block is required here
191        unsafe {
192            match fork() {
193                Ok(ForkResult::Parent { child: cpid }) => {
194                    if let Some(hook) = self.after_fork_parent_hook {
195                        hook(parent_pid.as_raw(), cpid.as_raw());
196                    } else {
197                        exit(0)
198                    }
199                }
200                Ok(ForkResult::Child) => {
201                    // Set up stream redirection as early as possible
202                    redirect_stdio(&self.stdin, &self.stdout, &self.stderr)?;
203                    pid = getpid();
204                    if let Some(hook) = self.after_fork_child_hook {
205                        hook(parent_pid.as_raw(), pid.as_raw());
206                    }
207                    ()
208                }
209                Err(_) => return Err(DaemonError::Fork),
210            }
211        }
212
213        if self.chown_pid_file && (self.user.is_none() || self.group.is_none()) {
214            return Err(DaemonError::InvalidUserGroupPair);
215        } else if (self.user.is_some() || self.group.is_some())
216            && (self.user.is_none() || self.group.is_none())
217        {
218            return Err(DaemonError::InvalidUserGroupPair);
219        }
220
221        if let Some(proc_name) = &self.name {
222            match set_proc_name(proc_name.as_ref()) {
223                Ok(()) => (),
224                Err(e) => return Err(e)
225            }
226        }
227        // Set the umask either to 0o027 (rwxr-x---) or provided value
228        let umask_mode = match Mode::from_bits(self.umask as _) {
229            Some(mode) => mode,
230            None => return Err(DaemonError::InvalidUmaskBits),
231        };
232        umask(umask_mode);
233
234        // Set the sid so the process isn't session orphan
235        if let Err(_) = setsid() {
236            return Err(DaemonError::SetSid);
237        };
238        if let Err(_) = chdir::<Path>(self.chdir.as_path()) {
239            return Err(DaemonError::ChDir);
240        };
241        pid = getpid();
242
243        // create pid file and if configured to, chmod it
244        if has_pid_file {
245            // chmod of the pid file is deferred to after checking for the presence of the user and group
246            let pid_file = &pid_file_path;
247            match File::create(pid_file) {
248                Ok(mut fp) => {
249                    if let Err(_) = fp.write_all(pid.to_string().as_ref()) {
250                        return Err(DaemonError::WritePid);
251                    }
252                }
253                Err(_) => return Err(DaemonError::WritePid),
254            };
255        }
256
257        // Drop privileges and chown the requested files
258        if self.user.is_some() && self.group.is_some() {
259            let user = match self.user {
260                Some(user) => Uid::from_raw(user.id),
261                None => return Err(InvalidUser),
262            };
263
264            let uname = match PasswdRecord::lookup_record_by_id(user.as_raw()) {
265                Ok(record) => record.pw_name,
266                Err(_) => return Err(DaemonError::InvalidUser),
267            };
268
269            let gr = match self.group {
270                Some(grp) => Gid::from_raw(grp.id),
271                None => return Err(InvalidGroup),
272            };
273
274            if self.chown_pid_file && has_pid_file {
275                match chown(&pid_file_path, Some(user), Some(gr)) {
276                    Ok(_) => (),
277                    Err(_) => return Err(DaemonError::ChownPid),
278                };
279            }
280
281            match setgid(gr) {
282                Ok(_) => (),
283                Err(_) => return Err(DaemonError::SetGid),
284            };
285            #[cfg(not(target_os = "macos"))]
286                {
287                    let u_cstr = match CString::new(uname) {
288                        Ok(cstr) => cstr,
289                        Err(_) => return Err(DaemonError::SetGid),
290                    };
291                    match initgroups(&u_cstr, gr) {
292                        Ok(_) => (),
293                        Err(_) => return Err(DaemonError::InitGroups),
294                    };
295                }
296            match setuid(user) {
297                Ok(_) => (),
298                Err(_) => return Err(DaemonError::SetUid),
299            }
300        };
301        // chdir
302        let chdir_path = self.chdir.to_owned();
303        match chdir::<Path>(chdir_path.as_ref()) {
304            Ok(_) => (),
305            Err(_) => return Err(DaemonError::ChDir),
306        };
307
308        // Now this process should be a daemon, we run the hook and return or just return
309        if let Some(hook) = self.after_init_hook {
310            hook(self.after_init_hook_data);
311            Ok(())
312        } else {
313            Ok(())
314        }
315    }
316}