emu_cli/
config_storage.rs

1use 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}