emu_cli/
command_handler.rs

1use super::{
2    config_storage::XDGConfigStorage,
3    image::{QEmuImageHandler, QEMU_IMG_DEFAULT_FORMAT},
4    launcher::QEmuLauncher,
5    supervisor::SystemdSupervisor,
6    traits::{ConfigStorageHandler, ImageHandler, Launcher, SupervisorHandler},
7    vm::VM,
8};
9use crate::{qmp::client::Client, util::valid_filename};
10use anyhow::{anyhow, Result};
11use std::{path::PathBuf, process::Command, sync::Arc};
12use tokio::{
13    io::{AsyncReadExt, AsyncWriteExt, Interest},
14    sync::Mutex,
15};
16
17#[derive(Debug, Clone)]
18pub struct CommandHandler {
19    launcher: Arc<Box<dyn Launcher>>,
20    config: Arc<Box<dyn ConfigStorageHandler>>,
21    image: Arc<Box<dyn ImageHandler>>,
22}
23
24impl Default for CommandHandler {
25    fn default() -> Self {
26        Self {
27            launcher: Arc::new(Box::new(QEmuLauncher::default())),
28            config: Arc::new(Box::new(XDGConfigStorage::default())),
29            image: Arc::new(Box::new(QEmuImageHandler::default())),
30        }
31    }
32}
33
34impl CommandHandler {
35    pub fn reset(&self, vm: &VM) -> Result<()> {
36        self.launcher.reset(vm)
37    }
38
39    pub fn restart(&self, vm: &VM) -> Result<()> {
40        self.launcher.restart(vm)
41    }
42
43    pub fn snapshot_save(&self, vm: &VM, snapshot: String) -> Result<()> {
44        self.launcher.snapshot(vm, snapshot)
45    }
46
47    pub fn snapshot_load(&self, vm: &VM, snapshot: String) -> Result<()> {
48        self.launcher.restore(vm, snapshot)
49    }
50
51    pub fn snapshot_delete(&self, vm: &VM, snapshot: String) -> Result<()> {
52        self.launcher.delete_snapshot(vm, snapshot)
53    }
54
55    pub fn save_state(&self, vm: &VM) -> Result<()> {
56        self.launcher.save_state(vm)
57    }
58
59    pub fn load_state(&self, vm: &VM) -> Result<()> {
60        self.launcher.load_state(vm)
61    }
62
63    pub fn clear_state(&self, vm: &VM) -> Result<()> {
64        self.launcher.clear_state(vm)
65    }
66
67    pub fn list(&self, running: bool) -> Result<()> {
68        if running {
69            let mut v = Vec::new();
70
71            for item in self.config.vm_list()? {
72                if item.supervisor().is_active(&item).unwrap_or_default() {
73                    v.push(item)
74                }
75            }
76
77            Ok(v)
78        } else {
79            self.config.vm_list()
80        }?
81        .iter()
82        .for_each(|vm| {
83            let supervisor = vm.supervisor();
84
85            let (status, is_running) = if supervisor.supervised() {
86                match supervisor.is_active(vm) {
87                    Ok(res) => {
88                        if res {
89                            ("supervised: running".to_string(), true)
90                        } else {
91                            ("supervised: not running".to_string(), false)
92                        }
93                    }
94                    Err(e) => (
95                        format!("supervised: could not determine status: {}", e.to_string()),
96                        false,
97                    ),
98                }
99            } else if supervisor.is_active(vm).unwrap_or_default() {
100                (format!("pid: {}", supervisor.pidof(vm).unwrap()), true)
101            } else {
102                ("stopped".to_string(), false)
103            };
104
105            if running && is_running || !running {
106                println!(
107                    "{} ({}) (size: {:.2})",
108                    vm.name(),
109                    status,
110                    byte_unit::Byte::from_u128(self.config.size(vm).unwrap() as u128)
111                        .unwrap()
112                        .get_appropriate_unit(byte_unit::UnitType::Decimal)
113                );
114            }
115        });
116
117        Ok(())
118    }
119
120    pub fn rename(&self, old: &VM, new: &VM) -> Result<()> {
121        match self.config.rename(old, new) {
122            Ok(_) => {
123                println!("Renamed {} to {}", old, new);
124            }
125            Err(_) => {
126                println!(
127                    "Could not rename {}. Does it exist, or does {} already exist?",
128                    old, new
129                );
130            }
131        }
132
133        Ok(())
134    }
135
136    pub fn supervised(&self) -> Result<()> {
137        for item in self.config.vm_list()? {
138            if item.supervisor().supervised() {
139                let status = if item.supervisor().is_active(&item).unwrap_or_default() {
140                    "running"
141                } else {
142                    "not running"
143                };
144                println!("{}: {}", item, status)
145            }
146        }
147
148        Ok(())
149    }
150
151    pub async fn nc(&self, vm: &VM, port: u16) -> Result<()> {
152        let config = vm.config();
153
154        if config.ports.contains_key(&port.to_string()) {
155            let (s, mut r) = tokio::sync::mpsc::unbounded_channel();
156            let (close_s, close_r) = tokio::sync::mpsc::unbounded_channel();
157            let close_r = Arc::new(Mutex::new(close_r));
158
159            let close_s2 = close_s.clone();
160            let close_r2 = close_r.clone();
161
162            tokio::spawn(async move {
163                let mut buf = [0_u8; 4096];
164                while let Ok(size) = tokio::io::stdin().read(&mut buf).await {
165                    if size > 0 {
166                        s.send(buf[..size].to_vec()).unwrap();
167                    } else {
168                        break;
169                    }
170
171                    if close_r2.lock().await.try_recv().is_ok() {
172                        return;
173                    }
174                }
175                close_s2.send(()).unwrap();
176            });
177
178            let mut stream = tokio::net::TcpStream::connect(
179                format!("127.0.0.1:{}", port).parse::<std::net::SocketAddr>()?,
180            )
181            .await?;
182
183            let mut buf = [0_u8; 4096];
184            let interest = Interest::WRITABLE.clone();
185            let interest = interest.add(Interest::READABLE);
186            let interest = interest.add(Interest::ERROR);
187
188            loop {
189                let state = stream.ready(interest).await?;
190
191                if state.is_error() {
192                    close_s.send(())?;
193                    break;
194                }
195
196                if state.is_readable() {
197                    while let Ok(size) = stream.try_read(&mut buf) {
198                        if size > 0 {
199                            tokio::io::stdout().write(&buf[..size]).await?;
200                        } else {
201                            break;
202                        }
203                    }
204                }
205
206                if state.is_writable() {
207                    while let Ok(buf) = r.try_recv() {
208                        stream.write(&buf).await?;
209                    }
210                }
211
212                if close_r.lock().await.try_recv().is_ok() {
213                    break;
214                }
215            }
216        }
217
218        Ok(())
219    }
220
221    pub fn ssh(&self, vm: &VM, args: Option<Vec<String>>) -> Result<()> {
222        let mut cmd = Command::new("ssh");
223        let port = vm.config().machine.ssh_port.to_string();
224        let mut all_args = vec!["-p", &port, "localhost"];
225
226        let args = args.unwrap_or_default();
227        all_args.append(&mut args.iter().map(String::as_str).collect());
228
229        if cmd.args(all_args).spawn()?.wait()?.success() {
230            Ok(())
231        } else {
232            Err(anyhow!("SSH failed with non-zero status"))
233        }
234    }
235
236    pub fn create(&self, vm: &VM, size: usize, append: bool) -> Result<()> {
237        if !append {
238            if self.config.vm_exists(vm) {
239                return Err(anyhow!("vm already exists"));
240            }
241
242            if !valid_filename(&vm.name()) {
243                return Err(anyhow!("filename contains invalid characters"));
244            }
245
246            self.config.create(vm)?;
247        }
248
249        self.image.create(self.config.vm_root(vm), size)?;
250        Ok(())
251    }
252
253    pub fn list_disks(&self, vm: &VM) -> Result<()> {
254        if !self.config.vm_exists(vm) {
255            return Err(anyhow!("vm doesn't exist"));
256        }
257
258        for disk in self.config.disk_list(vm)? {
259            let disk = disk
260                .file_name()
261                .unwrap()
262                .to_str()
263                .unwrap()
264                .trim_start_matches("qemu-")
265                .trim_end_matches(QEMU_IMG_DEFAULT_FORMAT)
266                .trim_end_matches(".");
267            println!("{}", disk);
268        }
269
270        Ok(())
271    }
272
273    pub fn delete(&self, vm: &VM, disk: Option<String>) -> Result<()> {
274        self.config.delete(vm, disk)?;
275
276        if vm.supervisor().supervised() {
277            if let Err(_) = self.unsupervise(vm) {
278                println!("Could not remove systemd unit")
279            }
280        }
281
282        Ok(())
283    }
284
285    pub fn supervise(&self, vm: &VM) -> Result<()> {
286        if !self.config.vm_exists(vm) {
287            return Err(anyhow!("vm doesn't exist"));
288        }
289
290        let supervisor = SystemdSupervisor::default();
291
292        supervisor.storage().create(vm)?;
293        supervisor.reload()
294    }
295
296    pub fn unsupervise(&self, vm: &VM) -> Result<()> {
297        let supervisor = vm.supervisor();
298        supervisor.storage().remove(vm)?;
299        supervisor.reload()
300    }
301
302    pub fn is_active(&self, vm: &VM) -> Result<()> {
303        if vm.supervisor().is_active(&vm).unwrap_or_default() {
304            println!("{} is active", vm);
305        } else {
306            println!("{} is not active", vm);
307        }
308
309        Ok(())
310    }
311
312    pub fn shutdown(&self, vm: &VM, nowait: bool) -> Result<()> {
313        if nowait {
314            self.launcher.shutdown_immediately(vm)
315        } else {
316            if let Ok(status) = self.launcher.shutdown_wait(vm) {
317                println!(
318                    "qemu exited with {} status",
319                    status.code().unwrap_or_default()
320                );
321            }
322
323            Ok(())
324        }
325    }
326
327    pub fn run(&self, vm: &VM, detach: bool) -> Result<()> {
328        for running in self.config.running_vms()? {
329            if running.config().is_port_conflict(&vm.config()) {
330                return Err(anyhow!("{} will fail to launch because {} already occupies a network port it would use", vm, running));
331            }
332        }
333
334        if detach {
335            self.launcher.launch_detached(vm)
336        } else {
337            match self.launcher.launch_attached(vm) {
338                Ok(status) => {
339                    if status.success() {
340                        Ok(())
341                    } else {
342                        Err(anyhow!("qemu exited uncleanly: {}", status))
343                    }
344                }
345                Err(e) => Err(e),
346            }
347        }
348    }
349
350    pub fn import(&self, vm: &VM, from_file: PathBuf, format: String) -> Result<()> {
351        if !self.config.vm_exists(vm) {
352            self.config.create(vm)?;
353        }
354
355        self.image.import(
356            self.config.vm_root(vm).join(from_file.file_name().unwrap()),
357            from_file,
358            format,
359        )
360    }
361
362    pub fn clone_vm(&self, from: &VM, to: &VM, config: bool) -> Result<()> {
363        if self.config.vm_exists(to) {
364            return Err(anyhow!("vm already exists"));
365        }
366
367        // this next little bit just aligns the descriptions so the progress meters are uniform on
368        // the screen.
369
370        let images = self.config.disk_list(from)?;
371
372        let mut descriptions = Vec::new();
373        let mut len = 0;
374        for img in &images {
375            let l = img.file_name().unwrap().to_string_lossy().len();
376            if l > len {
377                len = l
378            }
379        }
380
381        for img in images.clone() {
382            let mut s = img.file_name().unwrap().to_string_lossy().to_string();
383
384            if s.len() < len {
385                s += &" ".repeat(len - s.len())
386            }
387
388            descriptions.push(s.to_string())
389        }
390
391        self.config.create(to)?;
392        for (x, img) in images.iter().enumerate() {
393            self.image.clone_image(
394                descriptions[x].to_string(),
395                img.clone(),
396                self.config.vm_root(to).join(img.file_name().unwrap()),
397            )?;
398
399            if x < images.len() - 1 {
400                println!();
401            }
402        }
403
404        if config && self.config.config_path(from).exists() {
405            println!("Configuration found in {}; copying to {}", from, to);
406            std::fs::copy(self.config.config_path(from), self.config.config_path(to))?;
407        }
408
409        Ok(())
410    }
411
412    pub fn config_copy(&self, from: &VM, to: &VM) -> Result<()> {
413        if !self.config.vm_exists(from) {
414            println!("VM {} does not exist", from);
415            return Ok(());
416        }
417
418        let mut to = to.clone();
419
420        to.set_config(from.config());
421        self.config.write_config(to)
422    }
423
424    pub fn show_config(&self, vm: &VM) -> Result<()> {
425        if !self.config.vm_exists(vm) {
426            println!("VM {} does not exist", vm);
427            return Ok(());
428        }
429        println!("{}", vm.config().to_string());
430        Ok(())
431    }
432
433    pub fn config_set(&self, vm: &VM, key: String, value: String) -> Result<()> {
434        let mut vm = vm.clone();
435        let mut config = vm.config();
436        config.set_machine_value(&key, &value)?;
437        vm.set_config(config);
438        match self.config.write_config(vm.clone()) {
439            Ok(_) => {}
440            Err(_) => {
441                println!("VM {} does not exist", vm);
442            }
443        }
444
445        Ok(())
446    }
447
448    pub fn port_map(&self, vm: &VM, hostport: u16, guestport: u16) -> Result<()> {
449        let mut vm = vm.clone();
450        let mut config = vm.config();
451        config.map_port(hostport, guestport);
452        vm.set_config(config);
453        self.config.write_config(vm)
454    }
455
456    pub fn port_unmap(&self, vm: &VM, hostport: u16) -> Result<()> {
457        let mut vm = vm.clone();
458        let mut config = vm.config();
459        config.unmap_port(hostport);
460        vm.set_config(config);
461        self.config.write_config(vm)
462    }
463
464    pub fn qmp(&self, vm: &VM, command: &str, args: Option<&str>) -> Result<()> {
465        let mut us = Client::new(self.config.monitor_path(vm))?;
466        us.handshake()?;
467        // this command hangs if the type isn't provided (for some reason)
468        us.send_command::<serde_json::Value>("qmp_capabilities", None)?;
469        let val = match args {
470            Some(args) => {
471                us.send_command::<serde_json::Value>(command, Some(serde_json::from_str(args)?))?
472            }
473            None => us.send_command::<serde_json::Value>(command, None)?,
474        };
475
476        println!("{}", serde_json::to_string_pretty(&val)?);
477        Ok(())
478    }
479}