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