Skip to contentSkip to conversation

blktap3: Introduce the tapback daemon (most of blkback in user-space).

PATCH Superseded Thanos Makatos 1 participant 9 messages Mar 6, 2013

v3 of 5 Superseded Latest is v5 →

lore
Share & tools
lore
#0 lore raw

PATCH v3 1/1 blktap3: Introduce the tapback daemon (most of blkback in user-space).

This patch series introduces the tapback daemon, the user space daemon that acts as a device's back-end, essentially most of blkback in user space. The daemon is responsible for coordinating the front-end and tapdisk. It creates tapdisk process as needed, instructs them to connect to/disconnect from the shared ring, and manages the state of the back-end.

The shared ring between the front-end and the tapdisk is provided by a piece of code that lives inside the tapdisk and will be introduced by the next patch series.

---
Changed since v1:
The series has been largely reorganised:
* Renamed the daemon from xenio to tapback.
* Improved description in patch 0.
* Merged structures and functions.
* Disaggregated functionality from the core daemon source file to smaller ones in order to facilitate the review process and improve maintenance.

Changed since v2:
* Added a new patch that ignores tapback binaries.
* For the rest of the patches, see the description in each patch.

#1 lore raw

PATCH v3 1/1 blktap3/tapback: Introduce core defines and structure definitions

---
Changed since v2:
* Removed BUG_ON macro.
* Fixed whitespace in tapback_backend_find_device.
* Clarified back-end name (xenio).
* Removed references to the "serial" thing.
* Remove unused member dev from struct vbd.
* Introduce prototype of function XenbusState2str.

