]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/bhyveload/bhyveload.c
ident(1): Normalizing date format
[FreeBSD/FreeBSD.git] / usr.sbin / bhyveload / bhyveload.c
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 }