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