tools/blktap3/tapback/tapback.h new file +244 -0
new file mode 100644
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2012      Citrix Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#ifndef __TAPBACK_H__
+#define __TAPBACK_H__
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include <xen/xen.h>
+#include <xenstore.h>
+#include <xen/io/xenbus.h>
+#include <xen/event_channel.h>
+#include <xen/grant_table.h>
+
+#include "tap-ctl.h"
+
+void tapback_log(int prio, const char *fmt, ...);
+void (*tapback_vlog) (int prio, const char *fmt, va_list ap);
+
+#define DBG(_fmt, _args...)  tapback_log(LOG_DEBUG, "%s:%d "_fmt, __FILE__, \
+        __LINE__, ##_args)
+#define INFO(_fmt, _args...) tapback_log(LOG_INFO, _fmt, ##_args)
+#define WARN(_fmt, _args...) tapback_log(LOG_WARNING, "%s:%d "_fmt, __FILE__, \
+        __LINE__, ##_args)
+
+#define WARN_ON(_cond, fmt, ...)    \
+    if (unlikely(_cond)) {          \
+        printf(fmt, ##__VA_ARGS__); \
+    }
+
+/*
+ * Pre-defined XenStore path components used for running the XenBus protocol.
+ *
+ * To avoid confusion with blktap2, we'll use a new kind of device for libxl
+ * defining it in tools/libxl/libxl_types_internal.idl. This will be done by
+ * the patch that adds libxl support for blktap3. The temporary back-end name
+ * is "xenio". Once blktap3 becomes the default back-end, its back-end name
+ * should be "vbd" and "xenio" will be removed. TODO When that patch is sent,
+ * use the definition from there instead of hard-coding it here.
+ */
+#define XENSTORE_BACKEND        "backend"
+#define BLKTAP3_BACKEND_NAME    "xenio"
+#define BLKTAP3_BACKEND_PATH    XENSTORE_BACKEND"/"BLKTAP3_BACKEND_NAME
+#define BLKTAP3_BACKEND_TOKEN   XENSTORE_BACKEND"-"BLKTAP3_BACKEND_NAME
+#define BLKTAP3_FRONTEND_TOKEN  "otherend-state"
+
+/*
+ * TODO Put the rest of the front-end nodes defined in blkif.h here and group
+ * them. e.g. FRONTEND_NODE_xxx.
+ */
+#define RING_REF                "ring-ref"
+#define FEAT_PERSIST            "feature-persistent"
+
+/**
+ * A Virtual Block Device (VBD), represents a block device in a guest VM.
+ * Contains all relevant information.
+ */
+typedef struct vbd {
+
+    /**
+     * Device name, as retrieved from XenStore at probe-time.
+     */
+    char *name;
+    
+    /**
+     * The device ID. Same as vbd.name, we keep it around because the tapdisk
+     * control libreary wants it as an int and not as a string.
+     */
+    int devid;
+
+    /**
+     * For linked lists.
+     */
+     TAILQ_ENTRY(vbd) backend_entry;
+
+    /**
+     * The domain ID this VBD belongs to.
+     */
+    domid_t domid;
+
+    /**
+     * The root directory in XenStore for this VBD. This is where all
+     * directories and key/value pairs related to this VBD are stored.
+     */
+    char *frontend_path;
+
+    /**
+     * XenStore path to the VBD's state. This is just
+     * vbd.frontend_path + "/state", we keep it around so we don't have to
+     * allocate/free memory all the time.
+     */
+    char *frontend_state_path;
+
+    /**
+     * Indicates whether the tapdisk is connected to the shared ring.
+     */
+    bool connected;
+
+    /**
+     * Descriptor of the tapdisk process serving this virtual block device. We
+     * need this until the end of the VBD's lifetime in order to disconnect
+     * the tapdisk from the shared ring.
+     */
+    tap_list_t tap;
+
+    /*
+     * XXX We keep sector_size, sectors, and info because we need to
+     * communicate them to the front-end not only when the front-end goes to
+     * XenbusStateInitialised, but to XenbusStateConnected as well.
+     */
+
+    /**
+     * Sector size, supplied by the tapdisk, communicated to blkfront.
+     */
+    unsigned int sector_size;
+
+    /**
+     * Number of sectors, supplied by the tapdisk, communicated to blkfront.
+     */
+    unsigned long long sectors;
+
+    /**
+     * VDISK_???, defined in include/xen/interface/io/blkif.h.
+     */
+    unsigned int info;
+
+} vbd_t;
+
+TAILQ_HEAD(tqh_vbd, vbd);
+
+/**
+ * The collection of all necessary handles and descriptors.
+ */
+struct _blktap3_daemon {
+
+    /**
+     * A handle to XenStore.
+     */
+    struct xs_handle *xs;
+
+    /**
+     * For executing transacted operations on XenStore.
+     */
+    xs_transaction_t xst;
+
+    /**
+     * The list of virtual block devices.
+     *
+     * TODO We sometimes have to scan the whole list to find the device/domain
+     * we're interested in, should we optimize this? E.g. use a hash table
+     * for O(1) access?
+     * TODO Replace with a hash table (hcreate etc.)?
+     */
+    struct tqh_vbd devices;
+
+    /**
+     * TODO From xen/include/public/io/blkif.h: "The maximum supported size of
+     * the request ring buffer" 
+     */
+    int max_ring_page_order;
+};
+
+extern struct _blktap3_daemon blktap3_daemon;
+
+#define tapback_backend_for_each_device(_device, _next)	\
+	TAILQ_FOREACH_SAFE(_device, &blktap3_daemon.devices, backend_entry, _next)
+
+/**
+ * Iterates over all devices and returns the one for which the condition is
+ * true.
+ */
+#define tapback_backend_find_device(_device, _cond)     \
+do {                                                    \
+    vbd_t *__next;                                      \
+    int found = 0;                                      \
+    tapback_backend_for_each_device(_device, __next) {  \
+        if (_cond) {                                    \
+            found = 1;                                  \
+            break;                                      \
+        }                                               \
+    }                                                   \
+    if (!found)                                         \
+        _device = NULL;                                 \
+} while (0)
+
+/**
+ * Act in response to a change in the front-end XenStore path.
+ *
+ * @param path the front-end's XenStore path that changed
+ * @returns 0 on success, an error code otherwise
+ *
+ * XXX Only called by tapback_read_watch
+ */
+int
+tapback_backend_handle_otherend_watch(const char * const path);
+
+/**
+ * Act in response to a change in the back-end directory in XenStore.
+ *
+ * If the path is "/backend" or "/backend/<backend name>", all devices are
+ * probed. Otherwise, the path should be
+ * "backend/<backend name>/<domid>/<device name>"
+ * (i.e. backend/<backend name>/1/51712), and in this case this specific device
+ * is probed.
+ *
+ * @param path the back-end's XenStore path that changed @returns 0 on success,
+ * an error code otherwise
+ *
+ * XXX Only called by tapback_read_watch.
+ */
+int
+tapback_backend_handle_backend_watch(char * const path);
+
+/**
+ * Converts XenbusState values to a printable string, e.g. XenbusStateConnected
+ * corresponds to "connected".
+ *
+ * @param xbs the XenbusState to convert
+ * @returns a printable string
+ */
+char *
+XenbusState2str(const XenbusState xbs);
+
+#endif /* __TAPBACK_H__ */
#2 lore raw

PATCH v3 1/1 blktap3/tapback: Introduces functionality required to access XenStore

In reply to Thanos Makatos

This patch introduces convenience functions that read/write values from/to XenStore.

---
Changed since v2:
* Introduced makefile to facilitate development.
* Removed functions (v)mprintf as (v)asprintf suffice.
* Ensure tapback_xs_vread (a) returns a NULL terminated string, and (b) the returned string doesn't contain NULL characters, apart from the NULL-terminating one.
* Removed unnecessary variable initialisations.
* Minor whitespace clean up.
* Fixed typo in tapback_xs_vread.
* function tapback_xs_vread: handle corner case where the value returned by xs_read is a zero-length string.

tools/blktap3/tapback/Makefile new file +31 -0
new file mode 100644
@@ -0,0 +1,31 @@
+XEN_ROOT := $(CURDIR)/../../../
+include $(XEN_ROOT)/tools/Rules.mk
+
+BLKTAP_ROOT := ..
+
+# -D_GNU_SOURCE is required by vasprintf.
+override CFLAGS += \
+    -I$(BLKTAP_ROOT)/include \
+    -I$(BLKTAP_ROOT)/control \
+    -D_GNU_SOURCE \
+    $(CFLAGS_libxenstore) \
+    $(CFLAGS_libxenctrl) \
+    $(CFLAGS_xeninclude) \
+    -Wall \
+    -Wextra \
+    -Werror
+
+# FIXME cause trouble
+override CFLAGS += \
+    -Wno-old-style-declaration \
+    -Wno-sign-compare \
+    -Wno-type-limits
+
+override LDFLAGS += \
+    $(LDLIBS_libxenstore) \
+    $(LDFLAGS_libxenctrl)
+
+clean:
+	rm -f *.o *.o.d .*.o.d
+
+.PHONY: clean install
tools/blktap3/tapback/xenstore.c new file +195 -0

195 lines not on this page · raw message

tools/blktap3/tapback/xenstore.h new file +95 -0

95 lines not on this page · raw message

#3 lore raw

PATCH v3 1/1 blktap3/tapback: Logging for the tapback daemon and libxenio

In reply to Thanos Makatos

---
Changed since v2:
* minor whitespace clean up

tools/blktap3/tapback/log.c new file +33 -0
new file mode 100644
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012      Citrix Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include "compiler.h"
+#include <stdarg.h>
+#include <syslog.h>
+
+void (*tapback_vlog) (int prio, const char *fmt, va_list ap) = vsyslog;
+
+__printf(2, 3) void
+tapback_log(int prio, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    tapback_vlog(prio, fmt, ap);
+    va_end(ap);
+}
#4 lore raw

PATCH v3 1/1 blktap3/tapback: Introduce back-end XenStore path handler

In reply to Thanos Makatos

This patch introduces the handler executed when the back-end XenStore path gets modified. A back-end XenStore path is modified as a result of a device creation/removal request. The device is created/removed depending on whether its path exists/does not exist in XenStore.

Creating a device comprises creating the in-memory representation of it and adding it to the list of devices, locating the tapdisk designated to serve this VBD, and setting a XenStore watch to the front-end path of the newly-created device.

Deleting a device comprises removing that XenStore watch and deallocating its in-memory representation.

---
Changed since v2:
* Removed the "serial" thing.
* Function tapback_backend_handle_backend_watch doesn't always have to scan
* the entire back-end sub-tree, this is left as a future optimisation (relevant comment inside code updated).
* Replaced mprintf with asprintf.
* Check for failures in tapback_backend_scan.
* Minor code and whitespace clean up.
* The tapback daemon searches for a suitable tapdisk and creates one if none is found. It also kills it when the front-end disconnects.

tools/blktap3/tapback/backend.c new file +503 -0
new file mode 100644
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2012      Citrix Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ *
+ * This file contains the handler executed when the back-end XenStore path gets
+ * modified.
+ */
+
+#include "tapback.h"
+#include "xenstore.h"
+#include "tap-ctl-info.h"
+
+/**
+ * Removes the XenStore watch from the front-end.
+ *
+ * @param device the VBD whose front-end XenStore path should stop being
+ * watched
+ */
+static void
+tapback_device_unwatch_frontend_state(vbd_t * const device)
+{
+    assert(device);
+
+    if (device->frontend_state_path)
+        xs_unwatch(blktap3_daemon.xs, device->frontend_state_path,
+                BLKTAP3_FRONTEND_TOKEN);
+
+    free(device->frontend_state_path);
+    device->frontend_state_path = NULL;
+}
+
+/**
+ * Destroys and deallocates the back-end part of a VBD.
+ *
+ * @param device the VBD to destroy
+ */
+static void
+tapback_backend_destroy_device(vbd_t * const device)
+{
+    int err;
+
+    assert(device);
+
+    TAILQ_REMOVE(&blktap3_daemon.devices, device, backend_entry);
+
+    tapback_device_unwatch_frontend_state(device);
+
+    /*
+     * kill the tapdisk
+     */
+    err = tap_ctl_destroy(device->tap.pid, device->tap.minor, 0, NULL);
+    if (err)
+        WARN("failed to destroy tapdisk %d/%d: %s (error ignored)\n",
+                device->tap.pid, device->tap.minor, strerror(err));
+
+    free(device->frontend_path);
+    free(device->name);
+
+    /*
+     * XXX device->bdev is expected to have been freed
+     */
+
+    free(device);
+}
+
+/**
+ * Retrieves the tapdisk designated to serve this device, storing this
+ * information in the supplied VBD handle.
+ *
+ * @param param <type>:/path/to/file
+ * @param tap output parameter that receives the tapdisk process information.
+ * The parameter is undefined when the function returns a non-zero value.
+ * @returns 0 if a suitable tapdisk is found, ESRCH if no suitable tapdisk is
+ * found, and an error code in case of error
+ *
+ * TODO rename function
+ *
+ * XXX Only called by blkback_probe.
+ */
+static inline int
+blkback_find_tapdisk(const char *params, tap_list_t *tap)
+{
+    struct tqh_tap_list list;
+    tap_list_t *_tap;
+    int err;
+    char *type;
+    char *path;
+    char *_params = NULL;
+
+    assert(params);
+    assert(tap);
+
+    _params = strdup(params);
+    if (!_params) {
+        WARN("failed to allocate memory\n");
+        err = ENOMEM;
+        goto out;
+    }
+
+    type = strtok(_params, ":");
+    if (type) {
+        path = strtok(NULL, ":");
+    }
+    if (!type || !path) {
+        WARN("malformed params \'%s\'\n", params);
+        err = EINVAL;
+        goto out;
+    }
+
+    err = tap_ctl_list(&list);
+    if (err) {
+        WARN("error listing tapdisks: %s\n", strerror(err));
+        goto out;
+    }
+
+    err = ESRCH;
+    if (!TAILQ_EMPTY(&list)) {
+        tap_list_for_each_entry(_tap, &list) {
+            if (_tap->type && !strcmp(_tap->type, type) && _tap->path
+                    && !strcmp(_tap->path, path)) {
+                err = 0;
+                memcpy(tap, _tap, sizeof(tap));
+                break;
+            }
+        }
+        tap_ctl_list_free(&list);
+    } else
+        DBG("no tapdisks\n");
+
+out:
+    free(_params);
+    return err;
+}
+
+/**
+ * Creates a device and adds it to the list of devices.
+ * Initiates a XenStore watch to the front-end state.
+ *
+ * Creating the device implies initializing the handle and retrieving all the
+ * information of the tapdisk serving this VBD.
+ *
+ * @param domid the ID of the domain where the VBD is created
+ * @param name the name of the device
+ * @returns 0 on success, an error code otherwise
+ */
+static inline int
+tapback_backend_create_device(const domid_t domid, const char * const name)
+{
+    vbd_t *device = NULL;
+    int err = 0;
+    char *params = NULL;
+
+    assert(name);
+
+    DBG("creating device %d/%s\n", domid, name);
+
+    if (!(device = calloc(1, sizeof(*device)))) {
+        WARN("error allocating memory\n");
+        err = -errno;
+        goto out;
+    }
+
+    device->domid = domid;
+
+    TAILQ_INSERT_TAIL(&blktap3_daemon.devices, device, backend_entry);
+
+    if (!(device->name = strdup(name))) {
+        err = -errno;
+        goto out;
+    }
+
+    /*
+     * Get the front-end path from XenStore. We need this to talk to the
+     * front-end.
+     */
+    if (!(device->frontend_path = tapback_device_read(device, "frontend"))) {
+        err = errno;
+        WARN("failed to read front-end path: %s\n", strerror(err));
+        goto out;
+    }
+
+    /*
+     * Get the file path backing the VBD.
+     */
+    params = tapback_xs_read(blktap3_daemon.xs, blktap3_daemon.xst,
+            "%s/%d/%s/params", BLKTAP3_BACKEND_PATH, domid, name);
+    if (!params) {
+        err = errno;
+        WARN("failed to read backing file: %s\n", strerror(err));
+        goto out;
+    }
+    DBG("need to find tapdisk serving \'%s\'\n", params);
+
+    err = blkback_find_tapdisk(params, &device->tap);
+    if (!err) {
+        DBG("found tapdisk %d/%d\n", device->tap.pid, device->tap.minor);
+    } else if (err == ESRCH) {
+        /* FIXME replace with tap_ctl_create */
+        DBG("no such tapdisk\n");
+        err = tap_ctl_spawn();
+        if (err <= 0) {
+            WARN("failed to create tapdisk: %s\n", strerror(err));
+            goto out;
+        }
+        device->tap.pid = err;
+        DBG("spawned tapdisk %d\n", device->tap.pid);
+        device->tap.minor = 0;
+        err = tap_ctl_attach(device->tap.pid, device->tap.minor);
+        if (err) {
+            WARN("failed to attach tapdisk: %s\n", strerror(err));
+            goto out;
+        }
+        DBG("attached tapdisk %d\n", device->tap.pid);
+        err = tap_ctl_open(device->tap.pid, 0, params, 0, -1, NULL);
+        if (err) {
+            WARN("failed to open tapdisk: %s\n", strerror(err));
+            goto out;
+        }
+        DBG("opened tapdisk %d\n", device->tap.pid);
+    } else  {
+        WARN("error looking for tapdisk: %s\n", strerror(err));
+        goto out;
+    }
+
+    /*
+     * get the VBD parameters from the tapdisk
+     */
+    if ((err = tap_ctl_info(device->tap.pid, 0, &device->sectors,
+                    &device->sector_size, &device->info))) {
+        WARN("error retrieving disk characteristics: %s\n", strerror(-err));
+        goto out;
+    }
+
+    DBG("got %d-%d with tapdev %d/%d\n", device->domid, device->devid,
+            device->tap.pid, device->tap.minor);
+
+	/*
+	 * Finally, watch the front-end path in XenStore for changes, i.e.
+     * /local/domain/<domid>/device/vbd/<devname>/state
+     * After this, we wait for the front-end to switch state to continue with
+     * the initialisation.
+	 */
+    if (asprintf(&device->frontend_state_path, "%s/state",
+                device->frontend_path) == -1) {
+        /* TODO log error */
+        err = -errno;
+        goto out;
+    }
+    assert(device->frontend_state_path);
+
+    /*
+     * We use the same token for all front-end watches. We don't have to use a
+     * unique token for each front-end watch because when a front-end watch
+     * fires we are given the XenStore path that changed.
+     */
+    if (!xs_watch(blktap3_daemon.xs, device->frontend_state_path,
+                BLKTAP3_FRONTEND_TOKEN)) {
+        free(device->frontend_state_path);
+        err = -errno;
+        goto out;
+    }
+
+out:
+    if (err) {
+        WARN("error creating device: domid=%d name=%s err=%d (%s)\n",
+                domid, name, err, strerror(err));
+        if (device)
+            tapback_backend_destroy_device(device);
+    }
+    free(params);
+    return err;
+}
+
+/**
+ * Creates (removes) a device depending on the existence (non-existence) of the
+ * "backend/<backend name>/@domid/@devname" XenStore path.
+ *
+ * @param domid the ID of the domain where the VBD is created
+ * @param devname device name
+ * @returns 0 on success, an error code otherwise
+ */
+static int
+tapback_backend_probe_device(const domid_t domid, const char * const devname)
+{
+    int should_exist = 0, create = 0, remove = 0;
+    vbd_t *device = NULL;
+    char * s = NULL;
+
+    assert(devname);
+
+    DBG("probing device domid=%d name=%s\n", domid, devname);
+
+    /*
+     * Ask XenStore if the device _should_ exist.
+     */
+    s = tapback_xs_read(blktap3_daemon.xs, blktap3_daemon.xst, "%s/%d/%s",
+            BLKTAP3_BACKEND_PATH, domid, devname);
+    should_exist = s != NULL;
+    free(s);
+
+    /*
+     * Search the device list for this specific device.
+     */
+    tapback_backend_find_device(device,
+            device->domid == domid && !strcmp(device->name, devname));
+
+    /*
+	 * If XenStore says that the device should exist but it's not in our device 
+     * list, we must create it. If it's the other way round, this is a removal.
+     */
+    remove = device && !should_exist;
+    create = !device && should_exist;
+
+    DBG("should exist=%d device=%p remove=%d create=%d\n",
+        should_exist, device, remove, create);
+
+    if (!create && !remove) {
+        /*
+         * A watch has triggered on a path we're not interested in.
+         * TODO Check if we can avoid probing the device completely based on
+         * the path that triggered.
+         */
+        DBG("spurious XenStore watch triggered on back-end\n");
+        return 0;
+    }
+
+    /*
+     * Remember that remove and create may both be true at the same time, as
+     * this indicates that the device has been removed and re-created too fast.
+     * In this case, we too need to remove and re-create the device,
+     * respectively.
+     */
+
+    if (remove)
+        tapback_backend_destroy_device(device);
+
+    if (create) {
+        const int err = tapback_backend_create_device(domid, devname);
+        if (0 != err) {
+            WARN("error creating device %s on domain %d: %s\n", devname, domid,
+                    strerror(err));
+            return err;
+        }
+    }
+
+    return 0;
+}
+
+/**
+ * Scans XenStore for all blktap3 devices and probes each one of them.
+ *
+ * XXX Only called by tapback_backend_handle_backend_watch.
+ */
+static int
+tapback_backend_scan(void)
+{
+    vbd_t *device = NULL, *next = NULL;
+    unsigned int i = 0, j = 0, n = 0, m = 0;
+    char **dir = NULL;
+    int err = 0;
+
+    DBG("scanning the back-end\n");
+
+    /*
+     * scrap all non-existent devices
+     * FIXME Why do we do this?
+     * FIXME Is this costly?
+     */
+
+    tapback_backend_for_each_device(device, next) {
+        err = tapback_backend_probe_device(device->domid, device->name);
+        if (err) {
+            WARN("error probing device %s of domain %d: %s\n", device->name,
+                    device->domid, strerror(err));
+            /* TODO Should we fail in this case of keep probing? */
+            goto out;
+        }
+    }
+
+    /*
+     * probe the new ones
+     *
+     * FIXME We're checking each and every device in each and every domain,
+     * could there be a performance issue in the presence of many VMs/VBDs?
+     * (e.g. boot-storm)
+     */
+    if (!(dir = xs_directory(blktap3_daemon.xs, blktap3_daemon.xst,
+                    BLKTAP3_BACKEND_PATH, &n))) {
+        err = errno;
+        if (err == ENOENT)
+            err = 0;
+        else
+            WARN("error listing %s: %s\n", BLKTAP3_BACKEND_PATH,
+                    strerror(err));
+        goto out;
+    }
+
+    DBG("probing %d domains\n", n);
+
+    for (i = 0; i < n; i++) { /* for each domain */
+        char *path = NULL, **sub = NULL, *end = NULL;
+        domid_t domid = 0;
+
+        /*
+         * Get the domain ID.
+         */
+        domid = strtoul(dir[i], &end, 0);
+        if (*end != 0 || end == dir[i])
+            continue;
+
+        /*
+         * Read the devices of this domain.
+         */
+        if (asprintf(&path, "%s/%d", BLKTAP3_BACKEND_PATH, domid) == -1) {
+            /* TODO log error */
+            err = errno;
+            goto out;
+        }
+        sub = xs_directory(blktap3_daemon.xs, blktap3_daemon.xst, path, &m);
+        err = errno;
+        free(path);
+
+        if (!sub) {
+            WARN("error listing %s: %s\n", path, strerror(err));
+            goto out;
+        }
+
+        /*
+         * Probe each device.
+         */
+        for (j = 0; j < m; j++) {
+            err = tapback_backend_probe_device(domid, sub[j]);
+            if (err) {
+                WARN("error probing device %s of domain %d: %s\n", sub[j],
+                        domid, strerror(err));
+                goto out;
+            }
+        }
+
+        free(sub);
+    }
+
+out:
+    free(dir);
+    return err;
+}
+
+int
+tapback_backend_handle_backend_watch(char * const path)
+{
+    char *s = NULL, *end = NULL, *name = NULL;
+    domid_t domid = 0;
+
+    assert(path);
+
+    DBG("handling watch triggered on path \'%s\'\n", path);
+
+    s = strtok(path, "/");
+    assert(!strcmp(s, XENSTORE_BACKEND));
+    if (!(s = strtok(NULL, "/")))
+        return tapback_backend_scan();
+
+    assert(!strcmp(s, BLKTAP3_BACKEND_NAME));
+    if (!(s = strtok(NULL, "/")))
+        return tapback_backend_scan();
+
+    domid = strtoul(s, &end, 0);
+    if (*end != 0 || end == s) {
+        WARN("invalid domain ID \'%s\'\n", s);
+        return EINVAL;
+    }
+
+    /*
+     * TODO Optimisation: since we know which domain changed, we don't have to
+     * scan the whole thing. Add the domid as an optional parameter to
+     * tapback_backend_scan.
+     */
+    if (!(name = strtok(NULL, "/")))
+        return tapback_backend_scan();
+
+    /*
+     * Create or remove a specific device.
+     *
+     * TODO tapback_backend_probe_device reads xenstore again to see if the
+     * device should exist, but we already know that in the current function.
+     * Optimise this case.
+     */
+    return tapback_backend_probe_device(domid, name);
+}
#5 lore raw

PATCH v3 1/1 blktap3/tapback: Introduce front-end XenStore path handler

In reply to Thanos Makatos

This patch introduces the handler executed when the front-end XenStore path of a VBD gets modified. This is done when the front-end switches state. The core of this functionality is connecting/disconnecting the tapdisk to/from the shared ring.

When the front-end goes to Initialised or Connected state, the daemon reads all the necessary information from XenStore provided by the front-end, initiates the shared ring creation (the actual creation is performed by a library that will be introduced by another patch series), and instructs the tapdisk designated to serve this VBD to connect to the ring. Finally, it communicates to the front-end all the necessary disk parameters.

When the front-end switches to Closed, the tapdisk is disconnected from the shared ring.

---
Changed since v2:
* Documented the frontend_state_change_map array of callbacks.
* Minor code and whitespace clean up.
* Added some debug prints.
* Don't fail if the front-end doesn't tell us whether or not it supports persistent grants, just assume it doesn't.

tools/blktap3/include/blktap3.h +15 -0
@@ -44,4 +44,19 @@
     TAILQ_REMOVE(src, node, entry);             \
     TAILQ_INSERT_TAIL(dst, node, entry);
 
+/**
+ * Block I/O protocol
+ *
+ * Taken from linux/drivers/block/xen-blkback/common.h so that blkfront can
+ * work both with blktap2 and blktap3.
+ *
+ * TODO linux/drivers/block/xen-blkback/common.h contains other definitions
+ * necessary for allowing tapdisk3 to talk directly to blkfront. Find a way to
+ * use the definitions from there.
+ */
+enum blkif_protocol {
+       BLKIF_PROTOCOL_NATIVE = 1,
+       BLKIF_PROTOCOL_X86_32 = 2,
+       BLKIF_PROTOCOL_X86_64 = 3,
+};
 #endif /* __BLKTAP_3_H__ */
tools/blktap3/tapback/frontend.c new file +418 -0

418 lines not on this page · raw message

#6 lore raw

PATCH v3 1/1 blktap3/tapback: Introduce the tapback daemon

In reply to Thanos Makatos

This patch introduces the core of the tapback daemon, the user space daemon that acts as a device's back-end, essentially most of blkback in user space. Similar to blkback, the daemon monitors XenStore for device creation/removal requests and front-end state changes, and acts in response to them (communicates to the front-end necessary information, switches the back-end's state etc.). The daemon creates/destroys tapdisk processes as needed.

