1 /* $OpenBSD: vmctl.c,v 1.15 2016/05/10 11:00:54 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/types.h> 20 #include <sys/queue.h> 21 #include <sys/uio.h> 22 #include <sys/stat.h> 23 #include <sys/socket.h> 24 #include <sys/un.h> 25 26 #include <machine/vmmvar.h> 27 28 #include <err.h> 29 #include <errno.h> 30 #include <fcntl.h> 31 #include <imsg.h> 32 #include <limits.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <unistd.h> 37 38 #include "vmd.h" 39 #include "vmctl.h" 40 41 extern char *__progname; 42 uint32_t info_id; 43 char info_name[VMM_MAX_NAME_LEN]; 44 int info_console; 45 46 /* 47 * start_vm 48 * 49 * Request vmd to start the VM defined by the supplied parameters 50 * 51 * Parameters: 52 * name: optional name of the VM 53 * memsize: memory size (MB) of the VM to create 54 * nnics: number of vionet network interfaces to create 55 * ndisks: number of disk images 56 * disks: disk image file names 57 * kernel: kernel image to load 58 * 59 * Return: 60 * 0 if the request to start the VM was sent successfully. 61 * ENOMEM if a memory allocation failure occurred. 62 */ 63 int 64 start_vm(const char *name, int memsize, int nnics, int ndisks, char **disks, 65 char *kernel) 66 { 67 struct vm_create_params *vcp; 68 int i; 69 70 if (memsize < 1) 71 errx(1, "specified memory size too small"); 72 if (kernel == NULL) 73 errx(1, "no kernel specified"); 74 if (ndisks > VMM_MAX_DISKS_PER_VM) 75 errx(1, "too many disks"); 76 else if (ndisks == 0) 77 warnx("starting without disks"); 78 if (nnics == -1) 79 nnics = 0; 80 if (nnics == 0) 81 warnx("starting without network interfaces"); 82 83 vcp = malloc(sizeof(struct vm_create_params)); 84 if (vcp == NULL) 85 return (ENOMEM); 86 87 bzero(vcp, sizeof(struct vm_create_params)); 88 89 /* 90 * XXX: vmd(8) fills in the actual memory ranges. vmctl(8) 91 * just passes in the actual memory size in MB here. 92 */ 93 vcp->vcp_nmemranges = 1; 94 vcp->vcp_memranges[0].vmr_size = memsize; 95 96 vcp->vcp_ncpus = 1; 97 vcp->vcp_ndisks = ndisks; 98 99 for (i = 0 ; i < ndisks; i++) 100 strlcpy(vcp->vcp_disks[i], disks[i], VMM_MAX_PATH_DISK); 101 102 if (name != NULL) 103 strlcpy(vcp->vcp_name, name, VMM_MAX_NAME_LEN); 104 strlcpy(vcp->vcp_kernel, kernel, VMM_MAX_KERNEL_PATH); 105 vcp->vcp_nnics = nnics; 106 107 imsg_compose(ibuf, IMSG_VMDOP_START_VM_REQUEST, 0, 0, -1, 108 vcp, sizeof(struct vm_create_params)); 109 110 free(vcp); 111 return (0); 112 } 113 114 /* 115 * start_vm_complete 116 * 117 * Callback function invoked when we are expecting an 118 * IMSG_VMDOP_START_VMM_RESPONSE message indicating the completion of 119 * a start vm operation. 120 * 121 * Parameters: 122 * imsg : response imsg received from vmd 123 * ret : return value 124 * autoconnect : open the console after startup 125 * 126 * Return: 127 * Always 1 to indicate we have processed the return message (even if it 128 * was an incorrect/failure message) 129 * 130 * The function also sets 'ret' to the error code as follows: 131 * 0 : Message successfully processed 132 * EINVAL: Invalid or unexpected response from vmd 133 * EIO : start_vm command failed 134 */ 135 int 136 start_vm_complete(struct imsg *imsg, int *ret, int autoconnect) 137 { 138 struct vmop_result *vmr; 139 int res; 140 141 if (imsg->hdr.type == IMSG_VMDOP_START_VM_RESPONSE) { 142 vmr = (struct vmop_result *)imsg->data; 143 res = vmr->vmr_result; 144 if (res) { 145 errno = res; 146 warn("start vm command failed"); 147 *ret = EIO; 148 } else if (autoconnect) { 149 /* does not return */ 150 ctl_openconsole(vmr->vmr_ttyname); 151 } else { 152 warnx("started vm %d successfully, tty %s", 153 vmr->vmr_id, vmr->vmr_ttyname); 154 *ret = 0; 155 } 156 } else { 157 warnx("unexpected response received from vmd"); 158 *ret = EINVAL; 159 } 160 161 return (1); 162 } 163 164 /* 165 * terminate_vm 166 * 167 * Request vmd to stop the VM indicated 168 * 169 * Parameters: 170 * terminate_id: ID of the vm to be terminated 171 * name: optional name of the VM to be terminated 172 */ 173 void 174 terminate_vm(uint32_t terminate_id, const char *name) 175 { 176 struct vmop_id vid; 177 178 memset(&vid, 0, sizeof(vid)); 179 vid.vid_id = terminate_id; 180 if (name != NULL) 181 (void)strlcpy(vid.vid_name, name, sizeof(vid.vid_name)); 182 183 imsg_compose(ibuf, IMSG_VMDOP_TERMINATE_VM_REQUEST, 0, 0, -1, 184 &vid, sizeof(vid)); 185 } 186 187 /* 188 * terminate_vm_complete 189 * 190 * Callback function invoked when we are expecting an 191 * IMSG_VMDOP_TERMINATE_VMM_RESPONSE message indicating the completion of 192 * a terminate vm operation. 193 * 194 * Parameters: 195 * imsg : response imsg received from vmd 196 * ret : return value 197 * 198 * Return: 199 * Always 1 to indicate we have processed the return message (even if it 200 * was an incorrect/failure message) 201 * 202 * The function also sets 'ret' to the error code as follows: 203 * 0 : Message successfully processed 204 * EINVAL: Invalid or unexpected response from vmd 205 * EIO : terminate_vm command failed 206 */ 207 int 208 terminate_vm_complete(struct imsg *imsg, int *ret) 209 { 210 struct vmop_result *vmr; 211 int res; 212 213 if (imsg->hdr.type == IMSG_VMDOP_TERMINATE_VM_RESPONSE) { 214 vmr = (struct vmop_result *)imsg->data; 215 res = vmr->vmr_result; 216 if (res) { 217 errno = res; 218 warn("terminate vm command failed"); 219 *ret = EIO; 220 } else { 221 warnx("terminated vm %d successfully", vmr->vmr_id); 222 *ret = 0; 223 } 224 } else { 225 warnx("unexpected response received from vmd"); 226 *ret = EINVAL; 227 } 228 229 return (1); 230 } 231 232 /* 233 * get_info_vm 234 * 235 * Return the list of all running VMs or find a specific VM by ID or name. 236 * 237 * Parameters: 238 * id: optional ID of a VM to list 239 * name: optional name of a VM to list 240 * console: if true, open the console of the selected VM (by name or ID) 241 * 242 * Request a list of running VMs from vmd 243 */ 244 void 245 get_info_vm(uint32_t id, const char *name, int console) 246 { 247 info_id = id; 248 if (name != NULL) 249 (void)strlcpy(info_name, name, sizeof(info_name)); 250 info_console = console; 251 imsg_compose(ibuf, IMSG_VMDOP_GET_INFO_VM_REQUEST, 0, 0, -1, NULL, 0); 252 } 253 254 /* 255 * check_info_id 256 * 257 * Check if requested name or ID of a VM matches specified arguments 258 * 259 * Parameters: 260 * name: name of the VM 261 * id: ID of the VM 262 */ 263 int 264 check_info_id(const char *name, uint32_t id) 265 { 266 if (info_id == 0 && *info_name == '\0') 267 return (-1); 268 if (info_id != 0 && info_id == id) 269 return (1); 270 if (*info_name != '\0' && name && strcmp(info_name, name) == 0) 271 return (1); 272 return (0); 273 } 274 275 /* 276 * add_info 277 * 278 * Callback function invoked when we are expecting an 279 * IMSG_VMDOP_GET_INFO_VM_DATA message indicating the receipt of additional 280 * "list vm" data, or an IMSG_VMDOP_GET_INFO_VM_END_DATA message indicating 281 * that no additional "list vm" data will be forthcoming. 282 * 283 * Parameters: 284 * imsg : response imsg received from vmd 285 * ret : return value 286 * 287 * Return: 288 * 0 : the returned data was successfully added to the "list vm" data. 289 * The caller can expect more data. 290 * 1 : IMSG_VMDOP_GET_INFO_VM_END_DATA received (caller should not call 291 * add_info again), or an error occurred adding the returned data 292 * to the "list vm" data. The caller should check the value of 293 * 'ret' to determine which case occurred. 294 * 295 * This function does not return if a VM is found and info_console is set. 296 * 297 * The function also sets 'ret' to the error code as follows: 298 * 0 : Message successfully processed 299 * EINVAL: Invalid or unexpected response from vmd 300 * ENOMEM: memory allocation failure 301 */ 302 int 303 add_info(struct imsg *imsg, int *ret) 304 { 305 static size_t ct = 0; 306 static struct vmop_info_result *vir = NULL; 307 308 if (imsg->hdr.type == IMSG_VMDOP_GET_INFO_VM_DATA) { 309 vir = reallocarray(vir, ct + 1, 310 sizeof(struct vmop_info_result)); 311 if (vir == NULL) { 312 *ret = ENOMEM; 313 return (1); 314 } 315 memcpy(&vir[ct], imsg->data, sizeof(struct vmop_info_result)); 316 ct++; 317 *ret = 0; 318 return (0); 319 } else if (imsg->hdr.type == IMSG_VMDOP_GET_INFO_VM_END_DATA) { 320 if (info_console) 321 vm_console(vir, ct); 322 else 323 print_vm_info(vir, ct); 324 free(vir); 325 *ret = 0; 326 return (1); 327 } else { 328 *ret = EINVAL; 329 return (1); 330 } 331 } 332 333 /* 334 * print_vm_info 335 * 336 * Prints the vm information returned from vmd in 'list' to stdout. 337 * 338 * Parameters 339 * list: the vm information (consolidated) returned from vmd via imsg 340 * ct : the size (number of elements in 'list') of the result 341 */ 342 void 343 print_vm_info(struct vmop_info_result *list, size_t ct) 344 { 345 struct vm_info_result *vir; 346 size_t i, j; 347 char *vcpu_state; 348 349 printf("%5s %5s %5s %9s %9s %*s %s\n", "ID", "PID", "VCPUS", "MAXMEM", 350 "CURMEM", VM_TTYNAME_MAX, "TTY", "NAME"); 351 for (i = 0; i < ct; i++) { 352 vir = &list[i].vir_info; 353 if (check_info_id(vir->vir_name, vir->vir_id)) { 354 printf("%5u %5u %5zd %7zdMB %7zdMB %*s %s\n", 355 vir->vir_id, vir->vir_creator_pid, 356 vir->vir_ncpus, vir->vir_memory_size, 357 vir->vir_used_size / 1024 / 1024 , VM_TTYNAME_MAX, 358 list[i].vir_ttyname, vir->vir_name); 359 } 360 if (check_info_id(vir->vir_name, vir->vir_id) > 0) { 361 for (j = 0; j < vir->vir_ncpus; j++) { 362 if (vir->vir_vcpu_state[j] == 363 VCPU_STATE_STOPPED) 364 vcpu_state = "STOPPED"; 365 else if (vir->vir_vcpu_state[j] == 366 VCPU_STATE_RUNNING) 367 vcpu_state = "RUNNING"; 368 else 369 vcpu_state = "UNKNOWN"; 370 371 printf(" VCPU: %2zd STATE: %s\n", 372 j, vcpu_state); 373 } 374 } 375 } 376 } 377 378 /* 379 * vm_console 380 * 381 * Connects to the vm console returned from vmd in 'list'. 382 * 383 * Parameters 384 * list: the vm information (consolidated) returned from vmd via imsg 385 * ct : the size (number of elements in 'list') of the result 386 */ 387 __dead void 388 vm_console(struct vmop_info_result *list, size_t ct) 389 { 390 struct vmop_info_result *vir; 391 size_t i; 392 393 for (i = 0; i < ct; i++) { 394 vir = &list[i]; 395 if (check_info_id(vir->vir_info.vir_name, 396 vir->vir_info.vir_id) > 0) { 397 /* does not return */ 398 ctl_openconsole(vir->vir_ttyname); 399 } 400 } 401 402 errx(1, "console %d not found", info_id); 403 } 404 405 /* 406 * create_imagefile 407 * 408 * Create an empty imagefile with the specified path and size. 409 * 410 * Parameters: 411 * imgfile_path: path to the image file to create 412 * imgsize : size of the image file to create (in MB) 413 * 414 * Return: 415 * EEXIST: The requested image file already exists 416 * 0 : Image file successfully created 417 * Exxxx : Various other Exxxx errno codes due to other I/O errors 418 */ 419 int 420 create_imagefile(const char *imgfile_path, long imgsize) 421 { 422 int fd, ret; 423 424 /* Refuse to overwrite an existing image */ 425 fd = open(imgfile_path, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 426 S_IRUSR | S_IWUSR); 427 if (fd == -1) 428 return (errno); 429 430 /* Extend to desired size */ 431 if (ftruncate(fd, (off_t)imgsize * 1024 * 1024) == -1) { 432 ret = errno; 433 close(fd); 434 unlink(imgfile_path); 435 return (ret); 436 } 437 438 ret = close(fd); 439 return (ret); 440 } 441