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 __unused, 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 __unused) 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 __unused) 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 __unused, 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 __unused, 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 __unused, 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 __unused, 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 __unused, void *h, uint32_t *fileno_return, 243 uint8_t *type_return, 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 __unused, 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 __unused, 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 __unused, 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 __unused, int unit, uint64_t offset, void *src, 318 size_t size, 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 __unused, 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 __unused, 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 __unused, 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 __unused, 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 __unused, 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 __unused, 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 __unused, 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 __unused, 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 __unused, int usec) 522 { 523 524 usleep(usec); 525 } 526 527 static void 528 cb_exit(void *arg __unused, int v) 529 { 530 531 tcsetattr(consout_fd, TCSAFLUSH, &oldterm); 532 exit(v); 533 } 534 535 static void 536 cb_getmem(void *arg __unused, 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(const char *str) 552 { 553 struct env *env; 554 555 env = malloc(sizeof(struct env)); 556 if (env == NULL) 557 err(EX_OSERR, "malloc"); 558 env->str = strdup(str); 559 if (env->str == NULL) 560 err(EX_OSERR, "strdup"); 561 SLIST_INSERT_HEAD(&envhead, env, next); 562 } 563 564 static char * 565 cb_getenv(void *arg __unused, int num) 566 { 567 int i; 568 struct env *env; 569 570 i = 0; 571 SLIST_FOREACH(env, &envhead, next) { 572 if (i == num) 573 return (env->str); 574 i++; 575 } 576 577 return (NULL); 578 } 579 580 static int 581 cb_vm_set_register(void *arg __unused, int vcpu, int reg, uint64_t val) 582 { 583 584 return (vm_set_register(ctx, vcpu, reg, val)); 585 } 586 587 static int 588 cb_vm_set_desc(void *arg __unused, int vcpu, int reg, uint64_t base, 589 u_int limit, u_int access) 590 { 591 592 return (vm_set_desc(ctx, vcpu, reg, base, limit, access)); 593 } 594 595 static void 596 cb_swap_interpreter(void *arg __unused, const char *interp_req) 597 { 598 599 /* 600 * If the user specified a loader but we detected a mismatch, we should 601 * not try to pivot to a different loader on them. 602 */ 603 free(loader); 604 if (explicit_loader == 1) { 605 perror("requested loader interpreter does not match guest userboot"); 606 cb_exit(NULL, 1); 607 } 608 if (interp_req == NULL || *interp_req == '\0') { 609 perror("guest failed to request an interpreter"); 610 cb_exit(NULL, 1); 611 } 612 613 if (asprintf(&loader, "/boot/userboot_%s.so", interp_req) == -1) 614 err(EX_OSERR, "malloc"); 615 need_reinit = 1; 616 longjmp(jb, 1); 617 } 618 619 static struct loader_callbacks cb = { 620 .getc = cb_getc, 621 .putc = cb_putc, 622 .poll = cb_poll, 623 624 .open = cb_open, 625 .close = cb_close, 626 .isdir = cb_isdir, 627 .read = cb_read, 628 .readdir = cb_readdir, 629 .seek = cb_seek, 630 .stat = cb_stat, 631 632 .diskread = cb_diskread, 633 .diskwrite = cb_diskwrite, 634 .diskioctl = cb_diskioctl, 635 636 .copyin = cb_copyin, 637 .copyout = cb_copyout, 638 .setreg = cb_setreg, 639 .setmsr = cb_setmsr, 640 .setcr = cb_setcr, 641 .setgdt = cb_setgdt, 642 .exec = cb_exec, 643 644 .delay = cb_delay, 645 .exit = cb_exit, 646 .getmem = cb_getmem, 647 648 .getenv = cb_getenv, 649 650 /* Version 4 additions */ 651 .vm_set_register = cb_vm_set_register, 652 .vm_set_desc = cb_vm_set_desc, 653 654 /* Version 5 additions */ 655 .swap_interpreter = cb_swap_interpreter, 656 }; 657 658 static int 659 altcons_open(char *path) 660 { 661 struct stat sb; 662 int err; 663 int fd; 664 665 /* 666 * Allow stdio to be passed in so that the same string 667 * can be used for the bhyveload console and bhyve com-port 668 * parameters 669 */ 670 if (!strcmp(path, "stdio")) 671 return (0); 672 673 err = stat(path, &sb); 674 if (err == 0) { 675 if (!S_ISCHR(sb.st_mode)) 676 err = ENOTSUP; 677 else { 678 fd = open(path, O_RDWR | O_NONBLOCK); 679 if (fd < 0) 680 err = errno; 681 else 682 consin_fd = consout_fd = fd; 683 } 684 } 685 686 return (err); 687 } 688 689 static int 690 disk_open(char *path) 691 { 692 int fd; 693 694 if (ndisks >= NDISKS) 695 return (ERANGE); 696 697 fd = open(path, O_RDWR); 698 if (fd < 0) 699 return (errno); 700 701 disk_fd[ndisks] = fd; 702 ndisks++; 703 704 return (0); 705 } 706 707 static void 708 usage(void) 709 { 710 711 fprintf(stderr, 712 "usage: %s [-S][-c <console-device>] [-d <disk-path>] [-e <name=value>]\n" 713 " %*s [-h <host-path>] [-m memsize[K|k|M|m|G|g|T|t]] <vmname>\n", 714 progname, 715 (int)strlen(progname), ""); 716 exit(1); 717 } 718 719 int 720 main(int argc, char** argv) 721 { 722 void (*func)(struct loader_callbacks *, void *, int, int); 723 uint64_t mem_size; 724 int opt, error, memflags; 725 726 progname = basename(argv[0]); 727 728 memflags = 0; 729 mem_size = 256 * MB; 730 731 consin_fd = STDIN_FILENO; 732 consout_fd = STDOUT_FILENO; 733 734 while ((opt = getopt(argc, argv, "CSc:d:e:h:l:m:")) != -1) { 735 switch (opt) { 736 case 'c': 737 error = altcons_open(optarg); 738 if (error != 0) 739 errx(EX_USAGE, "Could not open '%s'", optarg); 740 break; 741 742 case 'd': 743 error = disk_open(optarg); 744 if (error != 0) 745 errx(EX_USAGE, "Could not open '%s'", optarg); 746 break; 747 748 case 'e': 749 addenv(optarg); 750 break; 751 752 case 'h': 753 host_base = optarg; 754 break; 755 756 case 'l': 757 if (loader != NULL) 758 errx(EX_USAGE, "-l can only be given once"); 759 loader = strdup(optarg); 760 if (loader == NULL) 761 err(EX_OSERR, "malloc"); 762 explicit_loader = 1; 763 break; 764 765 case 'm': 766 error = vm_parse_memsize(optarg, &mem_size); 767 if (error != 0) 768 errx(EX_USAGE, "Invalid memsize '%s'", optarg); 769 break; 770 case 'C': 771 memflags |= VM_MEM_F_INCORE; 772 break; 773 case 'S': 774 memflags |= VM_MEM_F_WIRED; 775 break; 776 case '?': 777 usage(); 778 } 779 } 780 781 argc -= optind; 782 argv += optind; 783 784 if (argc != 1) 785 usage(); 786 787 vmname = argv[0]; 788 789 need_reinit = 0; 790 error = vm_create(vmname); 791 if (error) { 792 if (errno != EEXIST) { 793 perror("vm_create"); 794 exit(1); 795 } 796 need_reinit = 1; 797 } 798 799 ctx = vm_open(vmname); 800 if (ctx == NULL) { 801 perror("vm_open"); 802 exit(1); 803 } 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