Skip to main content

daemonize2/
lib.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//
3// This file is licensed under the MIT license or the Apache License, Version 2.0, at your choice.
4//
5// Copyright 2016 Fedor Gogolev
6// Copyright 2025 Oliver Old
7//
8// Licensed under the Apache License, Version 2.0 (the "License");
9// you may not use this file except in compliance with the License.
10// You may obtain a copy of the License at
11//
12//     http://www.apache.org/licenses/LICENSE-2.0
13//
14// Unless required by applicable law or agreed to in writing, software
15// distributed under the License is distributed on an "AS IS" BASIS,
16// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17// See the License for the specific language governing permissions and
18// limitations under the License.
19
20#![doc = include_str!("../README.md")]
21#![cfg(unix)]
22
23mod error;
24
25#[cfg(feature = "tester")]
26pub mod tester_lib;
27
28extern crate errno;
29extern crate libc;
30
31use std::env::set_current_dir;
32use std::ffi::{CStr, CString};
33use std::fmt;
34use std::fs::File;
35use std::os::fd::OwnedFd;
36use std::os::unix::ffi::OsStringExt;
37use std::os::unix::io::AsRawFd;
38use std::os::unix::process::ExitStatusExt;
39use std::path::PathBuf;
40use std::process::{ExitStatus, exit};
41
42use self::error::check_err;
43
44pub use self::error::{Error, ErrorKind};
45
46#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
47#[cfg_attr(
48    feature = "tester",
49    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
50)]
51enum UserImpl {
52    Name(String),
53    Id(libc::uid_t),
54}
55
56/// Expects system user ID or name. If a name is provided, it will be resolved to an ID later.
57#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
58#[cfg_attr(
59    feature = "tester",
60    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
61)]
62pub struct User {
63    inner: UserImpl,
64}
65
66impl From<&str> for User {
67    fn from(t: &str) -> User {
68        User {
69            inner: UserImpl::Name(t.to_owned()),
70        }
71    }
72}
73
74impl From<u32> for User {
75    fn from(t: u32) -> User {
76        User {
77            inner: UserImpl::Id(t as libc::uid_t),
78        }
79    }
80}
81
82#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
83#[cfg_attr(
84    feature = "tester",
85    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
86)]
87enum GroupImpl {
88    Name(String),
89    Id(libc::gid_t),
90}
91
92/// Expects system group ID or name. If a name is provided, it will be resolved to an ID later.
93#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
94#[cfg_attr(
95    feature = "tester",
96    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
97)]
98pub struct Group {
99    inner: GroupImpl,
100}
101
102impl From<&str> for Group {
103    fn from(t: &str) -> Group {
104        Group {
105            inner: GroupImpl::Name(t.to_owned()),
106        }
107    }
108}
109
110impl From<u32> for Group {
111    fn from(t: u32) -> Group {
112        Group {
113            inner: GroupImpl::Id(t as libc::gid_t),
114        }
115    }
116}
117
118/// File mode creation mask.
119#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
120#[cfg_attr(
121    feature = "tester",
122    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
123)]
124pub struct Mask {
125    inner: libc::mode_t,
126}
127
128impl From<u32> for Mask {
129    fn from(inner: u32) -> Mask {
130        Mask {
131            inner: inner as libc::mode_t,
132        }
133    }
134}
135
136#[derive(Debug)]
137enum StdioImpl {
138    Devnull,
139    RedirectToFile(File),
140    RedirectToFd(OwnedFd),
141    Keep,
142}
143
144/// Describes what to do with a standard I/O stream for a child process.
145#[derive(Debug)]
146pub struct Stdio {
147    inner: StdioImpl,
148}
149
150impl Stdio {
151    pub fn devnull() -> Self {
152        Self {
153            inner: StdioImpl::Devnull,
154        }
155    }
156
157    pub fn keep() -> Self {
158        Self {
159            inner: StdioImpl::Keep,
160        }
161    }
162}
163
164impl From<File> for Stdio {
165    fn from(file: File) -> Self {
166        Self {
167            inner: StdioImpl::RedirectToFile(file),
168        }
169    }
170}
171
172impl From<OwnedFd> for Stdio {
173    fn from(fd: OwnedFd) -> Self {
174        Self {
175            inner: StdioImpl::RedirectToFd(fd),
176        }
177    }
178}
179
180/// Parent process execution outcome.
181#[derive(Debug, PartialEq, Eq)]
182#[non_exhaustive]
183pub struct Parent {
184    pub first_child_exit_status: ExitStatus,
185}
186
187/// Child process execution outcome.
188#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
189#[non_exhaustive]
190pub struct Child<T> {
191    pub privileged_action_result: T,
192}
193
194/// Daemonization process outcome. Can be matched to check is it a parent process or a child
195/// process.
196#[derive(Debug, PartialEq, Eq)]
197pub enum Outcome<T> {
198    Parent(Result<Parent, Error>),
199    Child(Result<Child<T>, Error>),
200}
201
202impl<T> Outcome<T> {
203    pub fn is_parent(&self) -> bool {
204        match self {
205            Outcome::Parent(_) => true,
206            Outcome::Child(_) => false,
207        }
208    }
209
210    pub fn is_child(&self) -> bool {
211        match self {
212            Outcome::Parent(_) => false,
213            Outcome::Child(_) => true,
214        }
215    }
216}
217
218/// Daemonization options.
219///
220/// Fork the process in the background, disassociate from its process group and the control terminal.
221/// Change umask value to `0o027`, redirect all standard streams to `/dev/null`.
222///
223/// Optionally:
224///
225///   * change working directory to provided value;
226///   * maintain and lock the pid-file;
227///   * drop user privileges;
228///   * drop group privileges;
229///   * change root directory;
230///   * change the pid-file ownership to provided user (and/or) group;
231///   * execute any provided action just before dropping privileges.
232///
233pub struct Daemonize<T> {
234    directory: Option<PathBuf>,
235    pid_file: Option<PathBuf>,
236    chown_pid_file_user: Option<User>,
237    chown_pid_file_group: Option<Group>,
238    user: Option<User>,
239    group: Option<Group>,
240    umask: Mask,
241    root: Option<PathBuf>,
242    privileged_action: Box<dyn FnOnce() -> T>,
243    stdin: Stdio,
244    stdout: Stdio,
245    stderr: Stdio,
246}
247
248impl<T> fmt::Debug for Daemonize<T> {
249    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
250        fmt.debug_struct("Daemonize")
251            .field("directory", &self.directory)
252            .field("pid_file", &self.pid_file)
253            .field("chown_pid_file_user", &self.chown_pid_file_user)
254            .field("chown_pid_file_group", &self.chown_pid_file_group)
255            .field("user", &self.user)
256            .field("group", &self.group)
257            .field("umask", &self.umask)
258            .field("root", &self.root)
259            .field("stdin", &self.stdin)
260            .field("stdout", &self.stdout)
261            .field("stderr", &self.stderr)
262            .finish()
263    }
264}
265
266impl Default for Daemonize<()> {
267    fn default() -> Self {
268        Self::new()
269    }
270}
271
272impl Daemonize<()> {
273    pub fn new() -> Self {
274        Daemonize {
275            directory: None,
276            pid_file: None,
277            chown_pid_file_user: None,
278            chown_pid_file_group: None,
279            user: None,
280            group: None,
281            umask: 0o027.into(),
282            privileged_action: Box::new(|| ()),
283            root: None,
284            stdin: Stdio::devnull(),
285            stdout: Stdio::devnull(),
286            stderr: Stdio::devnull(),
287        }
288    }
289}
290
291impl<T> Daemonize<T> {
292    /// Create pid-file at `path`, lock it exclusive and write daemon pid.
293    pub fn pid_file<F: Into<PathBuf>>(mut self, path: F) -> Self {
294        self.pid_file = Some(path.into());
295        self
296    }
297
298    /// Changes the pid-file ownership to `user`.
299    pub fn chown_pid_file_user<U: Into<User>>(mut self, user: U) -> Self {
300        self.chown_pid_file_user = Some(user.into());
301        self
302    }
303
304    /// Changes the pid-file ownership to `group`.
305    pub fn chown_pid_file_group<G: Into<Group>>(mut self, group: G) -> Self {
306        self.chown_pid_file_group = Some(group.into());
307        self
308    }
309
310    /// Change working directory to `path`.
311    pub fn working_directory<F: Into<PathBuf>>(mut self, path: F) -> Self {
312        self.directory = Some(path.into());
313        self
314    }
315
316    /// Drop privileges to `user`.
317    pub fn user<U: Into<User>>(mut self, user: U) -> Self {
318        self.user = Some(user.into());
319        self
320    }
321
322    /// Drop privileges to `group`.
323    pub fn group<G: Into<Group>>(mut self, group: G) -> Self {
324        self.group = Some(group.into());
325        self
326    }
327
328    /// Change umask to `mask` or `0o027` by default.
329    pub fn umask<M: Into<Mask>>(mut self, mask: M) -> Self {
330        self.umask = mask.into();
331        self
332    }
333
334    /// Change root to `path`
335    pub fn chroot<F: Into<PathBuf>>(mut self, path: F) -> Self {
336        self.root = Some(path.into());
337        self
338    }
339
340    /// Execute `action` just before dropping privileges. Most common use case is to open
341    /// listening socket. Result of `action` execution will be returned by `start` method.
342    pub fn privileged_action<N, F: FnOnce() -> N + 'static>(self, action: F) -> Daemonize<N> {
343        Daemonize {
344            directory: self.directory,
345            pid_file: self.pid_file,
346            chown_pid_file_user: self.chown_pid_file_user,
347            chown_pid_file_group: self.chown_pid_file_group,
348            user: self.user,
349            group: self.group,
350            umask: self.umask,
351            root: self.root,
352            privileged_action: Box::new(action),
353            stdin: self.stdin,
354            stdout: self.stdout,
355            stderr: self.stderr,
356        }
357    }
358
359    /// Configuration for the child process's standard output stream.
360    pub fn stdout<S: Into<Stdio>>(mut self, stdio: S) -> Self {
361        self.stdout = stdio.into();
362        self
363    }
364
365    /// Configuration for the child process's standard error stream.
366    pub fn stderr<S: Into<Stdio>>(mut self, stdio: S) -> Self {
367        self.stderr = stdio.into();
368        self
369    }
370
371    /// Start daemonization process. Terminate parent after first fork. Returns privileged action
372    /// result to the child.
373    ///
374    /// # Safety
375    ///
376    /// This is not safe to call inside a multi-threaded process. Familiarize yourself with the
377    /// documentation for [fork(2)](https://man7.org/linux/man-pages/man2/fork.2.html).
378    pub unsafe fn start(self) -> Result<T, Error> {
379        unsafe {
380            match self.execute() {
381                Outcome::Parent(Ok(Parent {
382                    first_child_exit_status,
383                })) => exit(
384                    first_child_exit_status
385                        .code()
386                        .unwrap_or_else(|| libc::abort()),
387                ),
388                Outcome::Parent(Err(err)) => Err(err),
389                Outcome::Child(Ok(child)) => Ok(child.privileged_action_result),
390                Outcome::Child(Err(err)) => Err(err),
391            }
392        }
393    }
394
395    /// Execute daemonization process. Don't terminate parent after first fork.
396    ///
397    /// # Safety
398    ///
399    /// This is not safe to call inside a multi-threaded process. Familiarize yourself with the
400    /// documentation for [fork(2)](https://man7.org/linux/man-pages/man2/fork.2.html).
401    pub unsafe fn execute(self) -> Outcome<T> {
402        unsafe {
403            match perform_fork() {
404                Ok(Some(first_child_pid)) => Outcome::Parent(match waitpid(first_child_pid) {
405                    Err(err) => Err(err.into()),
406                    Ok(first_child_exit_status) => Ok(Parent {
407                        first_child_exit_status,
408                    }),
409                }),
410                Err(err) => Outcome::Parent(Err(err.into())),
411                Ok(None) => match self.execute_child() {
412                    Ok(privileged_action_result) => Outcome::Child(Ok(Child {
413                        privileged_action_result,
414                    })),
415                    Err(err) => Outcome::Child(Err(err.into())),
416                },
417            }
418        }
419    }
420
421    unsafe fn execute_child(self) -> Result<T, ErrorKind> {
422        unsafe {
423            if let Some(directory) = &self.directory {
424                set_current_dir(directory)
425                    .map_err(|_| ErrorKind::ChangeDirectory(errno::errno().into()))?;
426            }
427
428            set_sid()?;
429            libc::umask(self.umask.inner);
430
431            if perform_fork()?.is_some() {
432                exit(0)
433            };
434
435            let pid_file_fd = self
436                .pid_file
437                .clone()
438                .map(|pid_file| create_pid_file(pid_file))
439                .transpose()?;
440
441            redirect_standard_streams(self.stdin, self.stdout, self.stderr)?;
442
443            let uid = self.user.map(get_user).transpose()?;
444            let gid = self.group.map(get_group).transpose()?;
445
446            let args: Option<(PathBuf, libc::uid_t, libc::gid_t)> = if let Some(pid) = self.pid_file
447            {
448                match (
449                    self.chown_pid_file_user.map(get_user).transpose()?,
450                    self.chown_pid_file_group.map(get_group).transpose()?,
451                ) {
452                    (Some(uid), Some(gid)) => Some((pid, uid, gid)),
453                    (None, Some(gid)) => Some((pid, libc::uid_t::MAX - 1, gid)),
454                    (Some(uid), None) => Some((pid, uid, libc::gid_t::MAX - 1)),
455                    _ => None,
456                }
457            } else {
458                None
459            };
460
461            if let Some((pid, uid, gid)) = args {
462                chown_pid_file(pid, uid, gid)?;
463            }
464
465            if let Some(pid_file_fd) = pid_file_fd {
466                set_cloexec_pid_file(pid_file_fd)?;
467            }
468
469            let privileged_action_result = (self.privileged_action)();
470
471            if let Some(root) = self.root {
472                change_root(root)?;
473            }
474
475            if let Some(gid) = gid {
476                set_group(gid)?;
477            }
478
479            if let Some(uid) = uid {
480                set_user(uid)?;
481            }
482
483            if let Some(pid_file_fd) = pid_file_fd {
484                write_pid_file(pid_file_fd)?;
485            }
486
487            Ok(privileged_action_result)
488        }
489    }
490}
491
492unsafe fn perform_fork() -> Result<Option<libc::pid_t>, ErrorKind> {
493    let pid = check_err(unsafe { libc::fork() }, ErrorKind::Fork)?;
494    if pid == 0 { Ok(None) } else { Ok(Some(pid)) }
495}
496
497unsafe fn waitpid(pid: libc::pid_t) -> Result<ExitStatus, ErrorKind> {
498    let mut status = 0;
499    check_err(
500        unsafe { libc::waitpid(pid, &mut status, 0) },
501        ErrorKind::Wait,
502    )?;
503    Ok(ExitStatus::from_raw(status))
504}
505
506unsafe fn set_sid() -> Result<(), ErrorKind> {
507    check_err(unsafe { libc::setsid() }, ErrorKind::DetachSession)?;
508    Ok(())
509}
510
511unsafe fn redirect_standard_streams(
512    stdin: Stdio,
513    stdout: Stdio,
514    stderr: Stdio,
515) -> Result<(), ErrorKind> {
516    let devnull_fd = check_err(
517        unsafe { libc::open(b"/dev/null\0" as *const [u8; 10] as _, libc::O_RDWR) },
518        ErrorKind::OpenDevnull,
519    )?;
520
521    let process_stdio = |fd, stdio: Stdio| {
522        match stdio.inner {
523            StdioImpl::Devnull => {
524                check_err(
525                    unsafe { libc::dup2(devnull_fd, fd) },
526                    ErrorKind::RedirectStreams,
527                )?;
528            }
529            StdioImpl::RedirectToFile(file) => {
530                let raw_fd = file.as_raw_fd();
531                check_err(
532                    unsafe { libc::dup2(raw_fd, fd) },
533                    ErrorKind::RedirectStreams,
534                )?;
535            }
536            StdioImpl::RedirectToFd(owned_fd) => {
537                check_err(
538                    unsafe { libc::dup2(owned_fd.as_raw_fd(), fd) },
539                    ErrorKind::RedirectStreams,
540                )?;
541            }
542            StdioImpl::Keep => (),
543        };
544        Ok(())
545    };
546
547    process_stdio(libc::STDIN_FILENO, stdin)?;
548    process_stdio(libc::STDOUT_FILENO, stdout)?;
549    process_stdio(libc::STDERR_FILENO, stderr)?;
550
551    check_err(unsafe { libc::close(devnull_fd) }, ErrorKind::CloseDevnull)?;
552
553    Ok(())
554}
555
556fn get_group(group: Group) -> Result<libc::gid_t, ErrorKind> {
557    match group.inner {
558        GroupImpl::Id(id) => Ok(id),
559        GroupImpl::Name(name) => {
560            let s = CString::new(name).map_err(|_| ErrorKind::GroupContainsNul)?;
561            match get_gid_by_name(&s) {
562                Some(id) => get_group(id.into()),
563                None => Err(ErrorKind::GroupNotFound),
564            }
565        }
566    }
567}
568
569unsafe fn set_group(group: libc::gid_t) -> Result<(), ErrorKind> {
570    check_err(unsafe { libc::setregid(group, group) }, ErrorKind::SetGroup)?;
571    Ok(())
572}
573
574fn get_user(user: User) -> Result<libc::uid_t, ErrorKind> {
575    match user.inner {
576        UserImpl::Id(id) => Ok(id),
577        UserImpl::Name(name) => {
578            let s = CString::new(name).map_err(|_| ErrorKind::UserContainsNul)?;
579            match get_uid_by_name(&s) {
580                Some(id) => get_user(id.into()),
581                None => Err(ErrorKind::UserNotFound),
582            }
583        }
584    }
585}
586
587unsafe fn set_user(user: libc::uid_t) -> Result<(), ErrorKind> {
588    check_err(unsafe { libc::setreuid(user, user) }, ErrorKind::SetUser)?;
589    Ok(())
590}
591
592unsafe fn create_pid_file(path: PathBuf) -> Result<libc::c_int, ErrorKind> {
593    let path_c = pathbuf_into_cstring(path)?;
594
595    unsafe {
596        let fd = check_err(
597            libc::open(path_c.as_ptr(), libc::O_WRONLY | libc::O_CREAT, 0o666),
598            ErrorKind::OpenPidfile,
599        )?;
600
601        check_err(
602            libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB),
603            ErrorKind::LockPidfile,
604        )?;
605
606        Ok(fd)
607    }
608}
609
610fn chown_pid_file(path: PathBuf, uid: libc::uid_t, gid: libc::gid_t) -> Result<(), ErrorKind> {
611    let path_c = pathbuf_into_cstring(path)?;
612    check_err(
613        unsafe { libc::chown(path_c.as_ptr(), uid, gid) },
614        ErrorKind::ChownPidfile,
615    )?;
616    Ok(())
617}
618
619unsafe fn write_pid_file(fd: libc::c_int) -> Result<(), ErrorKind> {
620    let pid = unsafe { libc::getpid() };
621    let pid_buf = format!("{}\n", pid).into_bytes();
622    let pid_length = pid_buf.len();
623    let pid_c = CString::new(pid_buf).unwrap();
624
625    let written = unsafe {
626        check_err(libc::ftruncate(fd, 0), ErrorKind::TruncatePidfile)?;
627        check_err(
628            libc::write(fd, pid_c.as_ptr() as *const libc::c_void, pid_length),
629            ErrorKind::WritePid,
630        )?
631    };
632
633    if written < pid_length as isize {
634        return Err(ErrorKind::WritePidUnspecifiedError);
635    }
636
637    Ok(())
638}
639
640unsafe fn set_cloexec_pid_file(fd: libc::c_int) -> Result<(), ErrorKind> {
641    unsafe {
642        if cfg!(not(target_os = "redox")) {
643            let flags = check_err(libc::fcntl(fd, libc::F_GETFD), ErrorKind::GetPidfileFlags)?;
644
645            check_err(
646                libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC),
647                ErrorKind::SetPidfileFlags,
648            )?;
649        } else {
650            check_err(libc::ioctl(fd, libc::FIOCLEX), ErrorKind::SetPidfileFlags)?;
651        }
652        Ok(())
653    }
654}
655
656fn change_root(path: PathBuf) -> Result<(), ErrorKind> {
657    let path_c = pathbuf_into_cstring(path)?;
658    check_err(unsafe { libc::chroot(path_c.as_ptr()) }, ErrorKind::Chroot)?;
659    Ok(())
660}
661
662fn get_gid_by_name(name: &CStr) -> Option<libc::gid_t> {
663    unsafe {
664        let ptr = libc::getgrnam(name.as_ptr() as *const libc::c_char);
665        if ptr.is_null() {
666            None
667        } else {
668            let s = &*ptr;
669            Some(s.gr_gid)
670        }
671    }
672}
673
674fn get_uid_by_name(name: &CStr) -> Option<libc::uid_t> {
675    unsafe {
676        let ptr = libc::getpwnam(name.as_ptr() as *const libc::c_char);
677        if ptr.is_null() {
678            None
679        } else {
680            let s = &*ptr;
681            Some(s.pw_uid)
682        }
683    }
684}
685
686fn pathbuf_into_cstring(path: PathBuf) -> Result<CString, ErrorKind> {
687    CString::new(path.into_os_string().into_vec()).map_err(|_| ErrorKind::PathContainsNul)
688}