mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 18:21:39 +01:00
Move a lot of #include <stdfoo.h> from individual files into libxl_internal.h. This helps avoid portability mistakes where necessary system headers are omitted from individual files, and is also of course a convenience when developing. Also add #include "libxl_osdeps.h" /* must come before any other headers */ to the top of most libxl*.c files, so that anyone who adds any headers before libxl_internal.h will put the in the right place. Signed-off-by: Ian Jackson <ian.jackson@eu.citrix.com> Acked-by: Ian Campbell <ian.campbell@citrix.com> Committed-by: Ian Jackson <Ian.Jackson@eu.citrix.com>
481 lines
15 KiB
C
481 lines
15 KiB
C
/*
|
|
* 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" /* must come before any other headers */
|
|
|
|
#include <termios.h>
|
|
|
|
#include "libxl_internal.h"
|
|
|
|
#define XENCONSOLED_BUF_SIZE 16
|
|
#define BOOTLOADER_BUF_SIZE 4096
|
|
#define BOOTLOADER_TIMEOUT 1
|
|
|
|
static char **make_bootloader_args(libxl__gc *gc,
|
|
libxl_domain_build_info *info,
|
|
uint32_t domid,
|
|
const char *fifo, 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->u.pv.kernel.path)
|
|
flexarray_set(args, nr++, libxl__sprintf(gc, "--kernel=%s",
|
|
info->u.pv.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 *p = info->u.pv.bootloader_args[0];
|
|
while (*(p++))
|
|
flexarray_set(args, nr++, p);
|
|
}
|
|
|
|
flexarray_set(args, nr++, 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);
|
|
fcntl(*master, F_SETFD, FD_CLOEXEC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static pid_t fork_exec_bootloader(int *master, const 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;
|
|
struct timeval wait;
|
|
|
|
/* 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;
|
|
|
|
/* Set timeout to 1s before starting to discard data */
|
|
wait.tv_sec = BOOTLOADER_TIMEOUT;
|
|
wait.tv_usec = 0;
|
|
|
|
/* Move buffers around to drop already consumed data */
|
|
if (xenconsoled_cons > 0) {
|
|
xenconsoled_prod -= xenconsoled_cons;
|
|
memmove(xenconsoled_buf, &xenconsoled_buf[xenconsoled_cons],
|
|
xenconsoled_prod);
|
|
xenconsoled_cons = 0;
|
|
}
|
|
if (bootloader_cons > 0) {
|
|
bootloader_prod -= bootloader_cons;
|
|
memmove(bootloader_buf, &bootloader_buf[bootloader_cons],
|
|
bootloader_prod);
|
|
bootloader_cons = 0;
|
|
}
|
|
|
|
FD_ZERO(&rsel);
|
|
FD_SET(fifo_fd, &rsel);
|
|
nfds = fifo_fd + 1;
|
|
if (xenconsoled_prod < XENCONSOLED_BUF_SIZE) {
|
|
/* The buffer is not full, try to read more data */
|
|
FD_SET(xenconsoled_fd, &rsel);
|
|
nfds = xenconsoled_fd + 1 > nfds ? xenconsoled_fd + 1 : nfds;
|
|
}
|
|
if (bootloader_prod < BOOTLOADER_BUF_SIZE) {
|
|
/* The buffer is not full, try to read more data */
|
|
FD_SET(bootloader_fd, &rsel);
|
|
nfds = bootloader_fd + 1 > nfds ? bootloader_fd + 1 : nfds;
|
|
}
|
|
|
|
FD_ZERO(&wsel);
|
|
if (bootloader_prod > 0) {
|
|
/* The buffer has data to consume */
|
|
FD_SET(xenconsoled_fd, &wsel);
|
|
nfds = xenconsoled_fd + 1 > nfds ? xenconsoled_fd + 1 : nfds;
|
|
}
|
|
if (xenconsoled_prod > 0) {
|
|
/* The buffer has data to consume */
|
|
FD_SET(bootloader_fd, &wsel);
|
|
nfds = bootloader_fd + 1 > nfds ? bootloader_fd + 1 : nfds;
|
|
}
|
|
|
|
if (xenconsoled_prod == XENCONSOLED_BUF_SIZE ||
|
|
bootloader_prod == BOOTLOADER_BUF_SIZE)
|
|
ret = select(nfds, &rsel, &wsel, NULL, &wait);
|
|
else
|
|
ret = select(nfds, &rsel, &wsel, NULL, NULL);
|
|
if (ret < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
goto out_err;
|
|
}
|
|
|
|
/* Input from xenconsole, read xenconsoled_fd, write bootloader_fd */
|
|
if (ret == 0 && xenconsoled_prod == XENCONSOLED_BUF_SIZE) {
|
|
/* Drop the buffer */
|
|
xenconsoled_prod = 0;
|
|
xenconsoled_cons = 0;
|
|
} else 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 (ret == 0 && bootloader_prod == BOOTLOADER_BUF_SIZE) {
|
|
/* Drop the buffer */
|
|
bootloader_prod = 0;
|
|
bootloader_cons = 0;
|
|
} else 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__gc *gc,
|
|
libxl_domain_build_info *info,
|
|
const char *o)
|
|
{
|
|
while (*o != '\0') {
|
|
if (strncmp("kernel ", o, strlen("kernel ")) == 0) {
|
|
free(info->u.pv.kernel.path);
|
|
info->u.pv.kernel.path = strdup(o + strlen("kernel "));
|
|
libxl__file_reference_map(&info->u.pv.kernel);
|
|
unlink(info->u.pv.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(&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)
|
|
{
|
|
GC_INIT(ctx);
|
|
int ret, rc = 0;
|
|
char *fifo = NULL;
|
|
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->type != LIBXL_DOMAIN_TYPE_PV || !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/console/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, info->u.pv.bootloader, args);
|
|
if (pid < 0) {
|
|
goto out_close;
|
|
}
|
|
|
|
while (1) {
|
|
if (waitpid(pid, &blrc, WNOHANG) == pid)
|
|
goto out_close;
|
|
|
|
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;
|
|
}
|
|
|
|
parse_bootloader_result(gc, info, blout);
|
|
|
|
rc = 0;
|
|
out_close:
|
|
if (diskpath) {
|
|
libxl_device_disk_local_detach(ctx, disk);
|
|
free(diskpath);
|
|
}
|
|
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_slave);
|
|
|
|
if (fifo) {
|
|
unlink(fifo);
|
|
free(fifo);
|
|
}
|
|
|
|
rmdir(tempdir);
|
|
|
|
free(args);
|
|
|
|
out:
|
|
GC_FREE;
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* mode: C
|
|
* c-basic-offset: 4
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*/
|