emu_cli/
supervisor.rs

1use super::{
2    config_storage::XDGConfigStorage,
3    template::Systemd,
4    traits::{ConfigStorageHandler, SupervisorHandler, SupervisorStorageHandler, Supervisors},
5    vm::VM,
6};
7use crate::util::{path_exists, pid_running};
8use anyhow::{anyhow, Result};
9use std::{
10    fs::write,
11    path::PathBuf,
12    process::{Command, Stdio},
13    sync::Arc,
14};
15
16const SYSTEMD_USER_DIR: &str = "systemd/user";
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct SystemdSupervisorStorage {
20    basedir: PathBuf,
21}
22
23impl Default for SystemdSupervisorStorage {
24    fn default() -> Self {
25        let dir = dirs::config_dir().unwrap().join(SYSTEMD_USER_DIR);
26        std::fs::create_dir_all(&dir).unwrap_or_default();
27        Self { basedir: dir }
28    }
29}
30
31impl SupervisorStorageHandler for SystemdSupervisorStorage {
32    fn exists(&self, vm: &VM) -> bool {
33        path_exists(self.service_filename(vm))
34    }
35
36    fn service_filename(&self, vm: &VM) -> PathBuf {
37        self.basedir
38            .join(format!("{}.service", self.service_name(vm)))
39    }
40
41    fn service_name(&self, vm: &VM) -> String {
42        format!("emu.{}", vm.name())
43    }
44
45    fn remove(&self, vm: &VM) -> Result<()> {
46        Ok(std::fs::remove_file(self.service_filename(vm))?)
47    }
48
49    fn list(&self) -> Result<Vec<String>> {
50        let mut v: Vec<String> = Vec::new();
51        for item in std::fs::read_dir(&self.basedir)? {
52            match item {
53                Ok(item) => {
54                    let filename = item.file_name().to_str().unwrap().to_string();
55                    if filename.starts_with("emu.") && filename.ends_with(".service") {
56                        v.push(
57                            filename
58                                .trim_start_matches("emu.")
59                                .trim_end_matches(".service")
60                                .to_string(),
61                        )
62                    }
63                }
64                Err(_) => {}
65            }
66        }
67        Ok(v)
68    }
69
70    fn create(&self, vm: &VM) -> Result<()> {
71        Ok(write(
72            self.service_filename(vm),
73            Systemd::default().template(vm)?,
74        )?)
75    }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Default)]
79pub struct NullSupervisorStorage;
80
81impl SupervisorStorageHandler for NullSupervisorStorage {
82    fn list(&self) -> Result<Vec<String>> {
83        Err(anyhow!("Null storage: cannot list services"))
84    }
85
86    fn remove(&self, _: &VM) -> Result<()> {
87        Err(anyhow!("Null storage: cannot remove a service"))
88    }
89
90    fn create(&self, _: &VM) -> Result<()> {
91        Err(anyhow!("Null storage: cannot create a service"))
92    }
93
94    fn service_name(&self, vm: &VM) -> String {
95        vm.name()
96    }
97
98    fn service_filename(&self, vm: &VM) -> PathBuf {
99        vm.name().into()
100    }
101
102    fn exists(&self, _: &VM) -> bool {
103        false
104    }
105}
106
107fn systemd(mut command: Vec<&str>) -> Result<()> {
108    command.insert(0, "--user");
109    match Command::new("/bin/systemctl")
110        .args(command.clone())
111        .stderr(Stdio::null())
112        .stdout(Stdio::null())
113        .status()
114    {
115        Ok(es) => {
116            if matches!(es.code(), Some(0)) {
117                Ok(())
118            } else {
119                Err(anyhow!("systemctl exited uncleanly: {}", es))
120            }
121        }
122        Err(e) => Err(anyhow!(e)),
123    }
124}
125
126#[derive(Debug, Clone)]
127pub struct SystemdSupervisor {
128    config: Arc<Box<dyn ConfigStorageHandler>>,
129}
130
131impl Default for SystemdSupervisor {
132    fn default() -> Self {
133        Self {
134            config: Arc::new(Box::new(XDGConfigStorage::default())),
135        }
136    }
137}
138
139impl SupervisorHandler for SystemdSupervisor {
140    fn storage(&self) -> Arc<Box<dyn SupervisorStorageHandler>> {
141        Arc::new(Box::new(SystemdSupervisorStorage::default()))
142    }
143
144    fn supervised(&self) -> bool {
145        true
146    }
147
148    fn reload(&self) -> Result<()> {
149        systemd(vec!["daemon-reload"])
150    }
151
152    fn is_active(&self, vm: &VM) -> Result<bool> {
153        Ok(systemd(vec!["is-active", &self.storage().service_name(vm), "-q"]).is_ok())
154    }
155
156    fn pidof(&self, vm: &VM) -> Result<u32> {
157        Ok(std::fs::read_to_string(self.config.pidfile(vm))?.parse::<u32>()?)
158    }
159
160    fn kind(&self) -> Supervisors {
161        Supervisors::Systemd
162    }
163}
164
165#[derive(Debug, Clone)]
166pub struct PidSupervisor {
167    config: Arc<Box<dyn ConfigStorageHandler>>,
168}
169
170impl Default for PidSupervisor {
171    fn default() -> Self {
172        Self {
173            config: Arc::new(Box::new(XDGConfigStorage::default())),
174        }
175    }
176}
177
178impl SupervisorHandler for PidSupervisor {
179    fn storage(&self) -> Arc<Box<dyn SupervisorStorageHandler>> {
180        Arc::new(Box::new(NullSupervisorStorage::default()))
181    }
182
183    fn supervised(&self) -> bool {
184        false
185    }
186
187    fn reload(&self) -> Result<()> {
188        Err(anyhow!("PIDs cannot be reloaded"))
189    }
190
191    fn is_active(&self, vm: &VM) -> Result<bool> {
192        let f = match std::fs::read_to_string(self.config.pidfile(&vm)) {
193            Ok(f) => f,
194            Err(_) => return Ok(false),
195        };
196
197        Ok(pid_running(f.parse::<u32>()?))
198    }
199
200    fn pidof(&self, vm: &VM) -> Result<u32> {
201        Ok(std::fs::read_to_string(self.config.pidfile(vm))?.parse::<u32>()?)
202    }
203
204    fn kind(&self) -> Supervisors {
205        Supervisors::Pid
206    }
207}