service_install/install/init/systemd/
unit.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use std::ffi::OsString;
use std::path::{Path, PathBuf};

use crate::install::init::{extract_path, COMMENT_PREAMBLE, COMMENT_SUFFIX};

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Unit {
    body: String,
    pub(crate) path: PathBuf,
    pub(crate) file_name: OsString,
}

/// The executables location could not be found. It is needed to safely
/// uninstall.
#[derive(Debug, thiserror::Error)]
pub enum FindExeError {
    #[error("Could not read systemd unit file at: {path}, error: {err}")]
    ReadingUnit { err: std::io::Error, path: PathBuf },
    #[error("ExecStart (use to find binary) is missing from servic unit at: {0}")]
    ExecLineMissing(PathBuf),
    #[error("Path to binary extracted from systemd unit does not lead to a file, path: {0}")]
    ExecPathNotFile(PathBuf),
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("File has no file name, can not be a systemd unit")]
    NoName,
    #[error("Could not read unit's content: {0}")]
    FailedToRead(#[from] std::io::Error),
}

impl Unit {
    pub(crate) fn from_path(path: PathBuf) -> Result<Self, Error> {
        Ok(Self {
            body: std::fs::read_to_string(&path)?,
            file_name: path.file_name().ok_or(Error::NoName)?.to_os_string(),
            path,
        })
    }

    pub(crate) fn exe_path(&self) -> Result<PathBuf, FindExeError> {
        let exe_path = self
            .body
            .lines()
            .map(str::trim)
            .find_map(|l| l.strip_prefix("ExecStart="))
            .map(extract_path::split_unescaped_whitespace_once)
            .ok_or(FindExeError::ExecLineMissing(self.path.clone()))?;
        let exe_path = Path::new(&exe_path).to_path_buf();
        if exe_path.is_file() {
            Ok(exe_path)
        } else {
            Err(FindExeError::ExecPathNotFile(exe_path))
        }
    }

    pub(crate) fn our_service(&self) -> bool {
        self.body.contains(COMMENT_PREAMBLE) && self.body.contains(COMMENT_SUFFIX)
    }

    pub(crate) fn has_install(&self) -> bool {
        self.body.contains("[Install]")
    }

    pub(crate) fn name(&self) -> OsString {
        self.path
            .with_extension("")
            .file_name()
            .expect("Checked in Unit::from_path")
            .to_os_string()
    }
}