/* * Copyright (C) 2010 Citrix Ltd. * Author Ian Campbell * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; version 2.1 only. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. */ #include "libxl_osdeps.h" #include #include #include #include #include #include #include "libxl.h" #include "libxl_internal.h" #include "flexarray.h" #define XENCONSOLED_BUF_SIZE 16 #define BOOTLOADER_BUF_SIZE 1024 static char **make_bootloader_args(libxl_gc *gc, libxl_domain_build_info *info, uint32_t domid, const char *fifo, const char *disk) { flexarray_t *args; int nr = 0; args = flexarray_make(1, 1); if (!args) return NULL; flexarray_set(args, nr++, (char *)info->u.pv.bootloader); if (info->kernel.path) flexarray_set(args, nr++, libxl_sprintf(gc, "--kernel=%s", info->kernel.path)); if (info->u.pv.ramdisk.path) flexarray_set(args, nr++, libxl_sprintf(gc, "--ramdisk=%s", info->u.pv.ramdisk.path)); if (info->u.pv.cmdline && *info->u.pv.cmdline != '\0') flexarray_set(args, nr++, libxl_sprintf(gc, "--args=%s", info->u.pv.cmdline)); flexarray_set(args, nr++, libxl_sprintf(gc, "--output=%s", fifo)); flexarray_set(args, nr++, "--output-format=simple0"); flexarray_set(args, nr++, libxl_sprintf(gc, "--output-directory=%s", "/var/run/libxl/")); if (info->u.pv.bootloader_args) { char *saveptr; /* Operate on a duplicate since strtok modifes the argument */ char *dup = libxl_strdup(gc, info->u.pv.bootloader_args); char *t = strtok_r(dup, " \t\n", &saveptr); do { flexarray_set(args, nr++, t); } while ((t = strtok_r(NULL, " \t\n", &saveptr))); } flexarray_set(args, nr++, strdup(disk)); /* Sentinal for execv */ flexarray_set(args, nr++, NULL); return (char **) flexarray_contents(args); /* Frees args */ } static int open_xenconsoled_pty(int *master, int *slave, char *slave_path, size_t slave_path_len) { struct termios termattr; int ret; ret = openpty(master, slave, NULL, NULL, NULL); if (ret < 0) return -1; ret = ttyname_r(*slave, slave_path, slave_path_len); if (ret == -1) { close(*master); close(*slave); *master = *slave = -1; return -1; } /* * On Solaris, the pty master side will get cranky if we try * to write to it while there is no slave. To work around this, * keep the slave descriptor open until we're done. Set it * to raw terminal parameters, otherwise it will echo back * characters, which will confuse the I/O loop below. * Furthermore, a raw master pty device has no terminal * semantics on Solaris, so don't try to set any attributes * for it. */ #if !defined(__sun__) && !defined(__NetBSD__) tcgetattr(*master, &termattr); cfmakeraw(&termattr); tcsetattr(*master, TCSANOW, &termattr); close(*slave); *slave = -1; #else tcgetattr(*slave, &termattr); cfmakeraw(&termattr); tcsetattr(*slave, TCSANOW, &termattr); #endif fcntl(*master, F_SETFL, O_NDELAY); return 0; } static pid_t fork_exec_bootloader(int *master, char *arg0, char **args) { struct termios termattr; pid_t pid = forkpty(master, NULL, NULL, NULL); if (pid == -1) return -1; else if (pid == 0) { setenv("TERM", "vt100", 1); libxl_exec(-1, -1, -1, arg0, args); return -1; } /* * On Solaris, the master pty side does not have terminal semantics, * so don't try to set any attributes, as it will fail. */ #if !defined(__sun__) tcgetattr(*master, &termattr); cfmakeraw(&termattr); tcsetattr(*master, TCSANOW, &termattr); #endif fcntl(*master, F_SETFL, O_NDELAY); return pid; } /* * filedescriptors: * fifo_fd - bootstring output from the bootloader * xenconsoled_fd - input/output from/to xenconsole * bootloader_fd - input/output from/to pty that controls the bootloader * The filedescriptors are NDELAY, so it's ok to try to read * bigger chunks than may be available, to keep e.g. curses * screen redraws in the bootloader efficient. xenconsoled_fd is the side that * gets xenconsole input, which will be keystrokes, so a small number * is sufficient. bootloader_fd is pygrub output, which will be curses screen * updates, so a larger number (1024) is appropriate there. * * For writeable descriptors, only include them in the set for select * if there is actual data to write, otherwise this would loop too fast, * eating up CPU time. */ static char * bootloader_interact(libxl_gc *gc, int xenconsoled_fd, int bootloader_fd, int fifo_fd) { int ret; size_t nr_out = 0, size_out = 0; char *output = NULL; /* input from xenconsole. read on xenconsoled_fd write to bootloader_fd */ int xenconsoled_prod = 0, xenconsoled_cons = 0; char xenconsoled_buf[XENCONSOLED_BUF_SIZE]; /* output from bootloader. read on bootloader_fd write to xenconsoled_fd */ int bootloader_prod = 0, bootloader_cons = 0; char bootloader_buf[BOOTLOADER_BUF_SIZE]; while(1) { fd_set wsel, rsel; int nfds; if (xenconsoled_prod == xenconsoled_cons) xenconsoled_prod = xenconsoled_cons = 0; if (bootloader_prod == bootloader_cons) bootloader_prod = bootloader_cons = 0; FD_ZERO(&rsel); FD_SET(fifo_fd, &rsel); nfds = fifo_fd + 1; if (xenconsoled_prod == 0 || (xenconsoled_prod < BOOTLOADER_BUF_SIZE && xenconsoled_cons == 0)) { FD_SET(xenconsoled_fd, &rsel); nfds = xenconsoled_fd + 1 > nfds ? xenconsoled_fd + 1 : nfds; } if (bootloader_prod == 0 || (bootloader_prod < BOOTLOADER_BUF_SIZE && bootloader_cons == 0)) { FD_SET(bootloader_fd, &rsel); nfds = bootloader_fd + 1 > nfds ? bootloader_fd + 1 : nfds; } FD_ZERO(&wsel); if (bootloader_prod != bootloader_cons) { FD_SET(xenconsoled_fd, &wsel); nfds = xenconsoled_fd + 1 > nfds ? xenconsoled_fd + 1 : nfds; } if (xenconsoled_prod != xenconsoled_cons) { FD_SET(bootloader_fd, &wsel); nfds = bootloader_fd + 1 > nfds ? bootloader_fd + 1 : nfds; } ret = select(nfds, &rsel, &wsel, NULL, NULL); if (ret < 0) goto out_err; /* Input from xenconsole, read xenconsoled_fd, write bootloader_fd */ if (FD_ISSET(xenconsoled_fd, &rsel)) { ret = read(xenconsoled_fd, &xenconsoled_buf[xenconsoled_prod], XENCONSOLED_BUF_SIZE - xenconsoled_prod); if (ret < 0 && errno != EIO && errno != EAGAIN) goto out_err; if (ret > 0) xenconsoled_prod += ret; } if (FD_ISSET(bootloader_fd, &wsel)) { ret = write(bootloader_fd, &xenconsoled_buf[xenconsoled_cons], xenconsoled_prod - xenconsoled_cons); if (ret < 0 && errno != EIO && errno != EAGAIN) goto out_err; if (ret > 0) xenconsoled_cons += ret; } /* Input from bootloader, read bootloader_fd, write xenconsoled_fd */ if (FD_ISSET(bootloader_fd, &rsel)) { ret = read(bootloader_fd, &bootloader_buf[bootloader_prod], BOOTLOADER_BUF_SIZE - bootloader_prod); if (ret < 0 && errno != EIO && errno != EAGAIN) goto out_err; if (ret > 0) bootloader_prod += ret; } if (FD_ISSET(xenconsoled_fd, &wsel)) { ret = write(xenconsoled_fd, &bootloader_buf[bootloader_cons], bootloader_prod - bootloader_cons); if (ret < 0 && errno != EIO && errno != EAGAIN) goto out_err; if (ret > 0) bootloader_cons += ret; } if (FD_ISSET(fifo_fd, &rsel)) { if (size_out - nr_out < 256) { char *temp; size_t new_size = size_out == 0 ? 32 : size_out * 2; temp = realloc(output, new_size); if (temp == NULL) goto out_err; output = temp; memset(output + size_out, 0, new_size - size_out); size_out = new_size; } ret = read(fifo_fd, output + nr_out, size_out - nr_out); if (ret > 0) nr_out += ret; if (ret == 0) break; } } libxl_ptr_add(gc, output); return output; out_err: free(output); return NULL; } static void parse_bootloader_result(libxl_ctx *ctx, libxl_domain_build_info *info, const char *o) { while (*o != '\0') { if (strncmp("kernel ", o, strlen("kernel ")) == 0) { free(info->kernel.path); info->kernel.path = strdup(o + strlen("kernel ")); libxl_file_reference_map(ctx, &info->kernel); unlink(info->kernel.path); } else if (strncmp("ramdisk ", o, strlen("ramdisk ")) == 0) { free(info->u.pv.ramdisk.path); info->u.pv.ramdisk.path = strdup(o + strlen("ramdisk ")); libxl_file_reference_map(ctx, &info->u.pv.ramdisk); unlink(info->u.pv.ramdisk.path); } else if (strncmp("args ", o, strlen("args ")) == 0) { free(info->u.pv.cmdline); info->u.pv.cmdline = strdup(o + strlen("args ")); } o = o + strlen(o) + 1; } } int libxl_run_bootloader(libxl_ctx *ctx, libxl_domain_build_info *info, libxl_device_disk *disk, uint32_t domid) { libxl_gc gc = LIBXL_INIT_GC(ctx); int ret, rc = 0; char *fifo = NULL; const char *diskpath = NULL; char **args = NULL; char tempdir_template[] = "/var/run/libxl/bl.XXXXXX"; char *tempdir; char *dom_console_xs_path; char dom_console_slave_tty_path[PATH_MAX]; int xenconsoled_fd = -1, xenconsoled_slave = -1; int bootloader_fd = -1, fifo_fd = -1; int blrc; pid_t pid; char *blout; struct stat st_buf; if (info->hvm || !info->u.pv.bootloader) goto out; rc = ERROR_INVAL; if (!disk) goto out; rc = ERROR_FAIL; ret = mkdir("/var/run/libxl/", S_IRWXU); if (ret < 0 && errno != EEXIST) goto out; ret = stat("/var/run/libxl/", &st_buf); if (ret < 0) goto out; if (!S_ISDIR(st_buf.st_mode)) goto out; tempdir = mkdtemp(tempdir_template); if (tempdir == NULL) goto out; ret = asprintf(&fifo, "%s/fifo", tempdir); if (ret < 0) { fifo = NULL; goto out_close; } ret = mkfifo(fifo, 0600); if (ret < 0) { goto out_close; } diskpath = libxl_device_disk_local_attach(ctx, disk); if (!diskpath) { goto out_close; } args = make_bootloader_args(&gc, info, domid, fifo, diskpath); if (args == NULL) { rc = ERROR_NOMEM; goto out_close; } /* * We need to present the bootloader's tty as a pty slave that xenconsole * can access. Since the bootloader itself needs a pty slave, * we end up with a connection like this: * * xenconsole -- (slave pty1 master) <-> (master pty2 slave) -- bootloader * * where we copy characters between the two master fds, as well as * listening on the bootloader's fifo for the results. */ ret = open_xenconsoled_pty(&xenconsoled_fd, &xenconsoled_slave, &dom_console_slave_tty_path[0], sizeof(dom_console_slave_tty_path)); if (ret < 0) { goto out_close; } dom_console_xs_path = libxl_sprintf(&gc, "%s/serial/0/tty", libxl_xs_get_dompath(&gc, domid)); libxl_xs_write(&gc, XBT_NULL, dom_console_xs_path, "%s", dom_console_slave_tty_path); pid = fork_exec_bootloader(&bootloader_fd, (char *)info->u.pv.bootloader, args); if (pid < 0) { goto out_close; } while (1) { fifo_fd = open(fifo, O_RDONLY); if (fifo_fd > -1) break; if (errno == EINTR) continue; goto out_close; } fcntl(fifo_fd, F_SETFL, O_NDELAY); blout = bootloader_interact(&gc, xenconsoled_fd, bootloader_fd, fifo_fd); if (blout == NULL) { goto out_close; } pid = waitpid(pid, &blrc, 0); if (pid == -1 || (pid > 0 && WIFEXITED(blrc) && WEXITSTATUS(blrc) != 0)) { goto out_close; } libxl_device_disk_local_detach(ctx, disk); parse_bootloader_result(ctx, info, blout); rc = 0; out_close: if (fifo_fd > -1) close(fifo_fd); if (bootloader_fd > -1) close(bootloader_fd); if (xenconsoled_fd > -1) close(xenconsoled_fd); if (xenconsoled_slave > -1) close(xenconsoled_fd); if (fifo) { unlink(fifo); free(fifo); } rmdir(tempdir); free(args); out: libxl_free_all(&gc); return rc; }