Index: stand/userboot/Makefile =================================================================== --- stand/userboot/Makefile +++ stand/userboot/Makefile @@ -1,8 +1,10 @@ # $FreeBSD$ -.include +.include -SUBDIR= test userboot +SUBDIR.yes= test +SUBDIR.${MK_FORTH}+= userboot_4th +SUBDIR.${MK_LOADER_LUA}+= userboot_lua .include Index: stand/userboot/userboot.h =================================================================== --- stand/userboot/userboot.h +++ stand/userboot/userboot.h @@ -41,6 +41,13 @@ */ #define USERBOOT_VERSION_4 4 +/* + * Version 5 added a callback for indicating that the guest + * should be restarted with a different interpreter. The callback + * structure is still backward compatible. + */ +#define USERBOOT_VERSION_5 5 + /* * Exit codes from the loader */ @@ -210,4 +217,9 @@ int (*vm_set_register)(void *arg, int vcpu, int reg, uint64_t val); int (*vm_set_desc)(void *arg, int vcpu, int reg, uint64_t base, u_int limit, u_int access); + + /* + * Version 5 addition. + */ + void (*swap_interpreter)(void *arg, const char *interp); }; Index: stand/userboot/userboot/Makefile =================================================================== --- stand/userboot/userboot/Makefile +++ stand/userboot/userboot/Makefile @@ -5,14 +5,14 @@ LOADER_CD9660_SUPPORT?= no LOADER_EXT2FS_SUPPORT?= no PIC=yes -LOADER_INTERP=4th .include -SHLIB_NAME= userboot.so +SHLIB_NAME= userboot_${LOADER_INTERP}.so STRIP= LIBDIR= /boot +.PATH: ${.CURDIR}/../userboot SRCS= autoload.c SRCS+= bcache.c SRCS+= biossmap.c @@ -38,7 +38,12 @@ LDFLAGS+= -nostdlib -Wl,-Bsymbolic -NEWVERSWHAT= "User boot" ${MACHINE_CPUARCH} +NEWVERSWHAT= "User boot ${LOADER_INTERP}" ${MACHINE_CPUARCH} +VERSION_FILE= ${.CURDIR}/../userboot/version + +.if ${LOADER_INTERP} == ${LOADER_DEFAULT_INTERP} +LINKS+= ${BINDIR}/${SHLIB_NAME} ${BINDIR}/userboot.so +.endif .if ${MK_ZFS} != "no" CFLAGS+= -DUSERBOOT_ZFS_SUPPORT Index: stand/userboot/userboot/main.c =================================================================== --- stand/userboot/userboot/main.c +++ stand/userboot/userboot/main.c @@ -47,6 +47,9 @@ /* Minimum version required */ #define USERBOOT_VERSION USERBOOT_VERSION_3 +#define USERBOOT_PATH "/boot/userboot.so" +#define USERBOOT_VER_SEARCH "User boot " + #define MALLOCSZ (64*1024*1024) struct loader_callbacks *callbacks; @@ -57,6 +60,7 @@ struct arch_switch archsw; /* MI/MD interface boundary */ static void extract_currdev(void); +static void check_interpreter(void); void delay(int usec) @@ -73,6 +77,121 @@ longjmp(jb, 1); } +static char * +grab_str(char *haystack, const char *needle, size_t haystacksz, size_t *rem) +{ + size_t nlen; + char c, sc; + + c = *needle++; + nlen = strlen(needle); + do { + do { + if (haystacksz-- < 1) + return (NULL); + sc = *haystack++; + } while (sc != c); + if (nlen > haystacksz) + return (NULL); + } while (strncmp(haystack, needle, nlen) != 0); + haystack--; + *rem = ++haystacksz; + return (haystack); +} + +/* + * Grab the interpreter string from a buffer. The input buffer will be + * modified in place... + */ +static char * +grab_interpreter_string(char *haystack, size_t haystacksz) +{ + char *verstr, *trailer; + size_t searchsz, szleft; + + searchsz = strlen(USERBOOT_VER_SEARCH); + szleft = haystacksz; + verstr = NULL; + /* + * Here we have a problem. If we had dlopen, this would be trivial... + * We need to grab the version string from the target, so we search for + * the start of it. In embedding the string we're searching for, we've + * added an additional instance of the string we're searching for, so + * we'll need to loop until we find the right one. Here we call the + * right one "one with a trailing comma". + */ + while (verstr == NULL) { + verstr = grab_str(haystack, USERBOOT_VER_SEARCH, szleft, &szleft); + if (verstr == NULL) + return (NULL); + + verstr += searchsz; + /* Comma immediately follows the interpreter */ + trailer = strchr(verstr, ','); + if (trailer == NULL) { + /* Try again, for a later attempt. */ + haystack = verstr; + szleft -= searchsz; + verstr = NULL; + continue; + } + } + + *trailer = '\0'; + return (verstr); +} + +static void +check_interpreter(void) +{ + struct stat st; + size_t rdsize; + char *buf, *guest_interp, *my_interp, *verstring; + int fd; + + /* + * If we can't stat(2) or open(2) USERBOOT_PATH, then we'll fail by + * simply letting us roll on with whatever interpreter we were compiled + * with. This is likely not going to be an issue in reality. + */ + buf = verstring = NULL; + if (stat(USERBOOT_PATH, &st) != 0) + return; + if ((fd = open(USERBOOT_PATH, O_RDONLY)) < 0) + return; + + rdsize = st.st_size; + buf = malloc(rdsize); + if (buf == NULL) + goto out; + if (read(fd, buf, rdsize) < rdsize) + goto out; + + verstring = strdup(bootprog_info); + if (verstring == NULL) + goto out; + my_interp = grab_interpreter_string(verstring, strlen(verstring)); + guest_interp = grab_interpreter_string(buf, rdsize); + /* + * The guest interpreter may not have a version of userboot that + * specifies the interpreter installed. If that's the case, we'll + * assume it's legacy (4th) and request a swap to that if we're + * a Lua-userboot. + */ + if (guest_interp == NULL && strcmp(my_interp, "lua") == 0) + CALLBACK(swap_interpreter, "4th"); + else if (guest_interp == NULL) + /* We're already right; guest is likely 4th, and we're 4th */ + goto out; + else if (strcmp(my_interp, guest_interp) != 0) + CALLBACK(swap_interpreter, guest_interp); +out: + free(verstring); + free(buf); + close(fd); + return; +} + void loader_main(struct loader_callbacks *cb, void *arg, int version, int ndisks) { @@ -137,6 +256,13 @@ (devsw[i]->dv_init)(); extract_currdev(); + /* + * Checking the interpreter isn't worth the overhead unless we + * actually have the swap_interpreter callback, so we actually version + * check here rather than later on. + */ + if (version >= USERBOOT_VERSION_5) + check_interpreter(); if (setjmp(jb)) return; Index: stand/userboot/userboot/version =================================================================== --- stand/userboot/userboot/version +++ stand/userboot/userboot/version @@ -1,4 +1,5 @@ $FreeBSD$ +1.2: Userboot with lua or forth 1.1: Initial userland boot Index: stand/userboot/userboot_4th/Makefile =================================================================== --- /dev/null +++ stand/userboot/userboot_4th/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD$ + +LOADER_INTERP=4th + +.include "../userboot/Makefile" + Index: stand/userboot/userboot_lua/Makefile =================================================================== --- /dev/null +++ stand/userboot/userboot_lua/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD$ + +LOADER_INTERP=lua + +.include "../userboot/Makefile" + Index: usr.sbin/bhyveload/bhyveload.c =================================================================== --- usr.sbin/bhyveload/bhyveload.c +++ usr.sbin/bhyveload/bhyveload.c @@ -86,6 +86,8 @@ #include "userboot.h" +#define LOADER_MAXNAMELEN 32 + #define MB (1024 * 1024UL) #define GB (1024 * 1024 * 1024UL) #define BSP 0 @@ -97,6 +99,11 @@ static int disk_fd[NDISKS]; static int ndisks; static int consin_fd, consout_fd; +static struct loader_callbacks cb; + +static void *loader_hdl; +void (*loader_func)(struct loader_callbacks *, void *, int, int); +static int explicit_loader; static char *vmname, *progname; static struct vmctx *ctx; @@ -104,6 +111,8 @@ static uint64_t gdtbase, cr3, rsp; static void cb_exit(void *arg, int v); +static int setup_loader(const char *interp); +static int fetch_loader_func(const char *loader); /* * Console i/o callbacks @@ -560,6 +569,34 @@ return (vm_set_desc(ctx, vcpu, reg, base, limit, access)); } +static void +cb_swap_interpreter(void *arg, const char *requested_interp) +{ + + /* + * If the user specified a loader but we detected a mismatch, we should + * not try to pivot to a different loader on them. + */ + if (explicit_loader == 1) { + perror("requested loader interpreter does not match guest userboot"); + cb_exit(NULL, 1); + } + + if (requested_interp == NULL || *requested_interp == '\0') { + perror("guest failed to request an interpreter"); + cb_exit(NULL, 1); + } + + if (setup_loader(requested_interp) != 0) + cb_exit(NULL, 1); + loader_func(&cb, NULL, USERBOOT_VERSION_5, ndisks); + /* + * Never return; that will break things, since we've swapped out the + * caller out from under ourselves. + */ + cb_exit(NULL, 1); +} + static struct loader_callbacks cb = { .getc = cb_getc, .putc = cb_putc, @@ -593,6 +630,9 @@ /* Version 4 additions */ .vm_set_register = cb_vm_set_register, .vm_set_desc = cb_vm_set_desc, + + /* Version 5 additions */ + .swap_interpreter = cb_swap_interpreter, }; static int @@ -658,19 +698,65 @@ exit(1); } +static int +setup_loader(const char *interp) +{ + char *loader; + int ret; + + if (interp == NULL) { + loader = strdup("/boot/userboot.so"); + if (loader == NULL) + err(EX_OSERR, "malloc"); + } else { + loader = malloc(LOADER_MAXNAMELEN); + if (loader == NULL) + err(EX_OSERR, "malloc"); + snprintf(loader, LOADER_MAXNAMELEN, "/boot/userboot_%s.so", + interp); + /* + * We've not pivoted to a loader that has been requested + * explicitly, though by the guest this time. + */ + explicit_loader = 1; + } + + ret = fetch_loader_func(loader); + free(loader); + return (ret); +} + +static int +fetch_loader_func(const char *loader) +{ + + if (loader_hdl != NULL) + dlclose(loader_hdl); + + loader_hdl = dlopen(loader, RTLD_LOCAL); + if (!loader_hdl) { + printf("%s\n", dlerror()); + return (1); + } + + loader_func = dlsym(loader_hdl, "loader_main"); + if (!loader_func) { + printf("%s\n", dlerror()); + return (1); + } + return (0); +} + int main(int argc, char** argv) { - char *loader; - void *h; - void (*func)(struct loader_callbacks *, void *, int, int); uint64_t mem_size; + char *loader; int opt, error, need_reinit, memflags; progname = basename(argv[0]); loader = NULL; - memflags = 0; mem_size = 256 * MB; @@ -705,6 +791,7 @@ loader = strdup(optarg); if (loader == NULL) err(EX_OSERR, "malloc"); + explicit_loader = 1; break; case 'm': @@ -762,23 +849,11 @@ exit(1); } - if (loader == NULL) { - loader = strdup("/boot/userboot.so"); - if (loader == NULL) - err(EX_OSERR, "malloc"); - } - h = dlopen(loader, RTLD_LOCAL); - if (!h) { - printf("%s\n", dlerror()); - free(loader); - return (1); - } - func = dlsym(h, "loader_main"); - if (!func) { - printf("%s\n", dlerror()); - free(loader); - return (1); - } + if (explicit_loader == 0 && setup_loader(NULL) != 0) + exit(1); + else if (explicit_loader == 1 && fetch_loader_func(loader) != 0) + exit(1); + free(loader); tcgetattr(consout_fd, &term); oldterm = term; @@ -790,8 +865,7 @@ addenv("smbios.bios.vendor=BHYVE"); addenv("boot_serial=1"); - func(&cb, NULL, USERBOOT_VERSION_4, ndisks); + loader_func(&cb, NULL, USERBOOT_VERSION_5, ndisks); - free(loader); return (0); }