Files
xen/tools/libs/light/libxl_device.c
T
Demi Marie Obenour 531d3bea5e libxl: Fix handling XenStore errors in device creation
If xenstored runs out of memory it is possible for it to fail operations
that should succeed.  libxl wasn't robust against this, and could fail
to ensure that the TTY path of a non-initial console was created and
read-only for guests.  This doesn't qualify for an XSA because guests
should not be able to run xenstored out of memory, but it still needs to
be fixed.

Add the missing error checks to ensure that all errors are properly
handled and that at no point can a guest make the TTY path of its
frontend directory writable.

Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
Reviewed-by: Juergen Gross <jgross@suse.com>
2024-05-11 00:13:43 +01:00

2188 lines
68 KiB
C

/*
* Copyright (C) 2009 Citrix Ltd.
* Author Vincent Hanquez <vincent.hanquez@eu.citrix.com>
* Author Stefano Stabellini <stefano.stabellini@eu.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"
static char *libxl__device_frontend_path(libxl__gc *gc, libxl__device *device)
{
char *dom_path = libxl__xs_get_dompath(gc, device->domid);
/* Console 0 is a special case */
if (device->kind == LIBXL__DEVICE_KIND_CONSOLE && device->devid == 0)
return GCSPRINTF("%s/%s", dom_path,
libxl__device_kind_to_string(device->kind));
if (device->kind == LIBXL__DEVICE_KIND_VUART)
return GCSPRINTF("%s/%s/%d", dom_path,
libxl__device_kind_to_string(device->kind),
device->devid);
return GCSPRINTF("%s/device/%s/%d", dom_path,
libxl__device_kind_to_string(device->kind),
device->devid);
}
char *libxl__domain_device_frontend_path(libxl__gc *gc, uint32_t domid, uint32_t devid,
libxl__device_kind device_kind)
{
char *dom_path = libxl__xs_get_dompath(gc, domid);
return GCSPRINTF("%s/device/%s/%d", dom_path,
libxl__device_kind_to_string(device_kind), devid);
}
char *libxl__device_backend_path(libxl__gc *gc, libxl__device *device)
{
char *dom_path = libxl__xs_get_dompath(gc, device->backend_domid);
return GCSPRINTF("%s/backend/%s/%u/%d", dom_path,
libxl__device_kind_to_string(device->backend_kind),
device->domid, device->devid);
}
char *libxl__domain_device_backend_path(libxl__gc *gc, uint32_t backend_domid,
uint32_t domid, uint32_t devid,
libxl__device_kind backend_kind)
{
char *dom_path = libxl__xs_get_dompath(gc, backend_domid);
return GCSPRINTF("%s/backend/%s/%u/%d", dom_path,
libxl__device_kind_to_string(backend_kind),
domid, devid);
}
char *libxl__device_libxl_path(libxl__gc *gc, libxl__device *device)
{
char *libxl_dom_path = libxl__xs_libxl_path(gc, device->domid);
return GCSPRINTF("%s/device/%s/%d", libxl_dom_path,
libxl__device_kind_to_string(device->kind),
device->devid);
}
char *libxl__domain_device_libxl_path(libxl__gc *gc, uint32_t domid, uint32_t devid,
libxl__device_kind device_kind)
{
char *libxl_dom_path = libxl__xs_libxl_path(gc, domid);
return GCSPRINTF("%s/device/%s/%d", libxl_dom_path,
libxl__device_kind_to_string(device_kind), devid);
}
/* Returns 1 if device exists, 0 if not, ERROR_* (<0) on error. */
int libxl__device_exists(libxl__gc *gc, xs_transaction_t t,
libxl__device *device)
{
int rc;
char *be_path = libxl__device_libxl_path(gc, device);
const char *dir;
rc = libxl__xs_read_checked(gc, t, be_path, &dir);
if (rc)
return rc;
if (dir)
return 1;
return 0;
}
int libxl__parse_backend_path(libxl__gc *gc,
const char *path,
libxl__device *dev)
{
/* /local/domain/<domid>/backend/<kind>/<domid>/<devid> */
char strkind[16]; /* Longest is actually "console" */
int rc = sscanf(path, "/local/domain/%d/backend/%15[^/]/%u/%d",
&dev->backend_domid,
strkind,
&dev->domid,
&dev->devid);
if (rc != 4)
return ERROR_FAIL;
return libxl__device_kind_from_string(strkind, &dev->backend_kind);
}
int libxl__nic_type(libxl__gc *gc, libxl__device *dev, libxl_nic_type *nictype)
{
char *snictype, *be_path;
int rc = 0;
be_path = libxl__device_backend_path(gc, dev);
snictype = libxl__xs_read(gc, XBT_NULL,
GCSPRINTF("%s/%s", be_path, "type"));
if (!snictype) {
LOGED(ERROR, dev->domid, "unable to read nictype from %s", be_path);
rc = ERROR_FAIL;
goto out;
}
rc = libxl_nic_type_from_string(snictype, nictype);
if (rc) {
LOGED(ERROR, dev->domid, "unable to parse nictype from %s", be_path);
goto out;
}
rc = 0;
out:
return rc;
}
int libxl__device_generic_add(libxl__gc *gc, xs_transaction_t t,
libxl__device *device, char **bents, char **fents, char **ro_fents)
{
libxl_ctx *ctx = libxl__gc_owner(gc);
char *frontend_path = NULL, *backend_path = NULL, *libxl_path;
struct xs_permissions frontend_perms[2];
struct xs_permissions ro_frontend_perms[2];
struct xs_permissions backend_perms[2];
int create_transaction = t == XBT_NULL;
int libxl_only = device->backend_kind == LIBXL__DEVICE_KIND_NONE;
int rc;
if (libxl_only) {
/* bents should be set as this is used to setup libxl_path content. */
assert(!fents && !ro_fents);
} else {
frontend_path = libxl__device_frontend_path(gc, device);
backend_path = libxl__device_backend_path(gc, device);
}
libxl_path = libxl__device_libxl_path(gc, device);
frontend_perms[0].id = device->domid;
frontend_perms[0].perms = XS_PERM_NONE;
frontend_perms[1].id = device->backend_domid;
frontend_perms[1].perms = XS_PERM_READ;
ro_frontend_perms[0].id = backend_perms[0].id = device->backend_domid;
ro_frontend_perms[0].perms = backend_perms[0].perms = XS_PERM_NONE;
ro_frontend_perms[1].id = backend_perms[1].id = device->domid;
ro_frontend_perms[1].perms = backend_perms[1].perms = XS_PERM_READ;
retry_transaction:
if (create_transaction) {
t = xs_transaction_start(ctx->xsh);
if (t == XBT_NULL) {
LOGED(ERROR, device->domid, "xs_transaction_start failed");
return ERROR_FAIL;
}
}
/* FIXME: read frontend_path and check state before removing stuff */
rc = libxl__xs_rm_checked(gc, t, libxl_path);
if (rc) goto out;
if (!libxl_only) {
rc = libxl__xs_write_checked(gc, t, GCSPRINTF("%s/frontend",libxl_path),
frontend_path);
if (rc) goto out;
rc = libxl__xs_write_checked(gc, t, GCSPRINTF("%s/backend",libxl_path),
backend_path);
if (rc) goto out;
}
if (fents || ro_fents) {
if (!xs_rm(ctx->xsh, t, frontend_path) && errno != ENOENT)
goto out;
if (!xs_mkdir(ctx->xsh, t, frontend_path))
goto out;
/* Console 0 is a special case. It doesn't use the regular PV
* state machine but also the frontend directory has
* historically contained other information, such as the
* vnc-port, which we don't want the guest fiddling with.
*/
if ((device->kind == LIBXL__DEVICE_KIND_CONSOLE && device->devid == 0) ||
(device->kind == LIBXL__DEVICE_KIND_VUART)) {
if (!xs_set_permissions(ctx->xsh, t, frontend_path,
ro_frontend_perms, ARRAY_SIZE(ro_frontend_perms)))
goto out;
} else {
if (!xs_set_permissions(ctx->xsh, t, frontend_path,
frontend_perms, ARRAY_SIZE(frontend_perms)))
goto out;
}
if (!xs_write(ctx->xsh, t, GCSPRINTF("%s/backend", frontend_path),
backend_path, strlen(backend_path)))
goto out;
if (fents) {
rc = libxl__xs_writev_perms(gc, t, frontend_path, fents,
frontend_perms, ARRAY_SIZE(frontend_perms));
if (rc) goto out;
}
if (ro_fents) {
rc = libxl__xs_writev_perms(gc, t, frontend_path, ro_fents,
ro_frontend_perms, ARRAY_SIZE(ro_frontend_perms));
if (rc) goto out;
}
}
if (bents) {
if (!libxl_only) {
if (!xs_rm(ctx->xsh, t, backend_path) && errno != ENOENT)
goto out;
if (!xs_mkdir(ctx->xsh, t, backend_path))
goto out;
if (!xs_set_permissions(ctx->xsh, t, backend_path, backend_perms,
ARRAY_SIZE(backend_perms)))
goto out;
if (!xs_write(ctx->xsh, t, GCSPRINTF("%s/frontend", backend_path),
frontend_path, strlen(frontend_path)))
goto out;
rc = libxl__xs_writev(gc, t, backend_path, bents);
if (rc) goto out;
}
/*
* We make a copy of everything for the backend in the libxl
* path as well. This means we don't need to trust the
* backend. Ideally this information would not be used and we
* would use the information from the json configuration
* instead. But there are still places in libxl that try to
* reconstruct a config from xenstore.
*
* For devices without PV backend (e.g. USB devices emulated via qemu)
* only the libxl path is written.
*
* This duplication will typically produces duplicate keys
* which will go out of date, but that's OK because nothing
* reads those. For example, there is usually
* /libxl/$guest/device/$kind/$devid/state
* which starts out containing XenbusStateInitialising ("1")
* just like the copy in
* /local/domain/$driverdom/backend/$guest/$kind/$devid/state
* but which won't ever be updated.
*
* This duplication is superfluous and messy but as discussed
* the proper fix is more intrusive than we want to do now.
*/
rc = libxl__xs_writev(gc, t, libxl_path, bents);
if (rc) goto out;
}
if (!create_transaction)
return 0;
if (!xs_transaction_end(ctx->xsh, t, 0)) {
if (errno == EAGAIN)
goto retry_transaction;
else {
LOGED(ERROR, device->domid, "xs transaction failed");
return ERROR_FAIL;
}
}
return 0;
out:
if (create_transaction && t)
libxl__xs_transaction_abort(gc, &t);
return rc != 0 ? rc : ERROR_FAIL;
}
typedef struct {
libxl__gc *gc;
libxl_device_disk *disk;
struct stat stab;
} disk_try_backend_args;
static int disk_try_backend(disk_try_backend_args *a,
libxl_disk_backend backend)
{
libxl__gc *gc = a->gc;
libxl_disk_specification specification = a->disk->specification;
/* returns 0 (ie, DISK_BACKEND_UNKNOWN) on failure, or
* backend on success */
if ((specification == LIBXL_DISK_SPECIFICATION_VIRTIO &&
backend != LIBXL_DISK_BACKEND_STANDALONE) ||
(specification != LIBXL_DISK_SPECIFICATION_VIRTIO &&
backend == LIBXL_DISK_BACKEND_STANDALONE))
goto bad_specification;
switch (backend) {
case LIBXL_DISK_BACKEND_PHY:
if (libxl_defbool_val(a->disk->colo_enable))
goto bad_colo;
if (a->disk->is_cdrom && a->disk->format == LIBXL_DISK_FORMAT_EMPTY) {
LOG(DEBUG, "Disk vdev=%s is an empty cdrom", a->disk->vdev);
return backend;
}
if (a->disk->format != LIBXL_DISK_FORMAT_RAW)
goto bad_format;
if (a->disk->backend_domid != LIBXL_TOOLSTACK_DOMID) {
LOG(DEBUG, "Disk vdev=%s, is using a storage driver domain, "
"skipping physical device check", a->disk->vdev);
return backend;
}
if (a->disk->script) {
LOG(DEBUG, "Disk vdev=%s, uses script=... assuming phy backend",
a->disk->vdev);
return backend;
}
if (libxl__try_phy_backend(a->stab.st_mode))
return backend;
LOG(DEBUG, "Disk vdev=%s, backend phy unsuitable as phys path not a "
"block device", a->disk->vdev);
return 0;
case LIBXL_DISK_BACKEND_TAP:
if (a->disk->format != LIBXL_DISK_FORMAT_RAW &&
a->disk->format != LIBXL_DISK_FORMAT_VHD)
goto bad_format;
if (libxl_defbool_val(a->disk->colo_enable))
goto bad_colo;
LOG(DEBUG, "Disk vdev=%s, returning blktap", a->disk->vdev);
return backend;
case LIBXL_DISK_BACKEND_QDISK:
if (a->disk->script) goto bad_script;
return backend;
case LIBXL_DISK_BACKEND_STANDALONE:
if (a->disk->format != LIBXL_DISK_FORMAT_RAW)
goto bad_format;
if (a->disk->script)
goto bad_script;
if (libxl_defbool_val(a->disk->colo_enable))
goto bad_colo;
if (a->disk->backend_domid != LIBXL_TOOLSTACK_DOMID) {
LOG(DEBUG, "Disk vdev=%s, is using a storage driver domain, "
"skipping physical device check", a->disk->vdev);
return backend;
}
if (libxl__try_phy_backend(a->stab.st_mode))
return backend;
LOG(DEBUG, "Disk vdev=%s, backend standalone unsuitable as phys path not a "
"block device", a->disk->vdev);
return 0;
default:
LOG(DEBUG, "Disk vdev=%s, backend %d unknown", a->disk->vdev, backend);
return 0;
}
abort(); /* notreached */
bad_format:
LOG(DEBUG, "Disk vdev=%s, backend %s unsuitable due to format %s",
a->disk->vdev,
libxl_disk_backend_to_string(backend),
libxl_disk_format_to_string(a->disk->format));
return 0;
bad_script:
LOG(DEBUG, "Disk vdev=%s, backend %s not compatible with script=...",
a->disk->vdev, libxl_disk_backend_to_string(backend));
return 0;
bad_colo:
LOG(DEBUG, "Disk vdev=%s, backend %s not compatible with colo",
a->disk->vdev, libxl_disk_backend_to_string(backend));
return 0;
bad_specification:
LOG(DEBUG, "Disk vdev=%s, backend %s not compatible with specification %s",
a->disk->vdev, libxl_disk_backend_to_string(backend),
libxl_disk_specification_to_string(specification));
return 0;
}
int libxl__backendpath_parse_domid(libxl__gc *gc, const char *be_path,
libxl_domid *domid_out) {
int r;
unsigned int domid_sc;
char delim_sc;
r = sscanf(be_path, "/local/domain/%u%c", &domid_sc, &delim_sc);
if (!(r==2 && delim_sc=='/')) {
LOG(ERROR, "internal error: backend path %s unparseable!", be_path);
return ERROR_FAIL;
}
*domid_out = domid_sc;
return 0;
}
int libxl__device_disk_set_backend(libxl__gc *gc, libxl_device_disk *disk) {
libxl_disk_backend ok;
disk_try_backend_args a;
a.gc = gc;
a.disk = disk;
LOG(DEBUG, "Disk vdev=%s spec.backend=%s specification=%s", disk->vdev,
libxl_disk_backend_to_string(disk->backend),
libxl_disk_specification_to_string(disk->specification));
if (disk->format == LIBXL_DISK_FORMAT_EMPTY) {
if (!disk->is_cdrom) {
LOG(ERROR, "Disk vdev=%s is empty but not cdrom", disk->vdev);
return ERROR_INVAL;
}
if (disk->pdev_path != NULL &&
(strcmp(disk->pdev_path, "") &&
strncmp(disk->pdev_path, LIBXL_STUBDOM_EMPTY_CDROM,
strlen(LIBXL_STUBDOM_EMPTY_CDROM)))) {
LOG(ERROR,
"Disk vdev=%s is empty but an image has been provided: %s",
disk->vdev, disk->pdev_path);
return ERROR_INVAL;
}
memset(&a.stab, 0, sizeof(a.stab));
} else if ((disk->backend == LIBXL_DISK_BACKEND_UNKNOWN ||
disk->backend == LIBXL_DISK_BACKEND_PHY ||
disk->backend == LIBXL_DISK_BACKEND_STANDALONE) &&
disk->backend_domid == LIBXL_TOOLSTACK_DOMID &&
!disk->script) {
if (stat(disk->pdev_path, &a.stab)) {
LOGE(ERROR, "Disk vdev=%s failed to stat: %s",
disk->vdev, disk->pdev_path);
return ERROR_INVAL;
}
}
if (disk->backend != LIBXL_DISK_BACKEND_UNKNOWN) {
ok= disk_try_backend(&a, disk->backend);
} else {
ok=
disk_try_backend(&a, LIBXL_DISK_BACKEND_PHY) ?:
disk_try_backend(&a, LIBXL_DISK_BACKEND_QDISK) ?:
disk_try_backend(&a, LIBXL_DISK_BACKEND_TAP) ?:
disk_try_backend(&a, LIBXL_DISK_BACKEND_STANDALONE);
if (ok)
LOG(DEBUG, "Disk vdev=%s, using backend %s",
disk->vdev,
libxl_disk_backend_to_string(ok));
}
if (!ok) {
LOG(ERROR, "no suitable backend for disk %s", disk->vdev);
return ERROR_INVAL;
}
disk->backend = ok;
return 0;
}
char *libxl__device_disk_string_of_format(libxl_disk_format format)
{
switch (format) {
case LIBXL_DISK_FORMAT_QCOW: return "qcow";
case LIBXL_DISK_FORMAT_QCOW2: return "qcow2";
case LIBXL_DISK_FORMAT_VHD: return "vhd";
case LIBXL_DISK_FORMAT_RAW:
case LIBXL_DISK_FORMAT_EMPTY: return "aio";
case LIBXL_DISK_FORMAT_QED: return "qed";
default: return NULL;
}
}
char *libxl__device_disk_string_of_backend(libxl_disk_backend backend)
{
switch (backend) {
case LIBXL_DISK_BACKEND_QDISK: return "qdisk";
case LIBXL_DISK_BACKEND_TAP: return "vbd3";
case LIBXL_DISK_BACKEND_PHY: return "phy";
case LIBXL_DISK_BACKEND_STANDALONE: return "standalone";
default: return NULL;
}
}
char *libxl__device_disk_string_of_specification(libxl_disk_specification specification)
{
switch (specification) {
case LIBXL_DISK_SPECIFICATION_XEN: return "xen";
case LIBXL_DISK_SPECIFICATION_VIRTIO: return "virtio";
default: return NULL;
}
}
char *libxl__device_disk_string_of_transport(libxl_disk_transport transport)
{
return (transport == LIBXL_DISK_TRANSPORT_MMIO ? "mmio" : NULL);
}
const char *libxl__qemu_disk_format_string(libxl_disk_format format)
{
switch (format) {
case LIBXL_DISK_FORMAT_QCOW: return "qcow";
case LIBXL_DISK_FORMAT_QCOW2: return "qcow2";
case LIBXL_DISK_FORMAT_VHD: return "vpc";
case LIBXL_DISK_FORMAT_RAW: return "raw";
case LIBXL_DISK_FORMAT_EMPTY: return NULL;
case LIBXL_DISK_FORMAT_QED: return "qed";
default: return NULL;
}
}
int libxl__device_physdisk_major_minor(const char *physpath, int *major, int *minor)
{
struct stat buf;
if (stat(physpath, &buf) < 0)
return -1;
if (!S_ISBLK(buf.st_mode))
return -1;
*major = major(buf.st_rdev);
*minor = minor(buf.st_rdev);
return 0;
}
static int device_virtdisk_matches(const char *virtpath, const char *devtype,
int *index_r, int max_index,
int *partition_r, int max_partition) {
const char *p;
char *ep;
int tl, c;
long pl;
tl = strlen(devtype);
if (memcmp(virtpath, devtype, tl))
return 0;
/* We decode the drive letter as if it were in base 52
* with digits a-zA-Z, more or less */
*index_r = -1;
p = virtpath + tl;
for (;;) {
c = *p++;
if (c >= 'a' && c <= 'z') {
c -= 'a';
} else {
--p;
break;
}
(*index_r)++;
(*index_r) *= 26;
(*index_r) += c;
if (*index_r > max_index)
return 0;
}
if (!*p) {
*partition_r = 0;
return 1;
}
if (*p=='0')
return 0; /* leading zeroes not permitted in partition number */
pl = strtoul(p, &ep, 10);
if (pl > max_partition || *ep)
return 0;
*partition_r = pl;
return 1;
}
int libxl__device_disk_dev_number(const char *virtpath, int *pdisk,
int *ppartition)
{
int disk, partition;
char *ep;
unsigned long ul;
int chrused;
chrused = -1;
if ((sscanf(virtpath, "d%ip%i%n", &disk, &partition, &chrused) >= 2
&& chrused == strlen(virtpath) && disk < (1<<20) && partition < 256)
||
device_virtdisk_matches(virtpath, "xvd",
&disk, (1<<20)-1,
&partition, 255)) {
if (pdisk) *pdisk = disk;
if (ppartition) *ppartition = partition;
if (disk <= 15 && partition <= 15)
return (202 << 8) | (disk << 4) | partition;
else
return (1 << 28) | (disk << 8) | partition;
}
errno = 0;
ul = strtoul(virtpath, &ep, 0);
if (!errno && !*ep && ul <= INT_MAX) {
/* FIXME: should parse ul to determine these. */
if (pdisk || ppartition)
return -1;
return ul;
}
if (device_virtdisk_matches(virtpath, "hd",
&disk, 3,
&partition, 63)) {
if (pdisk) *pdisk = disk;
if (ppartition) *ppartition = partition;
return ((disk<2 ? 3 : 22) << 8) | ((disk & 1) << 6) | partition;
}
if (device_virtdisk_matches(virtpath, "sd",
&disk, 15,
&partition, 15)) {
if (pdisk) *pdisk = disk;
if (ppartition) *ppartition = partition;
return (8 << 8) | (disk << 4) | partition;
}
return -1;
}
static char *encode_disk_name(char *ptr, unsigned int n)
{
if (n >= 26)
ptr = encode_disk_name(ptr, n / 26 - 1);
*ptr = 'a' + n % 26;
return ptr + 1;
}
char *libxl__devid_to_vdev(libxl__gc *gc, int devid)
{
unsigned int minor;
int offset;
int nr_parts;
char *ptr = NULL;
/* Same as in Linux.
* encode_disk_name might end up using up to 29 bytes (BUFFER_SIZE - 3)
* including the trailing \0.
*
* The code is safe because 26 raised to the power of 28 (that is the
* maximum offset that can be stored in the allocated buffer as a
* string) is far greater than UINT_MAX on 64 bits so offset cannot be
* big enough to exhaust the available bytes in ret. */
#define BUFFER_SIZE 32
char *ret = libxl__zalloc(gc, BUFFER_SIZE);
#define EXT_SHIFT 28
#define EXTENDED (1<<EXT_SHIFT)
#define VDEV_IS_EXTENDED(dev) ((dev)&(EXTENDED))
#define BLKIF_MINOR_EXT(dev) ((dev)&(~EXTENDED))
/* the size of the buffer to store the device name is 32 bytes to match the
* equivalent buffer in the Linux kernel code */
if (!VDEV_IS_EXTENDED(devid)) {
minor = devid & 0xff;
nr_parts = 16;
} else {
minor = BLKIF_MINOR_EXT(devid);
nr_parts = 256;
}
offset = minor / nr_parts;
strcpy(ret, "xvd");
ptr = encode_disk_name(ret + 3, offset);
if (minor % nr_parts == 0)
*ptr = 0;
else
/* overflow cannot happen, thanks to the upper bound */
snprintf(ptr, ret + 32 - ptr,
"%d", minor & (nr_parts - 1));
return ret;
#undef BUFFER_SIZE
#undef EXT_SHIFT
#undef EXTENDED
#undef VDEV_IS_EXTENDED
#undef BLKIF_MINOR_EXT
}
/* Device AO operations */
void libxl__prepare_ao_device(libxl__ao *ao, libxl__ao_device *aodev)
{
aodev->ao = ao;
aodev->rc = 0;
aodev->dev = NULL;
aodev->num_exec = 0;
/* Initialize timer for QEMU Bodge */
libxl__ev_time_init(&aodev->timeout);
/*
* Initialize xs_watch, because it's not used on all possible
* execution paths, but it's unconditionally destroyed when finished.
*/
libxl__xswait_init(&aodev->xswait);
aodev->active = 1;
/* We init this here because we might call device_hotplug_done
* without actually calling any hotplug script */
libxl__async_exec_init(&aodev->aes);
libxl__ev_child_init(&aodev->child);
libxl__ev_qmp_init(&aodev->qmp);
}
/* multidev */
void libxl__multidev_begin(libxl__ao *ao, libxl__multidev *multidev)
{
AO_GC;
multidev->ao = ao;
multidev->array = 0;
multidev->used = multidev->allocd = 0;
/* We allocate an aodev to represent the operation of preparing
* all of the other operations. This operation is completed when
* we have started all the others (ie, when the user calls
* _prepared). That arranges automatically that
* (i) we do not think we have finished even if one of the
* operations completes while we are still preparing
* (ii) if we are starting zero operations, we do still
* make the callback as soon as we know this fact
* (iii) we have a nice consistent way to deal with any
* error that might occur while deciding what to initiate
*/
multidev->preparation = libxl__multidev_prepare(multidev);
}
void libxl__multidev_prepare_with_aodev(libxl__multidev *multidev,
libxl__ao_device *aodev) {
STATE_AO_GC(multidev->ao);
aodev->multidev = multidev;
aodev->callback = libxl__multidev_one_callback;
libxl__prepare_ao_device(ao, aodev);
if (multidev->used >= multidev->allocd) {
multidev->allocd = multidev->used * 2 + 5;
GCREALLOC_ARRAY(multidev->array, multidev->allocd);
}
multidev->array[multidev->used++] = aodev;
}
libxl__ao_device *libxl__multidev_prepare(libxl__multidev *multidev) {
STATE_AO_GC(multidev->ao);
libxl__ao_device *aodev;
GCNEW(aodev);
libxl__multidev_prepare_with_aodev(multidev, aodev);
return aodev;
}
void libxl__multidev_one_callback(libxl__egc *egc, libxl__ao_device *aodev)
{
STATE_AO_GC(aodev->ao);
libxl__multidev *multidev = aodev->multidev;
int i, error = 0;
aodev->active = 0;
for (i = 0; i < multidev->used; i++) {
if (multidev->array[i]->active)
return;
if (multidev->array[i]->rc)
error = multidev->array[i]->rc;
}
multidev->callback(egc, multidev, error);
return;
}
void libxl__multidev_prepared(libxl__egc *egc,
libxl__multidev *multidev, int rc)
{
multidev->preparation->rc = rc;
libxl__multidev_one_callback(egc, multidev->preparation);
}
/******************************************************************************/
int libxl__device_destroy(libxl__gc *gc, libxl__device *dev)
{
const char *be_path = NULL;
const char *fe_path = NULL;
const char *libxl_path = libxl__device_libxl_path(gc, dev);
xs_transaction_t t = 0;
int rc;
uint32_t domid;
int libxl_only = dev->backend_kind == LIBXL__DEVICE_KIND_NONE;
if (!libxl_only) {
be_path = libxl__device_backend_path(gc, dev);
fe_path = libxl__device_frontend_path(gc, dev);
}
rc = libxl__get_domid(gc, &domid);
if (rc) goto out;
for (;;) {
rc = libxl__xs_transaction_start(gc, &t);
if (rc) goto out;
if (domid == LIBXL_TOOLSTACK_DOMID) {
/*
* The toolstack domain is in charge of removing the
* frontend and libxl paths.
*/
if (!libxl_only)
libxl__xs_path_cleanup(gc, t, fe_path);
libxl__xs_path_cleanup(gc, t, libxl_path);
}
if (dev->backend_domid == domid && !libxl_only) {
/*
* The driver domain is in charge of removing what it can
* from the backend path.
*/
libxl__xs_path_cleanup(gc, t, be_path);
} else if (domid == LIBXL_TOOLSTACK_DOMID && !libxl_only) {
/*
* Then, toolstack domain is in charge of removing the parent
* directory if empty already.
*/
libxl__xs_path_cleanup(gc, t, be_path);
}
rc = libxl__xs_transaction_commit(gc, &t);
if (!rc) break;
if (rc < 0) goto out;
}
out:
libxl__xs_transaction_abort(gc, &t);
return rc;
}
/* Callback for device destruction */
static void devices_remove_callback(libxl__egc *egc,
libxl__multidev *multidev, int rc);
void libxl__devices_destroy(libxl__egc *egc, libxl__devices_remove_state *drs)
{
STATE_AO_GC(drs->ao);
uint32_t domid = drs->domid;
char *path;
unsigned int num_kinds, num_dev_xsentries;
char **kinds = NULL, **devs = NULL;
int i, j, rc = 0;
libxl__device *dev;
libxl__multidev *multidev = &drs->multidev;
libxl__ao_device *aodev;
libxl__device_kind kind;
libxl__multidev_begin(ao, multidev);
multidev->callback = devices_remove_callback;
path = GCSPRINTF("/libxl/%d/device", domid);
kinds = libxl__xs_directory(gc, XBT_NULL, path, &num_kinds);
if (!kinds) {
if (errno != ENOENT) {
LOGE(ERROR, "unable to get xenstore device listing %s", path);
goto out;
}
num_kinds = 0;
}
for (i = 0; i < num_kinds; i++) {
if (libxl__device_kind_from_string(kinds[i], &kind))
continue;
path = GCSPRINTF("/libxl/%d/device/%s", domid, kinds[i]);
devs = libxl__xs_directory(gc, XBT_NULL, path, &num_dev_xsentries);
if (!devs)
continue;
for (j = 0; j < num_dev_xsentries; j++) {
path = GCSPRINTF("/libxl/%d/device/%s/%s/backend",
domid, kinds[i], devs[j]);
path = libxl__xs_read(gc, XBT_NULL, path);
GCNEW(dev);
if (path && libxl__parse_backend_path(gc, path, dev) == 0) {
dev->domid = domid;
dev->kind = kind;
dev->devid = atoi(devs[j]);
if (dev->backend_kind == LIBXL__DEVICE_KIND_CONSOLE ||
dev->backend_kind == LIBXL__DEVICE_KIND_VUART) {
/* Currently console devices can be destroyed
* synchronously by just removing xenstore entries,
* this is what libxl__device_destroy does.
*/
libxl__device_destroy(gc, dev);
continue;
}
aodev = libxl__multidev_prepare(multidev);
aodev->action = LIBXL__DEVICE_ACTION_REMOVE;
aodev->dev = dev;
aodev->force = drs->force;
if (dev->kind == LIBXL__DEVICE_KIND_VUSB)
libxl__initiate_device_usbctrl_remove(egc, aodev);
else
libxl__initiate_device_generic_remove(egc, aodev);
}
}
}
out:
libxl__multidev_prepared(egc, multidev, rc);
}
/* Callbacks for device related operations */
/*
* device_backend_callback is the main callback entry point, for both device
* addition and removal. It gets called if we reach the desired state
* (XenbusStateClosed or XenbusStateInitWait). After that, all this
* functions get called in the order displayed below.
*
* If new device types are added, they should only need to modify the
* specific hotplug scripts call, which can be found in each OS specific
* file. If this new devices don't need a hotplug script, no modification
* should be needed.
*/
/* This callback is part of the Qemu devices Badge */
static void device_qemu_timeout(libxl__egc *egc, libxl__ev_time *ev,
const struct timeval *requested_abs, int rc);
static void device_backend_callback(libxl__egc *egc, libxl__ev_devstate *ds,
int rc);
static void device_backend_cleanup(libxl__gc *gc,
libxl__ao_device *aodev);
static void device_hotplug(libxl__egc *egc, libxl__ao_device *aodev);
static void device_hotplug_child_death_cb(libxl__egc *egc,
libxl__async_exec_state *aes,
int rc, int status);
static void device_destroy_be_watch_cb(libxl__egc *egc,
libxl__xswait_state *xswait,
int rc, const char *data);
static void device_hotplug_done(libxl__egc *egc, libxl__ao_device *aodev);
static void device_hotplug_clean(libxl__gc *gc, libxl__ao_device *aodev);
void libxl__wait_device_connection(libxl__egc *egc, libxl__ao_device *aodev)
{
STATE_AO_GC(aodev->ao);
char *be_path = libxl__device_backend_path(gc, aodev->dev);
char *state_path = GCSPRINTF("%s/state", be_path);
int rc = 0;
if (QEMU_BACKEND(aodev->dev)) {
/*
* If Qemu is not running, there's no point in waiting for
* it to change the state of the device.
*
* If Qemu is running, it will set the state of the device to
* 4 directly, without waiting in state 2 for any hotplug execution.
*/
device_hotplug(egc, aodev);
return;
}
rc = libxl__ev_devstate_wait(ao, &aodev->backend_ds,
device_backend_callback,
state_path, XenbusStateInitWait,
LIBXL_INIT_TIMEOUT * 1000);
if (rc) {
LOGD(ERROR, aodev->dev->domid, "unable to initialize device %s", be_path);
goto out;
}
return;
out:
aodev->rc = rc;
device_hotplug_done(egc, aodev);
return;
}
void libxl__initiate_device_generic_remove(libxl__egc *egc,
libxl__ao_device *aodev)
{
STATE_AO_GC(aodev->ao);
xs_transaction_t t = 0;
char *be_path = libxl__device_backend_path(gc, aodev->dev);
char *state_path = GCSPRINTF("%s/state", be_path);
char *online_path = GCSPRINTF("%s/online", be_path);
const char *state;
libxl_dominfo info;
uint32_t my_domid, domid = aodev->dev->domid;
int rc = 0;
libxl_dominfo_init(&info);
rc = libxl__get_domid(gc, &my_domid);
if (rc) {
LOGD(ERROR, domid, "unable to get my domid");
goto out;
}
if (my_domid == LIBXL_TOOLSTACK_DOMID) {
rc = libxl_domain_info(CTX, &info, domid);
if (rc) {
LOGD(ERROR, domid, "unable to get info for domain %d", domid);
goto out;
}
if (QEMU_BACKEND(aodev->dev) &&
(info.paused || info.dying || info.shutdown)) {
/*
* TODO: 4.2 Bodge due to QEMU, see comment on top of
* libxl__initiate_device_generic_remove in libxl_internal.h
*/
rc = libxl__ev_time_register_rel(ao, &aodev->timeout,
device_qemu_timeout,
LIBXL_QEMU_BODGE_TIMEOUT * 1000);
if (rc) {
LOGD(ERROR, domid, "unable to register timeout for Qemu device %s",
be_path);
goto out;
}
goto out_success;
}
}
for (;;) {
rc = libxl__xs_transaction_start(gc, &t);
if (rc) {
LOGD(ERROR, domid, "unable to start transaction");
goto out;
}
if (aodev->force.flag == LIBXL__FORCE_ON)
libxl__xs_path_cleanup(gc, t,
libxl__device_frontend_path(gc, aodev->dev));
rc = libxl__xs_read_checked(gc, t, state_path, &state);
if (rc) {
LOGD(ERROR, domid, "unable to read device state from path %s", state_path);
goto out;
}
/* if state_path is empty, assume backend is gone (backend domain
* shutdown?), cleanup frontend only; rc=0 */
if (!state) {
LOG(INFO, "backend %s already removed, cleanup frontend only", be_path);
goto out;
}
rc = libxl__xs_write_checked(gc, t, online_path, "0");
if (rc)
goto out;
/*
* Check if device is already in "closed" state, in which case
* it should not be changed.
*/
if (state && atoi(state) != XenbusStateClosed) {
rc = libxl__xs_write_checked(gc, t, state_path, GCSPRINTF("%d", XenbusStateClosing));
if (rc) {
LOGD(ERROR, domid, "unable to write to xenstore path %s", state_path);
goto out;
}
}
rc = libxl__xs_transaction_commit(gc, &t);
if (!rc) break;
if (rc < 0) goto out;
}
rc = libxl__ev_devstate_wait(ao, &aodev->backend_ds,
device_backend_callback,
state_path, XenbusStateClosed,
LIBXL_DESTROY_TIMEOUT * 1000);
if (rc) {
LOGD(ERROR, domid, "unable to remove device %s", be_path);
goto out;
}
out_success:
libxl_dominfo_dispose(&info);
return;
out:
aodev->rc = rc;
libxl_dominfo_dispose(&info);
libxl__xs_transaction_abort(gc, &t);
device_hotplug_done(egc, aodev);
return;
}
static void device_qemu_timeout(libxl__egc *egc, libxl__ev_time *ev,
const struct timeval *requested_abs, int rc)
{
libxl__ao_device *aodev = CONTAINER_OF(ev, *aodev, timeout);
STATE_AO_GC(aodev->ao);
char *be_path = libxl__device_backend_path(gc, aodev->dev);
char *state_path = GCSPRINTF("%s/state", be_path);
const char *xs_state;
xs_transaction_t t = 0;
if (rc != ERROR_TIMEDOUT)
goto out;
libxl__ev_time_deregister(gc, &aodev->timeout);
for (;;) {
rc = libxl__xs_transaction_start(gc, &t);
if (rc) {
LOGD(ERROR, aodev->dev->domid, "unable to start transaction");
goto out;
}
/*
* Check that the state path exists and is actually different than
* 6 before unconditionally setting it. If Qemu runs on a driver
* domain it is possible that the driver domain has already cleaned
* the backend path if the device has reached state 6.
*/
rc = libxl__xs_read_checked(gc, XBT_NULL, state_path, &xs_state);
if (rc) goto out;
if (xs_state && atoi(xs_state) != XenbusStateClosed) {
rc = libxl__xs_write_checked(gc, XBT_NULL, state_path, GCSPRINTF("%d", XenbusStateClosed));
if (rc) goto out;
}
rc = libxl__xs_transaction_commit(gc, &t);
if (!rc) break;
if (rc < 0) goto out;
}
device_hotplug(egc, aodev);
return;
out:
libxl__xs_transaction_abort(gc, &t);
aodev->rc = rc;
device_hotplug_done(egc, aodev);
}
static void device_backend_callback(libxl__egc *egc, libxl__ev_devstate *ds,
int rc) {
libxl__ao_device *aodev = CONTAINER_OF(ds, *aodev, backend_ds);
STATE_AO_GC(aodev->ao);
LOGD(DEBUG, aodev->dev->domid, "calling device_backend_cleanup");
device_backend_cleanup(gc, aodev);
if (rc == ERROR_TIMEDOUT &&
aodev->action == LIBXL__DEVICE_ACTION_REMOVE &&
aodev->force.flag == LIBXL__FORCE_AUTO) {
LOGD(DEBUG, aodev->dev->domid, "Timeout reached, initiating forced remove");
aodev->force.flag = LIBXL__FORCE_ON;
libxl__initiate_device_generic_remove(egc, aodev);
return;
}
if (rc) {
LOGD(ERROR, aodev->dev->domid,
"unable to %s device with path %s - rc %d",
libxl__device_action_to_string(aodev->action),
libxl__device_backend_path(gc, aodev->dev), rc);
goto out;
}
device_hotplug(egc, aodev);
return;
out:
aodev->rc = rc;
device_hotplug_done(egc, aodev);
return;
}
static void device_backend_cleanup(libxl__gc *gc, libxl__ao_device *aodev)
{
if (!aodev) return;
libxl__ev_devstate_cancel(gc, &aodev->backend_ds);
}
static void device_hotplug(libxl__egc *egc, libxl__ao_device *aodev)
{
STATE_AO_GC(aodev->ao);
libxl__async_exec_state *aes = &aodev->aes;
char *be_path = libxl__device_backend_path(gc, aodev->dev);
char **args = NULL, **env = NULL;
int rc = 0;
int hotplug, nullfd = -1;
uint32_t domid;
/*
* If device is attached from a driver domain don't try to execute
* hotplug scripts
*/
rc = libxl__get_domid(gc, &domid);
if (rc) {
LOGD(ERROR, aodev->dev->domid, "Failed to get domid");
goto out;
}
if (aodev->dev->backend_domid != domid) {
LOGD(DEBUG, aodev->dev->domid,
"Backend domid %d, domid %d, assuming driver domains",
aodev->dev->backend_domid, domid);
if (aodev->action != LIBXL__DEVICE_ACTION_REMOVE) {
LOG(DEBUG, "Not a remove, not executing hotplug scripts");
goto out;
}
aodev->xswait.ao = ao;
aodev->xswait.what = "removal of backend path";
aodev->xswait.path = be_path;
aodev->xswait.timeout_ms = LIBXL_DESTROY_TIMEOUT * 1000;
aodev->xswait.callback = device_destroy_be_watch_cb;
rc = libxl__xswait_start(gc, &aodev->xswait);
if (rc) {
LOGD(ERROR, aodev->dev->domid,
"Setup of backend removal watch failed (path %s)", be_path);
goto out;
}
return;
}
/* Check if we have to execute hotplug scripts for this device
* and return the necessary args/env vars for execution */
hotplug = libxl__get_hotplug_script_info(gc, aodev->dev, &args, &env,
aodev->action,
aodev->num_exec);
switch (hotplug) {
case 0:
/* no hotplug script to execute */
LOGD(DEBUG, aodev->dev->domid, "No hotplug script to execute");
goto out;
case 1:
/* execute hotplug script */
break;
default:
/* everything else is an error */
LOGD(ERROR, aodev->dev->domid,
"unable to get args/env to execute hotplug script for "
"device %s", libxl__device_backend_path(gc, aodev->dev));
rc = hotplug;
goto out;
}
assert(args != NULL);
LOGD(DEBUG, aodev->dev->domid, "calling hotplug script: %s %s", args[0], args[1]);
LOGD(DEBUG, aodev->dev->domid, "extra args:");
{
const char *arg;
unsigned int x;
for (x = 2; (arg = args[x]); x++)
LOGD(DEBUG, aodev->dev->domid, "\t%s", arg);
}
LOGD(DEBUG, aodev->dev->domid, "env:");
if (env != NULL) {
const char *k, *v;
unsigned int x;
for (x = 0; (k = env[x]); x += 2) {
v = env[x+1];
LOGD(DEBUG, aodev->dev->domid, "\t%s: %s", k, v);
}
}
nullfd = open("/dev/null", O_RDONLY);
if (nullfd < 0) {
LOGD(ERROR, aodev->dev->domid, "unable to open /dev/null for hotplug script");
rc = ERROR_FAIL;
goto out;
}
aes->ao = ao;
aes->what = GCSPRINTF("%s %s", args[0], args[1]);
aes->env = env;
aes->args = args;
aes->callback = device_hotplug_child_death_cb;
aes->timeout_ms = LIBXL_HOTPLUG_TIMEOUT * 1000;
aes->stdfds[0] = nullfd;
aes->stdfds[1] = 2;
aes->stdfds[2] = -1;
rc = libxl__async_exec_start(aes);
if (rc)
goto out;
close(nullfd);
assert(libxl__async_exec_inuse(&aodev->aes));
return;
out:
if (nullfd >= 0) close(nullfd);
aodev->rc = rc;
device_hotplug_done(egc, aodev);
return;
}
static void device_hotplug_child_death_cb(libxl__egc *egc,
libxl__async_exec_state *aes,
int rc, int status)
{
libxl__ao_device *aodev = CONTAINER_OF(aes, *aodev, aes);
STATE_AO_GC(aodev->ao);
char *be_path = libxl__device_backend_path(gc, aodev->dev);
char *hotplug_error;
device_hotplug_clean(gc, aodev);
if (status && !rc) {
hotplug_error = libxl__xs_read(gc, XBT_NULL,
GCSPRINTF("%s/hotplug-error", be_path));
if (hotplug_error)
LOG(ERROR, "script: %s", hotplug_error);
rc = ERROR_FAIL;
}
if (rc) {
if (!aodev->rc)
aodev->rc = rc;
if (aodev->action == LIBXL__DEVICE_ACTION_ADD)
/*
* Only fail on device connection, on disconnection
* ignore error, and continue with the remove process
*/
goto error;
}
/* Increase num_exec and call hotplug scripts again if necessary
* If no more executions are needed, device_hotplug will call
* device_hotplug_done breaking the loop.
*/
aodev->num_exec++;
device_hotplug(egc, aodev);
return;
error:
assert(aodev->rc);
device_hotplug_done(egc, aodev);
}
static void device_destroy_be_watch_cb(libxl__egc *egc,
libxl__xswait_state *xswait,
int rc, const char *dir)
{
libxl__ao_device *aodev = CONTAINER_OF(xswait, *aodev, xswait);
STATE_AO_GC(aodev->ao);
if (rc) {
if (rc == ERROR_TIMEDOUT)
LOGD(ERROR, aodev->dev->domid,
"timed out while waiting for %s to be removed",
xswait->path);
aodev->rc = rc;
goto out;
}
if (dir) {
/* backend path still exists, wait a little longer... */
return;
}
out:
/* We are done, backend path no longer exists */
device_hotplug_done(egc, aodev);
}
static void device_hotplug_done(libxl__egc *egc, libxl__ao_device *aodev)
{
STATE_AO_GC(aodev->ao);
int rc;
device_hotplug_clean(gc, aodev);
/* Clean xenstore if it's a disconnection */
if (aodev->action == LIBXL__DEVICE_ACTION_REMOVE &&
(aodev->force.flag == LIBXL__FORCE_ON || !aodev->rc)) {
rc = libxl__device_destroy(gc, aodev->dev);
if (!aodev->rc)
aodev->rc = rc;
}
aodev->callback(egc, aodev);
return;
}
static void device_hotplug_clean(libxl__gc *gc, libxl__ao_device *aodev)
{
/* Clean events and check reentrancy */
libxl__ev_time_deregister(gc, &aodev->timeout);
libxl__xswait_stop(gc, &aodev->xswait);
assert(!libxl__async_exec_inuse(&aodev->aes));
}
static void devices_remove_callback(libxl__egc *egc,
libxl__multidev *multidev, int rc)
{
libxl__devices_remove_state *drs = CONTAINER_OF(multidev, *drs, multidev);
STATE_AO_GC(drs->ao);
drs->callback(egc, drs, rc);
return;
}
int libxl__wait_for_device_model_deprecated(libxl__gc *gc,
uint32_t domid, char *state,
libxl__spawn_starting *spawning,
int (*check_callback)(libxl__gc *gc,
uint32_t domid,
const char *state,
void *userdata),
void *check_callback_userdata)
{
char *path;
uint32_t dm_domid = libxl_get_stubdom_id(CTX, domid);
path = DEVICE_MODEL_XS_PATH(gc, dm_domid, domid, "/state");
return libxl__xenstore_child_wait_deprecated(gc, domid,
LIBXL_DEVICE_MODEL_START_TIMEOUT,
"Device Model", path, state, spawning,
check_callback, check_callback_userdata);
}
int libxl__wait_for_backend(libxl__gc *gc, const char *be_path,
const char *state)
{
int watchdog = 100;
const char *p, *path = GCSPRINTF("%s/state", be_path);
int rc;
while (watchdog-- > 0) {
rc = libxl__xs_read_checked(gc, XBT_NULL, path, &p);
if (rc) return rc;
if (p == NULL) {
LOG(ERROR, "Backend %s does not exist", be_path);
return ERROR_FAIL;
}
if (!strcmp(p, state))
return 0;
usleep(100000);
}
LOG(ERROR, "Backend %s not ready", be_path);
return ERROR_FAIL;
}
/* generic callback for devices that only need to set ao_complete */
void device_addrm_aocomplete(libxl__egc *egc, libxl__ao_device *aodev)
{
STATE_AO_GC(aodev->ao);
if (aodev->rc) {
if (aodev->dev) {
LOGD(ERROR, aodev->dev->domid, "Unable to %s %s with id %u",
libxl__device_action_to_string(aodev->action),
libxl__device_kind_to_string(aodev->dev->kind),
aodev->dev->devid);
} else {
LOG(ERROR, "unable to %s device",
libxl__device_action_to_string(aodev->action));
}
goto out;
}
out:
libxl__ao_complete(egc, ao, aodev->rc);
return;
}
/* common function to get next device id */
int libxl__device_nextid(libxl__gc *gc, uint32_t domid,
libxl__device_kind device)
{
char *libxl_dom_path, **l;
unsigned int nb;
int nextid = -1;
if (!(libxl_dom_path = libxl__xs_libxl_path(gc, domid)))
return nextid;
l = libxl__xs_directory(gc, XBT_NULL,
GCSPRINTF("%s/device/%s", libxl_dom_path,
libxl__device_kind_to_string(device)), &nb);
if (l == NULL || nb == 0)
nextid = 0;
else
nextid = strtoul(l[nb - 1], NULL, 10) + 1;
return nextid;
}
static void device_complete(libxl__egc *egc, libxl__ao_device *aodev)
{
STATE_AO_GC(aodev->ao);
LOG(DEBUG, "device %s %s %s",
libxl__device_backend_path(gc, aodev->dev),
libxl__device_action_to_string(aodev->action),
aodev->rc ? "failed" : "succeed");
libxl__nested_ao_free(aodev->ao);
}
static void qemu_xenpv_spawn_outcome(libxl__egc *egc,
libxl__dm_spawn_state *dmss, int rc)
{
STATE_AO_GC(dmss->spawn.ao);
LOGD(DEBUG, dmss->guest_domid, "qemu xenpv backend spawn %s",
rc ? "failed" : "succeed");
libxl__nested_ao_free(dmss->spawn.ao);
}
/*
* Data structures used to track devices handled by driver domains
*/
/*
* Structure that describes a device handled by a driver domain
*/
typedef struct libxl__ddomain_device {
libxl__device *dev;
XEN_SLIST_ENTRY(struct libxl__ddomain_device) next;
} libxl__ddomain_device;
/*
* Structure that describes a domain and it's associated devices
*/
typedef struct libxl__ddomain_guest {
uint32_t domid;
int pvqemu_refcnt;
XEN_SLIST_HEAD(, struct libxl__ddomain_device) devices;
XEN_SLIST_ENTRY(struct libxl__ddomain_guest) next;
} libxl__ddomain_guest;
/*
* Main structure used by a driver domain to keep track of devices
* currently in use
*/
typedef struct {
libxl__ao *ao;
libxl__ev_xswatch watch;
XEN_SLIST_HEAD(, struct libxl__ddomain_guest) guests;
} libxl__ddomain;
static libxl__ddomain_guest *search_for_guest(libxl__ddomain *ddomain,
uint32_t domid)
{
libxl__ddomain_guest *dguest;
XEN_SLIST_FOREACH(dguest, &ddomain->guests, next) {
if (dguest->domid == domid)
return dguest;
}
return NULL;
}
static libxl__ddomain_device *search_for_device(libxl__ddomain_guest *dguest,
libxl__device *dev)
{
libxl__ddomain_device *ddev;
XEN_SLIST_FOREACH(ddev, &dguest->devices, next) {
#define LIBXL_DEVICE_CMP(dev1, dev2, entry) (dev1->entry == dev2->entry)
if (LIBXL_DEVICE_CMP(ddev->dev, dev, backend_devid) &&
LIBXL_DEVICE_CMP(ddev->dev, dev, backend_domid) &&
LIBXL_DEVICE_CMP(ddev->dev, dev, devid) &&
LIBXL_DEVICE_CMP(ddev->dev, dev, domid) &&
LIBXL_DEVICE_CMP(ddev->dev, dev, backend_kind) &&
LIBXL_DEVICE_CMP(ddev->dev, dev, kind))
return ddev;
#undef LIBXL_DEVICE_CMP
}
return NULL;
}
static void check_and_maybe_remove_guest(libxl__gc *gc,
libxl__ddomain *ddomain,
libxl__ddomain_guest *dguest)
{
assert(ddomain);
if (dguest != NULL && XEN_SLIST_FIRST(&dguest->devices) == NULL) {
XEN_SLIST_REMOVE(&ddomain->guests, dguest, libxl__ddomain_guest,
next);
LOGD(DEBUG, dguest->domid, "Removed domain from the list of active guests");
/* Clear any leftovers in libxl/<domid> */
libxl__xs_rm_checked(gc, XBT_NULL,
GCSPRINTF("libxl/%u", dguest->domid));
free(dguest);
}
}
/*
* The following comment applies to both add_device and remove_device.
*
* If the return value is greater than 0, it means there's no ao dispatched,
* so the free of the nested ao should be done by the parent when it has
* finished.
*/
static int add_device(libxl__egc *egc, libxl__ao *ao,
libxl__ddomain_guest *dguest,
libxl__device *dev)
{
AO_GC;
libxl__ao_device *aodev;
libxl__ddomain_device *ddev;
libxl__dm_spawn_state *dmss;
int rc = 0;
/*
* New device addition, allocate a struct to hold it and add it
* to the list of active devices for a given guest.
*/
ddev = libxl__zalloc(NOGC, sizeof(*ddev));
ddev->dev = libxl__zalloc(NOGC, sizeof(*ddev->dev));
*ddev->dev = *dev;
XEN_SLIST_INSERT_HEAD(&dguest->devices, ddev, next);
LOGD(DEBUG, dev->domid, "Added device %s to the list of active devices",
libxl__device_backend_path(gc, dev));
switch(dev->backend_kind) {
case LIBXL__DEVICE_KIND_QDISK:
case LIBXL__DEVICE_KIND_9PFS:
if (dguest->pvqemu_refcnt == 0) {
GCNEW(dmss);
dmss->guest_domid = dev->domid;
dmss->spawn.ao = ao;
dmss->callback = qemu_xenpv_spawn_outcome;
libxl__spawn_qemu_xenpv_backend(egc, dmss);
}
dguest->pvqemu_refcnt++;
break;
default:
GCNEW(aodev);
libxl__prepare_ao_device(ao, aodev);
/*
* Clone the libxl__device to avoid races if remove_device is called
* before the device addition has finished.
*/
GCNEW(aodev->dev);
*aodev->dev = *dev;
aodev->action = LIBXL__DEVICE_ACTION_ADD;
aodev->callback = device_complete;
libxl__wait_device_connection(egc, aodev);
break;
}
return rc;
}
static int remove_device(libxl__egc *egc, libxl__ao *ao,
libxl__ddomain_guest *dguest,
libxl__ddomain_device *ddev)
{
AO_GC;
libxl__device *dev = ddev->dev;
libxl__ao_device *aodev;
int rc = 0;
switch(ddev->dev->backend_kind) {
case LIBXL__DEVICE_KIND_QDISK:
case LIBXL__DEVICE_KIND_9PFS:
if (--dguest->pvqemu_refcnt == 0) {
rc = libxl__destroy_qemu_xenpv_backend(gc, dev->domid);
if (rc)
goto out;
}
libxl__device_destroy(gc, dev);
/* Return > 0, no ao has been dispatched */
rc = 1;
break;
default:
GCNEW(aodev);
libxl__prepare_ao_device(ao, aodev);
/*
* Clone the libxl__device to avoid races if there's a add_device
* running in parallel.
*/
GCNEW(aodev->dev);
*aodev->dev = *dev;
aodev->action = LIBXL__DEVICE_ACTION_REMOVE;
aodev->callback = device_complete;
libxl__initiate_device_generic_remove(egc, aodev);
break;
}
/*
* Removal of an active device, remove it from the list and
* free it's data structures if they are no longer needed.
*
* NB: the freeing is safe because all the async ops launched
* above or from add_device make a copy of the data they use, so
* there's no risk of dereferencing.
*/
XEN_SLIST_REMOVE(&dguest->devices, ddev, libxl__ddomain_device, next);
LOGD(DEBUG, dev->domid, "Removed device %s from the list of active devices",
libxl__device_backend_path(gc, dev));
free(ddev->dev);
free(ddev);
out:
return rc;
}
static void backend_watch_callback(libxl__egc *egc, libxl__ev_xswatch *watch,
const char *watch_path,
const char *event_path)
{
libxl__ddomain *ddomain = CONTAINER_OF(watch, *ddomain, watch);
libxl__ao *nested_ao = libxl__nested_ao_create(ddomain->ao);
STATE_AO_GC(nested_ao);
char *p, *path;
const char *sstate, *sonline;
int state, online, rc;
libxl__device *dev;
libxl__ddomain_device *ddev = NULL;
libxl__ddomain_guest *dguest = NULL;
bool free_ao = false;
/* Check if event_path ends with "state" or "online" and truncate it. */
path = libxl__strdup(gc, event_path);
p = strrchr(path, '/');
if (p == NULL)
goto skip;
if (strcmp(p, "/state") != 0 && strcmp(p, "/online") != 0)
goto skip;
/* Truncate the string so it points to the backend directory. */
*p = '\0';
/* Fetch the value of the state and online nodes. */
rc = libxl__xs_read_checked(gc, XBT_NULL, GCSPRINTF("%s/state", path),
&sstate);
if (rc || !sstate)
goto skip;
state = atoi(sstate);
rc = libxl__xs_read_checked(gc, XBT_NULL, GCSPRINTF("%s/online", path),
&sonline);
if (rc || !sonline)
goto skip;
online = atoi(sonline);
GCNEW(dev);
rc = libxl__parse_backend_path(gc, path, dev);
if (rc)
goto skip;
dguest = search_for_guest(ddomain, dev->domid);
if (dguest == NULL && state == XenbusStateClosed) {
/*
* Spurious state change, device has already been disconnected
* or never attached.
*/
goto skip;
}
if (dguest == NULL) {
/* Create a new guest struct and initialize it */
dguest = libxl__zalloc(NOGC, sizeof(*dguest));
dguest->domid = dev->domid;
XEN_SLIST_INIT(&dguest->devices);
XEN_SLIST_INSERT_HEAD(&ddomain->guests, dguest, next);
LOGD(DEBUG, dguest->domid, "Added domain to the list of active guests");
}
ddev = search_for_device(dguest, dev);
if (ddev == NULL && state == XenbusStateClosed) {
/*
* Spurious state change, device has already been disconnected
* or never attached.
*/
goto skip;
} else if (ddev == NULL) {
rc = add_device(egc, nested_ao, dguest, dev);
if (rc > 0)
free_ao = true;
} else if (state == XenbusStateClosed && online == 0) {
rc = remove_device(egc, nested_ao, dguest, ddev);
if (rc > 0)
free_ao = true;
check_and_maybe_remove_guest(gc, ddomain, dguest);
}
if (free_ao)
libxl__nested_ao_free(nested_ao);
return;
skip:
libxl__nested_ao_free(nested_ao);
check_and_maybe_remove_guest(gc, ddomain, dguest);
return;
}
/* Handler of events for device driver domains */
int libxl_device_events_handler(libxl_ctx *ctx,
const libxl_asyncop_how *ao_how)
{
AO_CREATE(ctx, 0, ao_how);
int rc;
uint32_t domid;
libxl__ddomain ddomain;
char *be_path;
char **kinds = NULL, **domains = NULL, **devs = NULL;
const char *sstate;
char *state_path;
int state;
unsigned int nkinds, ndomains, ndevs;
int i, j, k;
ddomain.ao = ao;
XEN_SLIST_INIT(&ddomain.guests);
rc = libxl__get_domid(gc, &domid);
if (rc) {
LOG(ERROR, "unable to get domain id");
goto out;
}
/*
* We use absolute paths because we want xswatch to also return
* absolute paths that can be parsed by libxl__parse_backend_path.
*/
be_path = GCSPRINTF("/local/domain/%u/backend", domid);
rc = libxl__ev_xswatch_register(gc, &ddomain.watch, backend_watch_callback,
be_path);
if (rc) goto out;
kinds = libxl__xs_directory(gc, XBT_NULL, be_path, &nkinds);
if (kinds) {
for (i = 0; i < nkinds; i++) {
domains = libxl__xs_directory(gc, XBT_NULL,
GCSPRINTF("%s/%s", be_path, kinds[i]), &ndomains);
if (!domains)
continue;
for (j = 0; j < ndomains; j++) {
devs = libxl__xs_directory(gc, XBT_NULL,
GCSPRINTF("%s/%s/%s", be_path, kinds[i], domains[j]), &ndevs);
if (!devs)
continue;
for (k = 0; k < ndevs; k++) {
state_path = GCSPRINTF("%s/%s/%s/%s/state",
be_path, kinds[i], domains[j], devs[k]);
rc = libxl__xs_read_checked(gc, XBT_NULL, state_path, &sstate);
if (rc || !sstate)
continue;
state = atoi(sstate);
if (state == XenbusStateInitWait)
backend_watch_callback(egc, &ddomain.watch,
be_path, state_path);
}
}
}
}
return AO_INPROGRESS;
out:
return AO_CREATE_FAIL(rc);
}
void device_add_domain_config(libxl__gc *gc, libxl_domain_config *d_config,
const libxl__device_type *dt, const void *dev)
{
int *num_dev;
unsigned int i;
void *item = NULL;
num_dev = libxl__device_type_get_num(dt, d_config);
/* Check for existing device */
for (i = 0; i < *num_dev; i++) {
if (dt->compare(libxl__device_type_get_elem(dt, d_config, i), dev)) {
item = libxl__device_type_get_elem(dt, d_config, i);
}
}
if (!item) {
void **devs = libxl__device_type_get_ptr(dt, d_config);
*devs = libxl__realloc(NOGC, *devs,
dt->dev_elem_size * (*num_dev + 1));
item = libxl__device_type_get_elem(dt, d_config, *num_dev);
(*num_dev)++;
} else {
dt->dispose(item);
}
dt->init(item);
dt->copy(CTX, item, dev);
}
void libxl__device_add_async(libxl__egc *egc, uint32_t domid,
const libxl__device_type *dt, void *type,
libxl__ao_device *aodev)
{
STATE_AO_GC(aodev->ao);
flexarray_t *back;
flexarray_t *front, *ro_front;
libxl__device *device;
xs_transaction_t t = XBT_NULL;
libxl_domain_config d_config;
void *type_saved;
libxl__flock *lock = NULL;
int rc;
libxl_domain_config_init(&d_config);
type_saved = libxl__malloc(gc, dt->dev_elem_size);
dt->init(type_saved);
dt->copy(CTX, type_saved, type);
if (dt->set_default) {
rc = dt->set_default(gc, domid, type, aodev->update_json);
if (rc) goto out;
}
if (dt->update_devid) {
rc = dt->update_devid(gc, domid, type);
if (rc) goto out;
}
if (dt->update_config)
dt->update_config(gc, type_saved, type);
GCNEW(device);
rc = dt->to_device(gc, domid, type, device);
if (rc) goto out;
if (aodev->update_json) {
lock = libxl__lock_domain_userdata(gc, domid);
if (!lock) {
rc = ERROR_LOCK_FAIL;
goto out;
}
rc = libxl__get_domain_configuration(gc, domid, &d_config);
if (rc) goto out;
device_add_domain_config(gc, &d_config, dt, type_saved);
rc = libxl__dm_check_start(gc, &d_config, domid);
if (rc) goto out;
}
back = flexarray_make(gc, 16, 1);
front = flexarray_make(gc, 16, 1);
ro_front = flexarray_make(gc, 16, 1);
flexarray_append_pair(back, "frontend-id", GCSPRINTF("%d", domid));
flexarray_append_pair(back, "online", "1");
flexarray_append_pair(back, "state",
GCSPRINTF("%d", XenbusStateInitialising));
flexarray_append_pair(front, "backend-id",
GCSPRINTF("%d", device->backend_domid));
flexarray_append_pair(front, "state",
GCSPRINTF("%d", XenbusStateInitialising));
if (dt->set_xenstore_config)
dt->set_xenstore_config(gc, domid, type, back, front, ro_front);
for (;;) {
rc = libxl__xs_transaction_start(gc, &t);
if (rc) goto out;
rc = libxl__device_exists(gc, t, device);
if (rc < 0) goto out;
if (rc == 1) { /* already exists in xenstore */
LOGD(ERROR, domid, "device already exists in xenstore");
aodev->action = LIBXL__DEVICE_ACTION_ADD; /* for error message */
rc = ERROR_DEVICE_EXISTS;
goto out;
}
if (aodev->update_json) {
rc = libxl__set_domain_configuration(gc, domid, &d_config);
if (rc) goto out;
}
libxl__device_generic_add(gc, t, device,
libxl__xs_kvs_of_flexarray(gc, back),
libxl__xs_kvs_of_flexarray(gc, front),
libxl__xs_kvs_of_flexarray(gc, ro_front));
rc = libxl__xs_transaction_commit(gc, &t);
if (!rc) break;
if (rc < 0) goto out;
}
aodev->dev = device;
aodev->action = LIBXL__DEVICE_ACTION_ADD;
libxl__wait_device_connection(egc, aodev);
rc = 0;
out:
libxl__xs_transaction_abort(gc, &t);
if (lock) libxl__unlock_file(lock);
dt->dispose(type_saved);
libxl_domain_config_dispose(&d_config);
aodev->rc = rc;
if (rc) aodev->callback(egc, aodev);
return;
}
int libxl__device_add(libxl__gc *gc, uint32_t domid,
const libxl__device_type *dt, void *type)
{
flexarray_t *back;
flexarray_t *front, *ro_front;
libxl__device *device;
int rc;
if (dt->set_default) {
rc = dt->set_default(gc, domid, type, false);
if (rc) goto out;
}
if (dt->update_devid) {
rc = dt->update_devid(gc, domid, type);
if (rc) goto out;
}
GCNEW(device);
rc = dt->to_device(gc, domid, type, device);
if (rc) goto out;
back = flexarray_make(gc, 16, 1);
front = flexarray_make(gc, 16, 1);
ro_front = flexarray_make(gc, 16, 1);
flexarray_append_pair(back, "frontend-id", GCSPRINTF("%d", domid));
flexarray_append_pair(back, "online", "1");
flexarray_append_pair(back, "state",
GCSPRINTF("%d", XenbusStateInitialising));
flexarray_append_pair(front, "backend-id",
libxl__sprintf(gc, "%d", device->backend_domid));
flexarray_append_pair(front, "state",
GCSPRINTF("%d", XenbusStateInitialising));
if (dt->set_xenstore_config)
dt->set_xenstore_config(gc, domid, type, back, front, ro_front);
rc = libxl__device_generic_add(gc, XBT_NULL, device,
libxl__xs_kvs_of_flexarray(gc, back),
libxl__xs_kvs_of_flexarray(gc, front),
libxl__xs_kvs_of_flexarray(gc, ro_front));
if (rc) goto out;
rc = 0;
out:
return rc;
}
void *libxl__device_list(libxl__gc *gc, const libxl__device_type *dt,
uint32_t domid, int *num)
{
void *r = NULL;
void *list = NULL;
void *item = NULL;
char *path;
char **dir = NULL;
unsigned int ndirs = 0;
unsigned int ndevs = 0;
int rc;
*num = 0;
if (dt->get_path) {
rc = dt->get_path(gc, domid, &path);
if (rc) goto out;
} else {
path = GCSPRINTF("%s/device/%s",
libxl__xs_libxl_path(gc, domid),
libxl__device_kind_to_string(dt->type));
}
if (dt->get_num) {
rc = dt->get_num(gc, path, &ndevs);
if (rc) goto out;
} else {
dir = libxl__xs_directory(gc, XBT_NULL, path, &ndirs);
if (dir && ndirs)
ndevs = ndirs;
}
if (!ndevs)
return NULL;
list = libxl__malloc(NOGC, dt->dev_elem_size * ndevs);
item = list;
while (*num < ndevs) {
dt->init(item);
if (dt->from_xenstore) {
int nr = dt->get_num ? *num : atoi(*dir);
char *device_path = dt->get_num ? path :
GCSPRINTF("%s/%d", path, nr);
rc = dt->from_xenstore(gc, device_path, nr, item);
if (rc) goto out;
}
item = (uint8_t *)item + dt->dev_elem_size;
++(*num);
if (!dt->get_num)
++dir;
}
r = list;
list = NULL;
out:
if (list) {
libxl__device_list_free(dt, list, *num);
*num = 0;
}
return r;
}
void libxl__device_list_free(const libxl__device_type *dt,
void *list, int num)
{
int i;
for (i = 0; i < num; i++)
dt->dispose((uint8_t*)list + i * dt->dev_elem_size);
free(list);
}
/*
* Local variables:
* mode: C
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/