---
Changed since v1:
* minor code clean up

Changed since v2:
* Introduce function XenbusState2str for printing XenbusStates in a more human-friendly way.
* Don't print the log identify if the tapback is run in non-daemon mode, in order to make debug output more concise.

tools/blktap3/tapback/tapback.c new file +301 -0
new file mode 100644
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2012      Citrix Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ *
+ * This file contains the core of the tapback daemon, the user space daemon
+ * that acts as a device's back-end.
+ */
+
+/*
+ * TODO Some of these includes may be useless.
+ * TODO Replace hard-coding strings with defines/const string.
+ */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <syslog.h>
+
+#include "blktap3.h"
+#include "stdio.h" /* TODO tap-ctl.h needs to include stdio.h */
+#include "tap-ctl.h"
+#include "tapback.h"
+
+void tapback_log(int prio, const char *fmt, ...);
+void (*tapback_vlog) (int prio, const char *fmt, va_list ap);
+
+struct _blktap3_daemon blktap3_daemon;
+
+char *XenbusState2str(const XenbusState xbs)
+{
+    static char * const str[] = {
+        [XenbusStateUnknown] = "unknown",
+        [XenbusStateInitialising] = "initialising",
+        [XenbusStateInitWait] = "init wait",
+        [XenbusStateInitialised] = "initialised",
+        [XenbusStateConnected] = "connected",
+        [XenbusStateClosing] = "closing",
+        [XenbusStateClosed] = "closed",
+        [XenbusStateReconfiguring] = "reconfiguring",
+        [XenbusStateReconfigured] = "reconfigured"
+    };
+    return str[xbs];
+}
+
+/**
+ * Read changes that occurred on the "backend/<backend name>" XenStore path
+ * or one of the front-end paths and act accordingly.
+ */
+static inline void
+tapback_read_watch(void)
+{
+    char **watch = NULL, *path = NULL, *token = NULL;
+    unsigned int n = 0;
+    int err = 0, _abort = 0;
+
+    /* read the change */
+    watch = xs_read_watch(blktap3_daemon.xs, &n);
+    path = watch[XS_WATCH_PATH];
+    token = watch[XS_WATCH_TOKEN];
+
+    /*
+     * TODO Put the body of "again:" into a loop instead of using goto.
+     */
+again:
+    if (!(blktap3_daemon.xst = xs_transaction_start(blktap3_daemon.xs))) {
+        WARN("error starting transaction\n");
+        goto fail;
+    }
+
+    /*
+     * The token indicates which XenStore watch triggered, the front-end one or
+     * the back-end one.
+     */
+    if (!strcmp(token, BLKTAP3_FRONTEND_TOKEN)) {
+        DBG("front-end triggered\n");
+        err = tapback_backend_handle_otherend_watch(path);
+    } else if (!strcmp(token, BLKTAP3_BACKEND_TOKEN)) {
+        DBG("back-end triggered\n");
+        err = tapback_backend_handle_backend_watch(path);
+    } else {
+        WARN("invalid token \'%s\'\n", token);
+        err = EINVAL;
+    }
+
+    _abort = !!err;
+    if (_abort)
+        DBG("aborting transaction: %s\n", strerror(err));
+
+    err = xs_transaction_end(blktap3_daemon.xs, blktap3_daemon.xst, _abort);
+    blktap3_daemon.xst = 0;
+    if (!err) {
+        err = -errno;
+        /*
+         * This is OK according to xs_transaction_end's semantics.
+         */
+        if (EAGAIN == errno)
+            goto again;
+        DBG("error ending transaction: %s\n", strerror(err));
+    }
+
+fail:
+    free(watch);
+    return;
+}
+
+static void
+tapback_backend_destroy(void)
+{
+    if (blktap3_daemon.xs) {
+        xs_daemon_close(blktap3_daemon.xs);
+        blktap3_daemon.xs = NULL;
+    }
+}
+
+/**
+ * Initializes the back-end descriptor. There is one back-end per tapback
+ * process. Also, it initiates a watch to XenStore on backend/<backend name>.
+ *
+ * @returns 0 on success, an error code otherwise
+ */
+static inline int
+tapback_backend_create(void)
+{
+    int err;
+
+    TAILQ_INIT(&blktap3_daemon.devices);
+    blktap3_daemon.xst = XBT_NULL;
+
+    if (!(blktap3_daemon.xs = xs_daemon_open())) {
+        err = EINVAL;
+        goto fail;
+    }
+
+    /*
+     * Watch the back-end.
+     */
+    if (!xs_watch(blktap3_daemon.xs, BLKTAP3_BACKEND_PATH,
+                BLKTAP3_BACKEND_TOKEN)) {
+        err = errno;
+        goto fail;
+    }
+
+    return 0;
+
+fail:
+	tapback_backend_destroy();
+
+    return err;
+}
+
+/**
+ * Runs the daemon.
+ *
+ * Watches backend/<backend name> and the front-end devices.
+ */
+static inline int
+tapback_backend_run(void)
+{
+    const int fd = xs_fileno(blktap3_daemon.xs);
+	int err;
+
+    do {
+        fd_set rfds;
+        int nfds = 0;
+
+        FD_ZERO(&rfds);
+        FD_SET(fd, &rfds);
+
+        /* poll the fd for changes in the XenStore path we're interested in */
+        if ((nfds = select(fd + 1, &rfds, NULL, NULL, NULL)) < 0) {
+            perror("error monitoring XenStore");
+            err = -errno;
+            break;
+        }
+
+        if (FD_ISSET(fd, &rfds))
+            tapback_read_watch();
+        DBG("--\n");
+    } while (1);
+
+    return err;
+}
+
+static char *blkback_ident = NULL;
+
+static void
+blkback_vlog_fprintf(const int prio, const char * const fmt, va_list ap)
+{
+    static const char *strprio[] = {
+        [LOG_DEBUG] = "DBG",
+        [LOG_INFO] = "INF",
+        [LOG_WARNING] = "WRN"
+    };
+
+    assert(LOG_DEBUG == prio || LOG_INFO == prio || LOG_WARNING == prio);
+    assert(strprio[prio]);
+
+    fprintf(stderr, "%s[%s] ", blkback_ident, strprio[prio]);
+    vfprintf(stderr, fmt, ap);
+}
+
+/**
+ * Print tapback's usage instructions.
+ */
+static void
+usage(FILE * const stream, const char * const prog)
+{
+    assert(stream);
+    assert(prog);
+
+    fprintf(stream,
+            "usage: %s\n"
+            "\t[-D|--debug]\n"
+			"\t[-h|--help]\n", prog);
+}
+
+int main(int argc, char **argv)
+{
+    const char *prog = NULL;
+    int opt_debug = 0;
+    int err = 0;
+
+    prog = basename(argv[0]);
+
+    opt_debug = 0;
+
+    do {
+        const struct option longopts[] = {
+            {"help", 0, NULL, 'h'},
+            {"debug", 0, NULL, 'D'},
+        };
+        int c;
+
+        c = getopt_long(argc, argv, "h:D", longopts, NULL);
+        if (c < 0)
+            break;
+
+        switch (c) {
+        case 'h':
+            usage(stdout, prog);
+            return 0;
+        case 'D':
+            opt_debug = 1;
+            break;
+        case '?':
+            goto usage;
+        }
+    } while (1);
+
+    if (opt_debug) {
+        blkback_ident = "";
+        tapback_vlog = blkback_vlog_fprintf;
+    }
+    else {
+        blkback_ident = BLKTAP3_BACKEND_TOKEN;
+        openlog(blkback_ident, 0, LOG_DAEMON);
+    }
+
+    if (!opt_debug) {
+        if ((err = daemon(0, 0))) {
+            err = -errno;
+            goto fail;
+        }
+    }
+
+	if ((err = tapback_backend_create())) {
+        WARN("error creating blkback: %s\n", strerror(err));
+        goto fail;
+    }
+
+    err = tapback_backend_run();
+
+    tapback_backend_destroy();
+
+fail:
+    return err ? -err : 0;
+
+usage:
+    usage(stderr, prog);
+    return 1;
+}
#7 lore raw

