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
27pub 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 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 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 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 pub fn user<T: Into<User>>(mut self, user: T) -> Self {
105 self.user = Some(user.into());
106 self
107 }
108
109 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 pub fn start(self) -> Result<()> {
173 let mut pid: Pid;
174 let parent_pid = getpid();
175 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 let Some(hook) = self.before_fork_hook {
184 hook(parent_pid.as_raw());
185 }
186
187 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 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 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 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 if has_pid_file {
245 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 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 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 if let Some(hook) = self.after_init_hook {
310 hook(self.after_init_hook_data);
311 Ok(())
312 } else {
313 Ok(())
314 }
315 }
316}