mirror of
https://github.com/xen-project/xen.git
synced 2026-10-29 10:40:03 +01:00
On rebooting a hvm, the blktapctrl daemon has died. gdb shows the following call trace: (gdb) where #0 0x00000039d1830155 in raise () from /lib64/libc.so.6 #1 0x00000039d1831bf0 in abort () from /lib64/libc.so.6 #2 0x00000039d186a38b in __libc_message () from /lib64/libc.so.6 #3 0x00000039d1871634 in _int_free () from /lib64/libc.so.6 #4 0x00000039d1874c5c in free () from /lib64/libc.so.6 #5 0x0000003320a01bdd in ueblktap_probe (h=3D0x6073b0,=20 w=<value optimized out>, bepath_im=<value optimized out>) at xenbus.c:270 #6 0x0000003320a020e0 in xs_fire_next_watch (h=3D0x6073b0) at xs_api.c:355 #7 0x0000000000401785 in main (argc=3D<value optimized out>, argv=<value optimized out>) at blktapctrl.c:907 There is a case that "/local/domain/0/backend/tap/<dom_id>" exists but "/local/domain/<dom_id>/vm" is not in the xenstore. Signed-off-by: Kouya Shimura <kouya@jp.fujitsu.com>
618 lines
14 KiB
C
618 lines
14 KiB
C
/*
|
|
* xenbus.c
|
|
*
|
|
* xenbus interface to the blocktap.
|
|
*
|
|
* this handles the top-half of integration with block devices through the
|
|
* store -- the tap driver negotiates the device channel etc, while the
|
|
* userland tap client needs to sort out the disk parameters etc.
|
|
*
|
|
* (c) 2005 Andrew Warfield and Julian Chesterfield
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation; or, when distributed
|
|
* separately from the Linux kernel or incorporated into other
|
|
* software packages, subject to the following license:
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this source file (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use, copy, modify,
|
|
* merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
|
* and to permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <err.h>
|
|
#include <stdarg.h>
|
|
#include <errno.h>
|
|
#include <xs.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
#include "blktaplib.h"
|
|
#include "list.h"
|
|
#include "xs_api.h"
|
|
|
|
#if 0
|
|
#define DPRINTF(_f, _a...) printf ( _f , ## _a )
|
|
#else
|
|
#define DPRINTF(_f, _a...) ((void)0)
|
|
#endif
|
|
|
|
struct backend_info
|
|
{
|
|
/* our communications channel */
|
|
blkif_t *blkif;
|
|
|
|
long int frontend_id;
|
|
long int pdev;
|
|
long int readonly;
|
|
|
|
char *backpath;
|
|
char *frontpath;
|
|
|
|
struct list_head list;
|
|
};
|
|
|
|
static LIST_HEAD(belist);
|
|
|
|
static int strsep_len(const char *str, char c, unsigned int len)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; str[i]; i++)
|
|
if (str[i] == c) {
|
|
if (len == 0)
|
|
return i;
|
|
len--;
|
|
}
|
|
return (len == 0) ? i : -ERANGE;
|
|
}
|
|
|
|
static int get_be_id(const char *str)
|
|
{
|
|
int len,end;
|
|
const char *ptr;
|
|
char *tptr, num[10];
|
|
|
|
len = strsep_len(str, '/', 6);
|
|
end = strlen(str);
|
|
if( (len < 0) || (end < 0) ) return -1;
|
|
|
|
ptr = str + len + 1;
|
|
strncpy(num, ptr, end - len);
|
|
tptr = num + (end - (len + 1));
|
|
*tptr = '\0';
|
|
|
|
return atoi(num);
|
|
}
|
|
|
|
static int get_be_domid(const char *str)
|
|
{
|
|
int len1, len2;
|
|
const char *ptr;
|
|
char *tptr, num[10];
|
|
|
|
len2 = strsep_len(str, '/', 3);
|
|
if ( len2 < 0 ) return -1;
|
|
len1 = strsep_len(str, '/', 2);
|
|
|
|
ptr = str + len1 + 1;
|
|
strncpy(num, ptr, len2 - len1 - 1);
|
|
tptr = num + (len2 - len1 - 1);
|
|
*tptr = '\0';
|
|
|
|
return atoi(num);
|
|
}
|
|
|
|
static struct backend_info *be_lookup_be(const char *bepath)
|
|
{
|
|
struct backend_info *be;
|
|
|
|
list_for_each_entry(be, &belist, list)
|
|
if (strcmp(bepath, be->backpath) == 0)
|
|
return be;
|
|
return (struct backend_info *)NULL;
|
|
}
|
|
|
|
static int be_exists_be(const char *bepath)
|
|
{
|
|
return (be_lookup_be(bepath) != NULL);
|
|
}
|
|
|
|
static struct backend_info *be_lookup_fe(const char *fepath)
|
|
{
|
|
struct backend_info *be;
|
|
|
|
list_for_each_entry(be, &belist, list)
|
|
if (strcmp(fepath, be->frontpath) == 0)
|
|
return be;
|
|
return (struct backend_info *)NULL;
|
|
}
|
|
|
|
static int backend_remove(struct xs_handle *h, struct backend_info *be)
|
|
{
|
|
/* Unhook from be list. */
|
|
list_del(&be->list);
|
|
|
|
/* Free everything else. */
|
|
if (be->blkif) {
|
|
DPRINTF("Freeing blkif dev [%d]\n",be->blkif->devnum);
|
|
free_blkif(be->blkif);
|
|
}
|
|
if (be->frontpath)
|
|
free(be->frontpath);
|
|
if (be->backpath)
|
|
free(be->backpath);
|
|
free(be);
|
|
return 0;
|
|
}
|
|
|
|
static const char *get_image_path(const char *path)
|
|
{
|
|
const char *tmp;
|
|
|
|
/* Strip off the image type */
|
|
if (!strncmp(path, "tapdisk:", strlen("tapdisk:"))) {
|
|
path += strlen("tapdisk:");
|
|
} else if (!strncmp(path, "ioemu:", strlen("ioemu:"))) {
|
|
path += strlen("ioemu:");
|
|
}
|
|
|
|
tmp = strchr(path, ':');
|
|
if (tmp != NULL)
|
|
path = tmp + 1;
|
|
|
|
return path;
|
|
}
|
|
|
|
static int check_sharing(struct xs_handle *h, struct backend_info *be)
|
|
{
|
|
char *dom_uuid;
|
|
char *cur_dom_uuid;
|
|
char *path;
|
|
char *mode;
|
|
char *params;
|
|
char **domains;
|
|
char **devices;
|
|
int i, j;
|
|
unsigned int num_dom, num_dev;
|
|
blkif_info_t *info = be->blkif->info;
|
|
int ret = 0;
|
|
const char *image_path[2];
|
|
int be_domid = get_be_domid(be->backpath);
|
|
|
|
image_path[0] = get_image_path(info->params);
|
|
|
|
/* If the mode contains '!' or doesn't contain 'w' don't check anything */
|
|
xs_gather(h, be->backpath, "mode", NULL, &mode, NULL);
|
|
if (strchr(mode, '!'))
|
|
goto out;
|
|
if (strchr(mode, 'w') == NULL)
|
|
goto out;
|
|
|
|
/* Get the UUID of the domain we want to attach to */
|
|
if (asprintf(&path, "/local/domain/%ld", be->frontend_id) == -1)
|
|
goto fail;
|
|
xs_gather(h, path, "vm", NULL, &dom_uuid, NULL);
|
|
free(path);
|
|
|
|
/* Iterate through the devices of all VMs */
|
|
if (asprintf(&path, "/local/domain/%d/backend/tap", be_domid) == -1)
|
|
goto fail;
|
|
domains = xs_directory(h, XBT_NULL, path, &num_dom);
|
|
free(path);
|
|
if (domains == NULL)
|
|
num_dom = 0;
|
|
|
|
for (i = 0; !ret && (i < num_dom); i++) {
|
|
|
|
/* If it's the same VM, no action needed */
|
|
if (asprintf(&path, "/local/domain/%s", domains[i]) == -1) {
|
|
ret = -1;
|
|
break;
|
|
}
|
|
cur_dom_uuid = NULL;
|
|
xs_gather(h, path, "vm", NULL, &cur_dom_uuid, NULL);
|
|
free(path);
|
|
if (!cur_dom_uuid)
|
|
continue;
|
|
|
|
if (!strcmp(cur_dom_uuid, dom_uuid)) {
|
|
free(cur_dom_uuid);
|
|
continue;
|
|
}
|
|
|
|
/* Check the devices */
|
|
if (asprintf(&path, "/local/domain/%d/backend/tap/%s", be_domid, domains[i]) == -1) {
|
|
ret = -1;
|
|
free(cur_dom_uuid);
|
|
break;
|
|
}
|
|
devices = xs_directory(h, XBT_NULL, path, &num_dev);
|
|
if (devices == NULL)
|
|
num_dev = 0;
|
|
free(path);
|
|
|
|
for (j = 0; !ret && (j < num_dev); j++) {
|
|
if (asprintf(&path, "/local/domain/%d/backend/tap/%s/%s", be_domid, domains[i], devices[j]) == -1) {
|
|
ret = -1;
|
|
break;
|
|
}
|
|
params = NULL;
|
|
xs_gather(h, path, "params", NULL, ¶ms, NULL);
|
|
free(path);
|
|
if (!params)
|
|
continue;
|
|
|
|
image_path[1] = get_image_path(params);
|
|
if (!strcmp(image_path[0], image_path[1])) {
|
|
ret = -1;
|
|
}
|
|
|
|
free(params);
|
|
}
|
|
|
|
free(cur_dom_uuid);
|
|
free(devices);
|
|
}
|
|
free(domains);
|
|
free(dom_uuid);
|
|
goto out;
|
|
|
|
fail:
|
|
ret = -1;
|
|
out:
|
|
free(mode);
|
|
return ret;
|
|
}
|
|
|
|
static int check_image(struct xs_handle *h, struct backend_info *be,
|
|
const char** errmsg)
|
|
{
|
|
const char *path;
|
|
int mode;
|
|
blkif_t *blkif = be->blkif;
|
|
blkif_info_t *info = blkif->info;
|
|
|
|
path = get_image_path(info->params);
|
|
|
|
/* Check if the image exists and access is permitted */
|
|
mode = R_OK;
|
|
if (!be->readonly)
|
|
mode |= W_OK;
|
|
if (access(path, mode)) {
|
|
if (errno == ENOENT)
|
|
*errmsg = "File not found.";
|
|
else
|
|
*errmsg = "Insufficient file permissions.";
|
|
return -1;
|
|
}
|
|
|
|
/* Check that the image is not attached to a different VM */
|
|
if (check_sharing(h, be)) {
|
|
*errmsg = "File already in use by other domain";
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ueblktap_setup(struct xs_handle *h, char *bepath)
|
|
{
|
|
struct backend_info *be;
|
|
char *path = NULL, *p,*dev;
|
|
int len, er, deverr;
|
|
long int pdev = 0, handle;
|
|
blkif_info_t *blk;
|
|
const char* errmsg = NULL;
|
|
|
|
be = be_lookup_be(bepath);
|
|
if (be == NULL)
|
|
{
|
|
DPRINTF("ERROR: backend changed called for nonexistent "
|
|
"backend! (%s)\n", bepath);
|
|
goto fail;
|
|
}
|
|
|
|
deverr = xs_gather(h, bepath, "physical-device", "%li", &pdev, NULL);
|
|
if (!deverr) {
|
|
DPRINTF("pdev set to %ld\n",pdev);
|
|
if (be->pdev && be->pdev != pdev) {
|
|
DPRINTF("changing physical-device not supported");
|
|
goto fail;
|
|
}
|
|
be->pdev = pdev;
|
|
}
|
|
|
|
/* Check to see if device is to be opened read-only. */
|
|
deverr = xs_gather(h, bepath, "mode", NULL, &path, NULL);
|
|
if (deverr) {
|
|
DPRINTF("ERROR: could not find read/write mode\n");
|
|
goto fail;
|
|
} else if (path[0] == 'r')
|
|
be->readonly = 1;
|
|
|
|
if (be->blkif == NULL) {
|
|
/* Front end dir is a number, which is used as the handle. */
|
|
p = strrchr(be->frontpath, '/') + 1;
|
|
handle = strtoul(p, NULL, 0);
|
|
|
|
be->blkif = alloc_blkif(be->frontend_id);
|
|
if (be->blkif == NULL)
|
|
goto fail;
|
|
|
|
be->blkif->be_id = get_be_id(bepath);
|
|
|
|
/* Insert device specific info, */
|
|
blk = malloc(sizeof(blkif_info_t));
|
|
if (!blk) {
|
|
DPRINTF("Out of memory - blkif_info_t\n");
|
|
goto fail;
|
|
}
|
|
er = xs_gather(h, bepath, "params", NULL, &blk->params, NULL);
|
|
if (er)
|
|
goto fail;
|
|
be->blkif->info = blk;
|
|
|
|
if (deverr) {
|
|
/*Dev number was not available, try to set manually*/
|
|
pdev = convert_dev_name_to_num(blk->params);
|
|
be->pdev = pdev;
|
|
}
|
|
|
|
if (check_image(h, be, &errmsg))
|
|
goto fail;
|
|
|
|
er = blkif_init(be->blkif, handle, be->pdev, be->readonly);
|
|
if (er != 0) {
|
|
DPRINTF("Unable to open device %s\n",blk->params);
|
|
goto fail;
|
|
}
|
|
|
|
DPRINTF("[BECHG]: ADDED A NEW BLKIF (%s)\n", bepath);
|
|
}
|
|
|
|
/* Supply the information about the device to xenstore */
|
|
er = xs_printf(h, be->backpath, "sectors", "%llu",
|
|
be->blkif->ops->get_size(be->blkif));
|
|
|
|
if (er == 0) {
|
|
DPRINTF("ERROR: Failed writing sectors");
|
|
goto fail;
|
|
}
|
|
|
|
er = xs_printf(h, be->backpath, "sector-size", "%lu",
|
|
be->blkif->ops->get_secsize(be->blkif));
|
|
|
|
if (er == 0) {
|
|
DPRINTF("ERROR: Failed writing sector-size");
|
|
goto fail;
|
|
}
|
|
|
|
er = xs_printf(h, be->backpath, "info", "%u",
|
|
be->blkif->ops->get_info(be->blkif));
|
|
|
|
if (er == 0) {
|
|
DPRINTF("ERROR: Failed writing info");
|
|
goto fail;
|
|
}
|
|
|
|
be->blkif->state = CONNECTED;
|
|
xs_printf(h, be->backpath, "hotplug-status", "connected");
|
|
|
|
DPRINTF("[SETUP] Complete\n\n");
|
|
goto close;
|
|
|
|
fail:
|
|
if (be) {
|
|
if (errmsg == NULL)
|
|
errmsg = "Setting up the backend failed. See the log "
|
|
"files in /var/log/xen/ for details.";
|
|
xs_printf(h, be->backpath, "hotplug-error", errmsg);
|
|
xs_printf(h, be->backpath, "hotplug-status", "error");
|
|
|
|
backend_remove(h, be);
|
|
}
|
|
close:
|
|
if (path)
|
|
free(path);
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Xenstore watch callback entry point. This code replaces the hotplug scripts,
|
|
* and as soon as the xenstore backend driver entries are created, this script
|
|
* gets called.
|
|
*/
|
|
static void ueblktap_probe(struct xs_handle *h, struct xenbus_watch *w,
|
|
const char *bepath_im)
|
|
{
|
|
struct backend_info *be = NULL;
|
|
char *frontend = NULL, *bepath = NULL, *p;
|
|
int er, len;
|
|
blkif_t *blkif;
|
|
|
|
|
|
bepath = strdup(bepath_im);
|
|
|
|
if (!bepath) {
|
|
DPRINTF("No path\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
*asserts that xenstore structure is always 7 levels deep
|
|
*e.g. /local/domain/0/backend/vbd/1/2049
|
|
*/
|
|
len = strsep_len(bepath, '/', 7);
|
|
if (len < 0)
|
|
goto free_be;
|
|
if (bepath[len] != '\0')
|
|
goto free_be;
|
|
|
|
be = malloc(sizeof(*be));
|
|
if (!be) {
|
|
DPRINTF("ERROR: allocating backend structure\n");
|
|
goto free_be;
|
|
}
|
|
memset(be, 0, sizeof(*be));
|
|
frontend = NULL;
|
|
|
|
er = xs_gather(h, bepath,
|
|
"frontend-id", "%li", &be->frontend_id,
|
|
"frontend", NULL, &frontend,
|
|
NULL);
|
|
|
|
if (er) {
|
|
/*
|
|
*Unable to find frontend entries,
|
|
*bus-id is no longer valid
|
|
*/
|
|
DPRINTF("ERROR: Frontend-id check failed, removing backend: "
|
|
"[%s]\n",bepath);
|
|
|
|
/**
|
|
* BE info should already exist,
|
|
* free new mem and find old entry
|
|
*/
|
|
free(be);
|
|
be = be_lookup_be(bepath);
|
|
if ( (be != NULL) && (be->blkif != NULL) )
|
|
backend_remove(h, be);
|
|
else goto free_be;
|
|
if (bepath)
|
|
free(bepath);
|
|
return;
|
|
}
|
|
|
|
/* Are we already tracking this device? */
|
|
if (be_exists_be(bepath))
|
|
goto free_be;
|
|
|
|
be->backpath = bepath;
|
|
be->frontpath = frontend;
|
|
|
|
list_add(&be->list, &belist);
|
|
|
|
DPRINTF("[PROBE]\tADDED NEW DEVICE (%s)\n", bepath);
|
|
DPRINTF("\tFRONTEND (%s),(%ld)\n", frontend,be->frontend_id);
|
|
|
|
ueblktap_setup(h, bepath);
|
|
return;
|
|
|
|
free_be:
|
|
if (frontend)
|
|
free(frontend);
|
|
if (bepath)
|
|
free(bepath);
|
|
if (be)
|
|
free(be);
|
|
}
|
|
|
|
/**
|
|
*We set a general watch on the backend vbd directory
|
|
*ueblktap_probe is called for every update
|
|
*Our job is to monitor for new entries. As they
|
|
*are created, we initalise the state and attach a disk.
|
|
*/
|
|
|
|
static int add_blockdevice_probe_watch(struct xs_handle *h, const char *domid)
|
|
{
|
|
char *path;
|
|
struct xenbus_watch *vbd_watch;
|
|
|
|
if (asprintf(&path, "/local/domain/%s/backend/tap", domid) == -1)
|
|
return -ENOMEM;
|
|
|
|
vbd_watch = (struct xenbus_watch *)malloc(sizeof(struct xenbus_watch));
|
|
if (!vbd_watch) {
|
|
DPRINTF("ERROR: unable to malloc vbd_watch [%s]\n", path);
|
|
return -EINVAL;
|
|
}
|
|
vbd_watch->node = path;
|
|
vbd_watch->callback = ueblktap_probe;
|
|
if (register_xenbus_watch(h, vbd_watch) != 0) {
|
|
DPRINTF("ERROR: adding vbd probe watch %s\n", path);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Asynch callback to check for /local/domain/<DOMID>/name */
|
|
static void check_dom(struct xs_handle *h, struct xenbus_watch *w,
|
|
const char *bepath_im)
|
|
{
|
|
char *domid;
|
|
|
|
domid = get_dom_domid(h);
|
|
if (domid == NULL)
|
|
return;
|
|
|
|
add_blockdevice_probe_watch(h, domid);
|
|
free(domid);
|
|
unregister_xenbus_watch(h, w);
|
|
}
|
|
|
|
/* We must wait for xend to register /local/domain/<DOMID> */
|
|
static int watch_for_domid(struct xs_handle *h)
|
|
{
|
|
struct xenbus_watch *domid_watch;
|
|
char *path = NULL;
|
|
|
|
if (asprintf(&path, "/local/domain") == -1)
|
|
return -ENOMEM;
|
|
|
|
domid_watch = malloc(sizeof(struct xenbus_watch));
|
|
if (domid_watch == NULL) {
|
|
DPRINTF("ERROR: unable to malloc domid_watch [%s]\n", path);
|
|
return -EINVAL;
|
|
}
|
|
|
|
domid_watch->node = path;
|
|
domid_watch->callback = check_dom;
|
|
|
|
if (register_xenbus_watch(h, domid_watch) != 0) {
|
|
DPRINTF("ERROR: adding vbd probe watch %s\n", path);
|
|
return -EINVAL;
|
|
}
|
|
|
|
DPRINTF("Set async watch for /local/domain\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int setup_probe_watch(struct xs_handle *h)
|
|
{
|
|
char *domid;
|
|
int ret;
|
|
|
|
domid = get_dom_domid(h);
|
|
if (domid == NULL)
|
|
return watch_for_domid(h);
|
|
|
|
ret = add_blockdevice_probe_watch(h, domid);
|
|
free(domid);
|
|
return ret;
|
|
}
|