1 /* $OpenBSD: ldomctl.c,v 1.41 2023/08/10 07:50:45 kn Exp $ */ 2 3 /* 4 * Copyright (c) 2012 Mark Kettenis 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/ioctl.h> 21 #include <sys/stat.h> 22 #include <err.h> 23 #include <errno.h> 24 #include <fcntl.h> 25 #include <stdlib.h> 26 #include <stdio.h> 27 #include <string.h> 28 #include <unistd.h> 29 #include <util.h> 30 31 #include "ds.h" 32 #include "hvctl.h" 33 #include "mdstore.h" 34 #include "mdesc.h" 35 #include "ldom_util.h" 36 #include "ldomctl.h" 37 38 extern struct ds_service pri_service; 39 40 struct command { 41 const char *cmd_name; 42 void (*cmd_func)(int, char **); 43 }; 44 45 __dead void usage(void); 46 47 struct guest_head guest_list; 48 49 uint64_t find_guest(const char *); 50 51 void fetch_pri(void); 52 53 void download(int argc, char **argv); 54 void dump(int argc, char **argv); 55 void list(int argc, char **argv); 56 void list_io(int argc, char **argv); 57 void xselect(int argc, char **argv); 58 void delete(int argc, char **argv); 59 void create_vdisk(int argc, char **argv); 60 void guest_start(int argc, char **argv); 61 void guest_stop(int argc, char **argv); 62 void guest_panic(int argc, char **argv); 63 void guest_status(int argc, char **argv); 64 void guest_console(int argc, char **argv); 65 void init_system(int argc, char **argv); 66 67 struct command commands[] = { 68 { "download", download }, 69 { "dump", dump }, 70 { "list", list }, 71 { "list-io", list_io }, 72 { "select", xselect }, 73 { "delete", delete }, 74 { "create-vdisk", create_vdisk }, 75 { "start", guest_start }, 76 { "stop", guest_stop }, 77 { "panic", guest_panic }, 78 { "status", guest_status }, 79 { "console", guest_console }, 80 { "init-system", init_system }, 81 { NULL, NULL } 82 }; 83 84 void hv_open(void); 85 void hv_close(void); 86 void hv_config(void); 87 void hv_read(uint64_t, void *, size_t); 88 void hv_write(uint64_t, void *, size_t); 89 90 int hvctl_seq = 1; 91 int hvctl_fd; 92 93 void *hvmd_buf; 94 size_t hvmd_len; 95 uint64_t hv_mdpa; 96 uint64_t hv_membase; 97 uint64_t hv_memsize; 98 99 extern void *pri_buf; 100 extern size_t pri_len; 101 102 int 103 main(int argc, char **argv) 104 { 105 struct command *cmdp; 106 107 if (argc < 2) 108 usage(); 109 110 /* Skip program name. */ 111 argv++; 112 argc--; 113 114 for (cmdp = commands; cmdp->cmd_name != NULL; cmdp++) 115 if (strcmp(argv[0], cmdp->cmd_name) == 0) 116 break; 117 if (cmdp->cmd_name == NULL) 118 usage(); 119 120 (cmdp->cmd_func)(argc, argv); 121 122 exit(EXIT_SUCCESS); 123 } 124 125 __dead void 126 usage(void) 127 { 128 fprintf(stderr, "usage:\t%1$s delete|select configuration\n" 129 "\t%1$s download directory\n" 130 "\t%1$s dump|list|list-io\n" 131 "\t%1$s init-system [-n] file\n" 132 "\t%1$s create-vdisk -s size file\n" 133 "\t%1$s panic|start [-c] domain\n" 134 "\t%1$s console|status|stop [domain]\n", 135 getprogname()); 136 137 exit(EXIT_FAILURE); 138 } 139 140 void 141 add_guest(struct md_node *node) 142 { 143 struct guest *guest; 144 struct md_prop *prop; 145 146 guest = xmalloc(sizeof(*guest)); 147 148 if (!md_get_prop_str(hvmd, node, "name", &guest->name)) 149 goto free; 150 if (!md_get_prop_val(hvmd, node, "gid", &guest->gid)) 151 goto free; 152 if (!md_get_prop_val(hvmd, node, "mdpa", &guest->mdpa)) 153 goto free; 154 155 guest->num_cpus = 0; 156 TAILQ_FOREACH(prop, &node->prop_list, link) { 157 if (prop->tag == MD_PROP_ARC && 158 strcmp(prop->name->str, "fwd") == 0) { 159 if (strcmp(prop->d.arc.node->name->str, "cpu") == 0) 160 guest->num_cpus++; 161 } 162 } 163 164 TAILQ_INSERT_TAIL(&guest_list, guest, link); 165 return; 166 167 free: 168 free(guest); 169 } 170 171 uint64_t 172 find_guest(const char *name) 173 { 174 struct guest *guest; 175 176 TAILQ_FOREACH(guest, &guest_list, link) { 177 if (strcmp(guest->name, name) == 0) 178 return guest->gid; 179 } 180 181 errx(EXIT_FAILURE, "unknown guest '%s'", name); 182 } 183 184 void 185 fetch_pri(void) 186 { 187 struct ds_conn *dc; 188 189 dc = ds_conn_open("/dev/spds", NULL); 190 ds_conn_register_service(dc, &pri_service); 191 while (pri_buf == NULL) 192 ds_conn_handle(dc); 193 } 194 195 void 196 dump(int argc, char **argv) 197 { 198 struct guest *guest; 199 struct md_header hdr; 200 void *md_buf; 201 size_t md_len; 202 char *name; 203 FILE *fp; 204 205 if (argc != 1) 206 usage(); 207 208 hv_config(); 209 210 fp = fopen("hv.md", "w"); 211 if (fp == NULL) 212 err(1, "fopen"); 213 fwrite(hvmd_buf, hvmd_len, 1, fp); 214 fclose(fp); 215 216 fetch_pri(); 217 218 fp = fopen("pri", "w"); 219 if (fp == NULL) 220 err(1, "fopen"); 221 fwrite(pri_buf, pri_len, 1, fp); 222 fclose(fp); 223 224 TAILQ_FOREACH(guest, &guest_list, link) { 225 hv_read(guest->mdpa, &hdr, sizeof(hdr)); 226 md_len = sizeof(hdr) + hdr.node_blk_sz + hdr.name_blk_sz + 227 hdr.data_blk_sz; 228 md_buf = xmalloc(md_len); 229 hv_read(guest->mdpa, md_buf, md_len); 230 231 if (asprintf(&name, "%s.md", guest->name) == -1) 232 err(1, "asprintf"); 233 234 fp = fopen(name, "w"); 235 if (fp == NULL) 236 err(1, "fopen"); 237 fwrite(md_buf, md_len, 1, fp); 238 fclose(fp); 239 240 free(name); 241 free(md_buf); 242 } 243 } 244 245 void 246 init_system(int argc, char **argv) 247 { 248 int ch, noaction = 0; 249 250 while ((ch = getopt(argc, argv, "n")) != -1) { 251 switch (ch) { 252 case 'n': 253 noaction = 1; 254 break; 255 default: 256 usage(); 257 } 258 } 259 argc -= optind; 260 argv += optind; 261 262 if (argc != 1) 263 usage(); 264 265 if (!noaction) 266 hv_config(); 267 268 build_config(argv[0], noaction); 269 } 270 271 void 272 list(int argc, char **argv) 273 { 274 struct ds_conn *dc; 275 struct mdstore_set *set; 276 277 if (argc != 1) 278 usage(); 279 280 hv_config(); 281 282 dc = ds_conn_open("/dev/spds", NULL); 283 mdstore_register(dc); 284 while (TAILQ_EMPTY(&mdstore_sets)) 285 ds_conn_handle(dc); 286 287 TAILQ_FOREACH(set, &mdstore_sets, link) { 288 printf("%s", set->name); 289 if (set->booted_set) 290 printf(" [current]"); 291 else if (set->boot_set) 292 printf(" [next]"); 293 printf("\n"); 294 } 295 } 296 297 void 298 list_io(int argc, char **argv) 299 { 300 if (argc != 1) 301 usage(); 302 303 list_components(); 304 } 305 306 void 307 xselect(int argc, char **argv) 308 { 309 struct ds_conn *dc; 310 311 if (argc != 2) 312 usage(); 313 314 hv_config(); 315 316 dc = ds_conn_open("/dev/spds", NULL); 317 mdstore_register(dc); 318 while (TAILQ_EMPTY(&mdstore_sets)) 319 ds_conn_handle(dc); 320 321 mdstore_select(dc, argv[1]); 322 } 323 324 void 325 delete(int argc, char **argv) 326 { 327 struct ds_conn *dc; 328 329 if (argc != 2) 330 usage(); 331 332 if (strcmp(argv[1], "factory-default") == 0) 333 errx(1, "\"%s\" should not be deleted", argv[1]); 334 335 hv_config(); 336 337 dc = ds_conn_open("/dev/spds", NULL); 338 mdstore_register(dc); 339 while (TAILQ_EMPTY(&mdstore_sets)) 340 ds_conn_handle(dc); 341 342 mdstore_delete(dc, argv[1]); 343 } 344 345 void 346 create_vdisk(int argc, char **argv) 347 { 348 int ch, fd, save_errno; 349 long long imgsize; 350 const char *imgfile_path; 351 352 while ((ch = getopt(argc, argv, "s:")) != -1) { 353 switch (ch) { 354 case 's': 355 if (scan_scaled(optarg, &imgsize) == -1) 356 err(1, "invalid size: %s", optarg); 357 break; 358 default: 359 usage(); 360 } 361 } 362 argc -= optind; 363 argv += optind; 364 365 if (argc != 1) 366 usage(); 367 368 imgfile_path = argv[0]; 369 370 /* Refuse to overwrite an existing image */ 371 if ((fd = open(imgfile_path, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 372 S_IRUSR | S_IWUSR)) == -1) 373 err(1, "open"); 374 375 /* Extend to desired size */ 376 if (ftruncate(fd, (off_t)imgsize) == -1) { 377 save_errno = errno; 378 close(fd); 379 unlink(imgfile_path); 380 errno = save_errno; 381 err(1, "ftruncate"); 382 } 383 384 close(fd); 385 } 386 387 void 388 download(int argc, char **argv) 389 { 390 struct ds_conn *dc; 391 392 if (argc != 2) 393 usage(); 394 395 hv_config(); 396 397 dc = ds_conn_open("/dev/spds", NULL); 398 mdstore_register(dc); 399 while (TAILQ_EMPTY(&mdstore_sets)) 400 ds_conn_handle(dc); 401 402 mdstore_download(dc, argv[1]); 403 } 404 405 void 406 console_exec(uint64_t gid) 407 { 408 struct guest *guest; 409 char console_str[8]; 410 411 if (gid == 0) 412 errx(1, "no console for primary domain"); 413 414 TAILQ_FOREACH(guest, &guest_list, link) { 415 if (guest->gid != gid) 416 continue; 417 snprintf(console_str, sizeof(console_str), 418 "ttyV%llu", guest->gid - 1); 419 420 closefrom(STDERR_FILENO + 1); 421 execl(LDOMCTL_CU, LDOMCTL_CU, "-r", "-l", console_str, 422 (char *)NULL); 423 err(1, "failed to open console"); 424 } 425 } 426 427 void 428 guest_start(int argc, char **argv) 429 { 430 struct hvctl_msg msg; 431 ssize_t nbytes; 432 uint64_t gid; 433 int ch, console = 0; 434 435 while ((ch = getopt(argc, argv, "c")) != -1) { 436 switch (ch) { 437 case 'c': 438 console = 1; 439 break; 440 default: 441 usage(); 442 } 443 } 444 argc -= optind; 445 argv += optind; 446 447 if (argc != 1) 448 usage(); 449 450 hv_config(); 451 452 gid = find_guest(argv[0]); 453 454 /* 455 * Start guest domain. 456 */ 457 bzero(&msg, sizeof(msg)); 458 msg.hdr.op = HVCTL_OP_GUEST_START; 459 msg.hdr.seq = hvctl_seq++; 460 msg.msg.guestop.guestid = gid; 461 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 462 if (nbytes != sizeof(msg)) 463 err(1, "write"); 464 465 bzero(&msg, sizeof(msg)); 466 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 467 if (nbytes != sizeof(msg)) 468 err(1, "read"); 469 470 if (console) 471 console_exec(gid); 472 } 473 474 void 475 guest_stop(int argc, char **argv) 476 { 477 struct hvctl_msg msg; 478 ssize_t nbytes; 479 480 if (argc != 2) 481 usage(); 482 483 hv_config(); 484 485 /* 486 * Stop guest domain. 487 */ 488 bzero(&msg, sizeof(msg)); 489 msg.hdr.op = HVCTL_OP_GUEST_STOP; 490 msg.hdr.seq = hvctl_seq++; 491 msg.msg.guestop.guestid = find_guest(argv[1]); 492 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 493 if (nbytes != sizeof(msg)) 494 err(1, "write"); 495 496 bzero(&msg, sizeof(msg)); 497 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 498 if (nbytes != sizeof(msg)) 499 err(1, "read"); 500 } 501 502 void 503 guest_panic(int argc, char **argv) 504 { 505 struct hvctl_msg msg; 506 ssize_t nbytes; 507 uint64_t gid; 508 int ch, console = 0; 509 510 while ((ch = getopt(argc, argv, "c")) != -1) { 511 switch (ch) { 512 case 'c': 513 console = 1; 514 break; 515 default: 516 usage(); 517 } 518 } 519 argc -= optind; 520 argv += optind; 521 522 if (argc != 1) 523 usage(); 524 525 hv_config(); 526 527 gid = find_guest(argv[0]); 528 529 /* 530 * Stop guest domain. 531 */ 532 bzero(&msg, sizeof(msg)); 533 msg.hdr.op = HVCTL_OP_GUEST_PANIC; 534 msg.hdr.seq = hvctl_seq++; 535 msg.msg.guestop.guestid = gid; 536 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 537 if (nbytes != sizeof(msg)) 538 err(1, "write"); 539 540 bzero(&msg, sizeof(msg)); 541 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 542 if (nbytes != sizeof(msg)) 543 err(1, "read"); 544 545 if (console) 546 console_exec(gid); 547 } 548 549 void 550 guest_status(int argc, char **argv) 551 { 552 struct hvctl_msg msg; 553 ssize_t nbytes; 554 struct hvctl_rs_guest_state state; 555 struct hvctl_rs_guest_softstate softstate; 556 struct hvctl_rs_guest_util util; 557 struct guest *guest; 558 uint64_t gid = -1; 559 uint64_t total_cycles, yielded_cycles; 560 double utilisation = 0.0; 561 const char *state_str; 562 char buf[32]; 563 char console_str[8] = "-"; 564 565 if (argc < 1 || argc > 2) 566 usage(); 567 568 hv_config(); 569 570 if (argc == 2) 571 gid = find_guest(argv[1]); 572 573 TAILQ_FOREACH(guest, &guest_list, link) { 574 if (gid != -1 && guest->gid != gid) 575 continue; 576 577 /* 578 * Request status. 579 */ 580 bzero(&msg, sizeof(msg)); 581 msg.hdr.op = HVCTL_OP_GET_RES_STAT; 582 msg.hdr.seq = hvctl_seq++; 583 msg.msg.resstat.res = HVCTL_RES_GUEST; 584 msg.msg.resstat.resid = guest->gid; 585 msg.msg.resstat.infoid = HVCTL_INFO_GUEST_STATE; 586 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 587 if (nbytes != sizeof(msg)) 588 err(1, "write"); 589 590 bzero(&msg, sizeof(msg)); 591 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 592 if (nbytes != sizeof(msg)) 593 err(1, "read"); 594 595 utilisation = 0.0; 596 597 memcpy(&state, msg.msg.resstat.data, sizeof(state)); 598 switch (state.state) { 599 case GUEST_STATE_STOPPED: 600 state_str = "stopped"; 601 break; 602 case GUEST_STATE_RESETTING: 603 state_str = "resetting"; 604 break; 605 case GUEST_STATE_NORMAL: 606 state_str = "running"; 607 608 bzero(&msg, sizeof(msg)); 609 msg.hdr.op = HVCTL_OP_GET_RES_STAT; 610 msg.hdr.seq = hvctl_seq++; 611 msg.msg.resstat.res = HVCTL_RES_GUEST; 612 msg.msg.resstat.resid = guest->gid; 613 msg.msg.resstat.infoid = HVCTL_INFO_GUEST_SOFT_STATE; 614 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 615 if (nbytes != sizeof(msg)) 616 err(1, "write"); 617 618 bzero(&msg, sizeof(msg)); 619 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 620 if (nbytes != sizeof(msg)) 621 err(1, "read"); 622 623 memcpy(&softstate, msg.msg.resstat.data, 624 sizeof(softstate)); 625 626 bzero(&msg, sizeof(msg)); 627 msg.hdr.op = HVCTL_OP_GET_RES_STAT; 628 msg.hdr.seq = hvctl_seq++; 629 msg.msg.resstat.res = HVCTL_RES_GUEST; 630 msg.msg.resstat.resid = guest->gid; 631 msg.msg.resstat.infoid = HVCTL_INFO_GUEST_UTILISATION; 632 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 633 if (nbytes != sizeof(msg)) 634 err(1, "write"); 635 636 bzero(&msg, sizeof(msg)); 637 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 638 if (nbytes != sizeof(msg)) 639 err(1, "read"); 640 641 memcpy(&util, msg.msg.resstat.data, sizeof(util)); 642 643 total_cycles = util.active_delta * guest->num_cpus 644 - util.stopped_cycles; 645 yielded_cycles = util.yielded_cycles; 646 if (yielded_cycles <= total_cycles) 647 utilisation = (100.0 * (total_cycles 648 - yielded_cycles)) / total_cycles; 649 650 break; 651 case GUEST_STATE_SUSPENDED: 652 state_str = "suspended"; 653 break; 654 case GUEST_STATE_EXITING: 655 state_str = "exiting"; 656 break; 657 case GUEST_STATE_UNCONFIGURED: 658 state_str = "unconfigured"; 659 break; 660 default: 661 snprintf(buf, sizeof(buf), "unknown (%lld)", 662 state.state); 663 state_str = buf; 664 break; 665 } 666 667 /* primary has no console */ 668 if (guest->gid != 0) { 669 snprintf(console_str, sizeof(console_str), 670 "ttyV%llu", guest->gid - 1); 671 } 672 673 printf("%-16s %-8s %-16s %-32s %3.0f%%\n", guest->name, 674 console_str, state_str, state.state == GUEST_STATE_NORMAL ? 675 softstate.soft_state_str : "-", utilisation); 676 } 677 } 678 679 void 680 guest_console(int argc, char **argv) 681 { 682 uint64_t gid; 683 684 if (argc != 2) 685 usage(); 686 687 hv_config(); 688 689 gid = find_guest(argv[1]); 690 691 console_exec(gid); 692 } 693 694 void 695 hv_open(void) 696 { 697 struct hvctl_msg msg; 698 ssize_t nbytes; 699 uint64_t code; 700 701 hvctl_fd = open("/dev/hvctl", O_RDWR); 702 if (hvctl_fd == -1) 703 err(1, "cannot open /dev/hvctl"); 704 705 /* 706 * Say "Hello". 707 */ 708 bzero(&msg, sizeof(msg)); 709 msg.hdr.op = HVCTL_OP_HELLO; 710 msg.hdr.seq = hvctl_seq++; 711 msg.msg.hello.major = 1; 712 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 713 if (nbytes != sizeof(msg)) 714 err(1, "write"); 715 716 bzero(&msg, sizeof(msg)); 717 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 718 if (nbytes != sizeof(msg)) 719 err(1, "read"); 720 721 code = msg.msg.clnge.code ^ 0xbadbeef20; 722 723 /* 724 * Respond to challenge. 725 */ 726 bzero(&msg, sizeof(msg)); 727 msg.hdr.op = HVCTL_OP_RESPONSE; 728 msg.hdr.seq = hvctl_seq++; 729 msg.msg.clnge.code = code ^ 0x12cafe42a; 730 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 731 if (nbytes != sizeof(msg)) 732 err(1, "write"); 733 734 bzero(&msg, sizeof(msg)); 735 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 736 if (nbytes != sizeof(msg)) 737 err(1, "read"); 738 } 739 740 void 741 hv_close(void) 742 { 743 close(hvctl_fd); 744 hvctl_fd = -1; 745 } 746 747 void 748 hv_config(void) 749 { 750 struct hvctl_msg msg; 751 ssize_t nbytes; 752 struct md_header hdr; 753 struct md_node *node; 754 struct md_prop *prop; 755 756 hv_open(); 757 758 /* 759 * Request config. 760 */ 761 bzero(&msg, sizeof(msg)); 762 msg.hdr.op = HVCTL_OP_GET_HVCONFIG; 763 msg.hdr.seq = hvctl_seq++; 764 nbytes = write(hvctl_fd, &msg, sizeof(msg)); 765 if (nbytes != sizeof(msg)) 766 err(1, "write"); 767 768 bzero(&msg, sizeof(msg)); 769 nbytes = read(hvctl_fd, &msg, sizeof(msg)); 770 if (nbytes != sizeof(msg)) 771 err(1, "read"); 772 773 hv_membase = msg.msg.hvcnf.hv_membase; 774 hv_memsize = msg.msg.hvcnf.hv_memsize; 775 776 hv_mdpa = msg.msg.hvcnf.hvmdp; 777 hv_read(hv_mdpa, &hdr, sizeof(hdr)); 778 hvmd_len = sizeof(hdr) + hdr.node_blk_sz + hdr.name_blk_sz + 779 hdr.data_blk_sz; 780 hvmd_buf = xmalloc(hvmd_len); 781 hv_read(hv_mdpa, hvmd_buf, hvmd_len); 782 783 hvmd = md_ingest(hvmd_buf, hvmd_len); 784 node = md_find_node(hvmd, "guests"); 785 TAILQ_INIT(&guest_list); 786 TAILQ_FOREACH(prop, &node->prop_list, link) { 787 if (prop->tag == MD_PROP_ARC && 788 strcmp(prop->name->str, "fwd") == 0) 789 add_guest(prop->d.arc.node); 790 } 791 } 792 793 void 794 hv_read(uint64_t addr, void *buf, size_t len) 795 { 796 struct hv_io hi; 797 798 hi.hi_cookie = addr; 799 hi.hi_addr = buf; 800 hi.hi_len = len; 801 802 if (ioctl(hvctl_fd, HVIOCREAD, &hi) == -1) 803 err(1, "ioctl"); 804 } 805 806 void 807 hv_write(uint64_t addr, void *buf, size_t len) 808 { 809 struct hv_io hi; 810 811 hi.hi_cookie = addr; 812 hi.hi_addr = buf; 813 hi.hi_len = len; 814 815 if (ioctl(hvctl_fd, HVIOCWRITE, &hi) == -1) 816 err(1, "ioctl"); 817 } 818