PATCH v3 1/1 blktap3/tapback: Introduce tapback daemon Makefile

In reply to Thanos Makatos

This patch introduces the Makefile that builds the tapback daemon. This Makefile is not yet hooked into the build system.

---
Changed since v2:
* Use $(BINDIR) as the daemon's installation directory.
* Fixed whitespace.

tools/blktap3/tapback/Makefile +18 -1
@@ -3,6 +3,10 @@ include $(XEN_ROOT)/tools/Rules.mk
 
 BLKTAP_ROOT := ..
 
+INST_DIR ?= $(BINDIR)
+
+IBIN = tapback
+
 # -D_GNU_SOURCE is required by vasprintf.
 override CFLAGS += \
     -I$(BLKTAP_ROOT)/include \
@@ -25,7 +29,20 @@ override LDFLAGS += \
     $(LDLIBS_libxenstore) \
     $(LDFLAGS_libxenctrl)
 
+TAPBACK-OBJS := log.o xenstore.o frontend.o backend.o
+
+TAPBACK-LIBS := $(BLKTAP_ROOT)/control/libblktapctl.so.1.0.0
+
+all: $(IBIN)
+
+$(IBIN): $(TAPBACK-OBJS) tapback.o
+	$(CC) -o $@ $^ $(TAPBACK-LIBS) $(LDFLAGS)
+
+install: all
+	$(INSTALL_DIR) -p $(DESTDIR)$(INST_DIR)
+	$(INSTALL_PROG) $(IBIN) $(DESTDIR)$(INST_DIR)
+
 clean:
-	rm -f *.o *.o.d .*.o.d
+	rm -f *.o *.o.d .*.o.d $(IBIN)
 
 .PHONY: clean install
#8 lore raw

PATCH v3 1/1 blktap3/tapback: Add the tapback binary to the Mercurial ignore list

In reply to Thanos Makatos
.hgignore +3 -0
@@ -371,4 +371,7 @@
 ^unmodified_drivers/linux-2.6/.*\.ko$
 ^unmodified_drivers/linux-2.6/.*\.mod\.c$
 ^LibVNCServer.*
+
+# blktap3
+^tools/blktap3/tapback/tapback$
 ^tools/blktap3/control/_paths.h$