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