solidb 1.2.1

A lightweight, high-performance structured database server written in Rust.
use std::fs::File;
use std::io;
use std::os::unix::io::AsRawFd;

#[derive(Debug)]
pub struct DaemonizeError(String);

impl std::fmt::Display for DaemonizeError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl std::error::Error for DaemonizeError {}

pub struct Daemonize {
    pid_file: Option<String>,
    working_directory: Option<String>,
    stdout: Option<File>,
    stderr: Option<File>,
}

impl Daemonize {
    pub fn new() -> Self {
        Self {
            pid_file: None,
            working_directory: None,
            stdout: None,
            stderr: None,
        }
    }

    pub fn pid_file(mut self, path: &str) -> Self {
        self.pid_file = Some(path.to_string());
        self
    }

    pub fn working_directory(mut self, path: &str) -> Self {
        self.working_directory = Some(path.to_string());
        self
    }

    pub fn stdout(mut self, file: File) -> Self {
        self.stdout = Some(file);
        self
    }

    pub fn stderr(mut self, file: File) -> Self {
        self.stderr = Some(file);
        self
    }

    pub fn start(self) -> Result<(), DaemonizeError> {
        unsafe {
            // First fork - detach from terminal
            match libc::fork() {
                -1 => {
                    return Err(DaemonizeError(format!(
                        "First fork failed: {}",
                        io::Error::last_os_error()
                    )))
                }
                0 => {
                    // Child process continues
                }
                _ => {
                    // Parent process exits
                    std::process::exit(0);
                }
            }

            // Create new session
            if libc::setsid() == -1 {
                return Err(DaemonizeError(format!(
                    "setsid failed: {}",
                    io::Error::last_os_error()
                )));
            }

            // Second fork to prevent reacquiring terminal
            match libc::fork() {
                -1 => {
                    return Err(DaemonizeError(format!(
                        "Second fork failed: {}",
                        io::Error::last_os_error()
                    )))
                }
                0 => {
                    // Grandchild process continues
                }
                _ => {
                    // Child process exits
                    std::process::exit(0);
                }
            }

            // Set umask.
            //
            // 0o077, not 0o022: everything this process creates holds
            // secrets. RocksDB writes the SSTs and WAL that back `_admins`
            // (argon2 hashes), `_api_keys` (SHA-256 hashes) and `_env`
            // (provider credentials in cleartext), plus the replication log —
            // and at 0o022 those land 0644, readable by every local account.
            // The one file that was explicitly chmod'ed 0600, the bootstrap
            // admin password, sat in a directory whose siblings exposed the
            // same secrets.
            libc::umask(0o077);

            // Change working directory
            if let Some(ref dir) = self.working_directory {
                let dir_cstring = std::ffi::CString::new(dir.as_bytes())
                    .map_err(|e| DaemonizeError(format!("Invalid working directory: {}", e)))?;
                if libc::chdir(dir_cstring.as_ptr()) == -1 {
                    return Err(DaemonizeError(format!(
                        "chdir failed: {}",
                        io::Error::last_os_error()
                    )));
                }
            }

            // Redirect stdout
            if let Some(ref file) = self.stdout {
                let fd = file.as_raw_fd();
                if libc::dup2(fd, libc::STDOUT_FILENO) == -1 {
                    return Err(DaemonizeError(format!(
                        "dup2 stdout failed: {}",
                        io::Error::last_os_error()
                    )));
                }
            }

            // Redirect stderr
            if let Some(ref file) = self.stderr {
                let fd = file.as_raw_fd();
                if libc::dup2(fd, libc::STDERR_FILENO) == -1 {
                    return Err(DaemonizeError(format!(
                        "dup2 stderr failed: {}",
                        io::Error::last_os_error()
                    )));
                }
            }

            // Write PID file
            if let Some(ref pid_file) = self.pid_file {
                let pid = std::process::id();
                std::fs::write(pid_file, format!("{}\n", pid))
                    .map_err(|e| DaemonizeError(format!("Failed to write PID file: {}", e)))?;
            }
        }

        Ok(())
    }
}

impl Default for Daemonize {
    fn default() -> Self {
        Self::new()
    }
}