1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD AND BSD-2-Clause 3 * 4 * Copyright (c) 2011 NetApp, Inc. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 * $FreeBSD$ 29 */ 30 31 /*- 32 * Copyright (c) 2011 Google, Inc. 33 * All rights reserved. 34 * 35 * Redistribution and use in source and binary forms, with or without 36 * modification, are permitted provided that the following conditions 37 * are met: 38 * 1. Redistributions of source code must retain the above copyright 39 * notice, this list of conditions and the following disclaimer. 40 * 2. Redistributions in binary form must reproduce the above copyright 41 * notice, this list of conditions and the following disclaimer in the 42 * documentation and/or other materials provided with the distribution. 43 * 44 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 45 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 46 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 47 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 48 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 49 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 50 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 51 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 52 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 53 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 54 * SUCH DAMAGE. 55 * 56 * $FreeBSD$ 57 */ 58 59 #include <sys/cdefs.h> 60 __FBSDID("$FreeBSD$"); 61 62 #include <sys/ioctl.h> 63 #include <sys/stat.h> 64 #include <sys/disk.h> 65 #include <sys/queue.h> 66 67 #include <machine/specialreg.h> 68 #include <machine/vmm.h> 69 70 #include <dirent.h> 71 #include <dlfcn.h> 72 #include <errno.h> 73 #include <err.h> 74 #include <fcntl.h> 75 #include <getopt.h> 76 #include <libgen.h> 77 #include <limits.h> 78 #include <setjmp.h> 79 #include <stdio.h> 80 #include <stdlib.h> 81 #include <string.h> 82 #include <sysexits.h> 83 #include <termios.h> 84 #include <unistd.h> 85 86 #include <vmmapi.h> 87 88 #include "userboot.h" 89 90 #define MB (1024 * 1024UL) 91 #define GB (1024 * 1024 * 1024UL) 92 #define BSP 0 93 94 #define NDISKS 32 95 96 static char *host_base; 97 static struct termios term, oldterm; 98 static int disk_fd[NDISKS]; 99 static int ndisks; 100 static int consin_fd, consout_fd; 101 102 static int need_reinit; 103 104 static void *loader_hdl; 105 static char *loader; 106 static int explicit_loader; 107 static jmp_buf jb; 108 109 static char *vmname, *progname; 110 static struct vmctx *ctx; 111 112 static uint64_t gdtbase, cr3, rsp; 113 114 static void cb_exit(void *arg, int v); 115 116 /* 117 * Console i/o callbacks 118 */ 119 120 static void 121 cb_putc(void *arg, int ch) 122 { 123 char c = ch; 124 125 (void) write(consout_fd, &c, 1); 126 } 127 128 static int 129 cb_getc(void *arg) 130 { 131 char c; 132 133 if (read(consin_fd, &c, 1) == 1) 134 return (c); 135 return (-1); 136 } 137 138 static int 139 cb_poll(void *arg) 140 { 141 int n; 142 143 if (ioctl(consin_fd, FIONREAD, &n) >= 0) 144 return (n > 0); 145 return (0); 146 } 147 148 /* 149 * Host filesystem i/o callbacks 150 */ 151 152 struct cb_file { 153 int cf_isdir; 154 size_t cf_size; 155 struct stat cf_stat; 156 union { 157 int fd; 158 DIR *dir; 159 } cf_u; 160 }; 161 162 static int 163 cb_open(void *arg, const char *filename, void **hp) 164 { 165 struct cb_file *cf; 166 char path[PATH_MAX]; 167 168 if (!host_base) 169 return (ENOENT); 170 171 strlcpy(path, host_base, PATH_MAX); 172 if (path[strlen(path) - 1] == '/') 173 path[strlen(path) - 1] = 0; 174 strlcat(path, filename, PATH_MAX); 175 cf = malloc(sizeof(struct cb_file)); 176 if (stat(path, &cf->cf_stat) < 0) { 177 free(cf); 178 return (errno); 179 } 180 181 cf->cf_size = cf->cf_stat.st_size; 182 if (S_ISDIR(cf->cf_stat.st_mode)) { 183 cf->cf_isdir = 1; 184 cf->cf_u.dir = opendir(path); 185 if (!cf->cf_u.dir) 186 goto out; 187 *hp = cf; 188 return (0); 189 } 190 if (S_ISREG(cf->cf_stat.st_mode)) { 191 cf->cf_isdir = 0; 192 cf->cf_u.fd = open(path, O_RDONLY); 193 if (cf->cf_u.fd < 0) 194 goto out; 195 *hp = cf; 196 return (0); 197 } 198 199 out: 200 free(cf); 201 return (EINVAL); 202 } 203 204 static int 205 cb_close(void *arg, void *h) 206 { 207 struct cb_file *cf = h; 208 209 if (cf->cf_isdir) 210 closedir(cf->cf_u.dir); 211 else 212 close(cf->cf_u.fd); 213 free(cf); 214 215 return (0); 216 } 217 218 static int 219 cb_isdir(void *arg, void *h) 220 { 221 struct cb_file *cf = h; 222 223 return (cf->cf_isdir); 224 } 225 226 static int 227 cb_read(void *arg, void *h, void *buf, size_t size, size_t *resid) 228 { 229 struct cb_file *cf = h; 230 ssize_t sz; 231 232 if (cf->cf_isdir) 233 return (EINVAL); 234 sz = read(cf->cf_u.fd, buf, size); 235 if (sz < 0) 236 return (EINVAL); 237 *resid = size - sz; 238 return (0); 239 } 240 241 static int 242 cb_readdir(void *arg, void *h, uint32_t *fileno_return, uint8_t *type_return, 243 size_t *namelen_return, char *name) 244 { 245 struct cb_file *cf = h; 246 struct dirent *dp; 247 248 if (!cf->cf_isdir) 249 return (EINVAL); 250 251 dp = readdir(cf->cf_u.dir); 252 if (!dp) 253 return (ENOENT); 254 255 /* 256 * Note: d_namlen is in the range 0..255 and therefore less 257 * than PATH_MAX so we don't need to test before copying. 258 */ 259 *fileno_return = dp->d_fileno; 260 *type_return = dp->d_type; 261 *namelen_return = dp->d_namlen; 262 memcpy(name, dp->d_name, dp->d_namlen); 263 name[dp->d_namlen] = 0; 264 265 return (0); 266 } 267 268 static int 269 cb_seek(void *arg, void *h, uint64_t offset, int whence) 270 { 271 struct cb_file *cf = h; 272 273 if (cf->cf_isdir) 274 return (EINVAL); 275 if (lseek(cf->cf_u.fd, offset, whence) < 0) 276 return (errno); 277 return (0); 278 } 279 280 static int 281 cb_stat(void *arg, void *h, struct stat *sbp) 282 { 283 struct cb_file *cf = h; 284 285 memset(sbp, 0, sizeof(struct stat)); 286 sbp->st_mode = cf->cf_stat.st_mode; 287 sbp->st_uid = cf->cf_stat.st_uid; 288 sbp->st_gid = cf->cf_stat.st_gid; 289 sbp->st_size = cf->cf_stat.st_size; 290 sbp->st_mtime = cf->cf_stat.st_mtime; 291 sbp->st_dev = cf->cf_stat.st_dev; 292 sbp->st_ino = cf->cf_stat.st_ino; 293 294 return (0); 295 } 296 297 /* 298 * Disk image i/o callbacks 299 */ 300 301 static int 302 cb_diskread(void *arg, int unit, uint64_t from, void *to, size_t size, 303 size_t *resid) 304 { 305 ssize_t n; 306 307 if (unit < 0 || unit >= ndisks) 308 return (EIO); 309 n = pread(disk_fd[unit], to, size, from); 310 if (n < 0) 311 return (errno); 312 *resid = size - n; 313 return (0); 314 } 315 316 static int 317 cb_diskwrite(void *arg, int unit, uint64_t offset, void *src, size_t size, 318 size_t *resid) 319 { 320 ssize_t n; 321 322 if (unit < 0 || unit >= ndisks) 323 return (EIO); 324 n = pwrite(disk_fd[unit], src, size, offset); 325 if (n < 0) 326 return (errno); 327 *resid = size - n; 328 return (0); 329 } 330 331 static int 332 cb_diskioctl(void *arg, int unit, u_long cmd, void *data) 333 { 334 struct stat sb; 335 336 if (unit < 0 || unit >= ndisks) 337 return (EBADF); 338 339 switch (cmd) { 340 case DIOCGSECTORSIZE: 341 *(u_int *)data = 512; 342 break; 343 case DIOCGMEDIASIZE: 344 if (fstat(disk_fd[unit], &sb) != 0) 345 return (ENOTTY); 346 if (S_ISCHR(sb.st_mode) && 347 ioctl(disk_fd[unit], DIOCGMEDIASIZE, &sb.st_size) != 0) 348 return (ENOTTY); 349 *(off_t *)data = sb.st_size; 350 break; 351 default: 352 return (ENOTTY); 353 } 354 355 return (0); 356 } 357 358 /* 359 * Guest virtual machine i/o callbacks 360 */ 361 static int 362 cb_copyin(void *arg, const void *from, uint64_t to, size_t size) 363 { 364 char *ptr; 365 366 to &= 0x7fffffff; 367 368 ptr = vm_map_gpa(ctx, to, size); 369 if (ptr == NULL) 370 return (EFAULT); 371 372 memcpy(ptr, from, size); 373 return (0); 374 } 375 376 static int 377 cb_copyout(void *arg, uint64_t from, void *to, size_t size) 378 { 379 char *ptr; 380 381 from &= 0x7fffffff; 382 383 ptr = vm_map_gpa(ctx, from, size); 384 if (ptr == NULL) 385 return (EFAULT); 386 387 memcpy(to, ptr, size); 388 return (0); 389 } 390 391 static void 392 cb_setreg(void *arg, int r, uint64_t v) 393 { 394 int error; 395 enum vm_reg_name vmreg; 396 397 vmreg = VM_REG_LAST; 398 399 switch (r) { 400 case 4: 401 vmreg = VM_REG_GUEST_RSP; 402 rsp = v; 403 break; 404 default: 405 break; 406 } 407 408 if (vmreg == VM_REG_LAST) { 409 printf("test_setreg(%d): not implemented\n", r); 410 cb_exit(NULL, USERBOOT_EXIT_QUIT); 411 } 412 413 error = vm_set_register(ctx, BSP, vmreg, v); 414 if (error) { 415 perror("vm_set_register"); 416 cb_exit(NULL, USERBOOT_EXIT_QUIT); 417 } 418 } 419 420 static void 421 cb_setmsr(void *arg, int r, uint64_t v) 422 { 423 int error; 424 enum vm_reg_name vmreg; 425 426 vmreg = VM_REG_LAST; 427 428 switch (r) { 429 case MSR_EFER: 430 vmreg = VM_REG_GUEST_EFER; 431 break; 432 default: 433 break; 434 } 435 436 if (vmreg == VM_REG_LAST) { 437 printf("test_setmsr(%d): not implemented\n", r); 438 cb_exit(NULL, USERBOOT_EXIT_QUIT); 439 } 440 441 error = vm_set_register(ctx, BSP, vmreg, v); 442 if (error) { 443 perror("vm_set_msr"); 444 cb_exit(NULL, USERBOOT_EXIT_QUIT); 445 } 446 } 447 448 static void 449 cb_setcr(void *arg, int r, uint64_t v) 450 { 451 int error; 452 enum vm_reg_name vmreg; 453 454 vmreg = VM_REG_LAST; 455 456 switch (r) { 457 case 0: 458 vmreg = VM_REG_GUEST_CR0; 459 break; 460 case 3: 461 vmreg = VM_REG_GUEST_CR3; 462 cr3 = v; 463 break; 464 case 4: 465 vmreg = VM_REG_GUEST_CR4; 466 break; 467 default: 468 break; 469 } 470 471 if (vmreg == VM_REG_LAST) { 472 printf("test_setcr(%d): not implemented\n", r); 473 cb_exit(NULL, USERBOOT_EXIT_QUIT); 474 } 475 476 error = vm_set_register(ctx, BSP, vmreg, v); 477 if (error) { 478 perror("vm_set_cr"); 479 cb_exit(NULL, USERBOOT_EXIT_QUIT); 480 } 481 } 482 483 static void 484 cb_setgdt(void *arg, uint64_t base, size_t size) 485 { 486 int error; 487 488 error = vm_set_desc(ctx, BSP, VM_REG_GUEST_GDTR, base, size - 1, 0); 489 if (error != 0) { 490 perror("vm_set_desc(gdt)"); 491 cb_exit(NULL, USERBOOT_EXIT_QUIT); 492 } 493 494 gdtbase = base; 495 } 496 497 static void 498 cb_exec(void *arg, uint64_t rip) 499 { 500 int error; 501 502 if (cr3 == 0) 503 error = vm_setup_freebsd_registers_i386(ctx, BSP, rip, gdtbase, 504 rsp); 505 else 506 error = vm_setup_freebsd_registers(ctx, BSP, rip, cr3, gdtbase, 507 rsp); 508 if (error) { 509 perror("vm_setup_freebsd_registers"); 510 cb_exit(NULL, USERBOOT_EXIT_QUIT); 511 } 512 513 cb_exit(NULL, 0); 514 } 515 516 /* 517 * Misc 518 */ 519 520 static void 521 cb_delay(void *arg, int usec) 522 { 523 524 usleep(usec); 525 } 526 527 static void 528 cb_exit(void *arg, int v) 529 { 530 531 tcsetattr(consout_fd, TCSAFLUSH, &oldterm); 532 exit(v); 533 } 534 535 static void 536 cb_getmem(void *arg, uint64_t *ret_lowmem, uint64_t *ret_highmem) 537 { 538 539 *ret_lowmem = vm_get_lowmem_size(ctx); 540 *ret_highmem = vm_get_highmem_size(ctx); 541 } 542 543 struct env { 544 char *str; /* name=value */ 545 SLIST_ENTRY(env) next; 546 }; 547 548 static SLIST_HEAD(envhead, env) envhead; 549 550 static void 551 addenv(char *str) 552 { 553 struct env *env; 554 555 env = malloc(sizeof(struct env)); 556 env->str = str; 557 SLIST_INSERT_HEAD(&envhead, env, next); 558 } 559 560 static char * 561 cb_getenv(void *arg, int num) 562 { 563 int i; 564 struct env *env; 565 566 i = 0; 567 SLIST_FOREACH(env, &envhead, next) { 568 if (i == num) 569 return (env->str); 570 i++; 571 } 572 573 return (NULL); 574 } 575 576 static int 577 cb_vm_set_register(void *arg, int vcpu, int reg, uint64_t val) 578 { 579 580 return (vm_set_register(ctx, vcpu, reg, val)); 581 } 582 583 static int 584 cb_vm_set_desc(void *arg, int vcpu, int reg, uint64_t base, u_int limit, 585 u_int access) 586 { 587 588 return (vm_set_desc(ctx, vcpu, reg, base, limit, access)); 589 } 590 591 static void 592 cb_swap_interpreter(void *arg, const char *interp_req) 593 { 594 595 /* 596 * If the user specified a loader but we detected a mismatch, we should 597 * not try to pivot to a different loader on them. 598 */ 599 free(loader); 600 if (explicit_loader == 1) { 601 perror("requested loader interpreter does not match guest userboot"); 602 cb_exit(NULL, 1); 603 } 604 if (interp_req == NULL || *interp_req == '\0') { 605 perror("guest failed to request an interpreter"); 606 cb_exit(NULL, 1); 607 } 608 609 if (asprintf(&loader, "/boot/userboot_%s.so", interp_req) == -1) 610 err(EX_OSERR, "malloc"); 611 need_reinit = 1; 612 longjmp(jb, 1); 613 } 614 615 static struct loader_callbacks cb = { 616 .getc = cb_getc, 617 .putc = cb_putc, 618 .poll = cb_poll, 619 620 .open = cb_open, 621 .close = cb_close, 622 .isdir = cb_isdir, 623 .read = cb_read, 624 .readdir = cb_readdir, 625 .seek = cb_seek, 626 .stat = cb_stat, 627 628 .diskread = cb_diskread, 629 .diskwrite = cb_diskwrite, 630 .diskioctl = cb_diskioctl, 631 632 .copyin = cb_copyin, 633 .copyout = cb_copyout, 634 .setreg = cb_setreg, 635 .setmsr = cb_setmsr, 636 .setcr = cb_setcr, 637 .setgdt = cb_setgdt, 638 .exec = cb_exec, 639 640 .delay = cb_delay, 641 .exit = cb_exit, 642 .getmem = cb_getmem, 643 644 .getenv = cb_getenv, 645 646 /* Version 4 additions */ 647 .vm_set_register = cb_vm_set_register, 648 .vm_set_desc = cb_vm_set_desc, 649 650 /* Version 5 additions */ 651 .swap_interpreter = cb_swap_interpreter, 652 }; 653 654 static int 655 altcons_open(char *path) 656 { 657 struct stat sb; 658 int err; 659 int fd; 660 661 /* 662 * Allow stdio to be passed in so that the same string 663 * can be used for the bhyveload console and bhyve com-port 664 * parameters 665 */ 666 if (!strcmp(path, "stdio")) 667 return (0); 668 669 err = stat(path, &sb); 670 if (err == 0) { 671 if (!S_ISCHR(sb.st_mode)) 672 err = ENOTSUP; 673 else { 674 fd = open(path, O_RDWR | O_NONBLOCK); 675 if (fd < 0) 676 err = errno; 677 else 678 consin_fd = consout_fd = fd; 679 } 680 } 681 682 return (err); 683 } 684 685 static int 686 disk_open(char *path) 687 { 688 int fd; 689 690 if (ndisks >= NDISKS) 691 return (ERANGE); 692 693 fd = open(path, O_RDONLY); 694 if (fd < 0) 695 return (errno); 696 697 disk_fd[ndisks] = fd; 698 ndisks++; 699 700 return (0); 701 } 702 703 static void 704 usage(void) 705 { 706 707 fprintf(stderr, 708 "usage: %s [-S][-c <console-device>] [-d <disk-path>] [-e <name=value>]\n" 709 " %*s [-h <host-path>] [-m memsize[K|k|M|m|G|g|T|t]] <vmname>\n", 710 progname, 711 (int)strlen(progname), ""); 712 exit(1); 713 } 714 715 int 716 main(int argc, char** argv) 717 { 718 void (*func)(struct loader_callbacks *, void *, int, int); 719 uint64_t mem_size; 720 int opt, error, memflags; 721 722 progname = basename(argv[0]); 723 724 memflags = 0; 725 mem_size = 256 * MB; 726 727 consin_fd = STDIN_FILENO; 728 consout_fd = STDOUT_FILENO; 729 730 while ((opt = getopt(argc, argv, "CSc:d:e:h:l:m:")) != -1) { 731 switch (opt) { 732 case 'c': 733 error = altcons_open(optarg); 734 if (error != 0) 735 errx(EX_USAGE, "Could not open '%s'", optarg); 736 break; 737 738 case 'd': 739 error = disk_open(optarg); 740 if (error != 0) 741 errx(EX_USAGE, "Could not open '%s'", optarg); 742 break; 743 744 case 'e': 745 addenv(optarg); 746 break; 747 748 case 'h': 749 host_base = optarg; 750 break; 751 752 case 'l': 753 if (loader != NULL) 754 errx(EX_USAGE, "-l can only be given once"); 755 loader = strdup(optarg); 756 if (loader == NULL) 757 err(EX_OSERR, "malloc"); 758 explicit_loader = 1; 759 break; 760 761 case 'm': 762 error = vm_parse_memsize(optarg, &mem_size); 763 if (error != 0) 764 errx(EX_USAGE, "Invalid memsize '%s'", optarg); 765 break; 766 case 'C': 767 memflags |= VM_MEM_F_INCORE; 768 break; 769 case 'S': 770 memflags |= VM_MEM_F_WIRED; 771 break; 772 case '?': 773 usage(); 774 } 775 } 776 777 argc -= optind; 778 argv += optind; 779 780 if (argc != 1) 781 usage(); 782 783 vmname = argv[0]; 784 785 need_reinit = 0; 786 error = vm_create(vmname); 787 if (error) { 788 if (errno != EEXIST) { 789 perror("vm_create"); 790 exit(1); 791 } 792 need_reinit = 1; 793 } 794 795 ctx = vm_open(vmname); 796 if (ctx == NULL) { 797 perror("vm_open"); 798 exit(1); 799 } 800 801 /* 802 * setjmp in the case the guest wants to swap out interpreter, 803 * cb_swap_interpreter will swap out loader as appropriate and set 804 * need_reinit so that we end up in a clean state once again. 805 */ 806 setjmp(jb); 807 808 if (need_reinit) { 809 error = vm_reinit(ctx); 810 if (error) { 811 perror("vm_reinit"); 812 exit(1); 813 } 814 } 815 816 vm_set_memflags(ctx, memflags); 817 error = vm_setup_memory(ctx, mem_size, VM_MMAP_ALL); 818 if (error) { 819 perror("vm_setup_memory"); 820 exit(1); 821 } 822 823 if (loader == NULL) { 824 loader = strdup("/boot/userboot.so"); 825 if (loader == NULL) 826 err(EX_OSERR, "malloc"); 827 } 828 if (loader_hdl != NULL) 829 dlclose(loader_hdl); 830 loader_hdl = dlopen(loader, RTLD_LOCAL); 831 if (!loader_hdl) { 832 printf("%s\n", dlerror()); 833 free(loader); 834 return (1); 835 } 836 func = dlsym(loader_hdl, "loader_main"); 837 if (!func) { 838 printf("%s\n", dlerror()); 839 free(loader); 840 return (1); 841 } 842 843 tcgetattr(consout_fd, &term); 844 oldterm = term; 845 cfmakeraw(&term); 846 term.c_cflag |= CLOCAL; 847 848 tcsetattr(consout_fd, TCSAFLUSH, &term); 849 850 addenv("smbios.bios.vendor=BHYVE"); 851 addenv("boot_serial=1"); 852 853 func(&cb, NULL, USERBOOT_VERSION_5, ndisks); 854 855 free(loader); 856 return (0); 857 } 858