1/*	$OpenBSD: vmctl.c,v 1.90 2024/05/02 15:46:10 mlarkin Exp $	*/
2
3/*
4 * Copyright (c) 2014 Mike Larkin <mlarkin@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/queue.h>
20#include <sys/uio.h>
21#include <sys/stat.h>
22#include <sys/socket.h>
23#include <sys/un.h>
24
25#include <ctype.h>
26#include <err.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <imsg.h>
30#include <limits.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <unistd.h>
35#include <util.h>
36#include <pwd.h>
37#include <grp.h>
38
39#include "vmd.h"
40#include "virtio.h"
41#include "vmctl.h"
42#include "atomicio.h"
43
44extern char *__progname;
45uint32_t info_id;
46char info_name[VMM_MAX_NAME_LEN];
47enum actions info_action;
48unsigned int info_flags;
49
50struct imsgbuf *ibuf;
51
52/*
53 * vm_start
54 *
55 * Request vmd to start the VM defined by the supplied parameters
56 *
57 * Parameters:
58 *  start_id: optional ID of the VM
59 *  name: optional name of the VM
60 *  memsize: memory size (in bytes) of the VM to create
61 *  nnics: number of vionet network interfaces to create
62 *  nics: switch names of the network interfaces to create
63 *  ndisks: number of disk images
64 *  disks: disk image file names
65 *  kernel: kernel image to load
66 *  iso: iso image file
67 *  instance: create instance from vm
68 *
69 * Return:
70 *  0 if the request to start the VM was sent successfully.
71 *  ENOMEM if a memory allocation failure occurred.
72 */
73int
74vm_start(uint32_t start_id, const char *name, size_t memsize, int nnics,
75    char **nics, int ndisks, char **disks, int *disktypes, char *kernel,
76    char *iso, char *instance, unsigned int bootdevice)
77{
78	struct vmop_create_params *vmc;
79	struct vm_create_params *vcp;
80	struct stat sb;
81	unsigned int flags = 0;
82	int i;
83	const char *s;
84
85	if (kernel) {
86		if (unveil(kernel, "r") == -1)
87			err(1, "unveil boot kernel");
88	} else {
89		/* We can drop sendfd promise. */
90		if (pledge("stdio rpath exec unix getpw unveil", NULL) == -1)
91			err(1, "pledge");
92	}
93
94	if (memsize)
95		flags |= VMOP_CREATE_MEMORY;
96	if (nnics)
97		flags |= VMOP_CREATE_NETWORK;
98	if (ndisks)
99		flags |= VMOP_CREATE_DISK;
100	if (kernel)
101		flags |= VMOP_CREATE_KERNEL;
102	if (iso)
103		flags |= VMOP_CREATE_CDROM;
104	if (instance)
105		flags |= VMOP_CREATE_INSTANCE;
106	else if (flags != 0) {
107		if (memsize < 1)
108			memsize = VM_DEFAULT_MEMORY;
109		if (ndisks > VM_MAX_DISKS_PER_VM)
110			errx(1, "too many disks");
111		else if (kernel == NULL && ndisks == 0)
112			warnx("starting without disks");
113		if (kernel == NULL && ndisks == 0 && !iso)
114			errx(1, "no kernel or disk/cdrom specified");
115		if (nnics == -1)
116			nnics = 0;
117		if (nnics > VM_MAX_NICS_PER_VM)
118			errx(1, "too many network interfaces");
119		if (kernel == NULL && nnics == 0)
120			warnx("starting without network interfaces");
121	}
122
123	if ((vmc = calloc(1, sizeof(struct vmop_create_params))) == NULL)
124		return (ENOMEM);
125	vmc->vmc_kernel = -1;
126	vmc->vmc_flags = flags;
127
128	/* vcp includes configuration that is shared with the kernel */
129	vcp = &vmc->vmc_params;
130
131	/*
132	 * XXX: vmd(8) fills in the actual memory ranges. vmctl(8)
133	 * just passes in the actual memory size here.
134	 */
135	vcp->vcp_nmemranges = 1;
136	vcp->vcp_memranges[0].vmr_size = memsize;
137
138	vcp->vcp_ncpus = 1;
139	vcp->vcp_id = start_id;
140
141	vmc->vmc_ndisks = ndisks;
142	vmc->vmc_nnics = nnics;
143
144	for (i = 0 ; i < ndisks; i++) {
145		if (strlcpy(vmc->vmc_disks[i], disks[i],
146		    sizeof(vmc->vmc_disks[i])) >=
147		    sizeof(vmc->vmc_disks[i]))
148			errx(1, "disk path too long");
149		vmc->vmc_disktypes[i] = disktypes[i];
150	}
151	for (i = 0 ; i < nnics; i++) {
152		vmc->vmc_ifflags[i] = VMIFF_UP;
153
154		if (strcmp(".", nics[i]) == 0) {
155			/* Add a "local" interface */
156			(void)strlcpy(vmc->vmc_ifswitch[i], "",
157			    sizeof(vmc->vmc_ifswitch[i]));
158			vmc->vmc_ifflags[i] |= VMIFF_LOCAL;
159		} else {
160			/* Add an interface to a switch */
161			if (strlcpy(vmc->vmc_ifswitch[i], nics[i],
162			    sizeof(vmc->vmc_ifswitch[i])) >=
163			    sizeof(vmc->vmc_ifswitch[i]))
164				errx(1, "interface name too long");
165		}
166	}
167	if (name != NULL) {
168		/*
169		 * Allow VMs names with alphanumeric characters, dot, hyphen
170		 * and underscore. But disallow dot, hyphen and underscore at
171		 * the start.
172		 */
173		if (*name == '-' || *name == '.' || *name == '_')
174			errx(1, "invalid VM name");
175
176		for (s = name; *s != '\0'; ++s) {
177			if (!(isalnum(*s) || *s == '.' || *s == '-' ||
178			    *s == '_'))
179				errx(1, "invalid VM name");
180		}
181
182		if (strlcpy(vcp->vcp_name, name,
183		    sizeof(vcp->vcp_name)) >= sizeof(vcp->vcp_name))
184			errx(1, "vm name too long");
185	}
186	if (kernel != NULL) {
187		if (strnlen(kernel, PATH_MAX) == PATH_MAX)
188			errx(1, "kernel name too long");
189		vmc->vmc_kernel = open(kernel, O_RDONLY);
190		if (vmc->vmc_kernel == -1)
191			err(1, "cannot open kernel '%s'", kernel);
192		memset(&sb, 0, sizeof(sb));
193		if (fstat(vmc->vmc_kernel, &sb) == -1)
194			err(1, "fstat kernel");
195		if (!S_ISREG(sb.st_mode))
196			errx(1, "kernel must be a regular file");
197	}
198	if (iso != NULL)
199		if (strlcpy(vmc->vmc_cdrom, iso,
200		    sizeof(vmc->vmc_cdrom)) >= sizeof(vmc->vmc_cdrom))
201			errx(1, "cdrom name too long");
202	if (instance != NULL)
203		if (strlcpy(vmc->vmc_instance, instance,
204		    sizeof(vmc->vmc_instance)) >= sizeof(vmc->vmc_instance))
205			errx(1, "instance vm name too long");
206	vmc->vmc_bootdevice = bootdevice;
207
208	imsg_compose(ibuf, IMSG_VMDOP_START_VM_REQUEST, 0, 0, vmc->vmc_kernel,
209	    vmc, sizeof(struct vmop_create_params));
210
211	free(vmc);
212	return (0);
213}
214
215/*
216 * vm_start_complete
217 *
218 * Callback function invoked when we are expecting an
219 * IMSG_VMDOP_START_VMM_RESPONSE message indicating the completion of
220 * a start vm operation.
221 *
222 * Parameters:
223 *  imsg : response imsg received from vmd
224 *  ret  : return value
225 *  autoconnect : open the console after startup
226 *
227 * Return:
228 *  Always 1 to indicate we have processed the return message (even if it
229 *  was an incorrect/failure message)
230 *
231 *  The function also sets 'ret' to the error code as follows:
232 *   0     : Message successfully processed
233 *   EINVAL: Invalid or unexpected response from vmd
234 *   EIO   : vm_start command failed
235 *   ENOENT: a specified component of the VM could not be found (disk image,
236 *    BIOS firmware image, etc)
237 */
238int
239vm_start_complete(struct imsg *imsg, int *ret, int autoconnect)
240{
241	struct vmop_result *vmr;
242	int res;
243
244	if (imsg->hdr.type == IMSG_VMDOP_START_VM_RESPONSE) {
245		vmr = (struct vmop_result *)imsg->data;
246		res = vmr->vmr_result;
247		if (res) {
248			switch (res) {
249			case VMD_BIOS_MISSING:
250				warnx("vmm bios firmware file not found.");
251				*ret = ENOENT;
252				break;
253			case VMD_DISK_MISSING:
254				warnx("could not open disk image(s)");
255				*ret = ENOENT;
256				break;
257			case VMD_CDROM_MISSING:
258				warnx("could not find specified iso image");
259				*ret = ENOENT;
260				break;
261			case VMD_CDROM_INVALID:
262				warnx("specified iso image is not a regular "
263				    "file");
264				*ret = ENOENT;
265				break;
266			case VMD_PARENT_INVALID:
267				warnx("invalid template");
268				*ret = EINVAL;
269				break;
270			default:
271				errno = res;
272				warn("start vm command failed");
273				*ret = EIO;
274			}
275		} else if (autoconnect) {
276			/* does not return */
277			ctl_openconsole(vmr->vmr_ttyname);
278		} else {
279			warnx("started vm %d successfully, tty %s",
280			    vmr->vmr_id, vmr->vmr_ttyname);
281			*ret = 0;
282		}
283	} else {
284		warnx("unexpected response received from vmd");
285		*ret = EINVAL;
286	}
287
288	return (1);
289}
290
291void
292send_vm(uint32_t id, const char *name)
293{
294	struct vmop_id vid;
295	int fds[2], readn, writen;
296	long pagesz;
297	char *buf;
298
299	pagesz = getpagesize();
300	buf = malloc(pagesz);
301	if (buf == NULL)
302		errx(1, "%s: memory allocation failure", __func__);
303
304	memset(&vid, 0, sizeof(vid));
305	vid.vid_id = id;
306	if (name != NULL)
307		strlcpy(vid.vid_name, name, sizeof(vid.vid_name));
308	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) == -1) {
309		warnx("%s: socketpair creation failed", __func__);
310	} else {
311		imsg_compose(ibuf, IMSG_VMDOP_SEND_VM_REQUEST, 0, 0, fds[0],
312				&vid, sizeof(vid));
313		imsg_flush(ibuf);
314		while (1) {
315			readn = atomicio(read, fds[1], buf, pagesz);
316			if (!readn)
317				break;
318			writen = atomicio(vwrite, STDOUT_FILENO, buf,
319					readn);
320			if (writen != readn)
321				break;
322		}
323		if (vid.vid_id)
324			warnx("sent vm %d successfully", vid.vid_id);
325		else
326			warnx("sent vm %s successfully", vid.vid_name);
327	}
328
329	free(buf);
330}
331
332void
333vm_receive(uint32_t id, const char *name)
334{
335	struct vmop_id vid;
336	int fds[2], readn, writen;
337	long pagesz;
338	char *buf;
339
340	pagesz = getpagesize();
341	buf = malloc(pagesz);
342	if (buf == NULL)
343		errx(1, "%s: memory allocation failure", __func__);
344
345	memset(&vid, 0, sizeof(vid));
346	if (name != NULL)
347		strlcpy(vid.vid_name, name, sizeof(vid.vid_name));
348	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) == -1) {
349		warnx("%s: socketpair creation failed", __func__);
350	} else {
351		imsg_compose(ibuf, IMSG_VMDOP_RECEIVE_VM_REQUEST, 0, 0, fds[0],
352		    &vid, sizeof(vid));
353		imsg_flush(ibuf);
354		while (1) {
355			readn = atomicio(read, STDIN_FILENO, buf, pagesz);
356			if (!readn) {
357				close(fds[1]);
358				break;
359			}
360			writen = atomicio(vwrite, fds[1], buf, readn);
361			if (writen != readn)
362				break;
363		}
364	}
365
366	free(buf);
367}
368
369void
370pause_vm(uint32_t pause_id, const char *name)
371{
372	struct vmop_id vid;
373
374	memset(&vid, 0, sizeof(vid));
375	vid.vid_id = pause_id;
376	if (name != NULL)
377		(void)strlcpy(vid.vid_name, name, sizeof(vid.vid_name));
378
379	imsg_compose(ibuf, IMSG_VMDOP_PAUSE_VM, 0, 0, -1,
380	    &vid, sizeof(vid));
381}
382
383int
384pause_vm_complete(struct imsg *imsg, int *ret)
385{
386	struct vmop_result *vmr;
387	int res;
388
389	if (imsg->hdr.type == IMSG_VMDOP_PAUSE_VM_RESPONSE) {
390		vmr = (struct vmop_result *)imsg->data;
391		res = vmr->vmr_result;
392		if (res) {
393			errno = res;
394			warn("pause vm command failed");
395			*ret = EIO;
396		} else {
397			warnx("paused vm %d successfully", vmr->vmr_id);
398			*ret = 0;
399		}
400	} else {
401		warnx("unexpected response received from vmd");
402		*ret = EINVAL;
403	}
404
405	return (1);
406}
407
408void
409unpause_vm(uint32_t pause_id, const char *name)
410{
411	struct vmop_id vid;
412
413	memset(&vid, 0, sizeof(vid));
414	vid.vid_id = pause_id;
415	if (name != NULL)
416		(void)strlcpy(vid.vid_name, name, sizeof(vid.vid_name));
417
418	imsg_compose(ibuf, IMSG_VMDOP_UNPAUSE_VM, 0, 0, -1,
419	    &vid, sizeof(vid));
420}
421
422int
423unpause_vm_complete(struct imsg *imsg, int *ret)
424{
425	struct vmop_result *vmr;
426	int res;
427
428	if (imsg->hdr.type == IMSG_VMDOP_UNPAUSE_VM_RESPONSE) {
429		vmr = (struct vmop_result *)imsg->data;
430		res = vmr->vmr_result;
431		if (res) {
432			errno = res;
433			warn("unpause vm command failed");
434			*ret = EIO;
435		} else {
436			warnx("unpaused vm %d successfully", vmr->vmr_id);
437			*ret = 0;
438		}
439	} else {
440		warnx("unexpected response received from vmd");
441		*ret = EINVAL;
442	}
443
444	return (1);
445}
446
447/*
448 * terminate_vm
449 *
450 * Request vmd to stop the VM indicated
451 *
452 * Parameters:
453 *  terminate_id: ID of the vm to be terminated
454 *  name: optional name of the VM to be terminated
455 *  flags: VMOP_FORCE or VMOP_WAIT flags
456 */
457void
458terminate_vm(uint32_t terminate_id, const char *name, unsigned int flags)
459{
460	struct vmop_id vid;
461
462	memset(&vid, 0, sizeof(vid));
463	vid.vid_id = terminate_id;
464	if (name != NULL) {
465		(void)strlcpy(vid.vid_name, name, sizeof(vid.vid_name));
466		fprintf(stderr, "stopping vm %s: ", name);
467	} else {
468		fprintf(stderr, "stopping vm: ");
469	}
470
471	vid.vid_flags = flags & (VMOP_FORCE|VMOP_WAIT);
472
473	imsg_compose(ibuf, IMSG_VMDOP_TERMINATE_VM_REQUEST,
474	    0, 0, -1, &vid, sizeof(vid));
475}
476
477/*
478 * terminate_vm_complete
479 *
480 * Callback function invoked when we are waiting for the response from an
481 * IMSG_VMDOP_TERMINATE_VM_REQUEST. We expect a reply of either an
482 * IMSG_VMDOP_TERMINATE_VM_EVENT indicating the termination of a vm or an
483 * IMSG_VMDOP_TERMINATE_VM_RESPONSE with a success/failure result.
484 *
485 * Parameters:
486 *  imsg : response imsg received from vmd
487 *  ret  : return value
488 *  flags: VMOP_FORCE or VMOP_WAIT flags
489 *
490 * Return:
491 *  Always 1 to indicate we have processed the return message (even if it
492 *  was an incorrect/failure message)
493 *
494 *  The function also sets 'ret' to the error code as follows:
495 *   0     : Message successfully processed
496 *   EINVAL: Invalid or unexpected response from vmd
497 *   EIO   : terminate_vm command failed
498 */
499int
500terminate_vm_complete(struct imsg *imsg, int *ret, unsigned int flags)
501{
502	struct vmop_result *vmr;
503	int res;
504
505	switch (imsg->hdr.type) {
506	case IMSG_VMDOP_TERMINATE_VM_RESPONSE:
507		IMSG_SIZE_CHECK(imsg, &vmr);
508		vmr = (struct vmop_result *)imsg->data;
509		res = vmr->vmr_result;
510
511		switch (res) {
512		case 0:
513			fprintf(stderr, "requested to shutdown vm %d\n",
514			    vmr->vmr_id);
515			*ret = 0;
516			break;
517		case VMD_VM_STOP_INVALID:
518			fprintf(stderr,
519			    "cannot stop vm that is not running\n");
520			*ret = EINVAL;
521			break;
522		case ENOENT:
523			fprintf(stderr, "vm not found\n");
524			*ret = EIO;
525			break;
526		case EINTR:
527			fprintf(stderr, "interrupted call\n");
528			*ret = EIO;
529			break;
530		default:
531			errno = res;
532			fprintf(stderr, "failed: %s\n",
533			    strerror(res));
534			*ret = EIO;
535		}
536		break;
537	case IMSG_VMDOP_TERMINATE_VM_EVENT:
538		IMSG_SIZE_CHECK(imsg, &vmr);
539		vmr = (struct vmop_result *)imsg->data;
540		if (flags & VMOP_WAIT) {
541			fprintf(stderr, "terminated vm %d\n", vmr->vmr_id);
542		} else if (flags & VMOP_FORCE) {
543			fprintf(stderr, "forced to terminate vm %d\n",
544			    vmr->vmr_id);
545		}
546		*ret = 0;
547		break;
548	default:
549		fprintf(stderr, "unexpected response received from vmd\n");
550		*ret = EINVAL;
551	}
552	errno = *ret;
553
554	return (1);
555}
556
557/*
558 * terminate_all
559 *
560 * Request to stop all VMs gracefully
561 *
562 * Parameters
563 *  list: the vm information (consolidated) returned from vmd via imsg
564 *  ct  : the size (number of elements in 'list') of the result
565 *  flags: VMOP_FORCE or VMOP_WAIT flags
566 */
567void
568terminate_all(struct vmop_info_result *list, size_t ct, unsigned int flags)
569{
570	struct vm_info_result *vir;
571	struct vmop_info_result *vmi;
572	struct parse_result res;
573	size_t i;
574
575	for (i = 0; i < ct; i++) {
576		vmi = &list[i];
577		vir = &vmi->vir_info;
578
579		/* The VM is already stopped */
580		if (vir->vir_creator_pid == 0 || vir->vir_id == 0)
581			continue;
582
583		memset(&res, 0, sizeof(res));
584		res.action = CMD_STOP;
585		res.id = 0;
586		res.flags = info_flags;
587
588		if ((res.name = strdup(vir->vir_name)) == NULL)
589			errx(1, "strdup");
590
591		vmmaction(&res);
592	}
593}
594
595/*
596 * waitfor_vm
597 *
598 * Wait until vmd stopped the indicated VM
599 *
600 * Parameters:
601 *  terminate_id: ID of the vm to be terminated
602 *  name: optional name of the VM to be terminated
603 */
604void
605waitfor_vm(uint32_t terminate_id, const char *name)
606{
607	struct vmop_id vid;
608
609	memset(&vid, 0, sizeof(vid));
610	vid.vid_id = terminate_id;
611	if (name != NULL) {
612		(void)strlcpy(vid.vid_name, name, sizeof(vid.vid_name));
613		fprintf(stderr, "waiting for vm %s: ", name);
614	} else {
615		fprintf(stderr, "waiting for vm: ");
616	}
617
618	imsg_compose(ibuf, IMSG_VMDOP_WAIT_VM_REQUEST,
619	    0, 0, -1, &vid, sizeof(vid));
620}
621
622/*
623 * get_info_vm
624 *
625 * Return the list of all running VMs or find a specific VM by ID or name.
626 *
627 * Parameters:
628 *  id: optional ID of a VM to list
629 *  name: optional name of a VM to list
630 *  action: if CMD_CONSOLE or CMD_STOP open a console or terminate the VM.
631 *  flags: optional flags used by the CMD_STOP action.
632 *
633 * Request a list of running VMs from vmd
634 */
635void
636get_info_vm(uint32_t id, const char *name, enum actions action,
637    unsigned int flags)
638{
639	info_id = id;
640	if (name != NULL)
641		(void)strlcpy(info_name, name, sizeof(info_name));
642	info_action = action;
643	info_flags = flags;
644	imsg_compose(ibuf, IMSG_VMDOP_GET_INFO_VM_REQUEST, 0, 0, -1, NULL, 0);
645}
646
647/*
648 * check_info_id
649 *
650 * Check if requested name or ID of a VM matches specified arguments
651 *
652 * Parameters:
653 *  name: name of the VM
654 *  id: ID of the VM
655 */
656int
657check_info_id(const char *name, uint32_t id)
658{
659	if (info_id == 0 && *info_name == '\0')
660		return (-1);
661	if (info_id != 0 && info_id == id)
662		return (1);
663	if (*info_name != '\0' && name && strcmp(info_name, name) == 0)
664		return (1);
665	return (0);
666}
667
668/*
669 * add_info
670 *
671 * Callback function invoked when we are expecting an
672 * IMSG_VMDOP_GET_INFO_VM_DATA message indicating the receipt of additional
673 * "list vm" data, or an IMSG_VMDOP_GET_INFO_VM_END_DATA message indicating
674 * that no additional "list vm" data will be forthcoming.
675 *
676 * Parameters:
677 *  imsg : response imsg received from vmd
678 *  ret  : return value
679 *
680 * Return:
681 *  0     : the returned data was successfully added to the "list vm" data.
682 *          The caller can expect more data.
683 *  1     : IMSG_VMDOP_GET_INFO_VM_END_DATA received (caller should not call
684 *          add_info again), or an error occurred adding the returned data
685 *          to the "list vm" data. The caller should check the value of
686 *          'ret' to determine which case occurred.
687 *
688 * This function does not return if a VM is found and info_action is CMD_CONSOLE
689 *
690 *  The function also sets 'ret' to the error code as follows:
691 *   0     : Message successfully processed
692 *   EINVAL: Invalid or unexpected response from vmd
693 *   ENOMEM: memory allocation failure
694 */
695int
696add_info(struct imsg *imsg, int *ret)
697{
698	static size_t ct = 0;
699	static struct vmop_info_result *vir = NULL;
700
701	if (imsg->hdr.type == IMSG_VMDOP_GET_INFO_VM_DATA) {
702		vir = reallocarray(vir, ct + 1,
703		    sizeof(struct vmop_info_result));
704		if (vir == NULL) {
705			*ret = ENOMEM;
706			return (1);
707		}
708		memcpy(&vir[ct], imsg->data, sizeof(struct vmop_info_result));
709		ct++;
710		*ret = 0;
711		return (0);
712	} else if (imsg->hdr.type == IMSG_VMDOP_GET_INFO_VM_END_DATA) {
713		switch (info_action) {
714		case CMD_CONSOLE:
715			vm_console(vir, ct);
716			break;
717		case CMD_STOPALL:
718			terminate_all(vir, ct, info_flags);
719			break;
720		default:
721			print_vm_info(vir, ct);
722			break;
723		}
724		free(vir);
725		*ret = 0;
726		return (1);
727	} else {
728		*ret = EINVAL;
729		return (1);
730	}
731}
732
733/*
734 * vm_state
735 *
736 * Returns a string representing the current VM state, note that the order
737 * matters. A paused VM does have the VM_STATE_RUNNING bit set, but
738 * VM_STATE_PAUSED is more significant to report. Same goes for stopping VMs.
739 *
740 * Parameters
741 *  vm_state: mask indicating the vm state
742 */
743const char *
744vm_state(unsigned int mask)
745{
746	if (mask & VM_STATE_PAUSED)
747		return "paused";
748	else if (mask & VM_STATE_WAITING)
749		return "waiting";
750	else if (mask & VM_STATE_SHUTDOWN)
751		return "stopping";
752	else if (mask & VM_STATE_RUNNING)
753		return "running";
754	/* Presence of absence of other flags */
755	else if (!mask || (mask & VM_STATE_DISABLED))
756		return "stopped";
757
758	return "unknown";
759}
760
761/*
762 * print_vm_info
763 *
764 * Prints the vm information returned from vmd in 'list' to stdout.
765 *
766 * Parameters
767 *  list: the vm information (consolidated) returned from vmd via imsg
768 *  ct  : the size (number of elements in 'list') of the result
769 */
770void
771print_vm_info(struct vmop_info_result *list, size_t ct)
772{
773	struct vm_info_result *vir;
774	struct vmop_info_result *vmi;
775	size_t i;
776	char *tty;
777	char curmem[FMT_SCALED_STRSIZE];
778	char maxmem[FMT_SCALED_STRSIZE];
779	char user[16], group[16];
780	const char *name;
781	int running;
782	extern int stat_rflag;
783
784	printf("%5s %5s %5s %7s %7s %7s %12s %8s %s\n", "ID", "PID", "VCPUS",
785	    "MAXMEM", "CURMEM", "TTY", "OWNER", "STATE", "NAME");
786
787	for (i = 0; i < ct; i++) {
788		vmi = &list[i];
789		vir = &vmi->vir_info;
790		running = (vir->vir_creator_pid != 0 && vir->vir_id != 0);
791		if (!running && stat_rflag)
792			continue;
793		if (check_info_id(vir->vir_name, vir->vir_id)) {
794			/* get user name */
795			name = user_from_uid(vmi->vir_uid, 1);
796			if (name == NULL)
797				(void)snprintf(user, sizeof(user),
798				    "%d", vmi->vir_uid);
799			else
800				(void)strlcpy(user, name, sizeof(user));
801			/* get group name */
802			if (vmi->vir_gid != -1) {
803				name = group_from_gid(vmi->vir_gid, 1);
804				if (name == NULL)
805					(void)snprintf(group, sizeof(group),
806					    ":%lld", vmi->vir_gid);
807				else
808					(void)snprintf(group, sizeof(group),
809					    ":%s", name);
810				(void)strlcat(user, group, sizeof(user));
811			}
812
813			(void)strlcpy(curmem, "-", sizeof(curmem));
814			(void)strlcpy(maxmem, "-", sizeof(maxmem));
815
816			(void)fmt_scaled(vir->vir_memory_size, maxmem);
817
818			if (running) {
819				if (*vmi->vir_ttyname == '\0')
820					tty = "-";
821				/* get tty - skip /dev/ path */
822				else if ((tty = strrchr(vmi->vir_ttyname,
823				    '/')) == NULL || *++tty == '\0')
824					tty = list[i].vir_ttyname;
825
826				(void)fmt_scaled(vir->vir_used_size, curmem);
827
828				/* running vm */
829				printf("%5u %5u %5zd %7s %7s %7s %12s %8s %s\n",
830				    vir->vir_id, vir->vir_creator_pid,
831				    vir->vir_ncpus, maxmem, curmem,
832				    tty, user, vm_state(vmi->vir_state),
833				    vir->vir_name);
834			} else {
835				/* disabled vm */
836				printf("%5u %5s %5zd %7s %7s %7s %12s %8s %s\n",
837				    vir->vir_id, "-",
838				    vir->vir_ncpus, maxmem, curmem,
839				    "-", user, vm_state(vmi->vir_state),
840				    vir->vir_name);
841			}
842		}
843	}
844}
845
846/*
847 * vm_console
848 *
849 * Connects to the vm console returned from vmd in 'list'.
850 *
851 * Parameters
852 *  list: the vm information (consolidated) returned from vmd via imsg
853 *  ct  : the size (number of elements in 'list') of the result
854 */
855__dead void
856vm_console(struct vmop_info_result *list, size_t ct)
857{
858	struct vmop_info_result *vir;
859	size_t i;
860
861	for (i = 0; i < ct; i++) {
862		vir = &list[i];
863		if ((check_info_id(vir->vir_info.vir_name,
864		    vir->vir_info.vir_id) > 0) &&
865			(vir->vir_ttyname[0] != '\0')) {
866			/* does not return */
867			ctl_openconsole(vir->vir_ttyname);
868		}
869	}
870
871	errx(1, "console not found");
872}
873
874/*
875 * open_imagefile
876 *
877 * Open an imagefile with the specified type, path and size.
878 *
879 * Parameters:
880 *  type        : format of the image file
881 *  imgfile_path: path to the image file to create
882 *  flags       : flags for open(2), e.g. O_RDONLY
883 *  file        : file structure
884 *  sz		: size of the image file
885 *
886 * Return:
887 *  fd          : Returns file descriptor of the new image file
888 *  -1          : Operation failed.  errno is set.
889 */
890int
891open_imagefile(int type, const char *imgfile_path, int flags,
892    struct virtio_backing *file, off_t *sz)
893{
894	int	 fd, ret, basefd[VM_MAX_BASE_PER_DISK], nfd, i;
895	char	 path[PATH_MAX];
896
897	*sz = 0;
898	if ((fd = open(imgfile_path, flags)) == -1)
899		return (-1);
900
901	basefd[0] = fd;
902	nfd = 1;
903
904	errno = 0;
905	switch (type) {
906	case VMDF_QCOW2:
907		if (strlcpy(path, imgfile_path, sizeof(path)) >= sizeof(path))
908			return (-1);
909		for (i = 0; i < VM_MAX_BASE_PER_DISK - 1; i++, nfd++) {
910			if ((ret = virtio_qcow2_get_base(basefd[i],
911			    path, sizeof(path), imgfile_path)) == -1) {
912				log_debug("%s: failed to get base %d", __func__, i);
913				return -1;
914			} else if (ret == 0)
915				break;
916
917			if ((basefd[i + 1] = open(path, O_RDONLY)) == -1) {
918				log_warn("%s: failed to open base %s",
919				    __func__, path);
920				return (-1);
921			}
922		}
923		ret = virtio_qcow2_init(file, sz, basefd, nfd);
924		break;
925	default:
926		ret = virtio_raw_init(file, sz, &fd, 1);
927		break;
928	}
929
930	if (ret == -1) {
931		for (i = 0; i < nfd; i++)
932			close(basefd[i]);
933		return (-1);
934	}
935
936	return (fd);
937}
938
939/*
940 * create_imagefile
941 *
942 * Create an empty imagefile with the specified type, path and size.
943 *
944 * Parameters:
945 *  type        : format of the image file
946 *  imgfile_path: path to the image file to create
947 *  base_path   : path to the qcow2 base image
948 *  imgsize     : size of the image file to create (in bytes)
949 *  format      : string identifying the format
950 *
951 * Return:
952 *  EEXIST: The requested image file already exists
953 *  0     : Image file successfully created
954 *  Exxxx : Various other Exxxx errno codes due to other I/O errors
955 */
956int
957create_imagefile(int type, const char *imgfile_path, const char *base_path,
958    uint64_t imgsize, const char **format)
959{
960	int	 ret;
961
962	switch (type) {
963	case VMDF_QCOW2:
964		*format = "qcow2";
965		ret = virtio_qcow2_create(imgfile_path, base_path, imgsize);
966		break;
967	default:
968		*format = "raw";
969		ret = virtio_raw_create(imgfile_path, imgsize);
970		break;
971	}
972
973	return (ret);
974}
975