aboutsummaryrefslogtreecommitdiffstats
path: root/tools/libxl/libxl_bootloader.c
diff options
context:
space:
mode:
authorIan Jackson <Ian.Jackson@eu.citrix.com>2010-07-14 16:45:38 +0100
committerIan Jackson <Ian.Jackson@eu.citrix.com>2010-07-14 16:45:38 +0100
commit6a8f8977a10e191fa63d2077e47dee7888fb93a3 (patch)
tree22f05759746f633741ca39fe2508e5b784e19fba /tools/libxl/libxl_bootloader.c
parent2587d9aa4efdc4a666852224d6846ea4726c0842 (diff)
downloadxen-6a8f8977a10e191fa63d2077e47dee7888fb93a3.tar.gz
xen-6a8f8977a10e191fa63d2077e47dee7888fb93a3.tar.bz2
xen-6a8f8977a10e191fa63d2077e47dee7888fb93a3.zip
libxl, xl: support running bootloader (e.g. pygrub) in domain 0
Much of the bootloader interaction (including the Solaris and NetBSD portability bits) are translated pretty much directly from the python in tools/python/xen/xend/XendBootloader.py Signed-off-by: Ian Campbell <ian.campbell@citrix.com>
Diffstat (limited to 'tools/libxl/libxl_bootloader.c')
-rw-r--r--tools/libxl/libxl_bootloader.c449
1 files changed, 449 insertions, 0 deletions
diff --git a/tools/libxl/libxl_bootloader.c b/tools/libxl/libxl_bootloader.c
new file mode 100644
index 0000000000..dfec4bca49
--- /dev/null
+++ b/tools/libxl/libxl_bootloader.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2010 Citrix Ltd.
+ * Author Ian Campbell <ian.campbell@citrix.com>
+ *
+ * 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 <string.h>
+#include <pty.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#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(struct libxl_ctx *ctx,
+ 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(ctx, "--kernel=%s", info->kernel.path));
+ if (info->u.pv.ramdisk.path)
+ flexarray_set(args, nr++, libxl_sprintf(ctx, "--ramdisk=%s", info->u.pv.ramdisk.path));
+ if (info->u.pv.cmdline && *info->u.pv.cmdline != '\0')
+ flexarray_set(args, nr++, libxl_sprintf(ctx, "--args=%s", info->u.pv.cmdline));
+
+ flexarray_set(args, nr++, libxl_sprintf(ctx, "--output=%s", fifo));
+ flexarray_set(args, nr++, "--output-format=simple0");
+ flexarray_set(args, nr++, libxl_sprintf(ctx, "--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(ctx, 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(struct libxl_ctx *ctx, 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, new_size - size_out, 0);
+ 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(ctx, output);
+ return output;
+
+out_err:
+ free(output);
+ return NULL;
+}
+
+static void parse_bootloader_result(struct 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(struct libxl_ctx *ctx,
+ libxl_domain_build_info *info,
+ libxl_device_disk *disk,
+ uint32_t domid)
+{
+ int ret;
+
+ 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)
+ return 0;
+
+ if (!disk)
+ return ERROR_INVAL;
+
+ ret = mkdir("/var/run/libxl/", S_IRWXU);
+ if (ret < 0 && errno != EEXIST)
+ return ERROR_FAIL;
+
+ ret = stat("/var/run/libxl/", &st_buf);
+ if (ret < 0)
+ return ERROR_FAIL;
+
+ if (!S_ISDIR(st_buf.st_mode))
+ return ERROR_FAIL;
+
+ tempdir = mkdtemp(tempdir_template);
+ if (tempdir == NULL)
+ return ERROR_FAIL;
+
+ ret = asprintf(&fifo, "%s/fifo", tempdir);
+ if (ret < 0) {
+ ret = ERROR_FAIL;
+ fifo = NULL;
+ goto out;
+ }
+
+ ret = mkfifo(fifo, 0600);
+ if (ret < 0) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ diskpath = libxl_device_disk_local_attach(ctx, disk);
+ if (!diskpath) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ args = make_bootloader_args(ctx, info, domid, fifo, diskpath);
+ if (args == NULL) {
+ ret = ERROR_NOMEM;
+ goto out;
+ }
+
+ /*
+ * 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) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ dom_console_xs_path = libxl_sprintf(ctx, "%s/serial/0/tty", libxl_xs_get_dompath(ctx, domid));
+ libxl_xs_write(ctx, XBT_NULL, dom_console_xs_path, dom_console_slave_tty_path);
+
+ pid = fork_exec_bootloader(&bootloader_fd, (char *)info->u.pv.bootloader, args);
+ if (pid < 0) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ while (1) {
+ fifo_fd = open(fifo, O_RDONLY);
+ if (fifo_fd > -1)
+ break;
+
+ if (errno == EINTR)
+ continue;
+
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ fcntl(fifo_fd, F_SETFL, O_NDELAY);
+
+ blout = bootloader_interact(ctx, xenconsoled_fd, bootloader_fd, fifo_fd);
+ if (blout == NULL) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ pid = waitpid(pid, &blrc, 0);
+ if (pid == -1 || (pid > 0 && WIFEXITED(blrc) && WEXITSTATUS(blrc) != 0)) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ libxl_device_disk_local_detach(ctx, disk);
+
+ parse_bootloader_result(ctx, info, blout);
+
+ ret = 0;
+out:
+ 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);
+
+ return ret;
+}
+