Skip to main content

a7n_platform/
autostart.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3//! Per-user start-at-login integration.
4
5use std::io;
6use std::path::{Path, PathBuf};
7use std::process::{Command, Stdio};
8
9#[cfg(target_os = "linux")]
10mod linux;
11#[cfg(target_os = "linux")]
12use linux as backend;
13#[cfg(target_os = "macos")]
14mod macos;
15#[cfg(target_os = "macos")]
16use macos as backend;
17#[cfg(windows)]
18mod windows;
19#[cfg(windows)]
20use windows as backend;
21
22/// Installs and starts the per-user login service.
23/// Returns a notice when the service manager is unavailable.
24///
25/// # Errors
26/// Returns file or service-manager errors.
27pub fn enable(exe: &Path) -> io::Result<Option<&'static str>> {
28    backend::enable(exe)
29}
30
31/// Disables and removes the per-user login service.
32///
33/// # Errors
34/// Returns file or service-manager errors.
35pub fn disable() -> io::Result<Option<&'static str>> {
36    backend::disable()
37}
38
39/// Whether the per-user login service is installed and enabled.
40///
41/// # Errors
42/// Returns file or service-manager errors.
43pub fn is_enabled() -> io::Result<bool> {
44    backend::is_enabled()
45}
46
47/// Finds the daemon beside the CLI, falling back to PATH.
48///
49/// # Errors
50/// Returns an error when the current executable cannot be located.
51pub fn daemon_executable() -> io::Result<PathBuf> {
52    let sibling = std::env::current_exe()?
53        .with_file_name(format!("apeirogond{}", std::env::consts::EXE_SUFFIX));
54    Ok(if sibling.is_file() {
55        sibling
56    } else {
57        PathBuf::from(format!("apeirogond{}", std::env::consts::EXE_SUFFIX))
58    })
59}
60
61/// Spawns a detached daemon with no inherited terminal streams.
62///
63/// # Errors
64/// Returns any process spawn error.
65pub fn start(exe: &Path) -> io::Result<()> {
66    let mut command = Command::new(exe);
67    command
68        .stdin(Stdio::null())
69        .stdout(Stdio::null())
70        .stderr(Stdio::null());
71    #[cfg(unix)]
72    {
73        use std::os::unix::process::CommandExt;
74        // SAFETY: setsid is async-signal-safe and touches no Rust state after fork.
75        unsafe {
76            command.pre_exec(|| {
77                rustix::process::setsid()
78                    .map(|_| ())
79                    .map_err(io::Error::from)
80            });
81        }
82    }
83    #[cfg(windows)]
84    {
85        use std::os::windows::process::CommandExt;
86        use windows_sys::Win32::System::Threading::{CREATE_NEW_PROCESS_GROUP, DETACHED_PROCESS};
87        command.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS);
88    }
89    command.spawn()?;
90    Ok(())
91}
92
93#[cfg(unix)]
94fn remove(path: &Path) -> io::Result<()> {
95    match std::fs::remove_file(path) {
96        Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
97        result => result,
98    }
99}
100
101#[cfg(unix)]
102fn run(command: &mut Command) -> io::Result<()> {
103    let output = command.output()?;
104    if output.status.success() {
105        return Ok(());
106    }
107    Err(io::Error::other(
108        String::from_utf8_lossy(&output.stderr).trim().to_owned(),
109    ))
110}