Thanos Makatos
2013-Mar-06 13:07 UTC
[PATCH 0 of 8 v3] 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. Signed-off-by: Thanos Makatos <thanos.makatos@citrix.com> --- 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.
Thanos Makatos
2013-Mar-06 13:07 UTC
[PATCH 1 of 8 v3] blktap3/tapback: Introduce core defines and structure definitions
Signed-off-by: Thanos Makatos <thanos.makatos@citrix.com> --- 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. diff --git a/tools/blktap3/tapback/tapback.h b/tools/blktap3/tapback/tapback.h new file mode 100644 --- /dev/null +++ b/tools/blktap3/tapback/tapback.h @@ -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__ */
Thanos Makatos
2013-Mar-06 13:07 UTC
[PATCH 2 of 8 v3] blktap3/tapback: Introduces functionality required to access XenStore
This patch introduces convenience functions that read/write values from/to XenStore. Signed-off-by: Thanos Makatos <thanos.makatos@citrix.com> --- 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. diff --git a/tools/blktap3/tapback/Makefile b/tools/blktap3/tapback/Makefile new file mode 100644 --- /dev/null +++ b/tools/blktap3/tapback/Makefile @@ -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 diff --git a/tools/blktap3/tapback/xenstore.c b/tools/blktap3/tapback/xenstore.c new file mode 100644 --- /dev/null +++ b/tools/blktap3/tapback/xenstore.c @@ -0,0 +1,195 @@ +/* + * 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 <stdarg.h> +#include <stdio.h> +#include <xenstore.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include "blktap3.h" +#include "tapback.h" +#include "xenstore.h" + +char * +tapback_xs_vread(struct xs_handle * const xs, xs_transaction_t xst, + const char * const fmt, va_list ap) +{ + char *path, *data = NULL; + unsigned int len = 0; + + assert(xs); + + if (vasprintf(&path, fmt, ap) == -1) + goto fail; + assert(path); + + data = xs_read(xs, xst, path, &len); + free(path); + + if (!data) + return NULL; + + assert(len >= 0); + + /* + * Make sure the returned string is NULL-terminated. + */ + if ((len > 0 && data[len - 1] != ''\0'') || (len == 0 && data[0] != ''\0'')) { + char *_data = strndup(data, len); + if (!_data) + /* TODO log error */ + goto fail; + free(data); + data = _data; + } + + /* + * Make sure the returned string does not containing NULL characters, apart + * from the NULL-terminating one. + * + * We should be checking for extraneous NULLs before duplicating the + * buffer, but this way logic is simplified. + */ + if (strrchr(data, ''\0'') - data != len) + /* TODO log error */ + goto fail; + + return data; +fail: + free(data); + return NULL; +} + +__printf(3, 4) +char * +tapback_xs_read(struct xs_handle * const xs, xs_transaction_t xst, + const char * const fmt, ...) +{ + va_list ap; + char *s; + + assert(xs); + + va_start(ap, fmt); + s = tapback_xs_vread(xs, xst, fmt, ap); + va_end(ap); + + return s; +} + +char * +tapback_device_read(const vbd_t * const device, const char * const path) +{ + assert(device); + assert(path); + + return tapback_xs_read(blktap3_daemon.xs, blktap3_daemon.xst, + "%s/%d/%s/%s", BLKTAP3_BACKEND_PATH, device->domid, device->name, + path); +} + +char * +tapback_device_read_otherend(vbd_t * const device, + const char * const path) +{ + assert(device); + assert(path); + + return tapback_xs_read(blktap3_daemon.xs, blktap3_daemon.xst, "%s/%s", + device->frontend_path, path); +} + +__scanf(3, 4) +int +tapback_device_scanf_otherend(vbd_t * const device, + const char * const path, const char * const fmt, ...) +{ + va_list ap; + int n = 0; + char *s = NULL; + + assert(device); + assert(path); + + if (!(s = tapback_device_read_otherend(device, path))) + return -1; + va_start(ap, fmt); + n = vsscanf(s, fmt, ap); + free(s); + va_end(ap); + + return n; +} + +__printf(4, 5) +int +tapback_device_printf(vbd_t * const device, const char * const key, + const bool mkread, const char * const fmt, ...) +{ + va_list ap; + int err = 0; + char *path = NULL, *val = NULL; + bool nerr = false; + + assert(device); + assert(key); + + if (-1 == asprintf(&path, "%s/%d/%s/%s", BLKTAP3_BACKEND_PATH, + device->domid, device->name, key)) { + err = -errno; + goto fail; + } + + va_start(ap, fmt); + if (-1 == vasprintf(&val, fmt, ap)) + val = NULL; + va_end(ap); + + if (!val) { + err = -errno; + goto fail; + } + + if (!(nerr = xs_write(blktap3_daemon.xs, blktap3_daemon.xst, path, val, + strlen(val)))) { + err = -errno; + goto fail; + } + + if (mkread) { + struct xs_permissions perms = { + device->domid, + XS_PERM_READ + }; + + if (!(nerr = xs_set_permissions(blktap3_daemon.xs, blktap3_daemon.xst, + path, &perms, 1))) { + err = -errno; + goto fail; + } + } + +fail: + free(path); + free(val); + + return err; +} diff --git a/tools/blktap3/tapback/xenstore.h b/tools/blktap3/tapback/xenstore.h new file mode 100644 --- /dev/null +++ b/tools/blktap3/tapback/xenstore.h @@ -0,0 +1,95 @@ +/* + * 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. + */ + +/** + * Retrieves the XenStore value of the specified key of the VBD''s front-end. + * The caller must free the returned buffer. + * + * @param device the VBD + * @param path key under the front-end directory + * @returns a buffer containing the value, or NULL on error + */ +char * +tapback_device_read_otherend(vbd_t * const device, + const char * const path); + +/** + * Writes to XenStore backened/tapback/<domid>/<devname>/@key = @fmt. + * + * @param device the VBD + * @param key the key to write to + * @param mkread TODO + * @param fmt format + * @returns 0 on success, an error code otherwise + */ +__printf(4, 5) +int +tapback_device_printf(vbd_t * const device, const char * const key, + const bool mkread, const char * const fmt, ...); + +/** + * Reads the specified XenStore path under the front-end directory in a + * scanf-like manner. + * + * @param device the VBD + * @param path XenStore path to read + * @param fmt format + */ +__scanf(3, 4) +int +tapback_device_scanf_otherend(vbd_t * const device, + const char * const path, const char * const fmt, ...); + +/** + * Retrieves the value of the specified of the device from XenStore, + * i.e. backend/tapback/<domid>/<devname>/@path + * The caller must free the returned buffer. + * + * @param device the VBD + * @param path the XenStore key + * @returns a buffer containing the value, or NULL on error + */ +char * +tapback_device_read(const vbd_t * const device, const char * const path); + +/** + * Reads the specified XenStore path. The caller must free the returned buffer. + * + * @param xs handle to XenStore + * @param xst XenStore transaction + * @param fmt format + * @param ap arguments + * @returns a buffer containing the value, or NULL on error + */ +char * +tapback_xs_vread(struct xs_handle * const xs, xs_transaction_t xst, + const char * const fmt, va_list ap); + +/** + * Reads the specified XenStore path. The caller must free the returned buffer. + * + * @param xs handle to XenStore + * @param xst XenStore transaction + * @param fmt format + * @returns a buffer containing the value, or NULL on error + */ +__printf(3, 4) +char * +tapback_xs_read(struct xs_handle * const xs, xs_transaction_t xst, + const char * const fmt, ...);
Thanos Makatos
2013-Mar-06 13:07 UTC
[PATCH 3 of 8 v3] blktap3/tapback: Logging for the tapback daemon and libxenio
Signed-off-by: Thanos Makatos <thanos.makatos@citrix.com> --- Changed since v2: * minor whitespace clean up diff --git a/tools/blktap3/tapback/log.c b/tools/blktap3/tapback/log.c new file mode 100644 --- /dev/null +++ b/tools/blktap3/tapback/log.c @@ -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); +}
Thanos Makatos
2013-Mar-06 13:07 UTC
[PATCH 4 of 8 v3] blktap3/tapback: Introduce back-end XenStore path handler
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. Signed-off-by: Thanos Makatos <thanos.makatos@citrix.com> --- 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. diff --git a/tools/blktap3/tapback/backend.c b/tools/blktap3/tapback/backend.c new file mode 100644 --- /dev/null +++ b/tools/blktap3/tapback/backend.c @@ -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); +}
Thanos Makatos
2013-Mar-06 13:07 UTC
[PATCH 5 of 8 v3] blktap3/tapback: Introduce front-end XenStore path handler
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. Signed-off-by: Thanos Makatos <thanos.makatos@citrix.com> --- 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. diff --git a/tools/blktap3/include/blktap3.h b/tools/blktap3/include/blktap3.h --- a/tools/blktap3/include/blktap3.h +++ b/tools/blktap3/include/blktap3.h @@ -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__ */ diff --git a/tools/blktap3/tapback/frontend.c b/tools/blktap3/tapback/frontend.c new file mode 100644 --- /dev/null +++ b/tools/blktap3/tapback/frontend.c @@ -0,0 +1,418 @@ +/* + * 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 front-end XenStore path of + * a VBD gets modified. + */ + +#include "tapback.h" +#include "xenstore.h" +#include "tap-ctl-xen.h" + +#include <xen/io/protocols.h> + +/** + * Switches the back-end state of the device by writing to XenStore. + * + * @param device the VBD + * @param state the state to switch to + * @returns 0 on success, an error code otherwise + */ +static int +tapback_device_switch_state(vbd_t * const device, + const XenbusState state) +{ + int err; + + assert(device); + + /* + * TODO Ensure @state contains a legitimate XenbusState value. + * TODO Check for valid state transitions? + */ + + err = tapback_device_printf(device, "state", false, "%u", state); + if (err) { + WARN("failed to switch back-end state to %s: %s\n", + XenbusState2str(state), strerror(err)); + } else + DBG("switched back-end state to %s\n", XenbusState2str(state)); + return err; +} + +/** + * Core functions that instructs the tapdisk to connect to the shared ring (if + * not already connected) and communicates essential information to the + * front-end. + * + * If the tapdisk is not already connected, all the necessary information is + * read from XenStore and the tapdisk gets connected using this information. + * + * TODO Why should this function be called on an already connected VBD? Why + * re-write the sector size etc. in XenStore for an already connected VBD? + * TODO rename function (no blkback, not only connects to tapdisk) + * + * @param xbdev the VBD the tapdisk should connect to + * @param state unused + * @returns 0 on success, an error code otherwise + * + * XXX Only called by blkback_frontend_changed, when the front-end switches to + * Initialised and Connected. + */ +static int +blkback_connect_tap(vbd_t * const bdev, + const XenbusState state __attribute__((unused))) +{ + evtchn_port_t port = 0; + grant_ref_t *gref = NULL; + int err = 0; + char *proto_str = NULL; + char *persistent_grants_str = NULL; + + assert(bdev); + + if (bdev->connected) { + DBG("front-end already connected to tapdisk.\n"); + } else { + /* + * TODO How can we make sure we''re not missing a node written by the + * front-end? Use xs_directory? + */ + int nr_pages = 0, proto = 0, order = 0; + bool persistent_grants = false; + + if (1 != tapback_device_scanf_otherend(bdev, "ring-page-order", "%d", + &order)) + order = 0; + + nr_pages = 1 << order; + + if (!(gref = calloc(nr_pages, sizeof(grant_ref_t)))) { + WARN("Failed to allocate memory for grant refs.\n"); + err = ENOMEM; + goto fail; + } + + /* + * Read the grant references. + */ + if (order) { + int i = 0; + /* + * +10 is for INT_MAX, +1 for NULL termination + */ + static const size_t len = sizeof(RING_REF) + 10 + 1; + char ring_ref[len]; + for (i = 0; i < nr_pages; i++) { + if (snprintf(ring_ref, len, "%s%d", RING_REF, i) >= len) { + DBG("error printing to buffer\n"); + err = EINVAL; + goto fail; + } + if (1 != tapback_device_scanf_otherend(bdev, ring_ref, "%u", + &gref[i])) { + WARN("Failed to read grant ref 0x%x.\n", i); + err = ENOENT; + goto fail; + } + } + } else { + if (1 != tapback_device_scanf_otherend(bdev, RING_REF, "%u", + &gref[0])) { + WARN("Failed to read grant ref.\n"); + err = ENOENT; + goto fail; + } + } + + /* + * Read the event channel. + */ + if (1 != tapback_device_scanf_otherend(bdev, "event-channel", "%u", + &port)) { + WARN("Failed to read event channel.\n"); + err = ENOENT; + goto fail; + } + + /* + * Read the guest VM''s ABI. + */ + if (!(proto_str = tapback_device_read_otherend(bdev, "protocol"))) + proto = BLKIF_PROTOCOL_NATIVE; + else if (!strcmp(proto_str, XEN_IO_PROTO_ABI_X86_32)) + proto = BLKIF_PROTOCOL_X86_32; + else if (!strcmp(proto_str, XEN_IO_PROTO_ABI_X86_64)) + proto = BLKIF_PROTOCOL_X86_64; + else { + WARN("unsupported protocol %s\n", proto_str); + err = EINVAL; + goto fail; + } + + /* + * Does the front-end support persistent grants? + */ + persistent_grants_str = tapback_device_read_otherend(bdev, + FEAT_PERSIST); + if (persistent_grants_str) { + if (!strcmp(persistent_grants_str, "0")) + persistent_grants = false; + else if (!strcmp(persistent_grants_str, "1")) + persistent_grants = true; + else { + WARN("invalid %s value: %s\n", FEAT_PERSIST, + persistent_grants_str); + err = EINVAL; + goto fail; + } + } + else + DBG("front-end doesn''t support persistent grants\n"); + + /* + * persistent grants are not yet supported + */ + if (persistent_grants) + WARN("front-end supports persistent grants but we don''t\n"); + + /* + * Create the shared ring and ask the tapdisk to connect to it. + */ + if ((err = tap_ctl_connect_xenblkif(bdev->tap.pid, bdev->tap.minor, + bdev->domid, bdev->devid, gref, order, port, proto, + NULL))) { + WARN("tapdisk failed to connect to the shared ring: %s\n", + strerror(err)); + goto fail; + } + DBG("tapdisk %d/%d connected to shared ring\n", bdev->tap.pid, + bdev->tap.minor); + + bdev->connected = true; + } + + /* + * Write the number of sectors, sector size, and info to the back-end path + * in XenStore so that the front-end creates a VBD with the appropriate + * characteristics. + */ + if ((err = tapback_device_printf(bdev, "sector-size", true, "%u", + bdev->sector_size))) { + WARN("Failed to write sector-size.\n"); + goto fail; + } + + if ((err = tapback_device_printf(bdev, "sectors", true, "%llu", + bdev->sectors))) { + WARN("Failed to write sectors.\n"); + goto fail; + } + + if ((err = tapback_device_printf(bdev, "info", true, "%u", bdev->info))) { + WARN("Failed to write info.\n"); + goto fail; + } + + if ((err = tapback_device_switch_state(bdev, XenbusStateConnected))) { + WARN("failed to switch back-end state to connected: %s\n", + strerror(err)); + } + +fail: + if (err && bdev->connected) { + const int err2 = tap_ctl_disconnect_xenblkif(bdev->tap.pid, + bdev->tap.minor, bdev->domid, bdev->devid, NULL); + if (err2) { + WARN("error disconnecting tapdisk from the shared ring (error " + "ignored): %s\n", strerror(err2)); + } + + bdev->connected = false; + } + + free(gref); + free(proto_str); + free(persistent_grants_str); + + return err; +} + +/** + * Callback that is executed when the front-end goes to StateClosed. + * + * Instructs the tapdisk to disconnect itself from the shared ring and switches + * the back-end state to StateClosed. + * + * @param xbdev the VBD whose tapdisk should be disconnected + * @param state unused + * @returns 0 on success, an error code otherwise + * + * XXX Only called by blkback_frontend_changed. + */ +static inline int +backend_close(vbd_t * const bdev, + const XenbusState state __attribute__((unused))) +{ + int err = 0; + + assert(bdev); + + if (!bdev->connected) { + /* + * TODO Is this safe? Shouldn''t we report an error? + */ + DBG("tapdisk not connected\n"); + return 0; + } + + DBG("disconnecting vbd-%d-%d from tapdisk %d minor %d\n", + bdev->domid, bdev->devid, bdev->tap.pid, bdev->tap.minor); + + if ((err = tap_ctl_disconnect_xenblkif(bdev->tap.pid, bdev->tap.minor, + bdev->domid, bdev->devid, NULL))){ + + /* + * TODO I don''t see how tap_ctl_disconnect_xenblkif can return + * ESRCH, so this is probably wrong. Probably there''s another error + * code indicating that there''s no tapdisk process. + */ + if (errno == -ESRCH) { + WARN("tapdisk not running\n"); + } else { + WARN("error disconnecting tapdisk from front-end: %s\n", + strerror(err)); + return err; + } + } + + bdev->connected = false; + + return tapback_device_switch_state(bdev, XenbusStateClosed); +} + +/** + * Acts on changes in the front-end state. + * + * TODO The back-end blindly follows the front-ends state transitions, should + * we check whether unexpected transitions are performed? + * + * @param xbdev the VBD whose front-end state changed + * @param state the new state + * @returns 0 on success, an error code otherwise + * + * XXX Only called by tapback_device_check_front-end_state. + */ +static inline int +blkback_frontend_changed(vbd_t * const xbdev, const XenbusState state) +{ + /* + * XXX The size of the array (9) comes from the XenbusState enum. + * + * TODO Send a patch that adds XenbusStateMin, XenbusStateMax, + * XenbusStateInvalid and in the XenbusState enum (located in xenbus.h). + * + * The front-end''s state is used as the array index. Each element contains + * a call-back function to be executed in response, and an optional state + * for the back-end to switch to. + */ + struct frontend_state_change { + int (*fn)(vbd_t * const, const XenbusState); + const XenbusState state; + } static const frontend_state_change_map[] = { + [XenbusStateUnknown] = {NULL, 0}, + [XenbusStateInitialising] + = {tapback_device_switch_state, XenbusStateInitWait}, + [XenbusStateInitWait] = {NULL, 0}, + + /* blkback_connect_tap swicthes back-end state to Connected */ + [XenbusStateInitialised] = {blkback_connect_tap, 0}, + [XenbusStateConnected] = {blkback_connect_tap, 0}, + + [XenbusStateClosing] + = {tapback_device_switch_state, XenbusStateClosing}, + [XenbusStateClosed] = {backend_close, 0}, + [XenbusStateReconfiguring] = {NULL, 0}, + [XenbusStateReconfigured] = {NULL, 0} + }; + + assert(xbdev); + assert(state >= XenbusStateUnknown && state <= XenbusStateReconfigured); + + DBG("front-end %d-%s went into state %s\n", + xbdev->domid, xbdev->name, XenbusState2str(state)); + + if (frontend_state_change_map[state].fn) + return frontend_state_change_map[state].fn(xbdev, + frontend_state_change_map[state].state); + else + DBG("ignoring front-end''s %d-%s transition to state %s\n", + xbdev->domid, xbdev->name, XenbusState2str(state)); + return 0; +} + +int +tapback_backend_handle_otherend_watch(const char * const path) +{ + vbd_t *device = NULL; + int err = 0, state = 0; + char *s = NULL, *end = NULL; + + assert(path); + + /* + * Find the device that has the same front-end state path. + * + * There should definitely be such a device in our list, otherwise this + * function would not have executed at all, since we would not be waiting + * on that XenStore path. The XenStore path we wait for is: + * /local/domain/<domid>/device/vbd/<devname>/state. In order to watch this + * path, it means that we have received a device create request, so the + * device will be there. + * + * TODO Instead of this linear search we could do better (hash table etc). + */ + tapback_backend_find_device(device, + !strcmp(device->frontend_state_path, path)); + if (!device) { + WARN("path \''%s\'' does not correspond to a known device\n", path); + return ENODEV; + } + + DBG("device: domid=%d name=%s\n", device->domid, device->name); + + /* + * Read the new front-end''s state. + */ + if (!(s = tapback_xs_read(blktap3_daemon.xs, blktap3_daemon.xst, "%s", + device->frontend_state_path))) { + err = -errno; + goto fail; + } + state = strtol(s, &end, 0); + if (*end != 0 || end == s) { + err = -EINVAL; + goto fail; + } + + err = blkback_frontend_changed(device, state); + +fail: + free(s); + return err; +}
Thanos Makatos
2013-Mar-06 13:07 UTC
[PATCH 6 of 8 v3] blktap3/tapback: Introduce the tapback daemon
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. Signed-off-by: Thanos Makatos <thanos.makatos@citrix.com> --- 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. diff --git a/tools/blktap3/tapback/tapback.c b/tools/blktap3/tapback/tapback.c new file mode 100644 --- /dev/null +++ b/tools/blktap3/tapback/tapback.c @@ -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; +}
Thanos Makatos
2013-Mar-06 13:07 UTC
[PATCH 7 of 8 v3] blktap3/tapback: Introduce tapback daemon Makefile
This patch introduces the Makefile that builds the tapback daemon. This Makefile is not yet hooked into the build system. Signed-off-by: Thanos Makatos <thanos.makatos@citrix.com> --- Changed since v2: * Use $(BINDIR) as the daemon''s installation directory. * Fixed whitespace. diff --git a/tools/blktap3/tapback/Makefile b/tools/blktap3/tapback/Makefile --- a/tools/blktap3/tapback/Makefile +++ b/tools/blktap3/tapback/Makefile @@ -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
Thanos Makatos
2013-Mar-06 13:07 UTC
[PATCH 8 of 8 v3] blktap3/tapback: Add the tapback binary to the Mercurial ignore list
Signed-off-by: Thanos Makatos <thanos.makatos@citrix.com> diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -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$