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 __FBSDID("$FreeBSD$"); 57 58 #include <sys/ioctl.h> 59 #include <sys/stat.h> 60 #include <sys/disk.h> 61 #include <sys/queue.h> 62 63 #include <machine/specialreg.h> 64 #include <machine/vmm.h> 65 66 #include <assert.h> 67 #include <dirent.h> 68 #include <dlfcn.h> 69 #include <errno.h> 70 #include <err.h> 71 #include <fcntl.h> 72 #include <getopt.h> 73 #include <libgen.h> 74 #include <limits.h> 75 #include <setjmp.h> 76 #include <stdio.h> 77 #include <stdlib.h> 78 #include <string.h> 79 #include <sysexits.h> 80 #include <termios.h> 81 #include <unistd.h> 82 83 #include <vmmapi.h> 84 85 #include "userboot.h" 86 87 #define MB (1024 * 1024UL) 88 #define GB (1024 * 1024 * 1024UL) 89 #define BSP 0 90 91 #define NDISKS 32 92 93 static char *host_base; 94 static struct termios term, oldterm; 95 static int disk_fd[NDISKS]; 96 static int ndisks; 97 static int consin_fd, consout_fd; 98 99 static int need_reinit; 100 101 static void *loader_hdl; 102 static char *loader; 103 static int explicit_loader; 104 static jmp_buf jb; 105 106 static char *vmname, *progname; 107 static struct vmctx *ctx; 108 static struct vcpu *vcpu; 109 110 static uint64_t gdtbase, cr3, rsp; 111 112 static void cb_exit(void *arg, int v); 113 114 /* 115 * Console i/o callbacks 116 */ 117 118 static void 119 cb_putc(void *arg __unused, int ch) 120 { 121 char c = ch; 122 123 (void) write(consout_fd, &c, 1); 124 } 125 126 static int 127 cb_getc(void *arg __unused) 128 { 129 char c; 130 131 if (read(consin_fd, &c, 1) == 1) 132 return (c); 133 return (-1); 134 } 135 136 static int 137 cb_poll(void *arg __unused) 138 { 139 int n; 140 141 if (ioctl(consin_fd, FIONREAD, &n) >= 0) 142 return (n > 0); 143 return (0); 144 } 145 146 /* 147 * Host filesystem i/o callbacks 148 */ 149 150 struct cb_file { 151 int cf_isdir; 152 size_t cf_size; 153 struct stat cf_stat; 154 union { 155 int fd; 156 DIR *dir; 157 } cf_u; 158 }; 159 160 static int 161 cb_open(void *arg __unused, const char *filename, void **hp) 162 { 163 struct cb_file *cf; 164 char path[PATH_MAX]; 165 166 if (!host_base) 167 return (ENOENT); 168 169 strlcpy(path, host_base, PATH_MAX); 170 if (path[strlen(path) - 1] == '/') 171 path[strlen(path) - 1] = 0; 172 strlcat(path, filename, PATH_MAX); 173 cf = malloc(sizeof(struct cb_file)); 174 if (stat(path, &cf->cf_stat) < 0) { 175 free(cf); 176 return (errno); 177 } 178 179 cf->cf_size = cf->cf_stat.st_size; 180 if (S_ISDIR(cf->cf_stat.st_mode)) { 181 cf->cf_isdir = 1; 182 cf->cf_u.dir = opendir(path); 183 if (!cf->cf_u.dir) 184 goto out; 185 *hp = cf; 186 return (0); 187 } 188 if (S_ISREG(cf->cf_stat.st_mode)) { 189 cf->cf_isdir = 0; 190 cf->cf_u.fd = open(path, O_RDONLY); 191 if (cf->cf_u.fd < 0) 192 goto out; 193 *hp = cf; 194 return (0); 195 } 196 197 out: 198 free(cf); 199 return (EINVAL); 200 } 201 202 static int 203 cb_close(void *arg __unused, void *h) 204 { 205 struct cb_file *cf = h; 206 207 if (cf->cf_isdir) 208 closedir(cf->cf_u.dir); 209 else 210 close(cf->cf_u.fd); 211 free(cf); 212 213 return (0); 214 } 215 216 static int 217 cb_isdir(void *arg __unused, void *h) 218 { 219 struct cb_file *cf = h; 220 221 return (cf->cf_isdir); 222 } 223 224 static int 225 cb_read(void *arg __unused, void *h, void *buf, size_t size, size_t *resid) 226 { 227 struct cb_file *cf = h; 228 ssize_t sz; 229 230 if (cf->cf_isdir) 231 return (EINVAL); 232 sz = read(cf->cf_u.fd, buf, size); 233 if (sz < 0) 234 return (EINVAL); 235 *resid = size - sz; 236 return (0); 237 } 238 239 static int 240 cb_readdir(void *arg __unused, void *h, uint32_t *fileno_return, 241 uint8_t *type_return, size_t *namelen_return, char *name) 242 { 243 struct cb_file *cf = h; 244 struct dirent *dp; 245 246 if (!cf->cf_isdir) 247 return (EINVAL); 248 249 dp = readdir(cf->cf_u.dir); 250 if (!dp) 251 return (ENOENT); 252 253 /* 254 * Note: d_namlen is in the range 0..255 and therefore less 255 * than PATH_MAX so we don't need to test before copying. 256 */ 257 *fileno_return = dp->d_fileno; 258 *type_return = dp->d_type; 259 *namelen_return = dp->d_namlen; 260 memcpy(name, dp->d_name, dp->d_namlen); 261 name[dp->d_namlen] = 0; 262 263 return (0); 264 } 265 266 static int 267 cb_seek(void *arg __unused, void *h, uint64_t offset, int whence) 268 { 269 struct cb_file *cf = h; 270 271 if (cf->cf_isdir) 272 return (EINVAL); 273 if (lseek(cf->cf_u.fd, offset, whence) < 0) 274 return (errno); 275 return (0); 276 } 277 278 static int 279 cb_stat(void *arg __unused, void *h, struct stat *sbp) 280 { 281 struct cb_file *cf = h; 282 283 memset(sbp, 0, sizeof(struct stat)); 284 sbp->st_mode = cf->cf_stat.st_mode; 285 sbp->st_uid = cf->cf_stat.st_uid; 286 sbp->st_gid = cf->cf_stat.st_gid; 287 sbp->st_size = cf->cf_stat.st_size; 288 sbp->st_mtime = cf->cf_stat.st_mtime; 289 sbp->st_dev = cf->cf_stat.st_dev; 290 sbp->st_ino = cf->cf_stat.st_ino; 291 292 return (0); 293 } 294 295 /* 296 * Disk image i/o callbacks 297 */ 298 299 static int 300 cb_diskread(void *arg __unused, int unit, uint64_t from, void *to, size_t size, 301 size_t *resid) 302 { 303 ssize_t n; 304 305 if (unit < 0 || unit >= ndisks) 306 return (EIO); 307 n = pread(disk_fd[unit], to, size, from); 308 if (n < 0) 309 return (errno); 310 *resid = size - n; 311 return (0); 312 } 313 314 static int 315 cb_diskwrite(void *arg __unused, int unit, uint64_t offset, void *src, 316 size_t size, size_t *resid) 317 { 318 ssize_t n; 319 320 if (unit < 0 || unit >= ndisks) 321 return (EIO); 322 n = pwrite(disk_fd[unit], src, size, offset); 323 if (n < 0) 324 return (errno); 325 *resid = size - n; 326 return (0); 327 } 328 329 static int 330 cb_diskioctl(void *arg __unused, int unit, u_long cmd, void *data) 331 { 332 struct stat sb; 333 334 if (unit < 0 || unit >= ndisks) 335 return (EBADF); 336 337 switch (cmd) { 338 case DIOCGSECTORSIZE: 339 *(u_int *)data = 512; 340 break; 341 case DIOCGMEDIASIZE: 342 if (fstat(disk_fd[unit], &sb) != 0) 343 return (ENOTTY); 344 if (S_ISCHR(sb.st_mode) && 345 ioctl(disk_fd[unit], DIOCGMEDIASIZE, &sb.st_size) != 0) 346 return (ENOTTY); 347 *(off_t *)data = sb.st_size; 348 break; 349 default: 350 return (ENOTTY); 351 } 352 353 return (0); 354 } 355 356 /* 357 * Guest virtual machine i/o callbacks 358 */ 359 static int 360 cb_copyin(void *arg __unused, const void *from, uint64_t to, size_t size) 361 { 362 char *ptr; 363 364 to &= 0x7fffffff; 365 366 ptr = vm_map_gpa(ctx, to, size); 367 if (ptr == NULL) 368 return (EFAULT); 369 370 memcpy(ptr, from, size); 371 return (0); 372 } 373 374 static int 375 cb_copyout(void *arg __unused, uint64_t from, void *to, size_t size) 376 { 377 char *ptr; 378 379 from &= 0x7fffffff; 380 381 ptr = vm_map_gpa(ctx, from, size); 382 if (ptr == NULL) 383 return (EFAULT); 384 385 memcpy(to, ptr, size); 386 return (0); 387 } 388 389 static void 390 cb_setreg(void *arg __unused, int r, uint64_t v) 391 { 392 int error; 393 enum vm_reg_name vmreg; 394 395 vmreg = VM_REG_LAST; 396 397 switch (r) { 398 case 4: 399 vmreg = VM_REG_GUEST_RSP; 400 rsp = v; 401 break; 402 default: 403 break; 404 } 405 406 if (vmreg == VM_REG_LAST) { 407 printf("test_setreg(%d): not implemented\n", r); 408 cb_exit(NULL, USERBOOT_EXIT_QUIT); 409 } 410 411 error = vm_set_register(vcpu, vmreg, v); 412 if (error) { 413 perror("vm_set_register"); 414 cb_exit(NULL, USERBOOT_EXIT_QUIT); 415 } 416 } 417 418 static void 419 cb_setmsr(void *arg __unused, int r, uint64_t v) 420 { 421 int error; 422 enum vm_reg_name vmreg; 423 424 vmreg = VM_REG_LAST; 425 426 switch (r) { 427 case MSR_EFER: 428 vmreg = VM_REG_GUEST_EFER; 429 break; 430 default: 431 break; 432 } 433 434 if (vmreg == VM_REG_LAST) { 435 printf("test_setmsr(%d): not implemented\n", r); 436 cb_exit(NULL, USERBOOT_EXIT_QUIT); 437 } 438 439 error = vm_set_register(vcpu, vmreg, v); 440 if (error) { 441 perror("vm_set_msr"); 442 cb_exit(NULL, USERBOOT_EXIT_QUIT); 443 } 444 } 445 446 static void 447 cb_setcr(void *arg __unused, int r, uint64_t v) 448 { 449 int error; 450 enum vm_reg_name vmreg; 451 452 vmreg = VM_REG_LAST; 453 454 switch (r) { 455 case 0: 456 vmreg = VM_REG_GUEST_CR0; 457 break; 458 case 3: 459 vmreg = VM_REG_GUEST_CR3; 460 cr3 = v; 461 break; 462 case 4: 463 vmreg = VM_REG_GUEST_CR4; 464 break; 465 default: 466 break; 467 } 468 469 if (vmreg == VM_REG_LAST) { 470 printf("test_setcr(%d): not implemented\n", r); 471 cb_exit(NULL, USERBOOT_EXIT_QUIT); 472 } 473 474 error = vm_set_register(vcpu, vmreg, v); 475 if (error) { 476 perror("vm_set_cr"); 477 cb_exit(NULL, USERBOOT_EXIT_QUIT); 478 } 479 } 480 481 static void 482 cb_setgdt(void *arg __unused, uint64_t base, size_t size) 483 { 484 int error; 485 486 error = vm_set_desc(vcpu, VM_REG_GUEST_GDTR, base, size - 1, 0); 487 if (error != 0) { 488 perror("vm_set_desc(gdt)"); 489 cb_exit(NULL, USERBOOT_EXIT_QUIT); 490 } 491 492 gdtbase = base; 493 } 494 495 static void 496 cb_exec(void *arg __unused, uint64_t rip) 497 { 498 int error; 499 500 if (cr3 == 0) 501 error = vm_setup_freebsd_registers_i386(vcpu, rip, gdtbase, 502 rsp); 503 else 504 error = vm_setup_freebsd_registers(vcpu, rip, cr3, gdtbase, 505 rsp); 506 if (error) { 507 perror("vm_setup_freebsd_registers"); 508 cb_exit(NULL, USERBOOT_EXIT_QUIT); 509 } 510 511 cb_exit(NULL, 0); 512 } 513 514 /* 515 * Misc 516 */ 517 518 static void 519 cb_delay(void *arg __unused, int usec) 520 { 521 522 usleep(usec); 523 } 524 525 static void 526 cb_exit(void *arg __unused, int v) 527 { 528 529 tcsetattr(consout_fd, TCSAFLUSH, &oldterm); 530 exit(v); 531 } 532 533 static void 534 cb_getmem(void *arg __unused, uint64_t *ret_lowmem, uint64_t *ret_highmem) 535 { 536 537 *ret_lowmem = vm_get_lowmem_size(ctx); 538 *ret_highmem = vm_get_highmem_size(ctx); 539 } 540 541 struct env { 542 char *str; /* name=value */ 543 SLIST_ENTRY(env) next; 544 }; 545 546 static SLIST_HEAD(envhead, env) envhead; 547 548 static void 549 addenv(const char *str) 550 { 551 struct env *env; 552 553 env = malloc(sizeof(struct env)); 554 if (env == NULL) 555 err(EX_OSERR, "malloc"); 556 env->str = strdup(str); 557 if (env->str == NULL) 558 err(EX_OSERR, "strdup"); 559 SLIST_INSERT_HEAD(&envhead, env, next); 560 } 561 562 static char * 563 cb_getenv(void *arg __unused, int num) 564 { 565 int i; 566 struct env *env; 567 568 i = 0; 569 SLIST_FOREACH(env, &envhead, next) { 570 if (i == num) 571 return (env->str); 572 i++; 573 } 574 575 return (NULL); 576 } 577 578 static int 579 cb_vm_set_register(void *arg __unused, int vcpuid, int reg, uint64_t val) 580 { 581 582 assert(vcpuid == BSP); 583 return (vm_set_register(vcpu, reg, val)); 584 } 585 586 static int 587 cb_vm_set_desc(void *arg __unused, int vcpuid, int reg, uint64_t base, 588 u_int limit, u_int access) 589 { 590 591 assert(vcpuid == BSP); 592 return (vm_set_desc(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 vcpu = vm_vcpu_open(ctx, BSP); 806 807 /* 808 * setjmp in the case the guest wants to swap out interpreter, 809 * cb_swap_interpreter will swap out loader as appropriate and set 810 * need_reinit so that we end up in a clean state once again. 811 */ 812 setjmp(jb); 813 814 if (need_reinit) { 815 error = vm_reinit(ctx); 816 if (error) { 817 perror("vm_reinit"); 818 exit(1); 819 } 820 } 821 822 vm_set_memflags(ctx, memflags); 823 error = vm_setup_memory(ctx, mem_size, VM_MMAP_ALL); 824 if (error) { 825 perror("vm_setup_memory"); 826 exit(1); 827 } 828 829 if (loader == NULL) { 830 loader = strdup("/boot/userboot.so"); 831 if (loader == NULL) 832 err(EX_OSERR, "malloc"); 833 } 834 if (loader_hdl != NULL) 835 dlclose(loader_hdl); 836 loader_hdl = dlopen(loader, RTLD_LOCAL); 837 if (!loader_hdl) { 838 printf("%s\n", dlerror()); 839 free(loader); 840 return (1); 841 } 842 func = dlsym(loader_hdl, "loader_main"); 843 if (!func) { 844 printf("%s\n", dlerror()); 845 free(loader); 846 return (1); 847 } 848 849 tcgetattr(consout_fd, &term); 850 oldterm = term; 851 cfmakeraw(&term); 852 term.c_cflag |= CLOCAL; 853 854 tcsetattr(consout_fd, TCSAFLUSH, &term); 855 856 addenv("smbios.bios.vendor=BHYVE"); 857 addenv("boot_serial=1"); 858 859 func(&cb, NULL, USERBOOT_VERSION_5, ndisks); 860 861 free(loader); 862 return (0); 863 } 864