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