service_install/install/init/systemd/
unit.rs1use std::ffi::OsString;
2use std::path::{Path, PathBuf};
3
4use crate::install::init::{extract_path, COMMENT_PREAMBLE, COMMENT_SUFFIX};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub(crate) struct Unit {
8 body: String,
9 pub(crate) path: PathBuf,
10 pub(crate) file_name: String,
11}
12
13#[derive(Debug, thiserror::Error)]
16pub enum FindExeError {
17 #[error("Could not read systemd unit file at: {path}")]
18 ReadingUnit {
19 #[source]
20 err: std::io::Error,
21 path: PathBuf,
22 },
23 #[error("ExecStart (use to find binary) is missing from servic unit at: {0}")]
24 ExecLineMissing(PathBuf),
25 #[error("Path to binary extracted from systemd unit does not lead to a file, path: {0}")]
26 ExecPathNotFile(PathBuf),
27 #[error("Could not un-escape/un-quote the Exec line in the unit file")]
28 Unquoting(#[source] extract_path::unsystemd_quote::UnquoteError),
29}
30
31#[derive(Debug, thiserror::Error)]
32pub enum Error {
33 #[error("File has no file name, can not be a systemd unit")]
34 NoName,
35 #[error("Could not read unit's content: {0}")]
36 FailedToRead(
37 #[from]
38 #[source]
39 std::io::Error,
40 ),
41 #[error("File has a non utf8 file name, that is not supported")]
42 NonUtf8,
43}
44
45impl Unit {
46 pub(crate) fn from_path(path: PathBuf) -> Result<Self, Error> {
47 Ok(Self {
48 body: std::fs::read_to_string(&path)?,
49 file_name: path
50 .file_name()
51 .ok_or(Error::NoName)?
52 .to_str()
53 .ok_or(Error::NonUtf8)?
54 .to_string(),
55 path,
56 })
57 }
58
59 pub(crate) fn exe_path(&self) -> Result<PathBuf, FindExeError> {
60 let exe_path = self
61 .body
62 .lines()
63 .map(str::trim)
64 .find_map(|l| l.strip_prefix("ExecStart="))
65 .map(extract_path::unsystemd_quote::first_segement)
66 .transpose()
67 .map_err(FindExeError::Unquoting)?
68 .ok_or(FindExeError::ExecLineMissing(self.path.clone()))?;
69 let exe_path = Path::new(exe_path.as_ref()).to_path_buf();
70 if exe_path.is_file() {
71 Ok(exe_path)
72 } else {
73 Err(FindExeError::ExecPathNotFile(exe_path))
74 }
75 }
76
77 pub(crate) fn our_service(&self) -> bool {
78 self.body.contains(COMMENT_PREAMBLE) && self.body.contains(COMMENT_SUFFIX)
79 }
80
81 pub(crate) fn has_install(&self) -> bool {
82 self.body.contains("[Install]")
83 }
84
85 pub(crate) fn name(&self) -> OsString {
86 self.path
87 .with_extension("")
88 .file_name()
89 .expect("Checked in Unit::from_path")
90 .to_os_string()
91 }
92}