1 /* $OpenBSD: vmctl.c,v 1.92 2024/11/21 13:17:02 claudio 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
44 extern char *__progname;
45 uint32_t info_id;
46 char info_name[VMM_MAX_NAME_LEN];
47 enum actions info_action;
48 unsigned int info_flags;
49
50 struct 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 */
73 int
vm_start(uint32_t start_id,const char * name,size_t memsize,int nnics,char ** nics,int ndisks,char ** disks,int * disktypes,char * kernel,char * iso,char * instance,unsigned int bootdevice)74 vm_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 */
238 int
vm_start_complete(struct imsg * imsg,int * ret,int autoconnect)239 vm_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
291 void
send_vm(uint32_t id,const char * name)292 send_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 imsgbuf_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
332 void
vm_receive(uint32_t id,const char * name)333 vm_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 imsgbuf_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
369 void
pause_vm(uint32_t pause_id,const char * name)370 pause_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
383 int
pause_vm_complete(struct imsg * imsg,int * ret)384 pause_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
408 void
unpause_vm(uint32_t pause_id,const char * name)409 unpause_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
422 int
unpause_vm_complete(struct imsg * imsg,int * ret)423 unpause_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 */
457 void
terminate_vm(uint32_t terminate_id,const char * name,unsigned int flags)458 terminate_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 */
499 int
terminate_vm_complete(struct imsg * imsg,int * ret,unsigned int flags)500 terminate_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 */
567 void
terminate_all(struct vmop_info_result * list,size_t ct,unsigned int flags)568 terminate_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 */
604 void
waitfor_vm(uint32_t terminate_id,const char * name)605 waitfor_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 */
635 void
get_info_vm(uint32_t id,const char * name,enum actions action,unsigned int flags)636 get_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 */
656 int
check_info_id(const char * name,uint32_t id)657 check_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 * ENOENT: no entries
695 */
696 int
add_info(struct imsg * imsg,int * ret)697 add_info(struct imsg *imsg, int *ret)
698 {
699 static size_t ct = 0;
700 static struct vmop_info_result *vir = NULL;
701
702 *ret = 0;
703
704 if (imsg->hdr.type == IMSG_VMDOP_GET_INFO_VM_DATA) {
705 vir = reallocarray(vir, ct + 1,
706 sizeof(struct vmop_info_result));
707 if (vir == NULL) {
708 *ret = ENOMEM;
709 return (1);
710 }
711 memcpy(&vir[ct], imsg->data, sizeof(struct vmop_info_result));
712 ct++;
713 return (0);
714 } else if (imsg->hdr.type == IMSG_VMDOP_GET_INFO_VM_END_DATA) {
715 switch (info_action) {
716 case CMD_CONSOLE:
717 vm_console(vir, ct);
718 break;
719 case CMD_STOPALL:
720 terminate_all(vir, ct, info_flags);
721 break;
722 default:
723 *ret = print_vm_info(vir, ct);
724 break;
725 }
726 free(vir);
727 return (1);
728 } else {
729 *ret = EINVAL;
730 return (1);
731 }
732 }
733
734 /*
735 * vm_state
736 *
737 * Returns a string representing the current VM state, note that the order
738 * matters. A paused VM does have the VM_STATE_RUNNING bit set, but
739 * VM_STATE_PAUSED is more significant to report. Same goes for stopping VMs.
740 *
741 * Parameters
742 * vm_state: mask indicating the vm state
743 */
744 const char *
vm_state(unsigned int mask)745 vm_state(unsigned int mask)
746 {
747 if (mask & VM_STATE_PAUSED)
748 return "paused";
749 else if (mask & VM_STATE_WAITING)
750 return "waiting";
751 else if (mask & VM_STATE_SHUTDOWN)
752 return "stopping";
753 else if (mask & VM_STATE_RUNNING)
754 return "running";
755 /* Presence of absence of other flags */
756 else if (!mask || (mask & VM_STATE_DISABLED))
757 return "stopped";
758
759 return "unknown";
760 }
761
762 /*
763 * print_vm_info
764 *
765 * Prints the vm information returned from vmd in 'list' to stdout.
766 *
767 * Parameters
768 * list: the vm information (consolidated) returned from vmd via imsg
769 * ct : the size (number of elements in 'list') of the result
770 *
771 * Return values:
772 * 0: no error
773 * ENOENT: no entries printed
774 */
775 int
print_vm_info(struct vmop_info_result * list,size_t ct)776 print_vm_info(struct vmop_info_result *list, size_t ct)
777 {
778 struct vm_info_result *vir;
779 struct vmop_info_result *vmi;
780 size_t i;
781 char *tty;
782 char curmem[FMT_SCALED_STRSIZE];
783 char maxmem[FMT_SCALED_STRSIZE];
784 char user[16], group[16];
785 const char *name;
786 int running, found_running;
787 extern int stat_rflag;
788
789 found_running = 0;
790
791 printf("%5s %5s %5s %7s %7s %7s %12s %8s %s\n", "ID", "PID", "VCPUS",
792 "MAXMEM", "CURMEM", "TTY", "OWNER", "STATE", "NAME");
793
794 for (i = 0; i < ct; i++) {
795 vmi = &list[i];
796 vir = &vmi->vir_info;
797 running = (vir->vir_creator_pid != 0 && vir->vir_id != 0);
798 if (!running && stat_rflag)
799 continue;
800
801 found_running++;
802
803 if (check_info_id(vir->vir_name, vir->vir_id)) {
804 /* get user name */
805 name = user_from_uid(vmi->vir_uid, 1);
806 if (name == NULL)
807 (void)snprintf(user, sizeof(user),
808 "%d", vmi->vir_uid);
809 else
810 (void)strlcpy(user, name, sizeof(user));
811 /* get group name */
812 if (vmi->vir_gid != -1) {
813 name = group_from_gid(vmi->vir_gid, 1);
814 if (name == NULL)
815 (void)snprintf(group, sizeof(group),
816 ":%lld", vmi->vir_gid);
817 else
818 (void)snprintf(group, sizeof(group),
819 ":%s", name);
820 (void)strlcat(user, group, sizeof(user));
821 }
822
823 (void)strlcpy(curmem, "-", sizeof(curmem));
824 (void)strlcpy(maxmem, "-", sizeof(maxmem));
825
826 (void)fmt_scaled(vir->vir_memory_size, maxmem);
827
828 if (running) {
829 if (*vmi->vir_ttyname == '\0')
830 tty = "-";
831 /* get tty - skip /dev/ path */
832 else if ((tty = strrchr(vmi->vir_ttyname,
833 '/')) == NULL || *++tty == '\0')
834 tty = list[i].vir_ttyname;
835
836 (void)fmt_scaled(vir->vir_used_size, curmem);
837
838 /* running vm */
839 printf("%5u %5u %5zd %7s %7s %7s %12s %8s %s\n",
840 vir->vir_id, vir->vir_creator_pid,
841 vir->vir_ncpus, maxmem, curmem,
842 tty, user, vm_state(vmi->vir_state),
843 vir->vir_name);
844 } else {
845 /* disabled vm */
846 printf("%5u %5s %5zd %7s %7s %7s %12s %8s %s\n",
847 vir->vir_id, "-",
848 vir->vir_ncpus, maxmem, curmem,
849 "-", user, vm_state(vmi->vir_state),
850 vir->vir_name);
851 }
852 }
853 }
854
855 if (found_running)
856 return (0);
857 else
858 return (ENOENT);
859 }
860
861 /*
862 * vm_console
863 *
864 * Connects to the vm console returned from vmd in 'list'.
865 *
866 * Parameters
867 * list: the vm information (consolidated) returned from vmd via imsg
868 * ct : the size (number of elements in 'list') of the result
869 */
870 __dead void
vm_console(struct vmop_info_result * list,size_t ct)871 vm_console(struct vmop_info_result *list, size_t ct)
872 {
873 struct vmop_info_result *vir;
874 size_t i;
875
876 for (i = 0; i < ct; i++) {
877 vir = &list[i];
878 if ((check_info_id(vir->vir_info.vir_name,
879 vir->vir_info.vir_id) > 0) &&
880 (vir->vir_ttyname[0] != '\0')) {
881 /* does not return */
882 ctl_openconsole(vir->vir_ttyname);
883 }
884 }
885
886 errx(1, "console not found");
887 }
888
889 /*
890 * open_imagefile
891 *
892 * Open an imagefile with the specified type, path and size.
893 *
894 * Parameters:
895 * type : format of the image file
896 * imgfile_path: path to the image file to create
897 * flags : flags for open(2), e.g. O_RDONLY
898 * file : file structure
899 * sz : size of the image file
900 *
901 * Return:
902 * fd : Returns file descriptor of the new image file
903 * -1 : Operation failed. errno is set.
904 */
905 int
open_imagefile(int type,const char * imgfile_path,int flags,struct virtio_backing * file,off_t * sz)906 open_imagefile(int type, const char *imgfile_path, int flags,
907 struct virtio_backing *file, off_t *sz)
908 {
909 int fd, ret, basefd[VM_MAX_BASE_PER_DISK], nfd, i;
910 char path[PATH_MAX];
911
912 *sz = 0;
913 if ((fd = open(imgfile_path, flags)) == -1)
914 return (-1);
915
916 basefd[0] = fd;
917 nfd = 1;
918
919 errno = 0;
920 switch (type) {
921 case VMDF_QCOW2:
922 if (strlcpy(path, imgfile_path, sizeof(path)) >= sizeof(path))
923 return (-1);
924 for (i = 0; i < VM_MAX_BASE_PER_DISK - 1; i++, nfd++) {
925 if ((ret = virtio_qcow2_get_base(basefd[i],
926 path, sizeof(path), imgfile_path)) == -1) {
927 log_debug("%s: failed to get base %d", __func__, i);
928 return -1;
929 } else if (ret == 0)
930 break;
931
932 if ((basefd[i + 1] = open(path, O_RDONLY)) == -1) {
933 log_warn("%s: failed to open base %s",
934 __func__, path);
935 return (-1);
936 }
937 }
938 ret = virtio_qcow2_init(file, sz, basefd, nfd);
939 break;
940 default:
941 ret = virtio_raw_init(file, sz, &fd, 1);
942 break;
943 }
944
945 if (ret == -1) {
946 for (i = 0; i < nfd; i++)
947 close(basefd[i]);
948 return (-1);
949 }
950
951 return (fd);
952 }
953
954 /*
955 * create_imagefile
956 *
957 * Create an empty imagefile with the specified type, path and size.
958 *
959 * Parameters:
960 * type : format of the image file
961 * imgfile_path: path to the image file to create
962 * base_path : path to the qcow2 base image
963 * imgsize : size of the image file to create (in bytes)
964 * format : string identifying the format
965 *
966 * Return:
967 * EEXIST: The requested image file already exists
968 * 0 : Image file successfully created
969 * Exxxx : Various other Exxxx errno codes due to other I/O errors
970 */
971 int
create_imagefile(int type,const char * imgfile_path,const char * base_path,uint64_t imgsize,const char ** format)972 create_imagefile(int type, const char *imgfile_path, const char *base_path,
973 uint64_t imgsize, const char **format)
974 {
975 int ret;
976
977 switch (type) {
978 case VMDF_QCOW2:
979 *format = "qcow2";
980 ret = virtio_qcow2_create(imgfile_path, base_path, imgsize);
981 break;
982 default:
983 *format = "raw";
984 ret = virtio_raw_create(imgfile_path, imgsize);
985 break;
986 }
987
988 return (ret);
989 }
990