emu_cli/
config_storage.rs1use super::{image::QEMU_IMG_DEFAULT_FORMAT, traits::ConfigStorageHandler, vm::VM};
2use crate::util::path_exists;
3use anyhow::{anyhow, Result};
4use std::{path::PathBuf, sync::Arc};
5
6#[derive(Debug, Clone)]
7pub struct XDGConfigStorage {
8 base: PathBuf,
9}
10
11impl XDGConfigStorage {
12 pub fn new(base: PathBuf) -> Self {
13 Self { base }
14 }
15}
16
17impl Default for XDGConfigStorage {
18 fn default() -> Self {
19 let dir = dirs::data_dir().unwrap_or(dirs::home_dir().unwrap());
20 let root = dir.join("emu");
21
22 std::fs::create_dir_all(root.clone()).unwrap_or(());
23
24 Self { base: root }
25 }
26}
27
28impl ConfigStorageHandler for XDGConfigStorage {
29 fn create(&self, vm: &VM) -> Result<()> {
30 Ok(std::fs::create_dir_all(self.vm_root(&vm))?)
31 }
32
33 fn rename(&self, old: &VM, new: &VM) -> Result<()> {
34 Ok(std::fs::rename(self.vm_root(old), self.vm_root(new))?)
35 }
36
37 fn vm_root(&self, vm: &VM) -> PathBuf {
38 self.base_path().join(vm.name())
39 }
40
41 fn running_vms(&self) -> Result<Vec<VM>> {
42 let mut ret = Vec::new();
43
44 for vm in self.vm_list()? {
45 if vm.supervisor().is_active(&vm)? {
46 ret.push(vm);
47 }
48 }
49
50 Ok(ret)
51 }
52
53 fn vm_list(&self) -> Result<Vec<VM>> {
54 match std::fs::read_dir(self.base_path()) {
55 Ok(rd) => {
56 let mut ret = Vec::new();
57 for dir in rd {
58 match dir {
59 Ok(dir) => match dir.file_name().into_string() {
60 Ok(s) => ret.push(VM::new(s, Arc::new(Box::new(self.clone())))),
61 Err(_) => {
62 return Err(anyhow!(
63 "could not iterate base directory; some vm filenames are invalid"
64 ))
65 }
66 },
67 Err(e) => return Err(anyhow!("could not iterate directory: {}", e)),
68 }
69 }
70
71 Ok(ret)
72 }
73 Err(e) => Err(anyhow!(e)),
74 }
75 }
76
77 fn vm_path(&self, vm: &VM, filename: &str) -> PathBuf {
78 self.vm_root(vm).join(filename)
79 }
80
81 fn vm_path_exists(&self, vm: &VM, filename: &str) -> bool {
82 path_exists(self.vm_path(vm, filename))
83 }
84
85 fn pidfile(&self, vm: &VM) -> PathBuf {
86 self.vm_path(&vm, "pid")
87 }
88
89 fn base_path(&self) -> PathBuf {
90 self.base.clone()
91 }
92
93 fn vm_exists(&self, vm: &VM) -> bool {
94 path_exists(self.vm_root(vm))
95 }
96
97 fn delete(&self, vm: &VM, disk: Option<String>) -> Result<()> {
98 if !self.vm_exists(vm) {
99 return Err(anyhow!("vm doesn't exist"));
100 }
101
102 let root = self.vm_root(vm);
103 if let Some(disk) = disk {
104 std::fs::remove_file(root.join(format!("qemu-{}.{}", disk, QEMU_IMG_DEFAULT_FORMAT)))?;
105 } else {
106 std::fs::remove_dir_all(root)?;
107 }
108
109 Ok(())
110 }
111
112 fn disk_list(&self, vm: &VM) -> Result<Vec<PathBuf>> {
113 if !self.vm_exists(vm) {
114 return Err(anyhow!("vm does not exist"));
115 }
116
117 let mut v = Vec::new();
118
119 let dir = std::fs::read_dir(self.vm_root(vm))?;
120 for item in dir {
121 if let Ok(item) = item {
122 if item
123 .path()
124 .to_str()
125 .unwrap()
126 .ends_with(&format!(".{}", QEMU_IMG_DEFAULT_FORMAT))
127 {
128 v.push(item.path());
129 }
130 }
131 }
132
133 v.sort();
134
135 Ok(v)
136 }
137
138 fn config_path(&self, vm: &VM) -> PathBuf {
139 self.vm_path(vm, "config")
140 }
141
142 fn monitor_path(&self, vm: &VM) -> PathBuf {
143 self.vm_path(vm, "mon")
144 }
145
146 fn write_config(&self, vm: VM) -> Result<()> {
147 vm.config().to_file(self.config_path(&vm))
148 }
149
150 fn size(&self, vm: &VM) -> Result<usize> {
151 let dir = std::fs::read_dir(self.vm_root(vm))?;
152 let mut total = 0;
153 let mut items = Vec::new();
154 let mut dirs = vec![dir];
155 while let Some(dir) = dirs.pop() {
156 for item in dir {
157 match item {
158 Ok(item) => {
159 let meta = item.metadata()?;
160 if meta.is_file() {
161 items.push(item);
162 }
163 }
164 _ => {}
165 }
166 }
167 }
168
169 for item in items {
170 let meta = item.metadata()?;
171 total += meta.len() as usize;
172 }
173
174 Ok(total)
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::vm::VM;
182 use anyhow::Result;
183 use tempfile::tempdir;
184
185 #[test]
186 fn test_xdg_storage() -> Result<()> {
187 let base = tempdir()?;
188 let base_path = base.into_path();
189 let storage = XDGConfigStorage::new(base_path.clone());
190
191 let vm1: VM = "vm1".to_string().into();
192 let vm2: VM = "vm2".to_string().into();
193 let vm3: VM = "vm3".to_string().into();
194
195 assert_eq!(storage.vm_list()?, vec![]);
196 storage.create(&vm1)?;
197 storage.create(&vm2)?;
198 assert_eq!(storage.vm_list()?, vec![vm1.clone(), vm2.clone()]);
199 storage.rename(&vm2, &vm3)?;
200 assert_eq!(storage.vm_list()?, vec![vm1.clone(), vm3.clone()]);
201 storage.create(&vm2)?;
202 assert_eq!(
203 storage.vm_list()?,
204 vec![vm1.clone(), vm3.clone(), vm2.clone()]
205 );
206
207 assert_eq!(storage.vm_root(&vm1), base_path.join(vm1.name()));
208 assert_eq!(storage.vm_root(&vm2), base_path.join(vm2.name()));
209
210 assert!(storage.vm_exists(&vm1));
211 storage.delete(&vm1, None)?;
212 assert!(!storage.vm_exists(&vm1));
213 storage.create(&vm1)?;
214
215 assert!(storage.size(&vm1)? == 0);
216 assert_eq!(
217 storage.vm_path(&vm1, "config"),
218 base_path.join("vm1/config")
219 );
220 assert!(!storage.vm_path_exists(&vm1, "config"));
221 assert_eq!(storage.config_path(&vm1), base_path.join("vm1/config"));
222 assert_eq!(storage.pidfile(&vm1), base_path.join("vm1/pid"));
223 assert_eq!(storage.monitor_path(&vm1), base_path.join("vm1/mon"));
224 assert_eq!(storage.disk_list(&vm1)?.len(), 0);
225 assert_eq!(storage.running_vms()?.len(), 0);
226 storage.write_config(vm1.clone())?;
227 assert!(storage.vm_path_exists(&vm1, "config"));
228
229 Ok(())
230 }
231}