Files
xen/tools/blktap2/drivers/tapdisk-vbd.c
T
Andres Lagar-Cavilla 447e175bad Update memshr API and tools
This patch is the folded version of API updates, along with the associated tool
changes to ensure that the build is always consistent.

API updates:
- The source domain in the sharing calls is no longer assumed to be dom0.
- Previously, the mem sharing code would return an opaque handle to index
  shared pages (and nominees) in its global hash table.  By removing the hash
  table, the handle becomes a version, to avoid sharing a stale version of a
  page. Thus, libxc wrappers and tools need to be updated to recall the share
  functions with the information needed to fetch the page (which they readily
  have).

Tool updates:
The only (in-tree, that we know of) consumer of the mem sharing API is the
memshr tool. This is updated to use the new API.

Signed-off-by: Andres Lagar-Cavilla <andres@lagarcavilla.org>
Signed-off-by: Adin Scannell <adin@scannell.ca>
Acked-by: Ian Campbell <ian.campbell@citrix.com>
Committed-by: Tim Deegan <tim@xen.org>
2012-01-26 12:46:26 +00:00

1724 lines
36 KiB
C

/*
* Copyright (c) 2008, XenSource Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of XenSource Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <regex.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#ifdef MEMSHR
#include <memshr.h>
#endif
#include "tapdisk-image.h"
#include "tapdisk-driver.h"
#include "tapdisk-server.h"
#include "tapdisk-interface.h"
#include "tapdisk-disktype.h"
#include "tapdisk-vbd.h"
#include "blktap2.h"
#define DBG(_level, _f, _a...) tlog_write(_level, _f, ##_a)
#define ERR(_err, _f, _a...) tlog_error(_err, _f, ##_a)
#if 1
#define ASSERT(p) \
do { \
if (!(p)) { \
DPRINTF("Assertion '%s' failed, line %d, " \
"file %s", #p, __LINE__, __FILE__); \
*(int*)0 = 0; \
} \
} while (0)
#else
#define ASSERT(p) ((void)0)
#endif
#define TD_VBD_EIO_RETRIES 10
#define TD_VBD_EIO_SLEEP 1
#define TD_VBD_WATCHDOG_TIMEOUT 10
static void tapdisk_vbd_ring_event(event_id_t, char, void *);
static void tapdisk_vbd_callback(void *, blkif_response_t *);
/*
* initialization
*/
static inline void
tapdisk_vbd_initialize_vreq(td_vbd_request_t *vreq)
{
memset(vreq, 0, sizeof(td_vbd_request_t));
INIT_LIST_HEAD(&vreq->next);
}
void
tapdisk_vbd_free(td_vbd_t *vbd)
{
if (vbd) {
tapdisk_vbd_free_stack(vbd);
list_del_init(&vbd->next);
free(vbd->name);
free(vbd);
}
}
td_vbd_t*
tapdisk_vbd_create(uint16_t uuid)
{
td_vbd_t *vbd;
int i;
vbd = calloc(1, sizeof(td_vbd_t));
if (!vbd) {
EPRINTF("failed to allocate tapdisk state\n");
return NULL;
}
vbd->uuid = uuid;
vbd->minor = -1;
vbd->ring.fd = -1;
/* default blktap ring completion */
vbd->callback = tapdisk_vbd_callback;
vbd->argument = vbd;
#ifdef MEMSHR
memshr_vbd_initialize();
#endif
INIT_LIST_HEAD(&vbd->driver_stack);
INIT_LIST_HEAD(&vbd->images);
INIT_LIST_HEAD(&vbd->new_requests);
INIT_LIST_HEAD(&vbd->pending_requests);
INIT_LIST_HEAD(&vbd->failed_requests);
INIT_LIST_HEAD(&vbd->completed_requests);
INIT_LIST_HEAD(&vbd->next);
gettimeofday(&vbd->ts, NULL);
for (i = 0; i < MAX_REQUESTS; i++)
tapdisk_vbd_initialize_vreq(vbd->request_list + i);
return vbd;
}
int
tapdisk_vbd_initialize(uint16_t uuid)
{
td_vbd_t *vbd;
vbd = tapdisk_server_get_vbd(uuid);
if (vbd) {
EPRINTF("duplicate vbds! %u\n", uuid);
return -EEXIST;
}
vbd = tapdisk_vbd_create(uuid);
tapdisk_server_add_vbd(vbd);
return 0;
}
void
tapdisk_vbd_set_callback(td_vbd_t *vbd, td_vbd_cb_t callback, void *argument)
{
vbd->callback = callback;
vbd->argument = argument;
}
static int
tapdisk_vbd_validate_chain(td_vbd_t *vbd)
{
int err;
td_image_t *image, *parent, *tmp;
DPRINTF("VBD CHAIN:\n");
tapdisk_vbd_for_each_image(vbd, image, tmp) {
DPRINTF("%s: %d\n", image->name, image->type);
if (tapdisk_vbd_is_last_image(vbd, image))
break;
parent = tapdisk_vbd_next_image(image);
err = td_validate_parent(image, parent);
if (err)
return err;
}
return 0;
}
void
tapdisk_vbd_close_vdi(td_vbd_t *vbd)
{
td_image_t *image, *tmp;
tapdisk_vbd_for_each_image(vbd, image, tmp) {
td_close(image);
tapdisk_image_free(image);
}
INIT_LIST_HEAD(&vbd->images);
td_flag_set(vbd->state, TD_VBD_CLOSED);
tapdisk_vbd_free_stack(vbd);
}
static int
tapdisk_vbd_add_block_cache(td_vbd_t *vbd)
{
int err;
td_driver_t *driver;
td_image_t *cache, *image, *target, *tmp;
target = NULL;
tapdisk_vbd_for_each_image(vbd, image, tmp)
if (td_flag_test(image->flags, TD_OPEN_RDONLY) &&
td_flag_test(image->flags, TD_OPEN_SHAREABLE)) {
target = image;
break;
}
if (!target)
return 0;
cache = tapdisk_image_allocate(target->name,
DISK_TYPE_BLOCK_CACHE,
target->storage,
target->flags,
target->private);
if (!cache)
return -ENOMEM;
/* try to load existing cache */
err = td_load(cache);
if (!err)
goto done;
/* hack driver to send open() correct image size */
if (!target->driver) {
err = -ENODEV;
goto fail;
}
cache->driver = tapdisk_driver_allocate(cache->type,
cache->name,
cache->flags,
cache->storage);
if (!cache->driver) {
err = -ENOMEM;
goto fail;
}
cache->driver->info = target->driver->info;
/* try to open new cache */
err = td_open(cache);
if (!err)
goto done;
fail:
/* give up */
tapdisk_image_free(target);
return err;
done:
/* insert cache before image */
list_add(&cache->next, target->next.prev);
return 0;
}
static int
tapdisk_vbd_add_dirty_log(td_vbd_t *vbd)
{
int err;
td_driver_t *driver;
td_image_t *log, *parent;
driver = NULL;
log = NULL;
parent = tapdisk_vbd_first_image(vbd);
log = tapdisk_image_allocate(parent->name,
DISK_TYPE_LOG,
parent->storage,
parent->flags,
vbd);
if (!log)
return -ENOMEM;
driver = tapdisk_driver_allocate(log->type,
log->name,
log->flags,
log->storage);
if (!driver) {
err = -ENOMEM;
goto fail;
}
driver->info = parent->driver->info;
log->driver = driver;
err = td_open(log);
if (err)
goto fail;
list_add(&log->next, &vbd->images);
return 0;
fail:
tapdisk_image_free(log);
return err;
}
static int
tapdisk_vbd_open_level(td_vbd_t *vbd, struct list_head *head,
const char *params, int driver_type,
td_disk_info_t *driver_info, td_flag_t flags)
{
const char *name;
int type, err;
td_image_t *image;
td_disk_id_t id;
td_driver_t *driver;
name = params;
id.name = NULL;
type = driver_type;
INIT_LIST_HEAD(head);
for (;;) {
err = -ENOMEM;
image = tapdisk_image_allocate(name, type,
vbd->storage, flags, vbd);
free(id.name);
if (!image)
goto out;
/* this breaks if a driver modifies its info within a layer */
err = __td_open(image, driver_info);
if (err)
goto out;
/* TODO: non-sink drivers that don't care about their child
* currently return EINVAL. Could return TD_PARENT_OK or
* TD_ANY_PARENT */
err = td_get_parent_id(image, &id);
if (err && (err != TD_NO_PARENT && err != -EINVAL)) {
td_close(image);
goto out;
}
/* add this image to the end of the list */
list_add_tail(&image->next, head);
image = NULL;
/* if the image does not have a parent we return the
* list of images generated by this level of the stack */
if (err == TD_NO_PARENT || err == -EINVAL) {
err = 0;
goto out;
}
name = id.name;
type = id.drivertype;
flags |= (TD_OPEN_RDONLY | TD_OPEN_SHAREABLE);
}
out:
if (err) {
if (image) {
td_close(image);
tapdisk_image_free(image);
}
while (!list_empty(head)) {
image = list_entry(&head->next, td_image_t, next);
td_close(image);
tapdisk_image_free(image);
}
}
return err;
}
static int
__tapdisk_vbd_open_vdi(td_vbd_t *vbd, td_flag_t extra_flags)
{
int err;
td_flag_t flags;
td_image_t *tmp;
td_vbd_driver_info_t *driver_info;
struct list_head *images;
td_disk_info_t *parent_info = NULL;
if (list_empty(&vbd->driver_stack))
return -ENOENT;
flags = (vbd->flags & ~TD_OPEN_SHAREABLE) | extra_flags;
/* loop on each user specified driver.
* NOTE: driver_info is in reverse order. That is, the first
* item is the 'parent' or 'sink' driver */
list_for_each_entry(driver_info, &vbd->driver_stack, next) {
LIST_HEAD(images);
err = tapdisk_vbd_open_level(vbd, &images,
driver_info->params,
driver_info->type,
parent_info, flags);
if (err)
goto fail;
/* after each loop,
* append the created stack to the result stack */
list_splice(&images, &vbd->images);
/* set the parent_info to the first diskinfo on the stack */
tmp = tapdisk_vbd_first_image(vbd);
parent_info = &tmp->info;
}
if (td_flag_test(vbd->flags, TD_OPEN_LOG_DIRTY)) {
err = tapdisk_vbd_add_dirty_log(vbd);
if (err)
goto fail;
}
if (td_flag_test(vbd->flags, TD_OPEN_ADD_CACHE)) {
err = tapdisk_vbd_add_block_cache(vbd);
if (err)
goto fail;
}
err = tapdisk_vbd_validate_chain(vbd);
if (err)
goto fail;
td_flag_clear(vbd->state, TD_VBD_CLOSED);
return 0;
fail:
tapdisk_vbd_close_vdi(vbd);
return err;
}
/* this populates a vbd type based on path */
int
tapdisk_vbd_parse_stack(td_vbd_t *vbd, const char *path)
{
int err;
char *params, *driver_str;
td_vbd_driver_info_t *driver;
err = tapdisk_namedup(&params, path);
if (err)
return err;
/* tokenize params based on pipe '|' */
driver_str = strtok(params, "|");
while (driver_str != NULL) {
const char *path;
int type;
/* parse driver info and add to vbd */
driver = calloc(1, sizeof(td_vbd_driver_info_t));
if (!driver) {
PERROR("malloc");
err = -errno;
goto out;
}
INIT_LIST_HEAD(&driver->next);
err = tapdisk_parse_disk_type(driver_str, &path, &type);
if (err) {
free(driver);
goto out;
}
driver->type = type;
driver->params = strdup(path);
if (!driver->params) {
err = -ENOMEM;
free(driver);
goto out;
}
/* build the list backwards as the last driver will be the
* first driver to open in the stack */
list_add(&driver->next, &vbd->driver_stack);
/* get next driver string */
driver_str = strtok(NULL, "|");
}
out:
free(params);
if (err)
tapdisk_vbd_free_stack(vbd);
return err;
}
void
tapdisk_vbd_free_stack(td_vbd_t *vbd)
{
td_vbd_driver_info_t *driver;
while (!list_empty(&vbd->driver_stack)) {
driver = list_entry(vbd->driver_stack.next,
td_vbd_driver_info_t, next);
list_del(&driver->next);
free(driver->params);
free(driver);
}
}
/* NOTE: driver type, etc. must be set */
int
tapdisk_vbd_open_stack(td_vbd_t *vbd, uint16_t storage, td_flag_t flags)
{
int i, err;
vbd->flags = flags;
vbd->storage = storage;
for (i = 0; i < TD_VBD_EIO_RETRIES; i++) {
err = __tapdisk_vbd_open_vdi(vbd, 0);
if (err != -EIO)
break;
sleep(TD_VBD_EIO_SLEEP);
}
if (err)
goto fail;
return 0;
fail:
return err;
}
int
tapdisk_vbd_open_vdi(td_vbd_t *vbd, const char *path,
uint16_t drivertype, uint16_t storage, td_flag_t flags)
{
int i, err;
const struct tap_disk *ops;
ops = tapdisk_disk_drivers[drivertype];
if (!ops)
return -EINVAL;
DPRINTF("Loaded %s driver for vbd %u %s 0x%08x\n",
ops->disk_type, vbd->uuid, path, flags);
err = tapdisk_namedup(&vbd->name, path);
if (err)
return err;
vbd->flags = flags;
vbd->storage = storage;
for (i = 0; i < TD_VBD_EIO_RETRIES; i++) {
err = __tapdisk_vbd_open_vdi(vbd, 0);
if (err != -EIO)
break;
sleep(TD_VBD_EIO_SLEEP);
}
if (err)
goto fail;
return 0;
fail:
free(vbd->name);
vbd->name = NULL;
return err;
}
static int
tapdisk_vbd_register_event_watches(td_vbd_t *vbd)
{
event_id_t id;
id = tapdisk_server_register_event(SCHEDULER_POLL_READ_FD,
vbd->ring.fd, 0,
tapdisk_vbd_ring_event, vbd);
if (id < 0)
return id;
vbd->ring_event_id = id;
return 0;
}
static void
tapdisk_vbd_unregister_events(td_vbd_t *vbd)
{
if (vbd->ring_event_id)
tapdisk_server_unregister_event(vbd->ring_event_id);
}
static int
tapdisk_vbd_map_device(td_vbd_t *vbd, const char *devname)
{
int err, psize;
td_ring_t *ring;
ring = &vbd->ring;
psize = getpagesize();
ring->fd = open(devname, O_RDWR);
if (ring->fd == -1) {
err = -errno;
EPRINTF("failed to open %s: %d\n", devname, err);
goto fail;
}
ring->mem = mmap(0, psize * BLKTAP_MMAP_REGION_SIZE,
PROT_READ | PROT_WRITE, MAP_SHARED, ring->fd, 0);
if (ring->mem == MAP_FAILED) {
err = -errno;
EPRINTF("failed to mmap %s: %d\n", devname, err);
goto fail;
}
ring->sring = (blkif_sring_t *)((unsigned long)ring->mem);
BACK_RING_INIT(&ring->fe_ring, ring->sring, psize);
ring->vstart =
(unsigned long)ring->mem + (BLKTAP_RING_PAGES * psize);
ioctl(ring->fd, BLKTAP_IOCTL_SETMODE, BLKTAP_MODE_INTERPOSE);
return 0;
fail:
if (ring->mem && ring->mem != MAP_FAILED)
munmap(ring->mem, psize * BLKTAP_MMAP_REGION_SIZE);
if (ring->fd != -1)
close(ring->fd);
ring->fd = -1;
ring->mem = NULL;
return err;
}
static int
tapdisk_vbd_unmap_device(td_vbd_t *vbd)
{
int psize;
psize = getpagesize();
if (vbd->ring.fd != -1)
close(vbd->ring.fd);
if (vbd->ring.mem > 0)
munmap(vbd->ring.mem, psize * BLKTAP_MMAP_REGION_SIZE);
return 0;
}
void
tapdisk_vbd_detach(td_vbd_t *vbd)
{
tapdisk_vbd_unregister_events(vbd);
tapdisk_vbd_unmap_device(vbd);
vbd->minor = -1;
}
int
tapdisk_vbd_attach(td_vbd_t *vbd, const char *devname, int minor)
{
int err;
err = tapdisk_vbd_map_device(vbd, devname);
if (err)
goto fail;
err = tapdisk_vbd_register_event_watches(vbd);
if (err)
goto fail;
vbd->minor = minor;
return 0;
fail:
tapdisk_vbd_detach(vbd);
return err;
}
int
tapdisk_vbd_open(td_vbd_t *vbd, const char *name, uint16_t type,
uint16_t storage, int minor, const char *ring, td_flag_t flags)
{
int err;
err = tapdisk_vbd_open_stack(vbd, storage, flags);
if (err)
goto out;
err = tapdisk_vbd_attach(vbd, ring, minor);
if (err)
goto out;
return 0;
out:
tapdisk_vbd_detach(vbd);
tapdisk_vbd_close_vdi(vbd);
free(vbd->name);
vbd->name = NULL;
return err;
}
static void
tapdisk_vbd_queue_count(td_vbd_t *vbd, int *new,
int *pending, int *failed, int *completed)
{
int n, p, f, c;
td_vbd_request_t *vreq, *tvreq;
n = 0;
p = 0;
f = 0;
c = 0;
tapdisk_vbd_for_each_request(vreq, tvreq, &vbd->new_requests)
n++;
tapdisk_vbd_for_each_request(vreq, tvreq, &vbd->pending_requests)
p++;
tapdisk_vbd_for_each_request(vreq, tvreq, &vbd->failed_requests)
f++;
tapdisk_vbd_for_each_request(vreq, tvreq, &vbd->completed_requests)
c++;
*new = n;
*pending = p;
*failed = f;
*completed = c;
}
static int
tapdisk_vbd_shutdown(td_vbd_t *vbd)
{
int new, pending, failed, completed;
if (!list_empty(&vbd->pending_requests))
return -EAGAIN;
tapdisk_vbd_kick(vbd);
tapdisk_vbd_queue_count(vbd, &new, &pending, &failed, &completed);
DPRINTF("%s: state: 0x%08x, new: 0x%02x, pending: 0x%02x, "
"failed: 0x%02x, completed: 0x%02x\n",
vbd->name, vbd->state, new, pending, failed, completed);
DPRINTF("last activity: %010ld.%06lld, errors: 0x%04"PRIx64", "
"retries: 0x%04"PRIx64", received: 0x%08"PRIx64", "
"returned: 0x%08"PRIx64", kicked: 0x%08"PRIx64"\n",
vbd->ts.tv_sec, (unsigned long long)vbd->ts.tv_usec,
vbd->errors, vbd->retries, vbd->received, vbd->returned,
vbd->kicked);
tapdisk_vbd_close_vdi(vbd);
tapdisk_vbd_detach(vbd);
tapdisk_server_remove_vbd(vbd);
tapdisk_vbd_free(vbd);
tlog_print_errors();
return 0;
}
int
tapdisk_vbd_close(td_vbd_t *vbd)
{
/*
* don't close if any requests are pending in the aio layer
*/
if (!list_empty(&vbd->pending_requests))
goto fail;
/*
* if the queue is still active and we have more
* requests, try to complete them before closing.
*/
if (tapdisk_vbd_queue_ready(vbd) &&
(!list_empty(&vbd->new_requests) ||
!list_empty(&vbd->failed_requests) ||
!list_empty(&vbd->completed_requests)))
goto fail;
return tapdisk_vbd_shutdown(vbd);
fail:
td_flag_set(vbd->state, TD_VBD_SHUTDOWN_REQUESTED);
DBG(TLOG_WARN, "%s: requests pending\n", vbd->name);
return -EAGAIN;
}
/*
* control operations
*/
void
tapdisk_vbd_debug(td_vbd_t *vbd)
{
td_image_t *image, *tmp;
int new, pending, failed, completed;
tapdisk_vbd_queue_count(vbd, &new, &pending, &failed, &completed);
DBG(TLOG_WARN, "%s: state: 0x%08x, new: 0x%02x, pending: 0x%02x, "
"failed: 0x%02x, completed: 0x%02x, last activity: %010ld.%06lld, "
"errors: 0x%04"PRIx64", retries: 0x%04"PRIx64", received: 0x%08"PRIx64", "
"returned: 0x%08"PRIx64", kicked: 0x%08"PRIx64"\n",
vbd->name, vbd->state, new, pending, failed, completed,
vbd->ts.tv_sec, (unsigned long long)vbd->ts.tv_usec,
vbd->errors, vbd->retries,
vbd->received, vbd->returned, vbd->kicked);
tapdisk_vbd_for_each_image(vbd, image, tmp)
td_debug(image);
}
static void
tapdisk_vbd_drop_log(td_vbd_t *vbd)
{
if (td_flag_test(vbd->state, TD_VBD_LOG_DROPPED))
return;
tapdisk_vbd_debug(vbd);
tlog_flush();
td_flag_set(vbd->state, TD_VBD_LOG_DROPPED);
}
int
tapdisk_vbd_get_image_info(td_vbd_t *vbd, image_t *img)
{
td_image_t *image;
memset(img, 0, sizeof(image_t));
if (list_empty(&vbd->images))
return -EINVAL;
image = tapdisk_vbd_first_image(vbd);
img->size = image->info.size;
img->secsize = image->info.sector_size;
img->info = image->info.info;
return 0;
}
int
tapdisk_vbd_queue_ready(td_vbd_t *vbd)
{
return (!td_flag_test(vbd->state, TD_VBD_DEAD) &&
!td_flag_test(vbd->state, TD_VBD_CLOSED) &&
!td_flag_test(vbd->state, TD_VBD_QUIESCED) &&
!td_flag_test(vbd->state, TD_VBD_QUIESCE_REQUESTED));
}
int
tapdisk_vbd_retry_needed(td_vbd_t *vbd)
{
return td_flag_test(vbd->state, TD_VBD_RETRY_NEEDED);
}
int
tapdisk_vbd_lock(td_vbd_t *vbd)
{
return 0;
}
int
tapdisk_vbd_quiesce_queue(td_vbd_t *vbd)
{
if (!list_empty(&vbd->pending_requests)) {
td_flag_set(vbd->state, TD_VBD_QUIESCE_REQUESTED);
return -EAGAIN;
}
td_flag_clear(vbd->state, TD_VBD_QUIESCE_REQUESTED);
td_flag_set(vbd->state, TD_VBD_QUIESCED);
return 0;
}
int
tapdisk_vbd_start_queue(td_vbd_t *vbd)
{
td_flag_clear(vbd->state, TD_VBD_QUIESCED);
td_flag_clear(vbd->state, TD_VBD_QUIESCE_REQUESTED);
return 0;
}
int
tapdisk_vbd_kill_queue(td_vbd_t *vbd)
{
tapdisk_vbd_quiesce_queue(vbd);
td_flag_set(vbd->state, TD_VBD_DEAD);
return 0;
}
static int
tapdisk_vbd_open_image(td_vbd_t *vbd, td_image_t *image)
{
int err;
td_image_t *parent;
err = td_open(image);
if (err)
return err;
if (!tapdisk_vbd_is_last_image(vbd, image)) {
parent = tapdisk_vbd_next_image(image);
err = td_validate_parent(image, parent);
if (err) {
td_close(image);
return err;
}
}
return 0;
}
static int
tapdisk_vbd_close_and_reopen_image(td_vbd_t *vbd, td_image_t *image)
{
int i, err;
td_close(image);
for (i = 0; i < TD_VBD_EIO_RETRIES; i++) {
err = tapdisk_vbd_open_image(vbd, image);
if (err != -EIO)
break;
sleep(TD_VBD_EIO_SLEEP);
}
if (err)
td_flag_set(vbd->state, TD_VBD_CLOSED);
return err;
}
int
tapdisk_vbd_pause(td_vbd_t *vbd)
{
int err;
td_flag_set(vbd->state, TD_VBD_PAUSE_REQUESTED);
err = tapdisk_vbd_quiesce_queue(vbd);
if (err)
return err;
tapdisk_vbd_close_vdi(vbd);
td_flag_clear(vbd->state, TD_VBD_PAUSE_REQUESTED);
td_flag_set(vbd->state, TD_VBD_PAUSED);
return 0;
}
int
tapdisk_vbd_resume(td_vbd_t *vbd, const char *path, uint16_t drivertype)
{
int i, err;
if (!td_flag_test(vbd->state, TD_VBD_PAUSED)) {
EPRINTF("resume request for unpaused vbd %s\n", vbd->name);
return -EINVAL;
}
if (path) {
free(vbd->name);
vbd->name = strdup(path);
if (!vbd->name) {
EPRINTF("copying new vbd %s name failed\n", path);
return -EINVAL;
}
}
for (i = 0; i < TD_VBD_EIO_RETRIES; i++) {
err = __tapdisk_vbd_open_vdi(vbd, TD_OPEN_STRICT);
if (err != -EIO)
break;
sleep(TD_VBD_EIO_SLEEP);
}
if (err)
return err;
tapdisk_vbd_start_queue(vbd);
td_flag_clear(vbd->state, TD_VBD_PAUSED);
td_flag_clear(vbd->state, TD_VBD_PAUSE_REQUESTED);
tapdisk_vbd_check_state(vbd);
return 0;
}
int
tapdisk_vbd_kick(td_vbd_t *vbd)
{
int n;
td_ring_t *ring;
tapdisk_vbd_check_state(vbd);
ring = &vbd->ring;
if (!ring->sring)
return 0;
n = (ring->fe_ring.rsp_prod_pvt - ring->fe_ring.sring->rsp_prod);
if (!n)
return 0;
vbd->kicked += n;
RING_PUSH_RESPONSES(&ring->fe_ring);
ioctl(ring->fd, BLKTAP_IOCTL_KICK_FE, 0);
DBG(TLOG_INFO, "kicking %d: rec: 0x%08"PRIx64", ret: 0x%08"PRIx64", kicked: "
"0x%08"PRIx64"\n", n, vbd->received, vbd->returned, vbd->kicked);
return n;
}
static inline void
tapdisk_vbd_write_response_to_ring(td_vbd_t *vbd, blkif_response_t *rsp)
{
td_ring_t *ring;
blkif_response_t *rspp;
ring = &vbd->ring;
rspp = RING_GET_RESPONSE(&ring->fe_ring, ring->fe_ring.rsp_prod_pvt);
memcpy(rspp, rsp, sizeof(blkif_response_t));
ring->fe_ring.rsp_prod_pvt++;
}
static void
tapdisk_vbd_callback(void *arg, blkif_response_t *rsp)
{
td_vbd_t *vbd = (td_vbd_t *)arg;
tapdisk_vbd_write_response_to_ring(vbd, rsp);
}
static void
tapdisk_vbd_make_response(td_vbd_t *vbd, td_vbd_request_t *vreq)
{
blkif_request_t tmp;
blkif_response_t *rsp;
tmp = vreq->req;
rsp = (blkif_response_t *)&vreq->req;
rsp->id = tmp.id;
rsp->operation = tmp.operation;
rsp->status = vreq->status;
DBG(TLOG_DBG, "writing req %d, sec 0x%08"PRIx64", res %d to ring\n",
(int)tmp.id, tmp.sector_number, vreq->status);
if (rsp->status != BLKIF_RSP_OKAY)
ERR(EIO, "returning BLKIF_RSP %d", rsp->status);
vbd->returned++;
vbd->callback(vbd->argument, rsp);
}
void
tapdisk_vbd_check_state(td_vbd_t *vbd)
{
td_vbd_request_t *vreq, *tmp;
tapdisk_vbd_for_each_request(vreq, tmp, &vbd->failed_requests)
if (vreq->num_retries >= TD_VBD_MAX_RETRIES)
tapdisk_vbd_complete_vbd_request(vbd, vreq);
if (!list_empty(&vbd->new_requests) ||
!list_empty(&vbd->failed_requests))
tapdisk_vbd_issue_requests(vbd);
tapdisk_vbd_for_each_request(vreq, tmp, &vbd->completed_requests) {
tapdisk_vbd_make_response(vbd, vreq);
list_del(&vreq->next);
tapdisk_vbd_initialize_vreq(vreq);
}
if (td_flag_test(vbd->state, TD_VBD_QUIESCE_REQUESTED))
tapdisk_vbd_quiesce_queue(vbd);
if (td_flag_test(vbd->state, TD_VBD_PAUSE_REQUESTED))
tapdisk_vbd_pause(vbd);
if (td_flag_test(vbd->state, TD_VBD_SHUTDOWN_REQUESTED))
tapdisk_vbd_close(vbd);
}
void
tapdisk_vbd_check_progress(td_vbd_t *vbd)
{
int diff;
struct timeval now;
if (list_empty(&vbd->pending_requests))
return;
gettimeofday(&now, NULL);
diff = now.tv_sec - vbd->ts.tv_sec;
if (diff >= TD_VBD_WATCHDOG_TIMEOUT) {
DBG(TLOG_WARN, "%s: watchdog timeout: pending requests "
"idle for %d seconds\n", vbd->name, diff);
tapdisk_vbd_drop_log(vbd);
return;
}
tapdisk_server_set_max_timeout(TD_VBD_WATCHDOG_TIMEOUT - diff);
}
/*
* request submission
*/
static int
tapdisk_vbd_check_queue(td_vbd_t *vbd)
{
int err;
td_image_t *image;
if (list_empty(&vbd->images))
return -ENOSYS;
if (!tapdisk_vbd_queue_ready(vbd))
return -EAGAIN;
if (!vbd->reopened) {
if (td_flag_test(vbd->state, TD_VBD_LOCKING)) {
err = tapdisk_vbd_lock(vbd);
if (err)
return err;
}
image = tapdisk_vbd_first_image(vbd);
td_flag_set(image->flags, TD_OPEN_STRICT);
if (tapdisk_vbd_close_and_reopen_image(vbd, image))
EPRINTF("reopening disks failed\n");
else {
DPRINTF("reopening disks succeeded\n");
vbd->reopened = 1;
}
}
return 0;
}
void
tapdisk_vbd_complete_vbd_request(td_vbd_t *vbd, td_vbd_request_t *vreq)
{
if (!vreq->submitting && !vreq->secs_pending) {
if (vreq->status == BLKIF_RSP_ERROR &&
vreq->num_retries < TD_VBD_MAX_RETRIES &&
!td_flag_test(vbd->state, TD_VBD_DEAD) &&
!td_flag_test(vbd->state, TD_VBD_SHUTDOWN_REQUESTED))
tapdisk_vbd_move_request(vreq, &vbd->failed_requests);
else
tapdisk_vbd_move_request(vreq, &vbd->completed_requests);
}
}
static uint64_t
tapdisk_vbd_breq_get_sector(blkif_request_t *breq, td_request_t treq)
{
int seg, nsects;
uint64_t sector_nr = breq->sector_number;
for(seg=0; seg < treq.sidx; seg++) {
nsects = breq->seg[seg].last_sect - breq->seg[seg].first_sect + 1;
sector_nr += nsects;
}
return sector_nr;
}
static void
__tapdisk_vbd_complete_td_request(td_vbd_t *vbd, td_vbd_request_t *vreq,
td_request_t treq, int res)
{
int err;
td_image_t *image = treq.image;
err = (res <= 0 ? res : -res);
vbd->secs_pending -= treq.secs;
vreq->secs_pending -= treq.secs;
vreq->blocked = treq.blocked;
if (err) {
vreq->status = BLKIF_RSP_ERROR;
vreq->error = (vreq->error ? : err);
if (err != -EBUSY) {
vbd->errors++;
ERR(err, "req %"PRIu64": %s 0x%04x secs to "
"0x%08"PRIx64, vreq->req.id,
(treq.op == TD_OP_WRITE ? "write" : "read"),
treq.secs, treq.sec);
}
} else {
#ifdef MEMSHR
if (treq.op == TD_OP_READ
&& td_flag_test(image->flags, TD_OPEN_RDONLY)) {
share_tuple_t hnd = treq.memshr_hnd;
uint16_t uid = image->memshr_id;
blkif_request_t *breq = &vreq->req;
uint64_t sec = tapdisk_vbd_breq_get_sector(breq, treq);
int secs = breq->seg[treq.sidx].last_sect -
breq->seg[treq.sidx].first_sect + 1;
if (hnd.handle != 0)
memshr_vbd_complete_ro_request(hnd, uid,
sec, secs);
}
#endif
}
tapdisk_vbd_complete_vbd_request(vbd, vreq);
}
static void
__tapdisk_vbd_reissue_td_request(td_vbd_t *vbd,
td_image_t *image, td_request_t treq)
{
td_image_t *parent;
td_vbd_request_t *vreq;
vreq = (td_vbd_request_t *)treq.private;
gettimeofday(&vreq->last_try, NULL);
vreq->submitting++;
if (tapdisk_vbd_is_last_image(vbd, image)) {
memset(treq.buf, 0, treq.secs << SECTOR_SHIFT);
td_complete_request(treq, 0);
goto done;
}
parent = tapdisk_vbd_next_image(image);
treq.image = parent;
/* return zeros for requests that extend beyond end of parent image */
if (treq.sec + treq.secs > parent->info.size) {
td_request_t clone = treq;
if (parent->info.size > treq.sec) {
int secs = parent->info.size - treq.sec;
clone.sec += secs;
clone.secs -= secs;
clone.buf += (secs << SECTOR_SHIFT);
treq.secs = secs;
} else
treq.secs = 0;
memset(clone.buf, 0, clone.secs << SECTOR_SHIFT);
td_complete_request(clone, 0);
if (!treq.secs)
goto done;
}
switch (treq.op) {
case TD_OP_WRITE:
td_queue_write(parent, treq);
break;
case TD_OP_READ:
#ifdef MEMSHR
if(td_flag_test(parent->flags, TD_OPEN_RDONLY)) {
int ret, seg = treq.sidx;
blkif_request_t *breq = &vreq->req;
ret = memshr_vbd_issue_ro_request(treq.buf,
breq->seg[seg].gref,
parent->memshr_id,
treq.sec,
treq.secs,
&treq.memshr_hnd);
if(ret == 0) {
/* Reset memshr handle. This'll prevent
* memshr_vbd_complete_ro_request being called
*/
treq.memshr_hnd.handle = 0;
td_complete_request(treq, 0);
} else
td_queue_read(parent, treq);
} else
#endif
td_queue_read(parent, treq);
break;
}
done:
vreq->submitting--;
if (!vreq->secs_pending)
tapdisk_vbd_complete_vbd_request(vbd, vreq);
}
void
tapdisk_vbd_forward_request(td_request_t treq)
{
td_vbd_t *vbd;
td_image_t *image;
td_vbd_request_t *vreq;
image = treq.image;
vbd = (td_vbd_t *)image->private;
vreq = (td_vbd_request_t *)treq.private;
gettimeofday(&vbd->ts, NULL);
if (tapdisk_vbd_queue_ready(vbd))
__tapdisk_vbd_reissue_td_request(vbd, image, treq);
else
__tapdisk_vbd_complete_td_request(vbd, vreq, treq, -EIO);
}
static void
tapdisk_vbd_complete_td_request(td_request_t treq, int res)
{
td_vbd_t *vbd;
td_image_t *image;
td_vbd_request_t *vreq;
image = treq.image;
vbd = (td_vbd_t *)image->private;
vreq = (td_vbd_request_t *)treq.private;
gettimeofday(&vbd->ts, NULL);
DBG(TLOG_DBG, "%s: req %d seg %d sec 0x%08"PRIx64" "
"secs 0x%04x buf %p op %d res %d\n", image->name,
(int)treq.id, treq.sidx, treq.sec, treq.secs,
treq.buf, (int)vreq->req.operation, res);
__tapdisk_vbd_complete_td_request(vbd, vreq, treq, res);
}
static int
tapdisk_vbd_issue_request(td_vbd_t *vbd, td_vbd_request_t *vreq)
{
char *page;
td_ring_t *ring;
td_image_t *image;
td_request_t treq;
uint64_t sector_nr;
blkif_request_t *req;
int i, err, id, nsects;
req = &vreq->req;
id = req->id;
ring = &vbd->ring;
sector_nr = req->sector_number;
image = tapdisk_vbd_first_image(vbd);
vreq->submitting = 1;
gettimeofday(&vbd->ts, NULL);
gettimeofday(&vreq->last_try, NULL);
tapdisk_vbd_move_request(vreq, &vbd->pending_requests);
#if 0
err = tapdisk_vbd_check_queue(vbd);
if (err)
goto fail;
#endif
err = tapdisk_image_check_ring_request(image, req);
if (err)
goto fail;
for (i = 0; i < req->nr_segments; i++) {
nsects = req->seg[i].last_sect - req->seg[i].first_sect + 1;
page = (char *)MMAP_VADDR(ring->vstart,
(unsigned long)req->id, i);
page += (req->seg[i].first_sect << SECTOR_SHIFT);
treq.id = id;
treq.sidx = i;
treq.blocked = 0;
treq.buf = page;
treq.sec = sector_nr;
treq.secs = nsects;
treq.image = image;
treq.cb = tapdisk_vbd_complete_td_request;
treq.cb_data = NULL;
treq.private = vreq;
DBG(TLOG_DBG, "%s: req %d seg %d sec 0x%08"PRIx64" secs 0x%04x "
"buf %p op %d\n", image->name, id, i, treq.sec, treq.secs,
treq.buf, (int)req->operation);
vreq->secs_pending += nsects;
vbd->secs_pending += nsects;
switch (req->operation) {
case BLKIF_OP_WRITE:
treq.op = TD_OP_WRITE;
td_queue_write(image, treq);
break;
case BLKIF_OP_READ:
treq.op = TD_OP_READ;
td_queue_read(image, treq);
break;
}
sector_nr += nsects;
}
err = 0;
out:
vreq->submitting--;
if (!vreq->secs_pending) {
err = (err ? : vreq->error);
tapdisk_vbd_complete_vbd_request(vbd, vreq);
}
return err;
fail:
vreq->status = BLKIF_RSP_ERROR;
goto out;
}
static int
tapdisk_vbd_reissue_failed_requests(td_vbd_t *vbd)
{
int err;
struct timeval now;
td_vbd_request_t *vreq, *tmp;
err = 0;
gettimeofday(&now, NULL);
tapdisk_vbd_for_each_request(vreq, tmp, &vbd->failed_requests) {
if (vreq->secs_pending)
continue;
if (td_flag_test(vbd->state, TD_VBD_SHUTDOWN_REQUESTED))
goto fail;
if (vreq->error != -EBUSY &&
now.tv_sec - vreq->last_try.tv_sec < TD_VBD_RETRY_INTERVAL)
continue;
if (vreq->num_retries >= TD_VBD_MAX_RETRIES) {
fail:
DBG(TLOG_INFO, "req %"PRIu64"retried %d times\n",
vreq->req.id, vreq->num_retries);
tapdisk_vbd_complete_vbd_request(vbd, vreq);
continue;
}
/*
* never fail due to too many retries if we are blocked on a
* dependency
*/
if (vreq->blocked) {
vreq->blocked = 0;
} else {
vbd->retries++;
vreq->num_retries++;
}
vreq->error = 0;
vreq->status = BLKIF_RSP_OKAY;
DBG(TLOG_DBG, "retry #%d of req %"PRIu64", "
"sec 0x%08"PRIx64", nr_segs: %d\n", vreq->num_retries,
vreq->req.id, vreq->req.sector_number,
vreq->req.nr_segments);
err = tapdisk_vbd_issue_request(vbd, vreq);
if (err)
break;
}
if (list_empty(&vbd->failed_requests))
td_flag_clear(vbd->state, TD_VBD_RETRY_NEEDED);
else
td_flag_set(vbd->state, TD_VBD_RETRY_NEEDED);
return err;
}
static int
tapdisk_vbd_issue_new_requests(td_vbd_t *vbd)
{
int err;
td_vbd_request_t *vreq, *tmp;
tapdisk_vbd_for_each_request(vreq, tmp, &vbd->new_requests) {
err = tapdisk_vbd_issue_request(vbd, vreq);
if (err)
return err;
}
return 0;
}
static int
tapdisk_vbd_kill_requests(td_vbd_t *vbd)
{
td_vbd_request_t *vreq, *tmp;
tapdisk_vbd_for_each_request(vreq, tmp, &vbd->new_requests) {
vreq->status = BLKIF_RSP_ERROR;
tapdisk_vbd_move_request(vreq, &vbd->completed_requests);
}
tapdisk_vbd_for_each_request(vreq, tmp, &vbd->failed_requests) {
vreq->status = BLKIF_RSP_ERROR;
tapdisk_vbd_move_request(vreq, &vbd->completed_requests);
}
return 0;
}
int
tapdisk_vbd_issue_requests(td_vbd_t *vbd)
{
int err;
if (td_flag_test(vbd->state, TD_VBD_DEAD))
return tapdisk_vbd_kill_requests(vbd);
if (!tapdisk_vbd_queue_ready(vbd))
return -EAGAIN;
err = tapdisk_vbd_reissue_failed_requests(vbd);
if (err)
return err;
return tapdisk_vbd_issue_new_requests(vbd);
}
static void
tapdisk_vbd_pull_ring_requests(td_vbd_t *vbd)
{
int idx;
RING_IDX rp, rc;
td_ring_t *ring;
blkif_request_t *req;
td_vbd_request_t *vreq;
ring = &vbd->ring;
if (!ring->sring)
return;
rp = ring->fe_ring.sring->req_prod;
xen_rmb();
for (rc = ring->fe_ring.req_cons; rc != rp; rc++) {
req = RING_GET_REQUEST(&ring->fe_ring, rc);
++ring->fe_ring.req_cons;
idx = req->id;
vreq = &vbd->request_list[idx];
ASSERT(list_empty(&vreq->next));
ASSERT(vreq->secs_pending == 0);
memcpy(&vreq->req, req, sizeof(blkif_request_t));
vbd->received++;
vreq->vbd = vbd;
tapdisk_vbd_move_request(vreq, &vbd->new_requests);
DBG(TLOG_DBG, "%s: request %d \n", vbd->name, idx);
}
}
static int
tapdisk_vbd_pause_ring(td_vbd_t *vbd)
{
int err;
if (td_flag_test(vbd->state, TD_VBD_PAUSED))
return 0;
td_flag_set(vbd->state, TD_VBD_PAUSE_REQUESTED);
err = tapdisk_vbd_quiesce_queue(vbd);
if (err) {
EPRINTF("%s: ring pause request on active queue\n", vbd->name);
return err;
}
tapdisk_vbd_close_vdi(vbd);
err = ioctl(vbd->ring.fd, BLKTAP2_IOCTL_PAUSE, 0);
if (err)
EPRINTF("%s: pause ioctl failed: %d\n", vbd->name, errno);
else {
td_flag_clear(vbd->state, TD_VBD_PAUSE_REQUESTED);
td_flag_set(vbd->state, TD_VBD_PAUSED);
}
return err;
}
static int
tapdisk_vbd_resume_ring(td_vbd_t *vbd)
{
int i, err, type;
char message[BLKTAP2_MAX_MESSAGE_LEN];
const char *path;
memset(message, 0, sizeof(message));
if (!td_flag_test(vbd->state, TD_VBD_PAUSED)) {
EPRINTF("%s: resume message for unpaused vbd\n", vbd->name);
return -EINVAL;
}
err = ioctl(vbd->ring.fd, BLKTAP2_IOCTL_REOPEN, &message);
if (err) {
EPRINTF("%s: resume ioctl failed: %d\n", vbd->name, errno);
return err;
}
err = tapdisk_parse_disk_type(message, &path, &type);
if (err) {
EPRINTF("%s: invalid resume string %s\n", vbd->name, message);
goto out;
}
free(vbd->name);
vbd->name = strdup(path);
if (!vbd->name) {
EPRINTF("resume malloc failed\n");
err = -ENOMEM;
goto out;
}
tapdisk_vbd_start_queue(vbd);
for (i = 0; i < TD_VBD_EIO_RETRIES; i++) {
err = __tapdisk_vbd_open_vdi(vbd, TD_OPEN_STRICT);
if (err != -EIO)
break;
sleep(TD_VBD_EIO_SLEEP);
}
out:
if (!err) {
image_t image;
struct blktap2_params params;
memset(&params, 0, sizeof(params));
tapdisk_vbd_get_image_info(vbd, &image);
params.sector_size = image.secsize;
params.capacity = image.size;
snprintf(params.name, sizeof(params.name) - 1, "%s", message);
ioctl(vbd->ring.fd, BLKTAP2_IOCTL_SET_PARAMS, &params);
td_flag_clear(vbd->state, TD_VBD_PAUSED);
}
ioctl(vbd->ring.fd, BLKTAP2_IOCTL_RESUME, err);
return err;
}
static int
tapdisk_vbd_check_ring_message(td_vbd_t *vbd)
{
if (!vbd->ring.sring)
return -EINVAL;
switch (vbd->ring.sring->private.tapif_user.msg) {
case 0:
return 0;
case BLKTAP2_RING_MESSAGE_PAUSE:
return tapdisk_vbd_pause_ring(vbd);
case BLKTAP2_RING_MESSAGE_RESUME:
return tapdisk_vbd_resume_ring(vbd);
case BLKTAP2_RING_MESSAGE_CLOSE:
return tapdisk_vbd_close(vbd);
default:
return -EINVAL;
}
}
static void
tapdisk_vbd_ring_event(event_id_t id, char mode, void *private)
{
td_vbd_t *vbd;
vbd = (td_vbd_t *)private;
tapdisk_vbd_pull_ring_requests(vbd);
tapdisk_vbd_issue_requests(vbd);
/* vbd may be destroyed after this call */
tapdisk_vbd_check_ring_message(vbd);
}
td_image_t *
tapdisk_vbd_first_image(td_vbd_t *vbd)
{
return list_entry(vbd->images.next, td_image_t, next);
}