use std::fmt;
use std::path::{Path, PathBuf};
#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(target_os = "windows")]
pub mod windows;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnitPath(pub PathBuf);
impl UnitPath {
pub fn as_path(&self) -> &Path {
&self.0
}
pub fn into_inner(self) -> PathBuf {
self.0
}
}
impl fmt::Display for UnitPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.display())
}
}
#[derive(Debug)]
pub enum BootAutostartError {
Resolve(String),
Io(std::io::Error),
InitSystem(String),
Unsupported(String),
}
impl fmt::Display for BootAutostartError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BootAutostartError::Resolve(m) => write!(f, "could not resolve install path: {m}"),
BootAutostartError::Io(e) => write!(f, "filesystem error: {e}"),
BootAutostartError::InitSystem(m) => write!(f, "init system error: {m}"),
BootAutostartError::Unsupported(os) => {
write!(f, "boot autostart not supported on {os}")
}
}
}
}
impl std::error::Error for BootAutostartError {}
impl From<std::io::Error> for BootAutostartError {
fn from(e: std::io::Error) -> Self {
BootAutostartError::Io(e)
}
}
pub fn install(daemon_binary: &Path) -> Result<UnitPath, BootAutostartError> {
#[cfg(target_os = "linux")]
{
linux::install(daemon_binary)
}
#[cfg(target_os = "macos")]
{
macos::install(daemon_binary)
}
#[cfg(target_os = "windows")]
{
windows::install(daemon_binary)
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
let _ = daemon_binary;
Err(BootAutostartError::Unsupported(
std::env::consts::OS.to_string(),
))
}
}
pub fn uninstall() -> Result<(), BootAutostartError> {
#[cfg(target_os = "linux")]
{
linux::uninstall()
}
#[cfg(target_os = "macos")]
{
macos::uninstall()
}
#[cfg(target_os = "windows")]
{
windows::uninstall()
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
Err(BootAutostartError::Unsupported(
std::env::consts::OS.to_string(),
))
}
}
pub fn render_unit(daemon_binary: &Path) -> String {
#[cfg(target_os = "linux")]
{
linux::render_unit(daemon_binary)
}
#[cfg(target_os = "macos")]
{
macos::render_unit(daemon_binary)
}
#[cfg(target_os = "windows")]
{
windows::render_unit(daemon_binary)
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
let _ = daemon_binary;
String::new()
}
}
#[cfg(any(target_os = "linux", test))]
pub(crate) fn shell_quote_single(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('\'');
for ch in s.chars() {
if ch == '\'' {
out.push_str("'\\''");
} else {
out.push(ch);
}
}
out.push('\'');
out
}
#[cfg(any(target_os = "macos", test))]
pub(crate) fn xml_escape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'&' => out.push_str("&"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
other => out.push(other),
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shell_quote_wraps_simple_path() {
assert_eq!(shell_quote_single("/usr/bin/foo"), "'/usr/bin/foo'");
}
#[test]
fn shell_quote_escapes_embedded_single_quote() {
assert_eq!(shell_quote_single("o'malley"), "'o'\\''malley'");
}
#[test]
fn xml_escape_handles_metacharacters() {
assert_eq!(
xml_escape("a<b&c>d\"e'f"),
"a<b&c>d"e'f"
);
}
}