Files
xen/tools/libs/light/libxl_dm.c
T
Anthony PERARD 1436593d5f libxl: Probe QEMU for -run-with user=user and use it
"-runas" is deprecated since QEMU 9.1 and will be remove in a future
release.

Signed-off-by: Anthony PERARD <anthony.perard@vates.tech>
Reviewed-by: Jason Andryuk <jason.andryuk@amd.com>
2024-08-30 18:14:11 +01:00

4101 lines
145 KiB
C

/*
* Copyright (C) 2010 Citrix Ltd.
* Author Vincent Hanquez <vincent.hanquez@eu.citrix.com>
* Author Stefano Stabellini <stefano.stabellini@eu.citrix.com>
* Author Gianni Tedesco <gianni.tedesco@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. with the special
* exception on linking described in file LICENSE.
*
* 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 "libxl_internal.h"
#include <xen/hvm/e820.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
static const char *libxl_tapif_script(libxl__gc *gc)
{
#if defined(__linux__) || defined(__FreeBSD__)
return libxl__strdup(gc, "no");
#else
return GCSPRINTF("%s/qemu-ifup", libxl__xen_script_dir_path());
#endif
}
const char *libxl__device_model_savefile(libxl__gc *gc, uint32_t domid)
{
return GCSPRINTF(LIBXL_DEVICE_MODEL_SAVE_FILE".%d", domid);
}
static const char *qemu_xen_path(libxl__gc *gc)
{
return QEMU_XEN_PATH;
}
static int libxl__create_qemu_logfile(libxl__gc *gc, char *name)
{
char *logfile;
int rc, logfile_w;
rc = libxl_create_logfile(CTX, name, &logfile);
if (rc) return rc;
logfile_w = open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0644);
if (logfile_w < 0) {
LOGE(ERROR, "unable to open Qemu logfile: %s", logfile);
free(logfile);
return ERROR_FAIL;
}
free(logfile);
return logfile_w;
}
/*
* userlookup_helper_getpwnam(libxl__gc*, const char *user,
* struct passwd **pwd_r);
*
* userlookup_helper_getpwuid(libxl__gc*, uid_t uid,
* struct passwd **pwd_r);
*
* If the user is found, return 0 and set *pwd_r to the appropriat
* value.
*
* If the user is not found but there are no errors, return 0
* and set *pwd_r to NULL.
*
* On error, return a libxl-style error code.
*/
#define DEFINE_USERLOOKUP_HELPER(NAME,SPEC_TYPE,STRUCTNAME,SYSCONF) \
int userlookup_helper_##NAME(libxl__gc *gc, \
SPEC_TYPE spec, \
struct STRUCTNAME *resultbuf, \
struct STRUCTNAME **out) \
{ \
struct STRUCTNAME *resultp = NULL; \
char *buf = NULL; \
long buf_size; \
int r; \
\
buf_size = sysconf(SYSCONF); \
if (buf_size < 0) { \
buf_size = 2048; \
LOG(DEBUG, \
"sysconf failed, setting the initial buffer size to %ld", \
buf_size); \
} \
\
while (1) { \
buf = libxl__realloc(gc, buf, buf_size); \
r = NAME##_r(spec, resultbuf, buf, buf_size, &resultp); \
if (r == ERANGE) { \
buf_size += 128; \
continue; \
} \
if (r != 0) { \
LOGEV(ERROR, r, "Looking up username/uid with " #NAME); \
return ERROR_FAIL; \
} \
*out = resultp; \
return 0; \
} \
}
DEFINE_USERLOOKUP_HELPER(getpwnam, const char*, passwd, _SC_GETPW_R_SIZE_MAX);
DEFINE_USERLOOKUP_HELPER(getpwuid, uid_t, passwd, _SC_GETPW_R_SIZE_MAX);
static int libxl__domain_get_device_model_uid(libxl__gc *gc,
libxl__dm_spawn_state *dmss)
{
int guest_domid = dmss->guest_domid;
libxl__domain_build_state *const state = dmss->build_state;
const libxl_domain_build_info *b_info = &dmss->guest_config->b_info;
struct passwd *user_base, user_pwbuf;
int rc;
char *user;
uid_t intended_uid = -1;
bool kill_by_uid;
/* Only qemu-upstream can run as a different uid */
if (b_info->device_model_version != LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN)
return 0;
/*
* From this point onward, all paths should go through the `out`
* label. The invariants should be:
* - rc may be 0, or an error code.
* - if rc is an error code, user and intended_uid are ignored.
* - if rc is 0, user may be set or not set.
* - if user is set, then intended_uid must be set to a UID matching
* the username `user`, and kill_by_uid must be set to the appropriate
* value. intended_uid will be checked for root (0).
*/
/*
* If device_model_user is present, set `-runas` even if
* dm_restrict isn't in use
*/
user = b_info->device_model_user;
if (user) {
rc = userlookup_helper_getpwnam(gc, user, &user_pwbuf, &user_base);
if (rc) {
kill_by_uid = false;
goto out;
}
if (!user_base) {
LOGD(ERROR, guest_domid, "Couldn't find device_model_user %s",
user);
rc = ERROR_INVAL;
kill_by_uid = false;
goto out;
}
intended_uid = user_base->pw_uid;
kill_by_uid = true;
rc = 0;
goto out;
}
/*
* If dm_restrict isn't set, and we don't have a specified user, don't
* bother setting a `-runas` parameter.
*/
if (!libxl_defbool_val(b_info->dm_restrict)) {
LOGD(DEBUG, guest_domid,
"dm_restrict disabled, starting QEMU as root");
user = NULL; /* Should already be null, but just in case */
kill_by_uid = false; /* Keep older versions of gcc happy */
rc = 0;
goto out;
}
/*
* dm_restrict is set, but device_model_user isn't set; look for
* QEMU_USER_BASE_RANGE
*/
rc = userlookup_helper_getpwnam(gc, LIBXL_QEMU_USER_RANGE_BASE,
&user_pwbuf, &user_base);
if (rc) {
kill_by_uid = false;
goto out;
}
if (user_base) {
struct passwd *user_clash, user_clash_pwbuf;
intended_uid = user_base->pw_uid + guest_domid;
rc = userlookup_helper_getpwuid(gc, intended_uid,
&user_clash_pwbuf, &user_clash);
if (rc) {
kill_by_uid = false;
goto out;
}
if (user_clash) {
LOGD(ERROR, guest_domid,
"wanted to use uid %ld (%s + %d) but that is user %s !",
(long)intended_uid, LIBXL_QEMU_USER_RANGE_BASE,
guest_domid, user_clash->pw_name);
rc = ERROR_INVAL;
kill_by_uid = false;
goto out;
}
LOGD(DEBUG, guest_domid, "using uid %ld", (long)intended_uid);
user = GCSPRINTF("%ld:%ld", (long)intended_uid,
(long)user_base->pw_gid);
kill_by_uid = true;
rc = 0;
goto out;
}
/*
* We couldn't find QEMU_USER_BASE_RANGE; look for
* QEMU_USER_SHARED. NB for QEMU_USER_SHARED, all QEMU will run
* as the same UID, we can't kill by uid; therefore don't set uid.
*/
user = LIBXL_QEMU_USER_SHARED;
rc = userlookup_helper_getpwnam(gc, user, &user_pwbuf, &user_base);
if (rc) {
kill_by_uid = false;
goto out;
}
if (user_base) {
LOGD(WARN, guest_domid, "Could not find user %s, falling back to %s",
LIBXL_QEMU_USER_RANGE_BASE, LIBXL_QEMU_USER_SHARED);
intended_uid = user_base->pw_uid;
kill_by_uid = false;
rc = 0;
goto out;
}
/*
* dm_depriv is set, but we can't find a non-root uid to run as;
* fail domain creation
*/
LOGD(ERROR, guest_domid,
"Could not find user %s or range base pseudo-user %s, cannot restrict",
LIBXL_QEMU_USER_SHARED, LIBXL_QEMU_USER_RANGE_BASE);
rc = ERROR_INVAL;
kill_by_uid = false;
out:
/* First, do a root check if appropriate */
if (!rc) {
if (user && intended_uid == 0) {
LOGD(ERROR, guest_domid, "intended_uid is 0 (root)!");
rc = ERROR_INVAL;
}
}
/* Then do the final set, if still appropriate */
if (!rc && user) {
state->dm_runas = user;
if (kill_by_uid)
state->dm_kill_uid = GCSPRINTF("%ld", (long)intended_uid);
}
return rc;
}
/*
* Look up "reaper UID". If present and non-root, returns 0 and sets
* reaper_uid. Otherwise returns libxl-style error.
*/
static int libxl__get_reaper_uid(libxl__gc *gc, uid_t *reaper_uid)
{
struct passwd *user_base, user_pwbuf;
int rc;
rc = userlookup_helper_getpwnam(gc, LIBXL_QEMU_USER_REAPER,
&user_pwbuf, &user_base);
/*
* Either there was an error, or we found a suitable user; stop
* looking
*/
if (rc || user_base)
goto out;
rc = userlookup_helper_getpwnam(gc, LIBXL_QEMU_USER_RANGE_BASE,
&user_pwbuf, &user_base);
if (rc || user_base)
goto out;
LOG(WARN, "Couldn't find uid for reaper process");
rc = ERROR_INVAL;
out:
/* First check to see if the discovered user maps to root */
if (!rc) {
if (user_base->pw_uid == 0) {
LOG(ERROR, "UID for reaper process maps to root!");
rc = ERROR_INVAL;
}
}
/* If everything is OK, set reaper_uid as appropriate */
if (!rc)
*reaper_uid = user_base->pw_uid;
return rc;
}
const char *libxl__domain_device_model(libxl__gc *gc,
const libxl_domain_build_info *info)
{
const char *dm;
if (libxl_defbool_val(info->device_model_stubdomain))
return NULL;
if (info->device_model) {
dm = libxl__strdup(gc, info->device_model);
} else {
switch (info->device_model_version) {
case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL:
dm = libxl__abs_path(gc, "qemu-dm", libxl__private_bindir_path());
break;
case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN:
dm = qemu_xen_path(gc);
break;
default:
LOG(ERROR, "invalid device model version %d",
info->device_model_version);
dm = NULL;
break;
}
}
return dm;
}
static int
libxl__xc_device_get_rdm(libxl__gc *gc,
uint32_t flags,
uint16_t seg,
uint8_t bus,
uint8_t devfn,
unsigned int *nr_entries,
struct xen_reserved_device_memory **xrdm)
{
int rc = 0, r;
/*
* We really can't presume how many entries we can get in advance.
*/
*nr_entries = 0;
r = xc_reserved_device_memory_map(CTX->xch, flags, seg, bus, devfn,
NULL, nr_entries);
assert(r <= 0);
/* "0" means we have no any rdm entry. */
if (!r) goto out;
if (errno != ENOBUFS) {
rc = ERROR_FAIL;
goto out;
}
GCNEW_ARRAY(*xrdm, *nr_entries);
r = xc_reserved_device_memory_map(CTX->xch, flags, seg, bus, devfn,
*xrdm, nr_entries);
if (r)
rc = ERROR_FAIL;
out:
if (rc) {
*nr_entries = 0;
*xrdm = NULL;
LOG(ERROR, "Could not get reserved device memory maps.");
}
return rc;
}
/*
* Check whether there exists rdm hole in the specified memory range.
* Returns true if exists, else returns false.
*/
static bool overlaps_rdm(uint64_t start, uint64_t memsize,
uint64_t rdm_start, uint64_t rdm_size)
{
return (start + memsize > rdm_start) && (start < rdm_start + rdm_size);
}
static void
add_rdm_entry(libxl__gc *gc, libxl_domain_config *d_config,
uint64_t rdm_start, uint64_t rdm_size, int rdm_policy)
{
d_config->rdms = libxl__realloc(NOGC, d_config->rdms,
(d_config->num_rdms+1) * sizeof(libxl_device_rdm));
d_config->rdms[d_config->num_rdms].start = rdm_start;
d_config->rdms[d_config->num_rdms].size = rdm_size;
d_config->rdms[d_config->num_rdms].policy = rdm_policy;
d_config->num_rdms++;
}
/*
* Check reported RDM regions and handle potential gfn conflicts according
* to user preferred policy.
*
* RDM can reside in address space beyond 4G theoretically, but we never
* see this in real world. So in order to avoid breaking highmem layout
* we don't solve highmem conflict. Note this means highmem rmrr could
* still be supported if no conflict.
*
* But in the case of lowmem, RDM probably scatter the whole RAM space.
* Especially multiple RDM entries would worsen this to lead a complicated
* memory layout. And then its hard to extend hvm_info_table{} to work
* hvmloader out. So here we're trying to figure out a simple solution to
* avoid breaking existing layout. So when a conflict occurs,
*
* #1. Above a predefined boundary (default 2G)
* - Move lowmem_end below reserved region to solve conflict;
*
* #2. Below a predefined boundary (default 2G)
* - Check strict/relaxed policy.
* "strict" policy leads to fail libxl.
* "relaxed" policy issue a warning message and also mask this entry
* INVALID to indicate we shouldn't expose this entry to hvmloader.
* Note when both policies are specified on a given region, the per-device
* policy should override the global policy.
*/
int libxl__domain_device_construct_rdm(libxl__gc *gc,
libxl_domain_config *d_config,
uint64_t rdm_mem_boundary,
struct xc_dom_image *dom)
{
int i, j, conflict, rc;
struct xen_reserved_device_memory *xrdm = NULL;
uint32_t strategy = d_config->b_info.u.hvm.rdm.strategy;
uint16_t seg;
uint8_t bus, devfn;
uint64_t rdm_start, rdm_size;
uint64_t highmem_end = dom->highmem_end;
/*
* We just want to construct RDM once since RDM is specific to the
* given platform, so this shouldn't change again.
*/
if (d_config->num_rdms)
return 0;
/* Might not expose rdm. */
if (strategy == LIBXL_RDM_RESERVE_STRATEGY_IGNORE &&
!d_config->num_pcidevs)
return 0;
/* Query all RDM entries in this platform */
if (strategy == LIBXL_RDM_RESERVE_STRATEGY_HOST) {
unsigned int nr_entries;
/* Collect all rdm info if exist. */
rc = libxl__xc_device_get_rdm(gc, XENMEM_RDM_ALL,
0, 0, 0, &nr_entries, &xrdm);
if (rc)
goto out;
if (!nr_entries)
return 0;
assert(xrdm);
for (i = 0; i < nr_entries; i++)
{
add_rdm_entry(gc, d_config,
pfn_to_paddr(xrdm[i].start_pfn),
pfn_to_paddr(xrdm[i].nr_pages),
d_config->b_info.u.hvm.rdm.policy);
}
}
/* Query RDM entries per-device */
for (i = 0; i < d_config->num_pcidevs; i++) {
unsigned int n, nr_entries;
seg = d_config->pcidevs[i].domain;
bus = d_config->pcidevs[i].bus;
devfn = PCI_DEVFN(d_config->pcidevs[i].dev,
d_config->pcidevs[i].func);
nr_entries = 0;
rc = libxl__xc_device_get_rdm(gc, 0,
seg, bus, devfn, &nr_entries, &xrdm);
if (rc)
goto out;
/* No RDM to associated with this device. */
if (!nr_entries)
continue;
assert(xrdm);
rc = libxl__device_pci_setdefault(gc, DOMID_INVALID,
&d_config->pcidevs[i], false);
if (rc)
goto out;
for (n = 0; n < nr_entries; ++n) {
bool new = true;
/*
* Need to check whether this entry is already saved in the
* array. This could come from two cases:
*
* - user may configure to get all RDMs in this platform,
* which is already queried before this point
* - or two assigned devices may share one RDM entry
*
* Different policies may be configured on the same RDM due to
* above two cases. But we don't allow to assign such a group
* of devices right now so it doesn't come true in our case.
*/
for (j = 0; j < d_config->num_rdms; j++) {
if (d_config->rdms[j].start
== pfn_to_paddr(xrdm[n].start_pfn))
{
/*
* So the per-device policy always override the
* global policy in this case.
*/
d_config->rdms[j].policy
= d_config->pcidevs[i].rdm_policy;
new = false;
break;
}
}
if (new)
add_rdm_entry(gc, d_config,
pfn_to_paddr(xrdm[n].start_pfn),
pfn_to_paddr(xrdm[n].nr_pages),
d_config->pcidevs[i].rdm_policy);
}
}
/*
* Next step is to check and avoid potential conflict between RDM
* entries and guest RAM. To avoid intrusive impact to existing
* memory layout {lowmem, mmio, highmem} which is passed around
* various function blocks, below conflicts are not handled which
* are rare and handling them would lead to a more scattered
* layout:
* - RDM in highmem area (>4G)
* - RDM lower than a defined memory boundary (e.g. 2G)
* Otherwise for conflicts between boundary and 4G, we'll simply
* move lowmem end below reserved region to solve conflict.
*
* If a conflict is detected on a given RDM entry, an error will
* be returned if 'strict' policy is specified. Instead, if
* 'relaxed' policy specified, this conflict is treated just as a
* warning, but we mark this RDM entry as INVALID to indicate that
* this entry shouldn't be exposed to hvmloader.
*
* Firstly we should check the case of rdm < 4G because we may
* need to expand highmem_end.
*/
for (i = 0; i < d_config->num_rdms; i++) {
rdm_start = d_config->rdms[i].start;
rdm_size = d_config->rdms[i].size;
conflict = overlaps_rdm(0, dom->lowmem_end, rdm_start, rdm_size);
if (!conflict)
continue;
/* Just check if RDM > our memory boundary. */
if (rdm_start > rdm_mem_boundary) {
/*
* For HAP guests round down to a 2Mb boundary to allow use
* of large page mappings.
*/
if (libxl_defbool_val(d_config->c_info.hap)
&& rdm_start > MB(2))
rdm_start &= ~(MB(2) - 1);
/*
* We will move downwards lowmem_end so we have to expand
* highmem_end.
*/
if (!highmem_end)
highmem_end = 1ull << 32;
highmem_end += (dom->lowmem_end - rdm_start);
/* Now move downwards lowmem_end. */
dom->lowmem_end = rdm_start;
}
}
/* Sync highmem_end. */
dom->highmem_end = highmem_end;
/*
* Finally we can take same policy to check lowmem(< 2G) and
* highmem adjusted above.
*/
for (i = 0; i < d_config->num_rdms; i++) {
rdm_start = d_config->rdms[i].start;
rdm_size = d_config->rdms[i].size;
/* Does this entry conflict with lowmem? */
conflict = overlaps_rdm(0, dom->lowmem_end,
rdm_start, rdm_size);
/* Does this entry conflict with highmem? */
if (highmem_end)
conflict |= overlaps_rdm((1ULL << 32),
highmem_end - (1ULL << 32),
rdm_start, rdm_size);
if (!conflict)
continue;
if (d_config->rdms[i].policy == LIBXL_RDM_RESERVE_POLICY_STRICT) {
LOG(ERROR, "RDM conflict at 0x%"PRIx64".\n",
d_config->rdms[i].start);
goto out;
} else {
LOG(WARN, "Ignoring RDM conflict at 0x%"PRIx64".\n",
d_config->rdms[i].start);
/*
* Then mask this INVALID to indicate we shouldn't expose this
* to hvmloader.
*/
d_config->rdms[i].policy = LIBXL_RDM_RESERVE_POLICY_INVALID;
}
}
return 0;
out:
return ERROR_FAIL;
}
/* XSA-180 / CVE-2014-3672
*
* The QEMU shipped with Xen has a bodge. It checks for
* XEN_QEMU_CONSOLE_LIMIT to see how much data QEMU is allowed
* to write to stderr. We set that to 1MB if it is not set by
* system administrator.
*/
static void libxl__set_qemu_env_for_xsa_180(libxl__gc *gc,
flexarray_t *dm_envs)
{
if (getenv("XEN_QEMU_CONSOLE_LIMIT")) return;
flexarray_append_pair(dm_envs, "XEN_QEMU_CONSOLE_LIMIT", "1048576");
}
const libxl_vnc_info *libxl__dm_vnc(const libxl_domain_config *guest_config)
{
const libxl_vnc_info *vnc = NULL;
if (guest_config->b_info.type == LIBXL_DOMAIN_TYPE_HVM) {
vnc = &guest_config->b_info.u.hvm.vnc;
} else if (guest_config->num_vfbs > 0) {
vnc = &guest_config->vfbs[0].vnc;
}
return vnc && libxl_defbool_val(vnc->enable) ? vnc : NULL;
}
static const libxl_sdl_info *dm_sdl(const libxl_domain_config *guest_config)
{
const libxl_sdl_info *sdl = NULL;
if (guest_config->b_info.type == LIBXL_DOMAIN_TYPE_HVM) {
sdl = &guest_config->b_info.u.hvm.sdl;
} else if (guest_config->num_vfbs > 0) {
sdl = &guest_config->vfbs[0].sdl;
}
return sdl && libxl_defbool_val(sdl->enable) ? sdl : NULL;
}
static const char *dm_keymap(const libxl_domain_config *guest_config)
{
if (guest_config->b_info.type == LIBXL_DOMAIN_TYPE_HVM) {
return guest_config->b_info.u.hvm.keymap;
} else if (guest_config->num_vfbs > 0) {
return guest_config->vfbs[0].keymap;
} else
return NULL;
}
static int libxl__build_device_model_args_old(libxl__gc *gc,
const char *dm, int domid,
const libxl_domain_config *guest_config,
char ***args, char ***envs,
const libxl__domain_build_state *state)
{
const libxl_domain_create_info *c_info = &guest_config->c_info;
const libxl_domain_build_info *b_info = &guest_config->b_info;
const libxl_device_nic *nics = guest_config->nics;
const libxl_vnc_info *vnc = libxl__dm_vnc(guest_config);
const libxl_sdl_info *sdl = dm_sdl(guest_config);
const int num_nics = guest_config->num_nics;
const char *keymap = dm_keymap(guest_config);
int i;
flexarray_t *dm_args, *dm_envs;
dm_args = flexarray_make(gc, 16, 1);
dm_envs = flexarray_make(gc, 16, 1);
assert(state->dm_monitor_fd == -1);
libxl__set_qemu_env_for_xsa_180(gc, dm_envs);
flexarray_vappend(dm_args, dm,
"-d", GCSPRINTF("%d", domid), NULL);
if (c_info->name)
flexarray_vappend(dm_args, "-domain-name", c_info->name, NULL);
if (vnc) {
char *vncarg = NULL;
flexarray_append(dm_args, "-vnc");
/*
* If vnc->listen is present and contains a :, and
* - vnc->display is 0, use vnc->listen
* - vnc->display is non-zero, be confused
* If vnc->listen is present but doesn't, use vnc->listen:vnc->display.
* If vnc->listen is not present, use 127.0.0.1:vnc->display
* (Remembering that vnc->display already defaults to 0.)
*/
if (vnc->listen) {
if (strchr(vnc->listen, ':') != NULL) {
if (vnc->display) {
LOGD(ERROR, domid, "vncdisplay set, vnclisten contains display");
return ERROR_INVAL;
}
vncarg = vnc->listen;
} else {
vncarg = GCSPRINTF("%s:%d", vnc->listen, vnc->display);
}
} else
vncarg = GCSPRINTF("127.0.0.1:%d", vnc->display);
if (vnc->passwd && vnc->passwd[0]) {
vncarg = GCSPRINTF("%s,password", vncarg);
}
flexarray_append(dm_args, vncarg);
if (libxl_defbool_val(vnc->findunused)) {
flexarray_append(dm_args, "-vncunused");
}
} else if (!sdl) {
/*
* VNC is not enabled by default by qemu-xen-traditional,
* however skipping -vnc causes SDL to be
* (unexpectedly) enabled by default. If undesired, disable graphics at
* all.
*/
flexarray_append(dm_args, "-nographic");
}
if (sdl) {
flexarray_append(dm_args, "-sdl");
if (!libxl_defbool_val(sdl->opengl)) {
flexarray_append(dm_args, "-disable-opengl");
}
if (sdl->display)
flexarray_append_pair(dm_envs, "DISPLAY", sdl->display);
if (sdl->xauthority)
flexarray_append_pair(dm_envs, "XAUTHORITY", sdl->xauthority);
}
if (keymap) {
flexarray_vappend(dm_args, "-k", keymap, NULL);
}
if (b_info->type == LIBXL_DOMAIN_TYPE_HVM) {
int ioemu_nics = 0;
int nr_set_cpus = 0;
char *s;
flexarray_append_pair(dm_envs, "XEN_DOMAIN_ID", GCSPRINTF("%d", domid));
if (b_info->kernel) {
LOGD(ERROR, domid, "HVM direct kernel boot is not supported by "
"qemu-xen-traditional");
return ERROR_INVAL;
}
if (b_info->u.hvm.serial || b_info->u.hvm.serial_list) {
if ( b_info->u.hvm.serial && b_info->u.hvm.serial_list )
{
LOGD(ERROR, domid, "Both serial and serial_list set");
return ERROR_INVAL;
}
if (b_info->u.hvm.serial) {
flexarray_vappend(dm_args,
"-serial", b_info->u.hvm.serial, NULL);
} else if (b_info->u.hvm.serial_list) {
char **p;
for (p = b_info->u.hvm.serial_list;
*p;
p++) {
flexarray_vappend(dm_args,
"-serial",
*p, NULL);
}
}
}
if (libxl_defbool_val(b_info->u.hvm.nographic) && (!sdl && !vnc)) {
flexarray_append(dm_args, "-nographic");
}
if (b_info->video_memkb) {
flexarray_vappend(dm_args, "-videoram",
GCSPRINTF("%d", libxl__sizekb_to_mb(b_info->video_memkb)),
NULL);
}
switch (b_info->u.hvm.vga.kind) {
case LIBXL_VGA_INTERFACE_TYPE_STD:
flexarray_append(dm_args, "-std-vga");
break;
case LIBXL_VGA_INTERFACE_TYPE_CIRRUS:
break;
case LIBXL_VGA_INTERFACE_TYPE_NONE:
flexarray_append_pair(dm_args, "-vga", "none");
break;
case LIBXL_VGA_INTERFACE_TYPE_QXL:
break;
default:
LOGD(ERROR, domid, "Invalid emulated video card specified");
return ERROR_INVAL;
}
if (b_info->u.hvm.boot) {
flexarray_vappend(dm_args, "-boot", b_info->u.hvm.boot, NULL);
}
if (libxl_defbool_val(b_info->u.hvm.usb)
|| b_info->u.hvm.usbdevice
|| libxl_string_list_length(&b_info->u.hvm.usbdevice_list)) {
if (b_info->u.hvm.usbdevice
&& libxl_string_list_length(&b_info->u.hvm.usbdevice_list)) {
LOGD(ERROR, domid, "Both usbdevice and usbdevice_list set");
return ERROR_INVAL;
}
flexarray_append(dm_args, "-usb");
if (b_info->u.hvm.usbdevice) {
flexarray_vappend(dm_args,
"-usbdevice", b_info->u.hvm.usbdevice, NULL);
} else if (b_info->u.hvm.usbdevice_list) {
char **p;
for (p = b_info->u.hvm.usbdevice_list;
*p;
p++) {
flexarray_vappend(dm_args,
"-usbdevice",
*p, NULL);
}
}
}
if (b_info->u.hvm.soundhw) {
flexarray_vappend(dm_args, "-soundhw", b_info->u.hvm.soundhw, NULL);
}
if (libxl__acpi_defbool_val(b_info)) {
flexarray_append(dm_args, "-acpi");
}
if (b_info->max_vcpus > 1) {
flexarray_vappend(dm_args, "-vcpus",
GCSPRINTF("%d", b_info->max_vcpus),
NULL);
}
nr_set_cpus = libxl_bitmap_count_set(&b_info->avail_vcpus);
s = libxl_bitmap_to_hex_string(CTX, &b_info->avail_vcpus);
flexarray_vappend(dm_args, "-vcpu_avail",
GCSPRINTF("%s", s), NULL);
free(s);
for (i = 0; i < num_nics; i++) {
if (nics[i].nictype == LIBXL_NIC_TYPE_VIF_IOEMU) {
char *smac = GCSPRINTF(
LIBXL_MAC_FMT, LIBXL_MAC_BYTES(nics[i].mac));
const char *ifname = libxl__device_nic_devname(gc,
domid, nics[i].devid,
LIBXL_NIC_TYPE_VIF_IOEMU);
flexarray_vappend(dm_args,
"-net",
GCSPRINTF(
"nic,vlan=%d,macaddr=%s,model=%s",
nics[i].devid, smac, nics[i].model),
"-net",
GCSPRINTF(
"tap,vlan=%d,ifname=%s,bridge=%s,"
"script=%s,downscript=%s",
nics[i].devid, ifname, nics[i].bridge,
libxl_tapif_script(gc),
libxl_tapif_script(gc)),
NULL);
ioemu_nics++;
}
}
/* If we have no emulated nics, tell qemu not to create any */
if ( ioemu_nics == 0 ) {
flexarray_vappend(dm_args, "-net", "none", NULL);
}
if (libxl_defbool_val(b_info->u.hvm.gfx_passthru)) {
switch (b_info->u.hvm.gfx_passthru_kind) {
case LIBXL_GFX_PASSTHRU_KIND_DEFAULT:
case LIBXL_GFX_PASSTHRU_KIND_IGD:
flexarray_append(dm_args, "-gfx_passthru");
break;
default:
LOGD(ERROR, domid, "unsupported gfx_passthru_kind.");
return ERROR_INVAL;
}
}
} else {
if (!sdl && !vnc)
flexarray_append(dm_args, "-nographic");
}
if (libxl_defbool_val(b_info->dm_restrict)) {
LOGD(ERROR, domid,
"dm_restrict not supported by qemu-xen-traditional");
return ERROR_INVAL;
}
if (state->saved_state) {
flexarray_vappend(dm_args, "-loadvm", state->saved_state, NULL);
}
for (i = 0; b_info->extra && b_info->extra[i] != NULL; i++)
flexarray_append(dm_args, b_info->extra[i]);
flexarray_append(dm_args, "-M");
switch (b_info->type) {
case LIBXL_DOMAIN_TYPE_PVH:
case LIBXL_DOMAIN_TYPE_PV:
flexarray_append(dm_args, "xenpv");
for (i = 0; b_info->extra_pv && b_info->extra_pv[i] != NULL; i++)
flexarray_append(dm_args, b_info->extra_pv[i]);
break;
case LIBXL_DOMAIN_TYPE_HVM:
flexarray_append(dm_args, "xenfv");
for (i = 0; b_info->extra_hvm && b_info->extra_hvm[i] != NULL; i++)
flexarray_append(dm_args, b_info->extra_hvm[i]);
break;
default:
abort();
}
flexarray_append(dm_args, NULL);
*args = (char **) flexarray_contents(dm_args);
flexarray_append(dm_envs, NULL);
if (envs)
*envs = (char **) flexarray_contents(dm_envs);
return 0;
}
static char *dm_spice_options(libxl__gc *gc,
const libxl_spice_info *spice)
{
char *opt;
if (!spice->port && !spice->tls_port) {
LOG(ERROR,
"at least one of the spiceport or tls_port must be provided");
return NULL;
}
if (!libxl_defbool_val(spice->disable_ticketing)) {
if (!spice->passwd) {
LOG(ERROR, "spice ticketing is enabled but missing password");
return NULL;
}
else if (!spice->passwd[0]) {
LOG(ERROR, "spice password can't be empty");
return NULL;
}
}
opt = GCSPRINTF("port=%d,tls-port=%d", spice->port, spice->tls_port);
if (spice->host)
opt = GCSPRINTF("%s,addr=%s", opt, spice->host);
if (libxl_defbool_val(spice->disable_ticketing))
opt = GCSPRINTF("%s,disable-ticketing=on", opt);
else
opt = GCSPRINTF("%s,password=%s", opt, spice->passwd);
opt = GCSPRINTF("%s,agent-mouse=%s", opt,
libxl_defbool_val(spice->agent_mouse) ? "on" : "off");
if (!libxl_defbool_val(spice->clipboard_sharing))
opt = GCSPRINTF("%s,disable-copy-paste=on", opt);
if (spice->image_compression)
opt = GCSPRINTF("%s,image-compression=%s", opt,
spice->image_compression);
if (spice->streaming_video)
opt = GCSPRINTF("%s,streaming-video=%s", opt, spice->streaming_video);
return opt;
}
static enum libxl_gfx_passthru_kind
libxl__detect_gfx_passthru_kind(libxl__gc *gc,
const libxl_domain_config *guest_config)
{
const libxl_domain_build_info *b_info = &guest_config->b_info;
if (b_info->u.hvm.gfx_passthru_kind != LIBXL_GFX_PASSTHRU_KIND_DEFAULT)
return b_info->u.hvm.gfx_passthru_kind;
if (libxl__is_igd_vga_passthru(gc, guest_config)) {
return LIBXL_GFX_PASSTHRU_KIND_IGD;
}
return LIBXL_GFX_PASSTHRU_KIND_DEFAULT;
}
/* colo mode */
enum {
LIBXL__COLO_NONE = 0,
LIBXL__COLO_PRIMARY,
LIBXL__COLO_SECONDARY,
};
static char *qemu_disk_scsi_drive_string(libxl__gc *gc, const char *target_path,
int unit, const char *format,
const libxl_device_disk *disk,
int colo_mode, const char **id_ptr)
{
char *drive = NULL;
char *common = GCSPRINTF("if=none,readonly=%s,cache=writeback",
disk->readwrite ? "off" : "on");
const char *exportname = disk->colo_export;
const char *active_disk = disk->active_disk;
const char *hidden_disk = disk->hidden_disk;
const char *id;
switch (colo_mode) {
case LIBXL__COLO_NONE:
id = GCSPRINTF("scsi0-hd%d", unit);
drive = GCSPRINTF("file=%s,id=%s,format=%s,%s",
target_path, id, format, common);
break;
case LIBXL__COLO_PRIMARY:
id = exportname;
drive = GCSPRINTF(
"%s,id=%s,driver=quorum,"
"children.0.file.filename=%s,"
"children.0.driver=%s,"
"read-pattern=fifo,"
"vote-threshold=1",
common, id, target_path, format);
break;
case LIBXL__COLO_SECONDARY:
id = "top-colo";
drive = GCSPRINTF(
"%s,id=%s,driver=replication,"
"mode=secondary,"
"top-id=top-colo,"
"file.driver=qcow2,"
"file.file.filename=%s,"
"file.backing.driver=qcow2,"
"file.backing.file.filename=%s,"
"file.backing.backing=%s",
common, id, active_disk, hidden_disk, exportname);
break;
default:
abort();
}
*id_ptr = id;
return drive;
}
static char *qemu_disk_ide_drive_string(libxl__gc *gc, const char *target_path,
int unit, const char *format,
const libxl_device_disk *disk,
int colo_mode)
{
char *drive = NULL;
const char *exportname = disk->colo_export;
const char *active_disk = disk->active_disk;
const char *hidden_disk = disk->hidden_disk;
assert(disk->readwrite); /* should have been checked earlier */
switch (colo_mode) {
case LIBXL__COLO_NONE:
drive = GCSPRINTF
("file=%s,if=ide,index=%d,media=disk,format=%s,cache=writeback",
target_path, unit, format);
break;
case LIBXL__COLO_PRIMARY:
/*
* primary:
* -dirve if=ide,index=x,media=disk,cache=writeback,driver=quorum,\
* id=exportname,\
* children.0.file.filename=target_path,\
* children.0.driver=format,\
* read-pattern=fifo,\
* vote-threshold=1
*/
drive = GCSPRINTF(
"if=ide,index=%d,media=disk,cache=writeback,driver=quorum,"
"id=%s,"
"children.0.file.filename=%s,"
"children.0.driver=%s,"
"read-pattern=fifo,"
"vote-threshold=1",
unit, exportname, target_path, format);
break;
case LIBXL__COLO_SECONDARY:
/*
* secondary:
* -drive if=ide,index=x,media=disk,cache=writeback,driver=replication,\
* mode=secondary,\
* file.driver=qcow2,\
* file.file.filename=active_disk,\
* file.backing.driver=qcow2,\
* file.backing.file.filename=hidden_disk,\
* file.backing.backing=exportname,
*/
drive = GCSPRINTF(
"if=ide,index=%d,id=top-colo,media=disk,cache=writeback,"
"driver=replication,"
"mode=secondary,"
"top-id=top-colo,"
"file.driver=qcow2,"
"file.file.filename=%s,"
"file.backing.driver=qcow2,"
"file.backing.file.filename=%s,"
"file.backing.backing=%s",
unit, active_disk, hidden_disk, exportname);
break;
default:
abort();
}
return drive;
}
static int libxl__pre_open_qmp_socket(libxl__gc *gc, libxl_domid domid,
int *fd_r)
{
int rc, r;
int fd;
struct sockaddr_un un;
const char *path = libxl__qemu_qmp_path(gc, domid);
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
LOGED(ERROR, domid, "socket() failed");
return ERROR_FAIL;
}
rc = libxl__prepare_sockaddr_un(gc, &un, path, "QEMU's QMP socket");
if (rc)
goto out;
rc = libxl__remove_file(gc, path);
if (rc)
goto out;
r = bind(fd, (struct sockaddr *) &un, sizeof(un));
if (r < 0) {
LOGED(ERROR, domid, "bind('%s') failed", path);
rc = ERROR_FAIL;
goto out;
}
r = listen(fd, 1);
if (r < 0) {
LOGED(ERROR, domid, "listen() failed");
rc = ERROR_FAIL;
goto out;
}
*fd_r = fd;
rc = 0;
out:
if (rc && fd >= 0)
close(fd);
return rc;
}
static int libxl__build_device_model_args_new(libxl__gc *gc,
const char *dm, int guest_domid,
const libxl_domain_config *guest_config,
char ***args, char ***envs,
const libxl__domain_build_state *state,
const libxl__qemu_available_opts *qemu_opts,
int *dm_state_fd)
{
const libxl_domain_create_info *c_info = &guest_config->c_info;
const libxl_domain_build_info *b_info = &guest_config->b_info;
const libxl_device_disk *disks = guest_config->disks;
const libxl_device_nic *nics = guest_config->nics;
const int num_disks = guest_config->num_disks;
const int num_nics = guest_config->num_nics;
const libxl_vnc_info *vnc = libxl__dm_vnc(guest_config);
const libxl_sdl_info *sdl = dm_sdl(guest_config);
const char *keymap = dm_keymap(guest_config);
char *machinearg;
flexarray_t *dm_args, *dm_envs;
int i, connection, devid;
uint64_t ram_size;
const char *path, *chardev;
bool is_stubdom = libxl_defbool_val(b_info->device_model_stubdomain);
int rc;
dm_args = flexarray_make(gc, 16, 1);
dm_envs = flexarray_make(gc, 16, 1);
libxl__set_qemu_env_for_xsa_180(gc, dm_envs);
flexarray_vappend(dm_args, dm,
"-xen-domid",
GCSPRINTF("%d", guest_domid), NULL);
flexarray_append(dm_args, "-no-shutdown");
/*
* QMP access to qemu running in stubdomain is done over vchan. The
* stubdomain init script adds the appropriate monitor options for
* vchan-socket-proxy.
*/
if (!is_stubdom) {
flexarray_append(dm_args, "-chardev");
if (state->dm_monitor_fd >= 0) {
flexarray_append(dm_args,
GCSPRINTF("socket,id=libxl-cmd,fd=%d,server=on,wait=off",
state->dm_monitor_fd));
/*
* Start QEMU with its "CPU" paused, it will not start any emulation
* until the QMP command "cont" is used. This also prevent QEMU from
* writing "running" to the "state" xenstore node so we only use this
* flag when we have the QMP based startup notification.
* */
flexarray_append(dm_args, "-S");
} else {
flexarray_append(dm_args,
GCSPRINTF("socket,id=libxl-cmd,"
"path=%s,server=on,wait=off",
libxl__qemu_qmp_path(gc, guest_domid)));
}
flexarray_append(dm_args, "-mon");
flexarray_append(dm_args, "chardev=libxl-cmd,mode=control");
flexarray_append(dm_args, "-chardev");
flexarray_append(dm_args,
GCSPRINTF("socket,id=libxenstat-cmd,"
"path=%s/qmp-libxenstat-%d,server=on,wait=off",
libxl__run_dir_path(), guest_domid));
flexarray_append(dm_args, "-mon");
flexarray_append(dm_args, "chardev=libxenstat-cmd,mode=control");
}
for (i = 0; i < guest_config->num_channels; i++) {
connection = guest_config->channels[i].connection;
devid = guest_config->channels[i].devid;
switch (connection) {
case LIBXL_CHANNEL_CONNECTION_PTY:
chardev = GCSPRINTF("pty,id=libxl-channel%d", devid);
break;
case LIBXL_CHANNEL_CONNECTION_SOCKET:
path = guest_config->channels[i].u.socket.path;
chardev = GCSPRINTF("socket,id=libxl-channel%d,path=%s,"
"server=on,wait=off", devid, path);
break;
default:
/* We've forgotten to add the clause */
LOGD(ERROR, guest_domid, "%s: unknown channel connection %d",
__func__, connection);
return ERROR_INVAL;
}
flexarray_append(dm_args, "-chardev");
flexarray_append(dm_args, (void*)chardev);
}
/*
* Remove default devices created by qemu. Qemu will create only devices
* defined by xen, since the devices not defined by xen are not usable.
*/
flexarray_append(dm_args, "-nodefaults");
/*
* Do not use any of the user-provided config files in sysconfdir,
* avoiding unknown and uncontrolled configuration.
*/
flexarray_append(dm_args, "-no-user-config");
if (b_info->type != LIBXL_DOMAIN_TYPE_HVM) {
flexarray_append(dm_args, "-xen-attach");
}
if (c_info->name) {
flexarray_vappend(dm_args, "-name", c_info->name, NULL);
}
if (vnc && !is_stubdom) {
char *vncarg = NULL;
flexarray_append(dm_args, "-vnc");
/*
* If vnc->listen is present and contains a :, and
* - vnc->display is 0, use vnc->listen
* - vnc->display is non-zero, be confused
* If vnc->listen is present but doesn't, use vnc->listen:vnc->display.
* If vnc->listen is not present, use 127.0.0.1:vnc->display
* (Remembering that vnc->display already defaults to 0.)
*/
if (vnc->listen) {
if (strchr(vnc->listen, ':') != NULL) {
if (vnc->display) {
LOGD(ERROR, guest_domid,
"vncdisplay set, vnclisten contains display");
return ERROR_INVAL;
}
vncarg = vnc->listen;
} else {
vncarg = GCSPRINTF("%s:%d", vnc->listen, vnc->display);
}
} else
vncarg = GCSPRINTF("127.0.0.1:%d", vnc->display);
if (vnc->passwd && vnc->passwd[0]) {
vncarg = GCSPRINTF("%s,password=on", vncarg);
}
if (libxl_defbool_val(vnc->findunused)) {
/* This option asks to QEMU to try this number of port before to
* give up. So QEMU will try ports between $display and $display +
* 99. This option needs to be the last one of the vnc options. */
vncarg = GCSPRINTF("%s,to=99", vncarg);
}
flexarray_append(dm_args, vncarg);
} else if (!is_stubdom) {
/*
* Ensure that by default no vnc server is created.
*/
flexarray_append_pair(dm_args, "-vnc", "none");
}
/*
* Ensure that by default no display backend is created. Further
* options given below might then enable more.
*/
flexarray_append_pair(dm_args, "-display", "none");
if (sdl && !is_stubdom) {
flexarray_append_pair(dm_args, "-display", "sdl");
if (sdl->display)
flexarray_append_pair(dm_envs, "DISPLAY", sdl->display);
if (sdl->xauthority)
flexarray_append_pair(dm_envs, "XAUTHORITY", sdl->xauthority);
}
if (keymap) {
flexarray_vappend(dm_args, "-k", keymap, NULL);
}
if (b_info->type == LIBXL_DOMAIN_TYPE_HVM) {
int ioemu_nics = 0;
if (b_info->kernel)
flexarray_vappend(dm_args, "-kernel", b_info->kernel, NULL);
if (b_info->ramdisk)
flexarray_vappend(dm_args, "-initrd", b_info->ramdisk, NULL);
if (b_info->cmdline)
flexarray_vappend(dm_args, "-append", b_info->cmdline, NULL);
/* Find out early if one of the disk is on the scsi bus and add a scsi
* controller. This is done ahead to keep the same behavior as previous
* version of QEMU (have the controller on the same PCI slot). */
for (i = 0; i < num_disks; i++) {
if (disks[i].is_cdrom) {
continue;
}
if (strncmp(disks[i].vdev, "sd", 2) == 0) {
flexarray_vappend(dm_args, "-device", "lsi53c895a", NULL);
break;
}
}
if (b_info->u.hvm.serial || b_info->u.hvm.serial_list) {
if ( b_info->u.hvm.serial && b_info->u.hvm.serial_list )
{
LOGD(ERROR, guest_domid, "Both serial and serial_list set");
return ERROR_INVAL;
} else {
if (b_info->u.hvm.serial) {
if (is_stubdom) {
/* see spawn_stub_launch_dm() for connecting STUBDOM_CONSOLE_SERIAL */
flexarray_vappend(dm_args,
"-serial",
GCSPRINTF("/dev/hvc%d", STUBDOM_CONSOLE_SERIAL),
NULL);
} else {
flexarray_vappend(dm_args,
"-serial", b_info->u.hvm.serial, NULL);
}
} else if (b_info->u.hvm.serial_list) {
char **p;
/* see spawn_stub_launch_dm() for connecting STUBDOM_CONSOLE_SERIAL */
for (p = b_info->u.hvm.serial_list, i = 0;
*p;
p++, i++) {
if (is_stubdom)
flexarray_vappend(dm_args,
"-serial",
GCSPRINTF("/dev/hvc%d", STUBDOM_CONSOLE_SERIAL + i),
NULL);
else
flexarray_vappend(dm_args,
"-serial",
*p, NULL);
}
}
}
}
if (libxl_defbool_val(b_info->u.hvm.nographic) && (!sdl && !vnc)) {
flexarray_append(dm_args, "-nographic");
}
if (libxl_defbool_val(b_info->u.hvm.spice.enable) && !is_stubdom) {
const libxl_spice_info *spice = &b_info->u.hvm.spice;
char *spiceoptions = dm_spice_options(gc, spice);
if (!spiceoptions)
return ERROR_INVAL;
flexarray_append(dm_args, "-spice");
flexarray_append(dm_args, spiceoptions);
if (libxl_defbool_val(b_info->u.hvm.spice.vdagent)) {
flexarray_vappend(dm_args, "-device", "virtio-serial",
"-chardev", "spicevmc,id=vdagent,name=vdagent", "-device",
"virtserialport,chardev=vdagent,name=com.redhat.spice.0",
NULL);
}
}
switch (b_info->u.hvm.vga.kind) {
case LIBXL_VGA_INTERFACE_TYPE_STD:
flexarray_append_pair(dm_args, "-device",
GCSPRINTF("VGA,vgamem_mb=%d",
libxl__sizekb_to_mb(b_info->video_memkb)));
break;
case LIBXL_VGA_INTERFACE_TYPE_CIRRUS:
flexarray_append_pair(dm_args, "-device",
GCSPRINTF("cirrus-vga,vgamem_mb=%d",
libxl__sizekb_to_mb(b_info->video_memkb)));
break;
case LIBXL_VGA_INTERFACE_TYPE_NONE:
break;
case LIBXL_VGA_INTERFACE_TYPE_QXL:
/* QXL have 2 ram regions, ram and vram */
flexarray_append_pair(dm_args, "-device",
GCSPRINTF("qxl-vga,vram_size_mb=%"PRIu64",ram_size_mb=%"PRIu64,
(b_info->video_memkb/2/1024), (b_info->video_memkb/2/1024) ) );
break;
default:
LOGD(ERROR, guest_domid, "Invalid emulated video card specified");
return ERROR_INVAL;
}
if (b_info->u.hvm.boot) {
flexarray_vappend(dm_args, "-boot",
GCSPRINTF("order=%s", b_info->u.hvm.boot), NULL);
}
if (libxl_defbool_val(b_info->u.hvm.usb)
|| b_info->u.hvm.usbdevice
|| libxl_string_list_length(&b_info->u.hvm.usbdevice_list)) {
if (b_info->u.hvm.usbdevice
&& libxl_string_list_length(&b_info->u.hvm.usbdevice_list)) {
LOGD(ERROR, guest_domid, "Both usbdevice and usbdevice_list set");
return ERROR_INVAL;
}
flexarray_append(dm_args, "-usb");
if (b_info->u.hvm.usbdevice) {
flexarray_vappend(dm_args,
"-usbdevice", b_info->u.hvm.usbdevice, NULL);
} else if (b_info->u.hvm.usbdevice_list) {
char **p;
for (p = b_info->u.hvm.usbdevice_list;
*p;
p++) {
flexarray_vappend(dm_args,
"-usbdevice",
*p, NULL);
}
}
} else if (b_info->u.hvm.usbversion) {
switch (b_info->u.hvm.usbversion) {
case 1:
flexarray_vappend(dm_args,
"-device", "piix3-usb-uhci,id=usb", NULL);
break;
case 2:
flexarray_append_pair(dm_args, "-device",
"ich9-usb-ehci1,id=usb,addr=0x1d.0x7,multifunction=on");
for (i = 1; i < 4; i++)
flexarray_append_pair(dm_args, "-device",
GCSPRINTF("ich9-usb-uhci%d,masterbus=usb.0,"
"firstport=%d,addr=0x1d.%#x,multifunction=on",
i, 2*(i-1), i-1));
break;
case 3:
flexarray_vappend(dm_args,
"-device", "nec-usb-xhci,id=usb", NULL);
break;
default:
LOGD(ERROR, guest_domid, "usbversion parameter is invalid, "
"must be between 1 and 3");
return ERROR_INVAL;
}
if (b_info->u.hvm.spice.usbredirection >= 0 &&
b_info->u.hvm.spice.usbredirection < 5) {
for (i = 1; i <= b_info->u.hvm.spice.usbredirection; i++)
flexarray_vappend(dm_args, "-chardev",
GCSPRINTF("spicevmc,name=usbredir,id=usbrc%d", i),
"-device",
GCSPRINTF("usb-redir,chardev=usbrc%d,"
"id=usbrc%d", i, i), NULL);
} else {
LOGD(ERROR, guest_domid, "usbredirection parameter is invalid, "
"it must be between 1 and 4");
return ERROR_INVAL;
}
}
if (b_info->u.hvm.soundhw) {
libxl__qemu_soundhw soundhw;
rc = libxl__qemu_soundhw_from_string(b_info->u.hvm.soundhw, &soundhw);
if (rc) {
LOGD(ERROR, guest_domid, "Unknown soundhw option '%s'", b_info->u.hvm.soundhw);
return ERROR_INVAL;
}
switch (soundhw) {
case LIBXL__QEMU_SOUNDHW_HDA:
flexarray_vappend(dm_args, "-device", "intel-hda",
"-device", "hda-duplex", NULL);
break;
default:
flexarray_append_pair(dm_args, "-device",
(char*)libxl__qemu_soundhw_to_string(soundhw));
}
}
if (!libxl__acpi_defbool_val(b_info)) {
flexarray_append(dm_args, "-no-acpi");
}
if (b_info->max_vcpus > 1) {
flexarray_append(dm_args, "-smp");
if (b_info->avail_vcpus.size) {
int nr_set_cpus = 0;
nr_set_cpus = libxl_bitmap_count_set(&b_info->avail_vcpus);
flexarray_append(dm_args, GCSPRINTF("%d,maxcpus=%d",
nr_set_cpus,
b_info->max_vcpus));
} else
flexarray_append(dm_args, GCSPRINTF("%d", b_info->max_vcpus));
}
for (i = 0; i < num_nics; i++) {
if (nics[i].nictype == LIBXL_NIC_TYPE_VIF_IOEMU) {
char *smac = GCSPRINTF(LIBXL_MAC_FMT,
LIBXL_MAC_BYTES(nics[i].mac));
const char *ifname = libxl__device_nic_devname(gc,
guest_domid, nics[i].devid,
LIBXL_NIC_TYPE_VIF_IOEMU);
flexarray_append(dm_args, "-device");
flexarray_append(dm_args,
GCSPRINTF("%s,id=nic%d,netdev=net%d,mac=%s",
nics[i].model, nics[i].devid,
nics[i].devid, smac));
flexarray_append(dm_args, "-netdev");
flexarray_append(dm_args,
GCSPRINTF("type=tap,id=net%d,ifname=%s,"
"br=%s,"
"script=%s,downscript=%s",
nics[i].devid, ifname,
nics[i].bridge,
libxl_tapif_script(gc),
libxl_tapif_script(gc)));
/* Userspace COLO Proxy need this */
#define APPEND_COLO_SOCK_SERVER(sock_id, sock_ip, sock_port) ({ \
if (nics[i].colo_##sock_id && \
nics[i].colo_##sock_ip && \
nics[i].colo_##sock_port) { \
flexarray_append(dm_args, "-chardev"); \
flexarray_append(dm_args, \
GCSPRINTF("socket,id=%s,host=%s,port=%s,server=on,wait=off", \
nics[i].colo_##sock_id, \
nics[i].colo_##sock_ip, \
nics[i].colo_##sock_port)); \
} \
})
#define APPEND_COLO_SOCK_CLIENT(sock_id, sock_ip, sock_port) ({ \
if (nics[i].colo_##sock_id && \
nics[i].colo_##sock_ip && \
nics[i].colo_##sock_port) { \
flexarray_append(dm_args, "-chardev"); \
flexarray_append(dm_args, \
GCSPRINTF("socket,id=%s,host=%s,port=%s", \
nics[i].colo_##sock_id, \
nics[i].colo_##sock_ip, \
nics[i].colo_##sock_port)); \
} \
})
if (state->saved_state) {
/* secondary colo run */
APPEND_COLO_SOCK_CLIENT(sock_sec_redirector0_id,
sock_sec_redirector0_ip,
sock_sec_redirector0_port);
APPEND_COLO_SOCK_CLIENT(sock_sec_redirector1_id,
sock_sec_redirector1_ip,
sock_sec_redirector1_port);
if (nics[i].colo_filter_sec_redirector0_queue &&
nics[i].colo_filter_sec_redirector0_indev) {
flexarray_append(dm_args, "-object");
flexarray_append(dm_args,
GCSPRINTF("filter-redirector,id=rs1,netdev=net%d,queue=%s,indev=%s",
nics[i].devid,
nics[i].colo_filter_sec_redirector0_queue,
nics[i].colo_filter_sec_redirector0_indev));
}
if (nics[i].colo_filter_sec_redirector1_queue &&
nics[i].colo_filter_sec_redirector1_outdev) {
flexarray_append(dm_args, "-object");
flexarray_append(dm_args,
GCSPRINTF("filter-redirector,id=rs2,netdev=net%d,queue=%s,outdev=%s",
nics[i].devid,
nics[i].colo_filter_sec_redirector1_queue,
nics[i].colo_filter_sec_redirector1_outdev));
}
if (nics[i].colo_filter_sec_rewriter0_queue) {
flexarray_append(dm_args, "-object");
flexarray_append(dm_args,
GCSPRINTF("filter-rewriter,id=rs3,netdev=net%d,queue=%s",
nics[i].devid,
nics[i].colo_filter_sec_rewriter0_queue));
}
} else {
/* primary colo run */
APPEND_COLO_SOCK_SERVER(sock_mirror_id,
sock_mirror_ip,
sock_mirror_port);
APPEND_COLO_SOCK_SERVER(sock_compare_pri_in_id,
sock_compare_pri_in_ip,
sock_compare_pri_in_port);
APPEND_COLO_SOCK_SERVER(sock_compare_sec_in_id,
sock_compare_sec_in_ip,
sock_compare_sec_in_port);
APPEND_COLO_SOCK_SERVER(sock_compare_notify_id,
sock_compare_notify_ip,
sock_compare_notify_port);
APPEND_COLO_SOCK_SERVER(sock_redirector0_id,
sock_redirector0_ip,
sock_redirector0_port);
APPEND_COLO_SOCK_CLIENT(sock_redirector1_id,
sock_redirector1_ip,
sock_redirector1_port);
APPEND_COLO_SOCK_CLIENT(sock_redirector2_id,
sock_redirector2_ip,
sock_redirector2_port);
if (nics[i].colo_filter_mirror_queue &&
nics[i].colo_filter_mirror_outdev) {
flexarray_append(dm_args, "-object");
flexarray_append(dm_args,
GCSPRINTF("filter-mirror,id=m1,netdev=net%d,queue=%s,outdev=%s",
nics[i].devid,
nics[i].colo_filter_mirror_queue,
nics[i].colo_filter_mirror_outdev));
}
if (nics[i].colo_filter_redirector0_queue &&
nics[i].colo_filter_redirector0_indev) {
flexarray_append(dm_args, "-object");
flexarray_append(dm_args,
GCSPRINTF("filter-redirector,id=r1,netdev=net%d,queue=%s,indev=%s",
nics[i].devid,
nics[i].colo_filter_redirector0_queue,
nics[i].colo_filter_redirector0_indev));
}
if (nics[i].colo_filter_redirector1_queue &&
nics[i].colo_filter_redirector1_outdev) {
flexarray_append(dm_args, "-object");
flexarray_append(dm_args,
GCSPRINTF("filter-redirector,id=r2,netdev=net%d,queue=%s,outdev=%s",
nics[i].devid,
nics[i].colo_filter_redirector1_queue,
nics[i].colo_filter_redirector1_outdev));
}
if (nics[i].colo_compare_pri_in &&
nics[i].colo_compare_sec_in &&
nics[i].colo_compare_out &&
nics[i].colo_compare_notify_dev) {
flexarray_append(dm_args, "-object");
flexarray_append(dm_args, "iothread,id=colo-compare-iothread-1");
flexarray_append(dm_args, "-object");
flexarray_append(dm_args,
GCSPRINTF("colo-compare,id=c1,primary_in=%s,secondary_in=%s,outdev=%s,notify_dev=%s,iothread=colo-compare-iothread-1",
nics[i].colo_compare_pri_in,
nics[i].colo_compare_sec_in,
nics[i].colo_compare_out,
nics[i].colo_compare_notify_dev));
}
}
ioemu_nics++;
#undef APPEND_COLO_SOCK_SERVER
#undef APPEND_COLO_SOCK_CLIENT
}
}
/* If we have no emulated nics, tell qemu not to create any */
if ( ioemu_nics == 0 ) {
flexarray_append(dm_args, "-net");
flexarray_append(dm_args, "none");
}
} else {
if (!sdl && !vnc) {
flexarray_append(dm_args, "-nographic");
}
}
if (libxl_defbool_val(b_info->dm_restrict)) {
char *chroot_dir = GCSPRINTF("%s/qemu-root-%d",
libxl__run_dir_path(), guest_domid);
int r;
flexarray_append(dm_args, "-xen-domid-restrict");
/*
* Run QEMU in a chroot at XEN_RUN_DIR/qemu-root-<domid>
*
* There is no library function to do the equivalent of `rm
* -rf`. However deprivileged QEMU in theory shouldn't be
* able to write any files, as the chroot would be owned by
* root, but it would be running as an unprivileged process.
* So in theory, old chroots should always be empty.
*
* rmdir the directory before attempting to create
* it; if it returns anything other than ENOENT, fail domain
* creation.
*/
r = rmdir(chroot_dir);
if (r != 0 && errno != ENOENT) {
LOGED(ERROR, guest_domid,
"failed to remove existing chroot dir %s", chroot_dir);
return ERROR_FAIL;
}
for (;;) {
r = mkdir(chroot_dir, 0000);
if (!r)
break;
if (errno == EINTR) continue;
LOGED(ERROR, guest_domid,
"failed to create chroot dir %s", chroot_dir);
return ERROR_FAIL;
}
/* Add "-chroot [dir]" to command-line */
if (qemu_opts->have_runwith_chroot) {
flexarray_append_pair(dm_args, "-run-with",
GCSPRINTF("chroot=%s", chroot_dir));
} else {
flexarray_append(dm_args, "-chroot");
flexarray_append(dm_args, chroot_dir);
}
}
if (state->saved_state) {
if (is_stubdom) {
/* Linux stubdomain must replace $STUBDOM_RESTORE_INCOMING_ARG
* with the approriate fd:$num argument for the
* STUBDOM_CONSOLE_RESTORE console 2.
*/
flexarray_append(dm_args, "-incoming");
flexarray_append(dm_args, "$STUBDOM_RESTORE_INCOMING_ARG");
} else {
/* This file descriptor is meant to be used by QEMU */
*dm_state_fd = open(state->saved_state, O_RDONLY);
flexarray_append(dm_args, "-incoming");
flexarray_append(dm_args, GCSPRINTF("fd:%d",*dm_state_fd));
}
}
for (i = 0; b_info->extra && b_info->extra[i] != NULL; i++)
flexarray_append(dm_args, b_info->extra[i]);
flexarray_append(dm_args, "-machine");
switch (b_info->type) {
case LIBXL_DOMAIN_TYPE_PVH:
case LIBXL_DOMAIN_TYPE_PV:
flexarray_append(dm_args, "xenpv");
for (i = 0; b_info->extra_pv && b_info->extra_pv[i] != NULL; i++)
flexarray_append(dm_args, b_info->extra_pv[i]);
break;
case LIBXL_DOMAIN_TYPE_HVM:
if (!libxl_defbool_val(b_info->u.hvm.xen_platform_pci)) {
/* Switching here to the machine "pc" which does not add
* the xen-platform device instead of the default "xenfv" machine.
*/
machinearg = libxl__strdup(gc, "pc,accel=xen,suppress-vmdesc=on");
} else {
machinearg = libxl__strdup(gc, "xenfv,suppress-vmdesc=on");
}
if (b_info->u.hvm.mmio_hole_memkb) {
uint64_t max_ram_below_4g = (1ULL << 32) -
(b_info->u.hvm.mmio_hole_memkb << 10);
if (max_ram_below_4g > HVM_BELOW_4G_MMIO_START) {
LOGD(WARN, guest_domid, "mmio_hole_memkb=%"PRIu64
" invalid ignored.\n",
b_info->u.hvm.mmio_hole_memkb);
} else {
machinearg = GCSPRINTF("%s,max-ram-below-4g=%"PRIu64,
machinearg, max_ram_below_4g);
}
}
if (libxl_defbool_val(b_info->u.hvm.gfx_passthru)) {
enum libxl_gfx_passthru_kind gfx_passthru_kind =
libxl__detect_gfx_passthru_kind(gc, guest_config);
switch (gfx_passthru_kind) {
case LIBXL_GFX_PASSTHRU_KIND_IGD:
machinearg = GCSPRINTF("%s,igd-passthru=on", machinearg);
break;
case LIBXL_GFX_PASSTHRU_KIND_DEFAULT:
LOGD(ERROR, guest_domid, "unable to detect required gfx_passthru_kind");
return ERROR_FAIL;
default:
LOGD(ERROR, guest_domid, "invalid value for gfx_passthru_kind");
return ERROR_INVAL;
}
}
flexarray_append(dm_args, machinearg);
for (i = 0; b_info->extra_hvm && b_info->extra_hvm[i] != NULL; i++)
flexarray_append(dm_args, b_info->extra_hvm[i]);
break;
default:
abort();
}
ram_size = libxl__sizekb_to_mb(b_info->max_memkb - b_info->video_memkb);
flexarray_append(dm_args, "-m");
flexarray_append(dm_args, GCSPRINTF("%"PRId64, ram_size));
if (b_info->type == LIBXL_DOMAIN_TYPE_HVM) {
flexarray_append_pair(dm_envs, "XEN_DOMAIN_ID", GCSPRINTF("%d", guest_domid));
if (b_info->u.hvm.hdtype == LIBXL_HDTYPE_AHCI)
flexarray_append_pair(dm_args, "-device", "ahci,id=ahci0");
for (i = 0; i < num_disks; i++) {
int disk, part;
int dev_number =
libxl__device_disk_dev_number(disks[i].vdev, &disk, &part);
const char *format;
char *drive;
const char *target_path = NULL;
int colo_mode;
if (dev_number == -1) {
LOGD(WARN, guest_domid, "unable to determine"" disk number for %s",
disks[i].vdev);
continue;
}
/*
* If qemu isn't doing the interpreting, the parameter is
* always raw
*/
if (libxl_defbool_val(b_info->device_model_stubdomain))
format = "host_device";
else if (disks[i].backend == LIBXL_DISK_BACKEND_QDISK)
format = libxl__qemu_disk_format_string(disks[i].format);
else
format = libxl__qemu_disk_format_string(LIBXL_DISK_FORMAT_RAW);
if (disks[i].format == LIBXL_DISK_FORMAT_EMPTY) {
if (!disks[i].is_cdrom) {
LOGD(WARN, guest_domid, "Cannot support empty disk format for %s",
disks[i].vdev);
continue;
}
} else if (libxl_defbool_val(b_info->device_model_stubdomain)) {
if (disk > 'z' - 'a') {
LOGD(WARN, guest_domid,
"Emulation of only first %d disks is supported with qemu-xen in stubdomain.\n"
"Disk %d will be available via PV drivers but not as an emulated disk.",
'z' - 'a',
disk);
continue;
}
target_path = GCSPRINTF("/dev/xvd%c", 'a' + disk);
} else {
if (format == NULL) {
LOGD(WARN, guest_domid,
"Unable to determine disk image format: %s\n"
"Disk will be available via PV drivers but not as an emulated disk.",
disks[i].vdev);
continue;
}
target_path = libxl__device_disk_find_local_path(gc,
guest_domid, &disks[i], true);
if (!target_path) {
LOGD(WARN, guest_domid, "No way to get local access disk to image: %s\n"
"Disk will be available via PV drivers but not as an emulated disk.",
disks[i].vdev);
continue;
}
}
if (disks[i].is_cdrom) {
const char *drive_id;
if (disk > 4) {
LOGD(WARN, guest_domid, "Emulated CDROM can be only one of the first 4 disks.\n"
"Disk %s will be available via PV drivers but not as an "
"emulated disk.",
disks[i].vdev);
continue;
}
drive_id = GCSPRINTF("ide-%i", dev_number);
drive = GCSPRINTF("if=none,readonly=on,id=%s", drive_id);
if (target_path)
drive = libxl__sprintf(gc, "%s,file=%s,format=%s",
drive, target_path, format);
flexarray_vappend(dm_args,
"-drive", drive,
"-device",
GCSPRINTF("ide-cd,id=%s,drive=%s,bus=ide.%u,unit=%u",
drive_id, drive_id,
disk / 2, disk % 2),
NULL);
continue;
} else {
/*
* Explicit sd disks are passed through as is.
*
* For other disks we translate devices 0..3 into
* hd[a-d] and ignore the rest.
*/
if (libxl_defbool_val(disks[i].colo_enable)) {
if (libxl_defbool_val(disks[i].colo_restore_enable))
colo_mode = LIBXL__COLO_SECONDARY;
else
colo_mode = LIBXL__COLO_PRIMARY;
} else {
colo_mode = LIBXL__COLO_NONE;
}
if (strncmp(disks[i].vdev, "sd", 2) == 0) {
const char *drive_id;
if (colo_mode == LIBXL__COLO_SECONDARY) {
drive = libxl__sprintf
(gc, "if=none,driver=%s,file=%s,id=%s,readonly=%s",
format, target_path, disks[i].colo_export,
disks[i].readwrite ? "off" : "on");
flexarray_append(dm_args, "-drive");
flexarray_append(dm_args, drive);
}
drive = qemu_disk_scsi_drive_string(gc, target_path, disk,
format,
&disks[i],
colo_mode,
&drive_id),
flexarray_vappend(dm_args,
"-drive", drive,
"-device", GCSPRINTF("scsi-hd,drive=%s,scsi-id=%d",
drive_id, disk),
NULL);
continue;
} else if (disk < 6 && b_info->u.hvm.hdtype == LIBXL_HDTYPE_AHCI) {
if (!disks[i].readwrite) {
LOGD(ERROR, guest_domid,
"qemu-xen doesn't support read-only AHCI disk drivers");
return ERROR_INVAL;
}
flexarray_vappend(dm_args, "-drive",
GCSPRINTF("file=%s,if=none,id=ahcidisk-%d,format=%s,cache=writeback",
target_path, disk, format),
"-device", GCSPRINTF("ide-hd,bus=ahci0.%d,unit=0,drive=ahcidisk-%d",
disk, disk), NULL);
continue;
} else if (disk < 4) {
if (!disks[i].readwrite) {
LOGD(ERROR, guest_domid,
"qemu-xen doesn't support read-only IDE disk drivers");
return ERROR_INVAL;
}
if (colo_mode == LIBXL__COLO_SECONDARY) {
drive = libxl__sprintf
(gc, "if=none,driver=%s,file=%s,id=%s",
format, target_path, disks[i].colo_export);
flexarray_append(dm_args, "-drive");
flexarray_append(dm_args, drive);
}
drive = qemu_disk_ide_drive_string(gc, target_path, disk,
format,
&disks[i],
colo_mode);
} else {
LOGD(WARN, guest_domid, "Only 4 emulated IDE disks are supported.\n"
"Disk %s will be available via PV drivers but not as an "
"emulated disk.",
disks[i].vdev);
continue; /* Do not emulate this disk */
}
if (!drive)
continue;
}
flexarray_append(dm_args, "-drive");
flexarray_append(dm_args, drive);
}
switch (b_info->u.hvm.vendor_device) {
case LIBXL_VENDOR_DEVICE_XENSERVER:
flexarray_append(dm_args, "-device");
flexarray_append(dm_args, "xen-pvdevice,device-id=0xc000");
break;
default:
break;
}
if (state->dm_runas) {
if (qemu_opts->have_runwith_user) {
flexarray_append_pair(dm_args, "-run-with",
GCSPRINTF("user=%s", state->dm_runas));
} else {
flexarray_append(dm_args, "-runas");
flexarray_append(dm_args, state->dm_runas);
}
}
}
flexarray_append(dm_args, NULL);
*args = (char **) flexarray_contents(dm_args);
flexarray_append(dm_envs, NULL);
if (envs)
*envs = (char **) flexarray_contents(dm_envs);
return 0;
}
static int libxl__build_device_model_args(libxl__gc *gc,
const char *dm, int guest_domid,
const libxl_domain_config *guest_config,
char ***args, char ***envs,
const libxl__domain_build_state *state,
const libxl__qemu_available_opts *qemu_opts,
int *dm_state_fd)
/* dm_state_fd may be NULL iff caller knows we are using stubdom
* and therefore will be passing a filename rather than a fd. */
{
switch (guest_config->b_info.device_model_version) {
case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL:
return libxl__build_device_model_args_old(gc, dm,
guest_domid, guest_config,
args, envs,
state);
case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN:
if (!libxl_defbool_val(guest_config->b_info.device_model_stubdomain)) {
assert(dm_state_fd != NULL);
assert(*dm_state_fd < 0);
}
return libxl__build_device_model_args_new(gc, dm,
guest_domid, guest_config,
args, envs,
state,
qemu_opts,
dm_state_fd);
default:
LOGED(ERROR, guest_domid, "unknown device model version %d",
guest_config->b_info.device_model_version);
return ERROR_INVAL;
}
}
static void libxl__dm_vifs_from_hvm_guest_config(libxl__gc *gc,
libxl_domain_config * const guest_config,
libxl_domain_config *dm_config)
{
int i, nr = guest_config->num_nics;
GCNEW_ARRAY(dm_config->nics, nr);
for (i=0; i<nr; i++) {
dm_config->nics[i] = guest_config->nics[i];
dm_config->nics[i].nictype = LIBXL_NIC_TYPE_VIF;
if (dm_config->nics[i].ifname)
dm_config->nics[i].ifname = GCSPRINTF("%s" TAP_DEVICE_SUFFIX,
dm_config->nics[i].ifname);
}
dm_config->num_nics = nr;
}
static int libxl__vfb_and_vkb_from_hvm_guest_config(libxl__gc *gc,
const libxl_domain_config *guest_config,
libxl_device_vfb *vfb,
libxl_device_vkb *vkb)
{
const libxl_domain_build_info *b_info = &guest_config->b_info;
if (b_info->type != LIBXL_DOMAIN_TYPE_HVM)
return ERROR_INVAL;
libxl_device_vfb_init(vfb);
libxl_device_vkb_init(vkb);
vfb->backend_domid = 0;
vfb->devid = 0;
vfb->vnc = b_info->u.hvm.vnc;
vfb->keymap = b_info->u.hvm.keymap;
vfb->sdl = b_info->u.hvm.sdl;
vkb->backend_domid = 0;
vkb->devid = 0;
return 0;
}
static int libxl__write_stub_dmargs(libxl__gc *gc,
int dm_domid, int guest_domid,
char **args, bool is_linux_stubdom)
{
struct xs_permissions roperm[2];
xs_transaction_t t = XBT_NULL;
char *dmargs = NULL;
int rc;
roperm[0].id = 0;
roperm[0].perms = XS_PERM_NONE;
roperm[1].id = dm_domid;
roperm[1].perms = XS_PERM_READ;
if (!is_linux_stubdom) {
int dmargs_size = 0;
int i = 0;
while (args[i] != NULL) {
dmargs_size = dmargs_size + strlen(args[i]) + 1;
i++;
}
dmargs_size++;
dmargs = (char *) libxl__malloc(gc, dmargs_size);
i = 1;
dmargs[0] = '\0';
while (args[i] != NULL) {
if (strcmp(args[i], "-sdl") && strcmp(args[i], "-M") && strcmp(args[i], "xenfv")) {
strcat(dmargs, " ");
strcat(dmargs, args[i]);
}
i++;
}
}
for (;;) {
const char *vm_path;
char *path;
rc = libxl__xs_transaction_start(gc, &t);
if (rc) goto out;
rc = libxl__xs_read_mandatory(gc, t,
GCSPRINTF("/local/domain/%d/vm",
guest_domid),
&vm_path);
if (rc) goto out;
if (is_linux_stubdom) {
int i;
path = GCSPRINTF("%s/image/dm-argv", vm_path);
rc = libxl__xs_mknod(gc, t, path, roperm, ARRAY_SIZE(roperm));
if (rc) goto out;
for (i=1; args[i] != NULL; i++) {
rc = libxl__xs_write_checked(gc, t,
GCSPRINTF("%s/%03d", path, i),
args[i]);
if (rc) goto out;
}
} else {
path = GCSPRINTF("%s/image/dmargs", vm_path);
rc = libxl__xs_mknod(gc, t, path, roperm, ARRAY_SIZE(roperm));
if (rc) goto out;
rc = libxl__xs_write_checked(gc, t, path, dmargs);
if (rc) goto out;
rc = libxl__xs_mknod(gc, t, GCSPRINTF("%s/rtc/timeoffset", vm_path),
roperm, ARRAY_SIZE(roperm));
if (rc) goto out;
}
rc = libxl__xs_transaction_commit(gc, &t);
if (!rc) break;
if (rc<0) goto out;
}
return 0;
out:
libxl__xs_transaction_abort(gc, &t);
return rc;
}
static int libxl__store_libxl_entry(libxl__gc *gc, uint32_t domid,
const char *name, const char *value)
{
char *path = NULL;
path = libxl__xs_libxl_path(gc, domid);
path = libxl__sprintf(gc, "%s/%s", path, name);
return libxl__xs_printf(gc, XBT_NULL, path, "%s", value);
}
static void dmss_init(libxl__dm_spawn_state *dmss)
{
libxl__ev_qmp_init(&dmss->qmp);
libxl__ev_time_init(&dmss->timeout);
}
static void dmss_dispose(libxl__gc *gc, libxl__dm_spawn_state *dmss)
{
libxl__ev_qmp_dispose(gc, &dmss->qmp);
libxl__ev_time_deregister(gc, &dmss->timeout);
}
static void spawn_stubdom_pvqemu_cb(libxl__egc *egc,
libxl__dm_spawn_state *stubdom_dmss,
int rc);
static void spawn_stub_launch_dm(libxl__egc *egc,
libxl__multidev *aodevs, int ret);
static void stubdom_pvqemu_cb(libxl__egc *egc,
libxl__multidev *aodevs,
int rc);
static void stubdom_pvqemu_unpaused(libxl__egc *egc,
libxl__dm_resume_state *dmrs,
int rc);
static void stubdom_xswait_cb(libxl__egc *egc, libxl__xswait_state *xswait,
int rc, const char *p);
static void spawn_qmp_proxy(libxl__egc *egc,
libxl__stub_dm_spawn_state *sdss);
static void qmp_proxy_confirm(libxl__egc *egc, libxl__spawn_state *spawn,
const char *xsdata);
static void qmp_proxy_startup_failed(libxl__egc *egc,
libxl__spawn_state *spawn,
int rc);
static void qmp_proxy_detached(libxl__egc *egc,
libxl__spawn_state *spawn);
static void qmp_proxy_spawn_outcome(libxl__egc *egc,
libxl__stub_dm_spawn_state *sdss,
int rc);
char *libxl__stub_dm_name(libxl__gc *gc, const char *guest_name)
{
return GCSPRINTF("%s-dm", guest_name);
}
void libxl__spawn_stub_dm(libxl__egc *egc, libxl__stub_dm_spawn_state *sdss)
{
STATE_AO_GC(sdss->dm.spawn.ao);
libxl_ctx *ctx = libxl__gc_owner(gc);
int ret;
libxl_device_vfb *vfb;
libxl_device_vkb *vkb;
char **args;
struct xs_permissions perm[2];
xs_transaction_t t;
/* convenience aliases */
libxl_domain_config *const dm_config = &sdss->dm_config;
libxl_domain_config *const guest_config = sdss->dm.guest_config;
const int guest_domid = sdss->dm.guest_domid;
libxl__domain_build_state *const d_state = sdss->dm.build_state;
libxl__domain_build_state *const stubdom_state = &sdss->dm_state;
/* Initialise private part of sdss */
libxl__domain_build_state_init(stubdom_state);
dmss_init(&sdss->dm);
dmss_init(&sdss->pvqemu);
libxl__xswait_init(&sdss->xswait);
assert(libxl_defbool_val(guest_config->b_info.device_model_stubdomain));
sdss->pvqemu.guest_domid = INVALID_DOMID;
libxl_domain_create_info_init(&dm_config->c_info);
dm_config->c_info.type = LIBXL_DOMAIN_TYPE_PV;
dm_config->c_info.name = libxl__stub_dm_name(gc,
libxl__domid_to_name(gc, guest_domid));
/* When we are here to launch stubdom, ssidref is a valid value
* already, no need to parse it again.
*/
dm_config->c_info.ssidref = guest_config->b_info.device_model_ssidref;
dm_config->c_info.ssid_label = NULL;
libxl_uuid_generate(&dm_config->c_info.uuid);
libxl_domain_build_info_init(&dm_config->b_info);
libxl_domain_build_info_init_type(&dm_config->b_info, LIBXL_DOMAIN_TYPE_PV);
dm_config->b_info.shadow_memkb = 0;
dm_config->b_info.max_vcpus = 1;
dm_config->b_info.max_memkb = guest_config->b_info.stubdomain_memkb;
dm_config->b_info.max_memkb += guest_config->b_info.video_memkb;
dm_config->b_info.target_memkb = dm_config->b_info.max_memkb;
dm_config->b_info.max_grant_frames = guest_config->b_info.max_grant_frames;
dm_config->b_info.max_maptrack_frames = guest_config->b_info.max_maptrack_frames;
dm_config->b_info.max_grant_version = guest_config->b_info.max_grant_version;
dm_config->b_info.u.pv.features = "";
dm_config->b_info.device_model_version =
guest_config->b_info.device_model_version;
dm_config->b_info.device_model =
guest_config->b_info.device_model;
dm_config->b_info.extra = guest_config->b_info.extra;
dm_config->b_info.extra_pv = guest_config->b_info.extra_pv;
dm_config->b_info.extra_hvm = guest_config->b_info.extra_hvm;
dm_config->disks = guest_config->disks;
dm_config->num_disks = guest_config->num_disks;
libxl__dm_vifs_from_hvm_guest_config(gc, guest_config, dm_config);
dm_config->c_info.run_hotplug_scripts =
guest_config->c_info.run_hotplug_scripts;
ret = libxl__domain_config_setdefault(gc, dm_config, guest_domid);
if (ret) goto out;
if (libxl_defbool_val(guest_config->b_info.u.hvm.vnc.enable)
|| libxl_defbool_val(guest_config->b_info.u.hvm.spice.enable)
|| libxl_defbool_val(guest_config->b_info.u.hvm.sdl.enable)) {
GCNEW(vfb);
GCNEW(vkb);
libxl__vfb_and_vkb_from_hvm_guest_config(gc, guest_config, vfb, vkb);
dm_config->vfbs = vfb;
dm_config->num_vfbs = 1;
dm_config->vkbs = vkb;
dm_config->num_vkbs = 1;
}
if (guest_config->b_info.stubdomain_kernel &&
access(guest_config->b_info.stubdomain_kernel, R_OK) != 0) {
LOGED(ERROR, guest_domid, "could not access stubdomain kernel %s",
guest_config->b_info.stubdomain_kernel);
ret = ERROR_INVAL;
goto out;
}
if (guest_config->b_info.stubdomain_ramdisk &&
access(guest_config->b_info.stubdomain_ramdisk, R_OK) != 0) {
LOGED(ERROR, guest_domid, "could not access stubdomain ramdisk %s",
guest_config->b_info.stubdomain_ramdisk);
ret = ERROR_INVAL;
goto out;
}
stubdom_state->pv_kernel.path = guest_config->b_info.stubdomain_kernel;
stubdom_state->pv_cmdline = guest_config->b_info.stubdomain_cmdline;
stubdom_state->pv_ramdisk.path = guest_config->b_info.stubdomain_ramdisk;
/* fixme: this function can leak the stubdom if it fails */
ret = libxl__domain_make(gc, dm_config, stubdom_state,
&sdss->pvqemu.guest_domid, false);
if (ret)
goto out;
uint32_t dm_domid = sdss->pvqemu.guest_domid;
ret = libxl__domain_build(gc, dm_config, dm_domid, stubdom_state);
if (ret)
goto out;
ret = libxl__build_device_model_args(gc, "stubdom-dm", guest_domid,
guest_config, &args, NULL,
d_state,
&sdss->dm.qemu_opts,
NULL);
if (ret) {
ret = ERROR_FAIL;
goto out;
}
libxl__store_libxl_entry(gc, guest_domid, "dm-version",
libxl_device_model_version_to_string(dm_config->b_info.device_model_version));
libxl__write_stub_dmargs(gc, dm_domid, guest_domid, args,
libxl__stubdomain_is_linux(&guest_config->b_info));
libxl__xs_printf(gc, XBT_NULL,
GCSPRINTF("%s/image/device-model-domid",
libxl__xs_get_dompath(gc, guest_domid)),
"%d", dm_domid);
libxl__xs_printf(gc, XBT_NULL,
GCSPRINTF("%s/target",
libxl__xs_get_dompath(gc, dm_domid)),
"%d", guest_domid);
if (guest_config->b_info.device_model_version == LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN) {
/* qemu-xen is used as a dm in the stubdomain, so we set the bios
* accroding to this */
libxl__xs_printf(gc, XBT_NULL,
libxl__sprintf(gc, "%s/hvmloader/bios",
libxl__xs_get_dompath(gc, guest_domid)),
"%s",
libxl_bios_type_to_string(guest_config->b_info.u.hvm.bios));
}
/* Disable relocating memory to make the MMIO hole larger
* unless we're running qemu-traditional and vNUMA is not
* configured. */
libxl__xs_printf(gc, XBT_NULL,
libxl__sprintf(gc, "%s/hvmloader/allow-memory-relocate",
libxl__xs_get_dompath(gc, guest_domid)),
"%d",
guest_config->b_info.device_model_version
== LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL &&
!libxl__vnuma_configured(&guest_config->b_info));
ret = xc_domain_set_target(ctx->xch, dm_domid, guest_domid);
if (ret<0) {
LOGED(ERROR, guest_domid, "setting target domain %d -> %d",
dm_domid, guest_domid);
ret = ERROR_FAIL;
goto out;
}
xs_set_target(ctx->xsh, dm_domid, guest_domid);
perm[0].id = dm_domid;
perm[0].perms = XS_PERM_NONE;
perm[1].id = guest_domid;
perm[1].perms = XS_PERM_READ;
retry_transaction:
t = xs_transaction_start(ctx->xsh);
xs_mkdir(ctx->xsh, t, DEVICE_MODEL_XS_PATH(gc, dm_domid, guest_domid, ""));
xs_set_permissions(ctx->xsh, t,
DEVICE_MODEL_XS_PATH(gc, dm_domid, guest_domid, ""),
perm, ARRAY_SIZE(perm));
if (!xs_transaction_end(ctx->xsh, t, 0))
if (errno == EAGAIN)
goto retry_transaction;
libxl__multidev_begin(ao, &sdss->multidev);
sdss->multidev.callback = spawn_stub_launch_dm;
libxl__add_disks(egc, ao, dm_domid, dm_config, &sdss->multidev);
libxl__multidev_prepared(egc, &sdss->multidev, 0);
return;
out:
assert(ret);
spawn_stubdom_pvqemu_cb(egc, &sdss->pvqemu, ret);
}
static void spawn_stub_launch_dm(libxl__egc *egc,
libxl__multidev *multidev, int ret)
{
libxl__stub_dm_spawn_state *sdss = CONTAINER_OF(multidev, *sdss, multidev);
STATE_AO_GC(sdss->dm.spawn.ao);
libxl_ctx *ctx = libxl__gc_owner(gc);
int i, num_console = STUBDOM_SPECIAL_CONSOLES;
libxl__device_console *console;
/* convenience aliases */
libxl_domain_config *const dm_config = &sdss->dm_config;
libxl_domain_config *const guest_config = sdss->dm.guest_config;
const int guest_domid = sdss->dm.guest_domid;
libxl__domain_build_state *const d_state = sdss->dm.build_state;
libxl__domain_build_state *const stubdom_state = &sdss->dm_state;
uint32_t dm_domid = sdss->pvqemu.guest_domid;
int need_qemu;
if (ret) {
LOGD(ERROR, guest_domid, "error connecting disk devices");
goto out;
}
for (i = 0; i < dm_config->num_nics; i++) {
/* We have to init the nic here, because we still haven't
* called libxl_device_nic_add at this point, but qemu needs
* the nic information to be complete.
*/
ret = libxl__nic_devtype.set_default(gc, dm_domid, &dm_config->nics[i],
false);
if (ret)
goto out;
}
if (dm_config->num_vfbs) {
ret = libxl__device_add(gc, dm_domid, &libxl__vfb_devtype,
&dm_config->vfbs[0]);
if (ret) goto out;
}
if (dm_config->num_vkbs) {
ret = libxl__device_add(gc, dm_domid, &libxl__vkb_devtype,
&dm_config->vkbs[0]);
if (ret) goto out;
}
if (guest_config->b_info.u.hvm.serial) {
num_console++;
} else if (guest_config->b_info.u.hvm.serial_list) {
char **serial = guest_config->b_info.u.hvm.serial_list;
while (*(serial++))
num_console++;
}
console = libxl__calloc(gc, num_console, sizeof(libxl__device_console));
for (i = 0; i < num_console; i++) {
console[i].devid = i;
console[i].consback = LIBXL__CONSOLE_BACKEND_IOEMU;
/* STUBDOM_CONSOLE_LOGGING (console 0) is for minios logging
* STUBDOM_CONSOLE_SAVE (console 1) is for writing the save file
* STUBDOM_CONSOLE_RESTORE (console 2) is for reading the save file
*/
switch (i) {
char *filename;
char *name;
case STUBDOM_CONSOLE_LOGGING:
name = GCSPRINTF("qemu-dm-%s",
libxl_domid_to_name(ctx, guest_domid));
ret = libxl_create_logfile(ctx, name, &filename);
if (ret) goto out;
console[i].output = GCSPRINTF("file:%s", filename);
free(filename);
/* will be changed back to LIBXL__CONSOLE_BACKEND_IOEMU if qemu
* will be in use */
console[i].consback = LIBXL__CONSOLE_BACKEND_XENCONSOLED;
break;
case STUBDOM_CONSOLE_SAVE:
console[i].output = GCSPRINTF("file:%s",
libxl__device_model_savefile(gc, guest_domid));
break;
case STUBDOM_CONSOLE_RESTORE:
if (d_state->saved_state)
console[i].output =
GCSPRINTF("pipe:%s", d_state->saved_state);
break;
case STUBDOM_CONSOLE_SERIAL:
if (guest_config->b_info.u.hvm.serial) {
console[i].output = guest_config->b_info.u.hvm.serial;
break;
}
/* fall-through */
default:
/* Serial_list is set, as otherwise num_consoles would be
* smaller and consoles 0-2 are handled above. */
assert(guest_config->b_info.u.hvm.serial_list);
console[i].output = guest_config->b_info.u.hvm.serial_list[
i-STUBDOM_CONSOLE_SERIAL];
break;
}
}
/*
* Until xenconsoled learns how to handle multiple consoles, require qemu
* in dom0 to serve consoles for a stubdomain - it require at least 3 of them.
*/
need_qemu = 1 || libxl__need_xenpv_qemu(gc, &sdss->dm_config);
for (i = 0; i < num_console; i++) {
libxl__device device;
if (need_qemu)
console[i].consback = LIBXL__CONSOLE_BACKEND_IOEMU;
ret = libxl__device_console_add(gc, dm_domid, &console[i],
i == STUBDOM_CONSOLE_LOGGING ? stubdom_state : NULL,
&device);
if (ret)
goto out;
}
if (libxl__stubdomain_is_linux(&guest_config->b_info)) {
spawn_qmp_proxy(egc, sdss);
} else {
qmp_proxy_spawn_outcome(egc, sdss, 0);
}
return;
out:
assert(ret);
qmp_proxy_spawn_outcome(egc, sdss, ret);
}
static void spawn_qmp_proxy(libxl__egc *egc,
libxl__stub_dm_spawn_state *sdss)
{
STATE_AO_GC(sdss->dm.spawn.ao);
const uint32_t guest_domid = sdss->dm.guest_domid;
const uint32_t dm_domid = sdss->pvqemu.guest_domid;
const char *dom_path = libxl__xs_get_dompath(gc, dm_domid);
char **args;
int nr = 0;
int rc, logfile_w, null;
if (access(STUBDOM_QMP_PROXY_PATH, X_OK) < 0) {
LOGED(ERROR, guest_domid, "qmp proxy %s is not executable", STUBDOM_QMP_PROXY_PATH);
rc = ERROR_FAIL;
goto out;
}
sdss->qmp_proxy_spawn.ao = ao;
sdss->qmp_proxy_spawn.what = GCSPRINTF("domain %d device model qmp proxy", guest_domid);
sdss->qmp_proxy_spawn.pidpath = GCSPRINTF("%s/image/qmp-proxy-pid", dom_path);
sdss->qmp_proxy_spawn.xspath = DEVICE_MODEL_XS_PATH(gc, LIBXL_TOOLSTACK_DOMID,
dm_domid, "/qmp-proxy-state");
sdss->qmp_proxy_spawn.timeout_ms = LIBXL_DEVICE_MODEL_START_TIMEOUT * 1000;
sdss->qmp_proxy_spawn.midproc_cb = libxl__spawn_record_pid;
sdss->qmp_proxy_spawn.confirm_cb = qmp_proxy_confirm;
sdss->qmp_proxy_spawn.failure_cb = qmp_proxy_startup_failed;
sdss->qmp_proxy_spawn.detached_cb = qmp_proxy_detached;
const int arraysize = 6;
GCNEW_ARRAY(args, arraysize);
args[nr++] = STUBDOM_QMP_PROXY_PATH;
args[nr++] = GCSPRINTF("--state-path=%s", sdss->qmp_proxy_spawn.xspath);
args[nr++] = GCSPRINTF("%u", dm_domid);
args[nr++] = GCSPRINTF("%s/device-model/%u/qmp-vchan", dom_path, guest_domid);
args[nr++] = (char*)libxl__qemu_qmp_path(gc, guest_domid);
args[nr++] = NULL;
assert(nr == arraysize);
logfile_w = libxl__create_qemu_logfile(gc, GCSPRINTF("qmp-proxy-%s",
sdss->dm_config.c_info.name));
if (logfile_w < 0) {
rc = logfile_w;
goto out;
}
null = open("/dev/null", O_RDWR);
if (null < 0) {
LOGED(ERROR, guest_domid, "unable to open /dev/null");
rc = ERROR_FAIL;
goto out_close;
}
rc = libxl__spawn_spawn(egc, &sdss->qmp_proxy_spawn);
if (rc < 0)
goto out_close;
if (!rc) { /* inner child */
setsid();
libxl__exec(gc, null, null, logfile_w, STUBDOM_QMP_PROXY_PATH, args, NULL);
/* unreachable */
}
rc = 0;
out_close:
if (logfile_w >= 0)
close(logfile_w);
if (null >= 0)
close(null);
out:
if (rc)
qmp_proxy_spawn_outcome(egc, sdss, rc);
}
static void qmp_proxy_confirm(libxl__egc *egc, libxl__spawn_state *spawn,
const char *xsdata)
{
STATE_AO_GC(spawn->ao);
if (!xsdata)
return;
if (strcmp(xsdata, "running"))
return;
libxl__spawn_initiate_detach(gc, spawn);
}
static void qmp_proxy_startup_failed(libxl__egc *egc,
libxl__spawn_state *spawn,
int rc)
{
libxl__stub_dm_spawn_state *sdss = CONTAINER_OF(spawn, *sdss, qmp_proxy_spawn);
qmp_proxy_spawn_outcome(egc, sdss, rc);
}
static void qmp_proxy_detached(libxl__egc *egc,
libxl__spawn_state *spawn)
{
libxl__stub_dm_spawn_state *sdss = CONTAINER_OF(spawn, *sdss, qmp_proxy_spawn);
qmp_proxy_spawn_outcome(egc, sdss, 0);
}
static void qmp_proxy_spawn_outcome(libxl__egc *egc,
libxl__stub_dm_spawn_state *sdss,
int rc)
{
STATE_AO_GC(sdss->dm.spawn.ao);
/*
* Until xenconsoled learns how to handle multiple consoles, require qemu
* in dom0 to serve consoles for a stubdomain - it require at least 3 of them.
*/
int need_pvqemu = 1 || libxl__need_xenpv_qemu(gc, &sdss->dm_config);
if (rc) goto out;
if (need_pvqemu < 0) {
rc = need_pvqemu;
goto out;
}
sdss->pvqemu.spawn.ao = ao;
sdss->pvqemu.guest_config = &sdss->dm_config;
sdss->pvqemu.build_state = &sdss->dm_state;
sdss->pvqemu.callback = spawn_stubdom_pvqemu_cb;
if (need_pvqemu) {
libxl__spawn_local_dm(egc, &sdss->pvqemu);
} else {
/* If dom0 qemu not needed, do not launch it */
spawn_stubdom_pvqemu_cb(egc, &sdss->pvqemu, 0);
}
return;
out:
assert(rc);
spawn_stubdom_pvqemu_cb(egc, &sdss->pvqemu, rc);
}
static void spawn_stubdom_pvqemu_cb(libxl__egc *egc,
libxl__dm_spawn_state *stubdom_dmss,
int rc)
{
libxl__stub_dm_spawn_state *sdss =
CONTAINER_OF(stubdom_dmss, *sdss, pvqemu);
STATE_AO_GC(sdss->dm.spawn.ao);
uint32_t dm_domid = sdss->pvqemu.guest_domid;
libxl_domain_config *d_config = stubdom_dmss->guest_config;
if (rc) goto out;
if (d_config->num_nics > 0) {
libxl__multidev_begin(ao, &sdss->multidev);
sdss->multidev.callback = stubdom_pvqemu_cb;
libxl__add_nics(egc, ao, dm_domid, d_config, &sdss->multidev);
libxl__multidev_prepared(egc, &sdss->multidev, 0);
return;
}
out:
stubdom_pvqemu_cb(egc, &sdss->multidev, rc);
}
static void stubdom_pvqemu_cb(libxl__egc *egc,
libxl__multidev *multidev,
int rc)
{
libxl__stub_dm_spawn_state *sdss = CONTAINER_OF(multidev, *sdss, multidev);
STATE_AO_GC(sdss->dm.spawn.ao);
uint32_t dm_domid = sdss->pvqemu.guest_domid;
if (rc) {
LOGED(ERROR, sdss->dm.guest_domid,
"error connecting nics devices");
goto out;
}
sdss->pvqemu.dmrs.ao = ao;
sdss->pvqemu.dmrs.domid = dm_domid;
sdss->pvqemu.dmrs.callback = stubdom_pvqemu_unpaused;
libxl__domain_unpause(egc, &sdss->pvqemu.dmrs); /* must be last */
return;
out:
stubdom_pvqemu_unpaused(egc, &sdss->pvqemu.dmrs, rc);
}
static void stubdom_pvqemu_unpaused(libxl__egc *egc,
libxl__dm_resume_state *dmrs,
int rc)
{
libxl__stub_dm_spawn_state *sdss =
CONTAINER_OF(dmrs, *sdss, pvqemu.dmrs);
STATE_AO_GC(sdss->dm.spawn.ao);
uint32_t dm_domid = sdss->pvqemu.guest_domid;
if (rc) goto out;
sdss->xswait.ao = ao;
sdss->xswait.what = GCSPRINTF("Stubdom %u for %u startup",
dm_domid, sdss->dm.guest_domid);
sdss->xswait.path = DEVICE_MODEL_XS_PATH(gc, dm_domid, sdss->dm.guest_domid,
"/state");
sdss->xswait.timeout_ms = LIBXL_STUBDOM_START_TIMEOUT * 1000;
sdss->xswait.callback = stubdom_xswait_cb;
rc = libxl__xswait_start(gc, &sdss->xswait);
if (rc) goto out;
return;
out:
stubdom_xswait_cb(egc, &sdss->xswait, rc, NULL);
}
static void stubdom_xswait_cb(libxl__egc *egc, libxl__xswait_state *xswait,
int rc, const char *p)
{
EGC_GC;
libxl__stub_dm_spawn_state *sdss = CONTAINER_OF(xswait, *sdss, xswait);
if (rc) {
if (rc == ERROR_TIMEDOUT)
LOGD(ERROR, sdss->dm.guest_domid,
"%s: startup timed out", xswait->what);
goto out;
}
if (!p) return;
if (strcmp(p, "running"))
return;
out:
libxl__domain_build_state_dispose(&sdss->dm_state);
libxl__xswait_stop(gc, xswait);
dmss_dispose(gc, &sdss->dm);
dmss_dispose(gc, &sdss->pvqemu);
sdss->callback(egc, &sdss->dm, rc);
}
/* callbacks passed to libxl__spawn_spawn */
static void device_model_confirm(libxl__egc *egc, libxl__spawn_state *spawn,
const char *xsdata);
static void device_model_startup_failed(libxl__egc *egc,
libxl__spawn_state *spawn,
int rc);
static void device_model_detached(libxl__egc *egc,
libxl__spawn_state *spawn);
static void device_model_qmp_cb(libxl__egc *egc, libxl__ev_qmp *ev,
const libxl__json_object *response,
int rc);
/* our "next step" function, called from those callbacks and elsewhere */
static void device_model_spawn_outcome(libxl__egc *egc,
libxl__dm_spawn_state *dmss,
int rc);
static void device_model_probe_startup_failed(libxl__egc *egc,
libxl__spawn_state *spawn, int rc);
static void device_model_probe_confirm(libxl__egc *egc,
libxl__spawn_state *spawn, const char *xsdata);
static void device_model_probe_detached(libxl__egc *egc,
libxl__spawn_state *spawn);
static void device_model_probe_cmdline(libxl__egc *egc,
libxl__ev_qmp *qmp, const libxl__json_object *response, int rc);
static void device_model_probe_quit(libxl__egc *egc,
libxl__ev_qmp *qmp, const libxl__json_object *response, int rc);
static void device_model_probe_spawn_outcome(libxl__egc *egc,
libxl__dm_spawn_state *dmss, int rc);
static void device_model_launch(libxl__egc *egc,
libxl__dm_spawn_state *dmss, int rc);
static void device_model_postconfig_chardev(libxl__egc *egc,
libxl__ev_qmp *qmp, const libxl__json_object *response, int rc);
static void device_model_postconfig_vnc(libxl__egc *egc,
libxl__ev_qmp *qmp, const libxl__json_object *response, int rc);
static void device_model_postconfig_vnc_passwd(libxl__egc *egc,
libxl__ev_qmp *qmp, const libxl__json_object *response, int rc);
static void devise_model_postconfig_timeout(libxl__egc *egc,
libxl__ev_time *ev, const struct timeval *requested_abs, int rc);
static void device_model_postconfig_done(libxl__egc *egc,
libxl__dm_spawn_state *dmss, int rc);
void libxl__spawn_local_dm(libxl__egc *egc, libxl__dm_spawn_state *dmss)
{
/* convenience aliases */
const int domid = dmss->guest_domid;
libxl__spawn_state *const spawn = &dmss->spawn;
STATE_AO_GC(dmss->spawn.ao);
libxl_domain_config *guest_config = dmss->guest_config;
const libxl_domain_create_info *c_info = &guest_config->c_info;
const libxl_domain_build_info *b_info = &guest_config->b_info;
int rc;
const char *dm;
int logfile_w = -1, null = -1;
int qmp_probe_fd = -1;
bool probe_spawned = false;
dmss_init(dmss);
if (libxl_defbool_val(b_info->device_model_stubdomain)) {
abort();
}
dm = libxl__domain_device_model(gc, b_info);
if (!dm) {
rc = ERROR_FAIL;
goto out;
}
dmss->dm = dm;
if (access(dm, X_OK) < 0) {
LOGED(ERROR, domid, "device model %s is not executable", dm);
rc = ERROR_FAIL;
goto out;
}
rc = libxl__domain_get_device_model_uid(gc, dmss);
if (rc)
goto out;
/* probe QEMU's available command line options */
if (b_info->device_model_version
== LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN) {
logfile_w = libxl__create_qemu_logfile(
gc, GCSPRINTF("qemu-probe-%s", c_info->name));
if (logfile_w < 0) {
rc = logfile_w;
goto out;
}
null = open("/dev/null", O_RDONLY);
if (null < 0) {
LOGED(ERROR, domid, "unable to open /dev/null");
rc = ERROR_FAIL;
goto out;
}
rc = libxl__pre_open_qmp_socket(gc, domid, &qmp_probe_fd);
if (rc) goto out;
flexarray_t *dm_args = flexarray_make(gc, 16, 1);
flexarray_vappend(dm_args, dm,
"-S",
"-no-user-config",
"-nodefaults",
"-nographic",
"-machine", "none,accel=xen",
NULL);
flexarray_vappend(dm_args,
"-chardev",
GCSPRINTF("socket,id=libxl-cmd,fd=%d,server=on,wait=off",
qmp_probe_fd),
"-mon", "chardev=libxl-cmd,mode=control",
NULL);
flexarray_append(dm_args, NULL);
char **exec_args = (char **) flexarray_contents(dm_args);
const char *dom_path = libxl__xs_get_dompath(gc, domid);
spawn->what = GCSPRINTF("domain %d qemu command line probe", domid);
spawn->xspath = "/dev/null"; /* No path to watch */
spawn->timeout_ms = LIBXL_DEVICE_MODEL_START_TIMEOUT * 1000;
spawn->pidpath = GCSPRINTF("%s/image/device-model-pid", dom_path);
spawn->midproc_cb = libxl__spawn_record_pid;
spawn->confirm_cb = device_model_probe_confirm;
spawn->failure_cb = device_model_probe_startup_failed;
spawn->detached_cb = device_model_probe_detached;
dmss->qmp.ao = ao;
dmss->qmp.callback = device_model_probe_cmdline;
dmss->qmp.domid = domid;
dmss->qmp.payload_fd = -1;
rc = libxl__ev_qmp_send(egc, &dmss->qmp, "query-command-line-options", NULL);
if (rc) goto out;
rc = libxl__spawn_spawn(egc, spawn);
if (rc < 0)
goto out;
if (!rc) { /* inner child */
setsid();
libxl__exec(gc, null, logfile_w, logfile_w, dm, exec_args, NULL);
}
probe_spawned = true;
} else {
/* Continue with launching DM instead of probing it */
probe_spawned = false;
}
rc = 0;
out:
if (qmp_probe_fd >= 0)
close(qmp_probe_fd);
if (null >= 0)
close(null);
if (logfile_w >= 0)
close(logfile_w);
if (rc || !probe_spawned)
device_model_launch(egc, dmss, rc);
}
static void device_model_probe_startup_failed(libxl__egc *egc,
libxl__spawn_state *spawn, int rc)
{
libxl__dm_spawn_state *dmss = CONTAINER_OF(spawn, *dmss, spawn);
device_model_probe_spawn_outcome(egc, dmss, rc);
}
static void device_model_probe_confirm(libxl__egc *egc,
libxl__spawn_state *spawn, const char *xsdata)
{
/* Nothing to do, confirmation is done via QMP instead */
}
static void device_model_probe_detached(libxl__egc *egc,
libxl__spawn_state *spawn)
{
libxl__dm_spawn_state *dmss = CONTAINER_OF(spawn, *dmss, spawn);
device_model_probe_spawn_outcome(egc, dmss, 0);
}
static void device_model_probe_cmdline(libxl__egc *egc,
libxl__ev_qmp *qmp, const libxl__json_object *response, int rc)
{
EGC_GC;
libxl__dm_spawn_state *dmss = CONTAINER_OF(qmp, *dmss, qmp);
if (rc)
goto out;
/*
* query-command-line-options response:
* [ { 'option': 'str', 'parameters': [{ 'name': 'str', ... }] } ]
*/
const libxl__json_object *option;
for (int i_option = 0;
(option = libxl__json_array_get(response, i_option));
i_option++) {
const libxl__json_object *o;
const char *opt_str;
o = libxl__json_map_get("option", option, JSON_STRING);
if (!o) {
rc = ERROR_QEMU_API;
goto out;
}
opt_str = libxl__json_object_get_string(o);
if (!strcmp("run-with", opt_str)) {
const libxl__json_object *params;
const libxl__json_object *item;
params = libxl__json_map_get("parameters", option, JSON_ARRAY);
for (int i = 0; (item = libxl__json_array_get(params, i)); i++) {
o = libxl__json_map_get("name", item, JSON_STRING);
if (!o) {
rc = ERROR_QEMU_API;
goto out;
}
if (!strcmp("chroot", libxl__json_object_get_string(o))) {
dmss->qemu_opts.have_runwith_chroot = true;
}
else if (!strcmp("user", libxl__json_object_get_string(o))) {
dmss->qemu_opts.have_runwith_user = true;
}
}
/*
* No need to parse more options, we are only interested with
* -run-with at the moment.
*/
break;
}
}
qmp->callback = device_model_probe_quit;
rc = libxl__ev_qmp_send(egc, qmp, "quit", NULL);
if (rc) goto out;
return;
out:
libxl__spawn_initiate_failure(egc, &dmss->spawn, rc);
}
static void device_model_probe_quit(libxl__egc *egc,
libxl__ev_qmp *qmp, const libxl__json_object *response, int rc)
{
EGC_GC;
libxl__dm_spawn_state *dmss = CONTAINER_OF(qmp, *dmss, qmp);
libxl__ev_qmp_dispose(gc, qmp);
libxl__spawn_initiate_detach(gc, &dmss->spawn);
}
static void device_model_probe_spawn_outcome(libxl__egc *egc,
libxl__dm_spawn_state *dmss, int rc)
{
EGC_GC;
libxl__ev_qmp_dispose(gc, &dmss->qmp);
/* Ensure our QEMU command line probe is killed. */
rc = libxl__kill_xs_path(gc, dmss->spawn.pidpath,
"qemu command-line probe");
if (rc) {
LOGD(WARN, dmss->guest_domid,
"Killing qemu command-line probe pid from path %s",
dmss->spawn.pidpath);
}
/*
* Ignore all failure from the QEMU command line probe, start the
* device model in any case.
*/
device_model_launch(egc, dmss, 0);
}
static void device_model_launch(libxl__egc *egc,
libxl__dm_spawn_state *dmss, int rc)
{
STATE_AO_GC(dmss->spawn.ao);
libxl_ctx *ctx = CTX;
libxl_domain_config *guest_config = dmss->guest_config;
const libxl_domain_create_info *c_info = &guest_config->c_info;
const libxl_domain_build_info *b_info = &guest_config->b_info;
const libxl_vnc_info *vnc = libxl__dm_vnc(guest_config);
char *path;
int logfile_w, null;
char **args, **arg, **envs;
xs_transaction_t t;
char *vm_path;
char **pass_stuff;
int dm_state_fd = -1;
/* convenience aliases */
const int domid = dmss->guest_domid;
libxl__domain_build_state *const state = dmss->build_state;
libxl__spawn_state *const spawn = &dmss->spawn;
const char *const dm = dmss->dm;
if (rc)
goto out;
if (b_info->device_model_version
== LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN &&
libxl_defbool_val(b_info->dm_restrict)) {
/* If we have to use dm_restrict, QEMU needs to be new enough
* and will have the new interface where we can pre-open the
* QMP socket. */
rc = libxl__pre_open_qmp_socket(gc, domid, &state->dm_monitor_fd);
if (rc) goto out;
}
rc = libxl__build_device_model_args(gc, dm, domid, guest_config,
&args, &envs, state,
&dmss->qemu_opts,
&dm_state_fd);
if (rc)
goto out;
if (b_info->type == LIBXL_DOMAIN_TYPE_HVM) {
path = xs_get_domain_path(ctx->xsh, domid);
libxl__xs_printf(gc, XBT_NULL,
GCSPRINTF("%s/hvmloader/bios", path),
"%s", libxl_bios_type_to_string(b_info->u.hvm.bios));
/* Disable relocating memory to make the MMIO hole larger
* unless we're running qemu-traditional and vNUMA is not
* configured. */
libxl__xs_printf(gc, XBT_NULL,
GCSPRINTF("%s/hvmloader/allow-memory-relocate", path),
"%d",
b_info->device_model_version==LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL &&
!libxl__vnuma_configured(b_info));
free(path);
}
path = DEVICE_MODEL_XS_PATH(gc, LIBXL_TOOLSTACK_DOMID, domid, "");
xs_mkdir(ctx->xsh, XBT_NULL, path);
if (b_info->type == LIBXL_DOMAIN_TYPE_HVM &&
b_info->device_model_version
== LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL)
libxl__xs_printf(gc, XBT_NULL, GCSPRINTF("%s/disable_pf", path),
"%d", !libxl_defbool_val(b_info->u.hvm.xen_platform_pci));
logfile_w = libxl__create_qemu_logfile(gc, GCSPRINTF("qemu-dm-%s",
c_info->name));
if (logfile_w < 0) {
rc = logfile_w;
goto out;
}
null = open("/dev/null", O_RDONLY);
if (null < 0) {
LOGED(ERROR, domid, "unable to open /dev/null");
rc = ERROR_FAIL;
goto out_close;
}
const char *dom_path = libxl__xs_get_dompath(gc, domid);
/*
* If we're starting the dm with a non-root UID, save the UID so
* that we can reliably kill it and any subprocesses
*/
if (state->dm_kill_uid)
libxl__xs_printf(gc, XBT_NULL,
GCSPRINTF("%s/image/device-model-kill-uid", dom_path),
"%s", state->dm_kill_uid);
if (vnc && vnc->passwd) {
/* This xenstore key will only be used by qemu-xen-traditionnal.
* The code to supply vncpasswd to qemu-xen is later. */
retry_transaction:
/* Find uuid and the write the vnc password to xenstore for qemu. */
t = xs_transaction_start(ctx->xsh);
vm_path = libxl__xs_read(gc,t,GCSPRINTF("%s/vm", dom_path));
if (vm_path) {
/* Now write the vncpassword into it. */
pass_stuff = libxl__calloc(gc, 3, sizeof(char *));
pass_stuff[0] = "vncpasswd";
pass_stuff[1] = vnc->passwd;
libxl__xs_writev(gc,t,vm_path,pass_stuff);
if (!xs_transaction_end(ctx->xsh, t, 0))
if (errno == EAGAIN)
goto retry_transaction;
}
}
LOGD(DEBUG, domid, "Spawning device-model %s with arguments:", dm);
for (arg = args; *arg; arg++)
LOGD(DEBUG, domid, " %s", *arg);
if (*envs) {
LOGD(DEBUG, domid, "Spawning device-model %s with additional environment:", dm);
for (arg = envs; *arg; arg += 2)
LOGD(DEBUG, domid, " %s=%s", arg[0], arg[1]);
}
spawn->what = GCSPRINTF("domain %d device model", domid);
spawn->xspath = DEVICE_MODEL_XS_PATH(gc, LIBXL_TOOLSTACK_DOMID, domid,
"/state");
spawn->timeout_ms = LIBXL_DEVICE_MODEL_START_TIMEOUT * 1000;
spawn->pidpath = GCSPRINTF("%s/image/device-model-pid", dom_path);
spawn->midproc_cb = libxl__spawn_record_pid;
spawn->confirm_cb = device_model_confirm;
spawn->failure_cb = device_model_startup_failed;
spawn->detached_cb = device_model_detached;
if (state->dm_monitor_fd >= 0) {
/* There is a valid QMP socket available now,
* use it to find out when QEMU is ready */
dmss->qmp.ao = ao;
dmss->qmp.callback = device_model_qmp_cb;
dmss->qmp.domid = domid;
dmss->qmp.payload_fd = -1;
rc = libxl__ev_qmp_send(egc, &dmss->qmp, "query-status", NULL);
if (rc) goto out_close;
}
rc = libxl__spawn_spawn(egc, spawn);
if (rc < 0)
goto out_close;
if (!rc) { /* inner child */
setsid();
if (libxl_defbool_val(b_info->dm_restrict)) {
rc = libxl__local_dm_preexec_restrict(gc);
if (rc)
_exit(-1);
}
libxl__exec(gc, null, logfile_w, logfile_w, dm, args, envs);
}
rc = 0;
out_close:
if (null >= 0) close(null);
if (logfile_w >= 0) close(logfile_w);
out:
if (dm_state_fd >= 0) close(dm_state_fd);
if (rc)
device_model_spawn_outcome(egc, dmss, rc);
}
bool libxl__query_qemu_backend(libxl__gc *gc, uint32_t domid,
uint32_t backend_id, const char *type, bool def)
{
char *path;
char **dir;
unsigned int n;
path = GCSPRINTF("%s/device-model/%u/backends",
libxl__xs_get_dompath(gc, backend_id), domid);
dir = libxl__xs_directory(gc, XBT_NULL, path, &n);
if (!dir)
return def;
path = GCSPRINTF("%s/device-model/%u/backends/%s",
libxl__xs_get_dompath(gc, backend_id), domid, type);
dir = libxl__xs_directory(gc, XBT_NULL, path, &n);
return !!dir;
}
static void device_model_confirm(libxl__egc *egc, libxl__spawn_state *spawn,
const char *xsdata)
{
STATE_AO_GC(spawn->ao);
if (!xsdata)
return;
if (strcmp(xsdata, "running"))
return;
libxl__spawn_initiate_detach(gc, spawn);
}
static void device_model_startup_failed(libxl__egc *egc,
libxl__spawn_state *spawn,
int rc)
{
libxl__dm_spawn_state *dmss = CONTAINER_OF(spawn, *dmss, spawn);
device_model_spawn_outcome(egc, dmss, rc);
}
static void device_model_detached(libxl__egc *egc,
libxl__spawn_state *spawn)
{
libxl__dm_spawn_state *dmss = CONTAINER_OF(spawn, *dmss, spawn);
device_model_spawn_outcome(egc, dmss, 0);
}
static void device_model_qmp_cb(libxl__egc *egc, libxl__ev_qmp *ev,
const libxl__json_object *response,
int rc)
{
EGC_GC;
libxl__dm_spawn_state *dmss = CONTAINER_OF(ev, *dmss, qmp);
const libxl__json_object *o;
const char *status;
const char *expected_state;
libxl__ev_qmp_dispose(gc, ev);
if (rc)
goto failed;
o = libxl__json_map_get("status", response, JSON_STRING);
if (!o) {
LOGD(ERROR, ev->domid,
"Missing 'status' in response to 'query-status'");
LOGD(DEBUG, ev->domid, ".. instead, got: %s", JSON(response));
rc = ERROR_QEMU_API;
goto failed;
}
status = libxl__json_object_get_string(o);
if (!dmss->build_state->saved_state)
expected_state = "prelaunch";
else
expected_state = "paused";
if (strcmp(status, expected_state)) {
LOGD(ERROR, ev->domid, "Unexpected QEMU status: %s", status);
rc = ERROR_NOT_READY;
goto failed;
}
libxl__spawn_initiate_detach(gc, &dmss->spawn);
return;
failed:
LOGD(ERROR, ev->domid, "QEMU did not start properly, rc=%d", rc);
libxl__spawn_initiate_failure(egc, &dmss->spawn, rc);
}
static void device_model_spawn_outcome(libxl__egc *egc,
libxl__dm_spawn_state *dmss,
int rc)
{
STATE_AO_GC(dmss->spawn.ao);
int ret2;
/* Convenience aliases */
libxl_domain_config *const d_config = dmss->guest_config;
if (rc)
LOGD(ERROR, dmss->guest_domid,
"%s: spawn failed (rc=%d)", dmss->spawn.what, rc);
libxl__domain_build_state *state = dmss->build_state;
if (state->saved_state) {
ret2 = unlink(state->saved_state);
if (ret2) {
LOGED(ERROR, dmss->guest_domid, "%s: failed to remove device-model state %s",
dmss->spawn.what, state->saved_state);
rc = ERROR_FAIL;
goto out;
}
}
/* Check if spawn failed */
if (rc) goto out;
/* d_config is NULL for xl devd/libxl__spawn_qemu_xenpv_backend(). */
if (d_config && d_config->b_info.device_model_version
== LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN) {
rc = libxl__ev_time_register_rel(ao, &dmss->timeout,
devise_model_postconfig_timeout,
LIBXL_QMP_CMD_TIMEOUT * 1000);
if (rc) goto out;
dmss->qmp.ao = ao;
dmss->qmp.domid = dmss->guest_domid;
dmss->qmp.payload_fd = -1;
dmss->qmp.callback = device_model_postconfig_chardev;
rc = libxl__ev_qmp_send(egc, &dmss->qmp, "query-chardev", NULL);
if (rc) goto out;
return;
}
out:
device_model_postconfig_done(egc, dmss, rc); /* must be last */
}
static void device_model_postconfig_chardev(libxl__egc *egc,
libxl__ev_qmp *qmp, const libxl__json_object *response, int rc)
{
EGC_GC;
libxl__dm_spawn_state *dmss = CONTAINER_OF(qmp, *dmss, qmp);
const libxl_vnc_info *vnc = libxl__dm_vnc(dmss->guest_config);
const libxl__json_object *item = NULL;
const libxl__json_object *o = NULL;
int i = 0;
const char *label;
const char *filename;
int port;
char *endptr;
const char *dompath;
const char serial[] = "serial";
const size_t seriall = sizeof(serial) - 1;
const char pty[] = "pty:";
const size_t ptyl = sizeof(pty) - 1;
if (rc) goto out;
/*
* query-chardev response:
* [{ 'label': 'str',
* 'filename': 'str',
* 'frontend-open': 'bool' }, ...]
*/
for (i = 0; (item = libxl__json_array_get(response, i)); i++) {
o = libxl__json_map_get("label", item, JSON_STRING);
if (!o) goto protocol_error;
label = libxl__json_object_get_string(o);
/* Check if the "label" start with "serial". */
if (!label || strncmp(label, serial, seriall))
continue;
port = strtol(label + seriall, &endptr, 10);
if (*(label + seriall) == '\0' || *endptr != '\0') {
LOGD(ERROR, qmp->domid,
"Invalid serial port number: %s", label);
rc = ERROR_QEMU_API;
goto out;
}
o = libxl__json_map_get("filename", item, JSON_STRING);
if (!o) goto protocol_error;
filename = libxl__json_object_get_string(o);
/* Check if filename start with "pty:" */
if (!filename || strncmp(filename, pty, ptyl))
continue;
dompath = libxl__xs_get_dompath(gc, qmp->domid);
if (!dompath) {
rc = ERROR_FAIL;
goto out;
}
rc = libxl__xs_printf(gc, XBT_NULL,
GCSPRINTF("%s/serial/%d/tty", dompath, port),
"%s", filename + ptyl);
if (rc) goto out;
}
if (!vnc)
goto out;
qmp->callback = device_model_postconfig_vnc;
rc = libxl__ev_qmp_send(egc, qmp, "query-vnc", NULL);
if (rc) goto out;
return;
protocol_error:
rc = ERROR_QEMU_API;
LOGD(ERROR, qmp->domid,
"unexpected response to QMP cmd 'query-chardev', received:\n%s",
JSON(response));
out:
device_model_postconfig_done(egc, dmss, rc); /* must be last */
}
static void device_model_postconfig_vnc(libxl__egc *egc,
libxl__ev_qmp *qmp, const libxl__json_object *response, int rc)
{
EGC_GC;
libxl__dm_spawn_state *dmss = CONTAINER_OF(qmp, *dmss, qmp);
const libxl_vnc_info *vnc = libxl__dm_vnc(dmss->guest_config);
const libxl__json_object *o;
libxl__json_object *args = NULL;
if (rc) goto out;
/*
* query-vnc response:
* { 'enabled': 'bool', '*host': 'str', '*service': 'str' }
*/
o = libxl__json_map_get("enabled", response, JSON_BOOL);
if (!o) goto protocol_error;
if (libxl__json_object_get_bool(o)) {
const char *addr, *port;
const char *dompath;
o = libxl__json_map_get("host", response, JSON_STRING);
if (!o) goto protocol_error;
addr = libxl__json_object_get_string(o);
o = libxl__json_map_get("service", response, JSON_STRING);
if (!o) goto protocol_error;
port = libxl__json_object_get_string(o);
dompath = libxl__xs_get_dompath(gc, qmp->domid);
if (!dompath) {
rc = ERROR_FAIL;
goto out;
}
rc = libxl__xs_printf(gc, XBT_NULL,
GCSPRINTF("%s/console/vnc-listen", dompath),
"%s", addr);
if (rc) goto out;
rc = libxl__xs_printf(gc, XBT_NULL,
GCSPRINTF("%s/console/vnc-port", dompath),
"%s", port);
if (rc) goto out;
}
if (vnc && vnc->passwd && vnc->passwd[0]) {
qmp->callback = device_model_postconfig_vnc_passwd;
libxl__qmp_param_add_string(gc, &args, "password", vnc->passwd);
rc = libxl__ev_qmp_send(egc, qmp, "change-vnc-password", args);
if (rc) goto out;
return;
}
rc = 0;
goto out;
protocol_error:
rc = ERROR_QEMU_API;
LOGD(ERROR, qmp->domid,
"unexpected response to QMP cmd 'query-vnc', received:\n%s",
JSON(response));
out:
device_model_postconfig_done(egc, dmss, rc); /* must be last */
}
static void device_model_postconfig_vnc_passwd(libxl__egc *egc,
libxl__ev_qmp *qmp, const libxl__json_object *response, int rc)
{
EGC_GC;
libxl__dm_spawn_state *dmss = CONTAINER_OF(qmp, *dmss, qmp);
const libxl_vnc_info *vnc = libxl__dm_vnc(dmss->guest_config);
const char *dompath;
if (rc) goto out;
dompath = libxl__xs_get_dompath(gc, qmp->domid);
if (!dompath) {
rc = ERROR_FAIL;
goto out;
}
rc = libxl__xs_printf(gc, XBT_NULL,
GCSPRINTF("%s/console/vnc-pass", dompath),
"%s", vnc->passwd);
out:
device_model_postconfig_done(egc, dmss, rc); /* must be last */
}
void devise_model_postconfig_timeout(libxl__egc *egc, libxl__ev_time *ev,
const struct timeval *requested_abs,
int rc)
{
libxl__dm_spawn_state *dmss = CONTAINER_OF(ev, *dmss, timeout);
device_model_postconfig_done(egc, dmss, rc); /* must be last */
}
static void device_model_postconfig_done(libxl__egc *egc,
libxl__dm_spawn_state *dmss,
int rc)
{
EGC_GC;
if (rc)
LOGD(ERROR, dmss->guest_domid,
"Post DM startup configs failed, rc=%d", rc);
dmss_dispose(gc, dmss);
dmss->callback(egc, dmss, rc);
}
void libxl__spawn_qemu_xenpv_backend(libxl__egc *egc,
libxl__dm_spawn_state *dmss)
{
STATE_AO_GC(dmss->spawn.ao);
flexarray_t *dm_args, *dm_envs;
char **args, **envs;
const char *dm;
int logfile_w, null = -1, rc;
uint32_t domid = dmss->guest_domid;
dmss_init(dmss);
/* Always use qemu-xen as device model */
dm = qemu_xen_path(gc);
dm_args = flexarray_make(gc, 15, 1);
dm_envs = flexarray_make(gc, 1, 1);
flexarray_vappend(dm_args, dm, "-xen-domid",
GCSPRINTF("%d", domid), NULL);
flexarray_append(dm_args, "-xen-attach");
flexarray_vappend(dm_args, "-name",
GCSPRINTF("domain-%u", domid), NULL);
flexarray_append(dm_args, "-nographic");
flexarray_vappend(dm_args, "-M", "xenpv", NULL);
flexarray_vappend(dm_args, "-monitor", "/dev/null", NULL);
flexarray_vappend(dm_args, "-serial", "/dev/null", NULL);
flexarray_vappend(dm_args, "-parallel", "/dev/null", NULL);
flexarray_append(dm_args, NULL);
args = (char **) flexarray_contents(dm_args);
libxl__set_qemu_env_for_xsa_180(gc, dm_envs);
envs = (char **) flexarray_contents(dm_envs);
logfile_w = libxl__create_qemu_logfile(gc, GCSPRINTF("qdisk-%u", domid));
if (logfile_w < 0) {
rc = logfile_w;
goto out;
}
null = open("/dev/null", O_RDONLY);
if (null < 0) {
rc = ERROR_FAIL;
goto out;
}
dmss->guest_config = NULL;
/*
* Clearly specify Qemu not using a saved state, so
* device_model_spawn_outcome doesn't try to unlink it.
*/
dmss->build_state = libxl__zalloc(gc, sizeof(*dmss->build_state));
dmss->build_state->saved_state = 0;
dmss->spawn.what = GCSPRINTF("domain %u Qdisk backend", domid);
dmss->spawn.xspath = GCSPRINTF("device-model/%u/state", domid);
dmss->spawn.timeout_ms = LIBXL_DEVICE_MODEL_START_TIMEOUT * 1000;
/*
* We cannot save Qemu pid anywhere in the xenstore guest dir,
* because we will call this from unprivileged driver domains,
* so save it in the current domain libxl private dir.
*/
dmss->spawn.pidpath = GCSPRINTF("libxl/%u/qdisk-backend-pid", domid);
dmss->spawn.midproc_cb = libxl__spawn_record_pid;
dmss->spawn.confirm_cb = device_model_confirm;
dmss->spawn.failure_cb = device_model_startup_failed;
dmss->spawn.detached_cb = device_model_detached;
rc = libxl__spawn_spawn(egc, &dmss->spawn);
if (rc < 0)
goto out;
if (!rc) { /* inner child */
setsid();
libxl__exec(gc, null, logfile_w, logfile_w, dm, args, envs);
}
rc = 0;
out:
dmss_dispose(gc, dmss);
if (logfile_w >= 0) close(logfile_w);
if (null >= 0) close(null);
/* callback on error only, success goes via dmss->spawn.*_cb */
if (rc) dmss->callback(egc, dmss, rc);
return;
}
/* Generic function to signal a Qemu instance to exit */
static int kill_device_model(libxl__gc *gc, const char *xs_path_pid)
{
return libxl__kill_xs_path(gc, xs_path_pid, "Device Model");
}
/* Helper to destroy a Qdisk backend */
int libxl__destroy_qemu_xenpv_backend(libxl__gc *gc, uint32_t domid)
{
char *pid_path;
int rc;
pid_path = GCSPRINTF("libxl/%u/qdisk-backend-pid", domid);
rc = kill_device_model(gc, pid_path);
if (rc)
goto out;
libxl__xs_rm_checked(gc, XBT_NULL, pid_path);
libxl__xs_rm_checked(gc, XBT_NULL,
GCSPRINTF("device-model/%u", domid));
out:
return rc;
}
/* Asynchronous device model destroy functions */
static int kill_device_model_uid_child(libxl__destroy_devicemodel_state *ddms,
const char *dm_kill_uid_str);
static void kill_device_model_uid_cb(libxl__egc *egc,
libxl__ev_child *destroyer,
pid_t pid, int status);
/*
* If we have a uid, we shouldn't kill by pid. This is because a
* hostile QEMU might have exited, in which case the pid we have may
* be that of another process.
*
* The running devicemodel has permission over a specific domain id;
* this means that ideally we wouldn't the domain in question (freeing
* up the domain id for reuse) until we're confident that we've killed
* the domain.
*
* In general, destroy as much as we can; but return an error if there
* are any errors, so that the domain destroy will be aborted, and the
* domain itself will remain, giving the admin an opportunity to fix
* any issues and re-try the domain destroy.
*/
void libxl__destroy_device_model(libxl__egc *egc,
libxl__destroy_devicemodel_state *ddms)
{
STATE_AO_GC(ddms->ao);
int rc;
int domid = ddms->domid;
char *path;
const char *dm_kill_uid_str = NULL;
int reaper_pid;
ddms->rc = 0;
path = DEVICE_MODEL_XS_PATH(gc, LIBXL_TOOLSTACK_DOMID, domid, "");
rc = libxl__xs_rm_checked(gc, XBT_NULL, path);
if (rc) {
ACCUMULATE_RC(ddms->rc);
LOGD(ERROR, domid, "xs_rm failed for %s", path);
}
/*
* See if we should try to kill by uid
*/
path = GCSPRINTF("/local/domain/%d/image/device-model-kill-uid", domid);
rc = libxl__xs_read_checked(gc, XBT_NULL, path, &dm_kill_uid_str);
/*
* If there was an error here, accumulate the error and fall back
* to killing by pid.
*/
if (rc) {
/*
* Technically the state of the string passed to libxl__xs_read_checked() is
* "undefined" in the case rc == 0 (according to libxl_internal.h). Set it to
* NULL to prevent undefined behavior.
*/
dm_kill_uid_str = NULL;
ACCUMULATE_RC(ddms->rc);
LOGD(ERROR, domid, "Reading dm UID path failed for %s", path);
}
/* The DM has its own uid; Attempt to kill all processes with that UID */
if (dm_kill_uid_str) {
LOGD(DEBUG, domid, "Found DM uid %s, destroying by uid",
dm_kill_uid_str);
reaper_pid = libxl__ev_child_fork(gc, &ddms->destroyer,
kill_device_model_uid_cb);
if (reaper_pid < 0) {
rc = ERROR_FAIL;
ACCUMULATE_RC(ddms->rc);
/*
* Note that if this fails, we still don't kill by pid, to
* make sure that an untrusted DM has not "maliciously"
* exited (potentially causing us to kill an unrelated
* process which happened to get the same pid).
*/
goto out;
}
if (!reaper_pid) { /* child */
rc = kill_device_model_uid_child(ddms, dm_kill_uid_str);
assert(rc <= 0 && rc >= -125);
_exit(-rc);
}
/*
* Parent of successful fork; execution will pick up in
* kill_device_model_uid_cb when child exits
*/
return;
}
/*
* No uid to kill; attept to kill by pid.
*/
LOGD(DEBUG, domid, "Didn't find dm UID; destroying by pid");
path = GCSPRINTF("/local/domain/%d/image/device-model-pid", domid);
rc = kill_device_model(gc, path);
if (rc) {
ACCUMULATE_RC(ddms->rc);
LOGD(ERROR, domid, "Killing device model pid from path %s", path);
}
out:
/*
* NB that we always pass '0' here for the "status of exited
* process"; since there is no process, it always "succeeds".
* Errors are accumulated in ddms->rc and will be handled
* correctly.
*/
kill_device_model_uid_cb(egc, &ddms->destroyer, -1, 0);
return;
}
/*
* Note that this attempts to grab a file lock, so must be called from
* a sub-process.
*/
static int get_reaper_lock_and_uid(libxl__destroy_devicemodel_state *ddms,
uid_t *reaper_uid)
{
STATE_AO_GC(ddms->ao);
int domid = ddms->domid;
int r;
const char * lockfile;
int fd; /* "leaked" into the general process context (even on failure) */
/* Try to lock the "reaper uid" */
lockfile = GCSPRINTF("%s/dm-reaper-lock", libxl__run_dir_path());
/*
* NB that since we've just forked, we can't have any
* threads; so we don't need the libxl__carefd
* infrastructure here.
*/
fd = open(lockfile, O_RDWR|O_CREAT, 0644);
if (fd < 0) {
LOGED(ERROR, domid,
"unexpected error while trying to open lockfile %s, errno=%d",
lockfile, errno);
return ERROR_FAIL;
}
/* Try to lock the file, retrying on EINTR */
for (;;) {
r = flock(fd, LOCK_EX);
if (!r)
break;
if (errno != EINTR) {
/* All other errno: EBADF, EINVAL, ENOLCK, EWOULDBLOCK */
LOGED(ERROR, domid,
"unexpected error while trying to lock %s, fd=%d, errno=%d",
lockfile, fd, errno);
return ERROR_FAIL;
}
}
/*
* Get reaper_uid. If we can't find such a uid, return an error.
*/
return libxl__get_reaper_uid(gc, reaper_uid);
}
/*
* Destroy all processes of the given uid by setresuid to the
* specified uid and kill(-1). NB this MUST BE CALLED FROM A SEPARATE
* PROCESS from the normal libxl process, and should exit immediately
* after return. Returns a libxl-style error code that is guaranteed
* to be >= -125.
*/
static int kill_device_model_uid_child(libxl__destroy_devicemodel_state *ddms,
const char *dm_kill_uid_str) {
STATE_AO_GC(ddms->ao);
int domid = ddms->domid;
int r, rc = 0;
uid_t dm_kill_uid = atoi(dm_kill_uid_str);
uid_t reaper_uid;
/*
* Try to kill the devicemodel by uid. The safest way to do this
* is to set euid == dm_uid, but the ruid to something else. If
* we can't get a separate ruid, carry on trying to kill the
* process anyway using dm_uid for the ruid. This is racy (the dm
* may be able to kill(-1) us before we kill them), but worth
* trying.
*
* NB: Even if we don't have a separate reaper_uid, the parent can
* know whether we won the race by looking at the status variable;
* so we don't strictly need to return failure in this case. But
* if there's a misconfiguration, it's better to alert the
* administator sooner rather than later; so if we fail to get a
* reaper uid, report an error even if the kill succeeds.
*/
rc = get_reaper_lock_and_uid(ddms, &reaper_uid);
if (rc) {
reaper_uid = dm_kill_uid;
LOGD(WARN, domid, "Couldn't get separate reaper uid;"
"carrying on with unsafe kill");
}
/*
* Should never happen; but if it does, better to have the
* toolstack crash with an error than nuking dom0.
*/
assert(reaper_uid);
assert(dm_kill_uid);
LOGD(DEBUG, domid, "DM reaper: calling setresuid(%d, %d, 0)",
reaper_uid, dm_kill_uid);
r = libxl__setresuid(reaper_uid, dm_kill_uid, 0);
if (r) {
LOGED(ERROR, domid, "setresuid to (%d, %d, 0)",
reaper_uid, dm_kill_uid);
rc = rc ?: ERROR_FAIL;
goto out;
}
/*
* And kill everyone but me.
*
* NB that it's not clear from either POSIX or the Linux man page
* that ESRCH would be returned with a pid value of -1, but it
* doesn't hurt to check.
*/
r = kill(-1, 9);
if (r && errno != ESRCH) {
LOGED(ERROR, domid, "kill(-1,9)");
rc = rc ?: ERROR_FAIL;
goto out;
}
rc = 0;
out:
return rc;
}
static void kill_device_model_uid_cb(libxl__egc *egc,
libxl__ev_child *destroyer,
pid_t pid, int status)
{
libxl__destroy_devicemodel_state *ddms = CONTAINER_OF(destroyer, *ddms, destroyer);
STATE_AO_GC(ddms->ao);
if (status) {
int rc = ERROR_FAIL;
if (WIFEXITED(status) && WEXITSTATUS(status) <= 125)
rc = -WEXITSTATUS(status);
ACCUMULATE_RC(ddms->rc);
libxl_report_child_exitstatus(CTX, XTL_ERROR,
"async domain destroy", pid, status);
}
/* Always try to clean up qmp, even if something went wrong */
libxl__qmp_cleanup(gc, ddms->domid);
ddms->callback(egc, ddms, ddms->rc);
}
/* Return 0 if no dm needed, 1 if needed and <0 if error. */
int libxl__need_xenpv_qemu(libxl__gc *gc, libxl_domain_config *d_config)
{
int idx, i, ret, num;
uint32_t domid;
const libxl__device_type *dt;
ret = libxl__get_domid(gc, &domid);
if (ret) {
LOG(ERROR, "unable to get domain id");
goto out;
}
if (d_config->num_vfbs > 0) {
ret = 1;
goto out;
}
for (idx = 0;; idx++) {
dt = device_type_tbl[idx];
if (!dt)
break;
num = *libxl__device_type_get_num(dt, d_config);
if (!dt->dm_needed || !num)
continue;
for (i = 0; i < num; i++) {
if (dt->dm_needed(libxl__device_type_get_elem(dt, d_config, i),
domid)) {
ret = 1;
goto out;
}
}
}
for (i = 0; i < d_config->num_channels; i++) {
if (d_config->channels[i].backend_domid == domid) {
/* xenconsoled is limited to the first console only.
Until this restriction is removed we must use qemu for
secondary consoles which includes all channels. */
ret = 1;
goto out;
}
}
out:
return ret;
}
int libxl__dm_active(libxl__gc *gc, uint32_t domid)
{
char *pid, *dm_domid, *path;
path = GCSPRINTF("/local/domain/%d/image/device-model-pid", domid);
pid = libxl__xs_read(gc, XBT_NULL, path);
if (pid)
return true;
path = GCSPRINTF("/local/domain/%d/image/device-model-domid", domid);
dm_domid = libxl__xs_read(gc, XBT_NULL, path);
return dm_domid != NULL;
}
int libxl__dm_check_start(libxl__gc *gc, libxl_domain_config *d_config,
uint32_t domid)
{
int rc;
if (libxl__dm_active(gc, domid))
return 0;
rc = libxl__need_xenpv_qemu(gc, d_config);
if (rc < 0)
goto out;
if (!rc)
return 0;
LOGD(ERROR, domid, "device model required but not running");
rc = ERROR_FAIL;
out:
return rc;
}
/*
* Local variables:
* mode: C
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/