Files
xen/tools/libxl/libxl_bootloader.c
T
Paul Durrant a5361ebc6d libxl: re-name libxl__xs_write() to libxl__xs_printf()...
...to denote what it actually does.

The name libxl__xs_write() suggests something taking a buffer and length,
akin to write(2), whereas the semantics of the function are actually more
akin to printf(3).

This patch is a textual substitution of libxl__xs_write with
libxl__xs_printf with some associated formatting fixes.

Signed-off-by: Paul Durrant <paul.durrant@citrix.com>
Acked-by: Ian Jackson <ian.jackson@eu.citrix.com>
Cc: Stefano Stabellini <stefano.stabellini@eu.citrix.com>
Cc: Ian Campbell <ian.campbell@citrix.com>
Cc: Wei Liu <wei.liu2@citrix.com>
2015-12-15 12:03:08 +00:00

668 lines
20 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>
#ifdef HAVE_UTMP_H
#include <utmp.h>
#endif
#ifdef INCLUDE_LIBUTIL_H
#include INCLUDE_LIBUTIL_H
#endif
#include "libxl_internal.h"
#define BOOTLOADER_BUF_OUT 65536
#define BOOTLOADER_BUF_IN 4096
static void bootloader_gotptys(libxl__egc *egc, libxl__openpty_state *op);
static void bootloader_keystrokes_copyfail(libxl__egc *egc,
libxl__datacopier_state *dc, int rc, int onwrite, int errnoval);
static void bootloader_display_copyfail(libxl__egc *egc,
libxl__datacopier_state *dc, int rc, int onwrite, int errnoval);
static void bootloader_domaindeath(libxl__egc*, libxl__domaindeathcheck *dc,
int rc);
static void bootloader_finished(libxl__egc *egc, libxl__ev_child *child,
pid_t pid, int status);
/*----- bootloader arguments -----*/
static void bootloader_arg(libxl__bootloader_state *bl, const char *arg)
{
assert(bl->nargs < bl->argsspace);
bl->args[bl->nargs++] = arg;
}
static void make_bootloader_args(libxl__gc *gc, libxl__bootloader_state *bl,
const char *bootloader_path)
{
const libxl_domain_build_info *info = bl->info;
bl->argsspace = 9 + libxl_string_list_length(&info->u.pv.bootloader_args);
GCNEW_ARRAY(bl->args, bl->argsspace);
#define ARG(arg) bootloader_arg(bl, (arg))
ARG(bootloader_path);
if (info->kernel)
ARG(GCSPRINTF("--kernel=%s", info->kernel));
if (info->ramdisk)
ARG(GCSPRINTF("--ramdisk=%s", info->ramdisk));
if (info->cmdline && *info->cmdline != '\0')
ARG(GCSPRINTF("--args=%s", info->cmdline));
ARG(GCSPRINTF("--output=%s", bl->outputpath));
ARG("--output-format=simple0");
ARG(GCSPRINTF("--output-directory=%s", bl->outputdir));
if (info->u.pv.bootloader_args) {
char **p = info->u.pv.bootloader_args;
while (*p) {
ARG(*p);
p++;
}
}
ARG(bl->dls.diskpath);
/* Sentinel for execv */
ARG(NULL);
#undef ARG
}
/*----- synchronous subroutines -----*/
static int setup_xenconsoled_pty(libxl__egc *egc, libxl__bootloader_state *bl,
char *slave_path, size_t slave_path_len)
{
STATE_AO_GC(bl->ao);
struct termios termattr;
int r, rc;
int slave = libxl__carefd_fd(bl->ptys[1].slave);
int master = libxl__carefd_fd(bl->ptys[1].master);
r = ttyname_r(slave, slave_path, slave_path_len);
if (r == -1) {
LOGE(ERROR,"ttyname_r failed");
rc = ERROR_FAIL;
goto out;
}
/*
* 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.
*/
tcgetattr(master, &termattr);
cfmakeraw(&termattr);
tcsetattr(master, TCSANOW, &termattr);
return 0;
out:
return rc;
}
static const char *bootloader_result_command(libxl__gc *gc, const char *buf,
const char *prefix, size_t prefixlen) {
if (strncmp(buf, prefix, prefixlen))
return 0;
const char *rhs = buf + prefixlen;
if (!CTYPE(isspace,*rhs))
return 0;
while (CTYPE(isspace,*rhs))
rhs++;
LOG(DEBUG,"bootloader output contained %s %s", prefix, rhs);
return rhs;
}
static int parse_bootloader_result(libxl__egc *egc,
libxl__bootloader_state *bl)
{
STATE_AO_GC(bl->ao);
char buf[PATH_MAX*2];
FILE *f = 0;
int rc = ERROR_FAIL;
f = fopen(bl->outputpath, "r");
if (!f) {
LOGE(ERROR,"open bootloader output file %s", bl->outputpath);
goto out;
}
for (;;) {
/* Read a nul-terminated "line" and put the result in
* buf, and its length (not including the nul) in l */
int l = 0, c;
while ((c = getc(f)) != EOF && c != '\0') {
if (l < sizeof(buf)-1)
buf[l] = c;
l++;
}
if (c == EOF) {
if (ferror(f)) {
LOGE(ERROR,"read bootloader output file %s", bl->outputpath);
goto out;
}
if (!l)
break;
}
if (l >= sizeof(buf)) {
LOG(WARN,"bootloader output contained"
" overly long item `%.150s...'", buf);
continue;
}
buf[l] = 0;
const char *rhs;
#define COMMAND(s) ((rhs = bootloader_result_command(gc, buf, s, sizeof(s)-1)))
if (COMMAND("kernel")) {
bl->kernel->path = libxl__strdup(gc, rhs);
libxl__file_reference_map(bl->kernel);
unlink(bl->kernel->path);
} else if (COMMAND("ramdisk")) {
bl->ramdisk->path = libxl__strdup(gc, rhs);
libxl__file_reference_map(bl->ramdisk);
unlink(bl->ramdisk->path);
} else if (COMMAND("args")) {
bl->cmdline = libxl__strdup(gc, rhs);
} else if (l) {
LOG(WARN, "unexpected output from bootloader: `%s'", buf);
}
}
rc = 0;
out:
if (f) fclose(f);
return rc;
}
/*----- init and cleanup -----*/
void libxl__bootloader_init(libxl__bootloader_state *bl)
{
assert(bl->ao);
bl->rc = 0;
bl->dls.diskpath = NULL;
bl->openpty.ao = bl->ao;
bl->dls.ao = bl->ao;
bl->ptys[0].master = bl->ptys[0].slave = 0;
bl->ptys[1].master = bl->ptys[1].slave = 0;
libxl__ev_child_init(&bl->child);
libxl__domaindeathcheck_init(&bl->deathcheck);
bl->keystrokes.ao = bl->ao; libxl__datacopier_init(&bl->keystrokes);
bl->display.ao = bl->ao; libxl__datacopier_init(&bl->display);
bl->got_pollhup = 0;
}
static void bootloader_cleanup(libxl__egc *egc, libxl__bootloader_state *bl)
{
STATE_AO_GC(bl->ao);
int i;
if (bl->outputpath) libxl__remove_file(gc, bl->outputpath);
if (bl->outputdir) libxl__remove_directory(gc, bl->outputdir);
libxl__domaindeathcheck_stop(gc,&bl->deathcheck);
libxl__datacopier_kill(&bl->keystrokes);
libxl__datacopier_kill(&bl->display);
for (i=0; i<2; i++) {
libxl__carefd_close(bl->ptys[i].master);
libxl__carefd_close(bl->ptys[i].slave);
}
if (bl->display.log) {
fclose(bl->display.log);
bl->display.log = NULL;
}
}
static void bootloader_setpaths(libxl__gc *gc, libxl__bootloader_state *bl)
{
uint32_t domid = bl->domid;
bl->outputdir = GCSPRINTF(XEN_RUN_DIR "/bootloader.%"PRIu32".d", domid);
bl->outputpath = GCSPRINTF(XEN_RUN_DIR "/bootloader.%"PRIu32".out", domid);
}
/* Callbacks */
static void bootloader_local_detached_cb(libxl__egc *egc,
libxl__disk_local_state *dls,
int rc);
static void bootloader_callback(libxl__egc *egc, libxl__bootloader_state *bl,
int rc)
{
if (!bl->rc)
bl->rc = rc;
bootloader_cleanup(egc, bl);
bl->dls.callback = bootloader_local_detached_cb;
libxl__device_disk_local_initiate_detach(egc, &bl->dls);
}
static void bootloader_local_detached_cb(libxl__egc *egc,
libxl__disk_local_state *dls,
int rc)
{
STATE_AO_GC(dls->ao);
libxl__bootloader_state *bl = CONTAINER_OF(dls, *bl, dls);
if (rc) {
LOG(ERROR, "unable to detach locally attached disk");
if (!bl->rc)
bl->rc = rc;
}
bl->callback(egc, bl, bl->rc);
}
/* might be called at any time, provided it's init'd */
static void bootloader_stop(libxl__egc *egc,
libxl__bootloader_state *bl, int rc)
{
STATE_AO_GC(bl->ao);
int r;
libxl__datacopier_kill(&bl->keystrokes);
libxl__datacopier_kill(&bl->display);
if (libxl__ev_child_inuse(&bl->child)) {
r = kill(bl->child.pid, SIGTERM);
if (r) LOGE(WARN, "%sfailed to kill bootloader [%lu]",
rc ? "after failure, " : "", (unsigned long)bl->child.pid);
}
if (!bl->rc)
bl->rc = rc;
}
/*----- main flow of control -----*/
/* Callbacks */
static void bootloader_disk_attached_cb(libxl__egc *egc,
libxl__disk_local_state *dls,
int rc);
void libxl__bootloader_run(libxl__egc *egc, libxl__bootloader_state *bl)
{
STATE_AO_GC(bl->ao);
const libxl_domain_build_info *info = bl->info;
uint32_t domid = bl->domid;
char *logfile_tmp = NULL;
int rc, r;
libxl__bootloader_init(bl);
if (info->type != LIBXL_DOMAIN_TYPE_PV) {
LOG(DEBUG, "not a PV domain, skipping bootloader");
rc = 0;
goto out_ok;
}
if (!info->u.pv.bootloader) {
LOG(DEBUG, "no bootloader configured, using user supplied kernel");
bl->kernel->path = bl->info->kernel;
bl->ramdisk->path = bl->info->ramdisk;
bl->cmdline = bl->info->cmdline;
rc = 0;
goto out_ok;
}
if (!bl->disk) {
LOG(ERROR, "cannot run bootloader with no boot disk");
rc = ERROR_FAIL;
goto out;
}
bootloader_setpaths(gc, bl);
const char *logfile_leaf = GCSPRINTF("bootloader.%"PRIu32, domid);
rc = libxl_create_logfile(CTX, logfile_leaf, &logfile_tmp);
if (rc) goto out;
/* Transfer ownership of log filename to bl and the gc */
bl->logfile = logfile_tmp;
libxl__ptr_add(gc, logfile_tmp);
logfile_tmp = NULL;
bl->display.log = fopen(bl->logfile, "a");
if (!bl->display.log) {
LOGE(ERROR, "failed to create bootloader logfile %s", bl->logfile);
rc = ERROR_FAIL;
goto out;
}
for (;;) {
r = mkdir(bl->outputdir, 0600);
if (!r) break;
if (errno == EINTR) continue;
if (errno == EEXIST) break;
LOGE(ERROR, "failed to create bootloader dir %s", bl->outputdir);
rc = ERROR_FAIL;
goto out;
}
for (;;) {
r = open(bl->outputpath, O_WRONLY|O_CREAT|O_TRUNC, 0600);
if (r>=0) { close(r); break; }
if (errno == EINTR) continue;
LOGE(ERROR, "failed to precreate bootloader output %s", bl->outputpath);
rc = ERROR_FAIL;
goto out;
}
/* This sets the state of the dls struct from Undefined to Idle */
libxl__device_disk_local_init(&bl->dls);
bl->dls.ao = ao;
bl->dls.in_disk = bl->disk;
bl->dls.blkdev_start = info->blkdev_start;
bl->dls.callback = bootloader_disk_attached_cb;
libxl__device_disk_local_initiate_attach(egc, &bl->dls);
return;
out:
assert(rc);
out_ok:
free(logfile_tmp);
bootloader_callback(egc, bl, rc);
}
static void bootloader_disk_attached_cb(libxl__egc *egc,
libxl__disk_local_state *dls,
int rc)
{
STATE_AO_GC(dls->ao);
libxl__bootloader_state *bl = CONTAINER_OF(dls, *bl, dls);
const libxl_domain_build_info *info = bl->info;
const char *bootloader;
if (rc) {
LOG(ERROR, "failed to attach local disk for bootloader execution");
goto out;
}
LOG(DEBUG, "Config bootloader value: %s", info->u.pv.bootloader);
if ( !strcmp(info->u.pv.bootloader, "/usr/bin/pygrub") )
LOG(WARN, "bootloader='/usr/bin/pygrub' is deprecated; use " \
"bootloader='pygrub' instead");
bootloader = info->u.pv.bootloader;
/* If the full path is not specified, check in the libexec path */
if ( bootloader[0] != '/' ) {
const char *bltmp;
struct stat st;
bltmp = libxl__abs_path(gc, bootloader, libxl__private_bindir_path());
/* Check to see if the file exists in this location; if not,
* fall back to checking the path */
LOG(DEBUG, "Checking for bootloader in libexec path: %s", bltmp);
if ( lstat(bltmp, &st) )
LOG(DEBUG, "%s doesn't exist, falling back to config path",
bltmp);
else
bootloader = bltmp;
}
make_bootloader_args(gc, bl, bootloader);
bl->openpty.ao = ao;
bl->openpty.callback = bootloader_gotptys;
bl->openpty.count = 2;
bl->openpty.results = bl->ptys;
rc = libxl__openptys(&bl->openpty, 0,0);
if (rc) goto out;
return;
out:
assert(rc);
bootloader_callback(egc, bl, rc);
}
static void bootloader_gotptys(libxl__egc *egc, libxl__openpty_state *op)
{
libxl__bootloader_state *bl = CONTAINER_OF(op, *bl, openpty);
STATE_AO_GC(bl->ao);
int rc, r;
char *const env[] = { "TERM", "vt100", NULL };
if (bl->openpty.rc) {
rc = bl->openpty.rc;
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.
*/
char *dom_console_xs_path;
char dom_console_slave_tty_path[PATH_MAX];
rc = setup_xenconsoled_pty(egc, bl,
&dom_console_slave_tty_path[0],
sizeof(dom_console_slave_tty_path));
if (rc) goto out;
char *dompath = libxl__xs_get_dompath(gc, bl->domid);
if (!dompath) { rc = ERROR_FAIL; goto out; }
dom_console_xs_path = GCSPRINTF("%s/console/tty", dompath);
rc = libxl__xs_printf(gc, XBT_NULL, dom_console_xs_path, "%s",
dom_console_slave_tty_path);
if (rc) {
LOGE(ERROR,"xs write console path %s := %s failed",
dom_console_xs_path, dom_console_slave_tty_path);
rc = ERROR_FAIL;
goto out;
}
bl->deathcheck.what = "stopping bootloader";
bl->deathcheck.domid = bl->domid;
bl->deathcheck.callback = bootloader_domaindeath;
rc = libxl__domaindeathcheck_start(ao, &bl->deathcheck);
if (rc) goto out;
if (bl->console_available)
bl->console_available(egc, bl);
int bootloader_master = libxl__carefd_fd(bl->ptys[0].master);
int xenconsole_master = libxl__carefd_fd(bl->ptys[1].master);
libxl_fd_set_nonblock(CTX, bootloader_master, 1);
libxl_fd_set_nonblock(CTX, xenconsole_master, 1);
bl->keystrokes.writefd = bl->display.readfd = bootloader_master;
bl->keystrokes.writewhat = bl->display.readwhat = "bootloader pty";
bl->keystrokes.readfd = bl->display.writefd = xenconsole_master;
bl->keystrokes.readwhat = bl->display.writewhat = "xenconsole client pty";
bl->keystrokes.ao = ao;
bl->keystrokes.maxsz = BOOTLOADER_BUF_OUT;
bl->keystrokes.bytes_to_read = -1;
bl->keystrokes.copywhat =
GCSPRINTF("bootloader input for domain %"PRIu32, bl->domid);
bl->keystrokes.callback = bootloader_keystrokes_copyfail;
bl->keystrokes.callback_pollhup = bootloader_keystrokes_copyfail;
/* pollhup gets called with errnoval==-1 which is not otherwise
* possible since errnos are nonnegative, so it's unambiguous */
rc = libxl__datacopier_start(&bl->keystrokes);
if (rc) goto out;
bl->display.ao = ao;
bl->display.maxsz = BOOTLOADER_BUF_IN;
bl->display.bytes_to_read = -1;
bl->display.copywhat =
GCSPRINTF("bootloader output for domain %"PRIu32, bl->domid);
bl->display.callback = bootloader_display_copyfail;
bl->display.callback_pollhup = bootloader_display_copyfail;
rc = libxl__datacopier_start(&bl->display);
if (rc) goto out;
LOG(DEBUG, "executing bootloader: %s", bl->args[0]);
for (const char **blarg = bl->args;
*blarg;
blarg++)
LOG(DEBUG, " bootloader arg: %s", *blarg);
struct termios termattr;
pid_t pid = libxl__ev_child_fork(gc, &bl->child, bootloader_finished);
if (pid == -1) {
rc = ERROR_FAIL;
goto out;
}
if (!pid) {
/* child */
r = login_tty(libxl__carefd_fd(bl->ptys[0].slave));
if (r) { LOGE(ERROR, "login_tty failed"); exit(-1); }
libxl__exec(gc, -1, -1, -1, bl->args[0], (char **) bl->args, env);
}
/* parent */
/*
* 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(bootloader_master, &termattr);
cfmakeraw(&termattr);
tcsetattr(bootloader_master, TCSANOW, &termattr);
#endif
return;
out:
bootloader_callback(egc, bl, rc);
}
/* perhaps one of these will be called, but perhaps not */
static void bootloader_copyfail(libxl__egc *egc, const char *which,
libxl__bootloader_state *bl, int ondisplay,
int rc, int onwrite, int errnoval)
{
STATE_AO_GC(bl->ao);
if (errnoval==-1) {
/* POLLHUP */
if (!!ondisplay != !!onwrite) {
rc = 0;
bl->got_pollhup = 1;
} else {
LOG(ERROR, "unexpected POLLHUP on %s", which);
}
}
if (!rc) {
LOG(ERROR, "unexpected eof copying %s", which);
rc = ERROR_FAIL;
}
bootloader_stop(egc, bl, rc);
}
static void bootloader_keystrokes_copyfail(libxl__egc *egc,
libxl__datacopier_state *dc, int rc, int onwrite, int errnoval)
{
libxl__bootloader_state *bl = CONTAINER_OF(dc, *bl, keystrokes);
bootloader_copyfail(egc, "bootloader input", bl, 0, rc,onwrite,errnoval);
}
static void bootloader_display_copyfail(libxl__egc *egc,
libxl__datacopier_state *dc, int rc, int onwrite, int errnoval)
{
libxl__bootloader_state *bl = CONTAINER_OF(dc, *bl, display);
bootloader_copyfail(egc, "bootloader output", bl, 1, rc,onwrite,errnoval);
}
static void bootloader_domaindeath(libxl__egc *egc,
libxl__domaindeathcheck *dc,
int rc)
{
libxl__bootloader_state *bl = CONTAINER_OF(dc, *bl, deathcheck);
bootloader_stop(egc, bl, rc);
}
static void bootloader_finished(libxl__egc *egc, libxl__ev_child *child,
pid_t pid, int status)
{
libxl__bootloader_state *bl = CONTAINER_OF(child, *bl, child);
STATE_AO_GC(bl->ao);
int rc;
libxl__datacopier_kill(&bl->keystrokes);
libxl__datacopier_kill(&bl->display);
if (status) {
if (bl->got_pollhup && WIFSIGNALED(status) && WTERMSIG(status)==SIGTERM)
LOG(ERROR, "got POLLHUP, sent SIGTERM");
LOG(ERROR, "bootloader failed - consult logfile %s", bl->logfile);
libxl_report_child_exitstatus(CTX, XTL_ERROR, "bootloader",
pid, status);
rc = ERROR_FAIL;
goto out;
} else {
LOG(DEBUG, "bootloader completed");
}
if (bl->rc) {
/* datacopier went wrong */
rc = bl->rc;
goto out;
}
rc = parse_bootloader_result(egc, bl);
if (rc) goto out;
rc = 0;
LOG(DEBUG, "bootloader execution successful");
out:
bootloader_callback(egc, bl, rc);
}
/*
* Local variables:
* mode: C
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/