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}