[PATCH 2/3] discover: Support creation of device-mapper devices
Cyril Bur
cyril.bur at au1.ibm.com
Fri May 15 16:38:19 AEST 2015
On Wed, 2015-05-13 at 17:14 +1000, Samuel Mendoza-Jonas wrote:
> Add discover/dm-snapshot that allows the creation of device-mapper
> snapshots that support merging changes back to disk.
>
> Device-mapper snapshots are a CoW device backed by a ramdisk, mirroring
> the contents of a source device. No changes are made to the original
> disk unless an explicit merge action is performed. This guarantees
> read-only mounting of host disks even when writes could implicitly
> occur, eg. when performing recovering a journaled filesystem.
>
> In the event that writing back to the disk is desired, such as when
> updating grubenv, the changes made to the snapshot can be merged back to
> the source disk.
>
> This patch adds support but does not change functionality.
Hey,
As discussed, reminder to not use dm_ for 'your' functions which call
into library functions.
Apart from that and a few comments.
Reviewed-by: Cyril Bur <cyril.bur at au1.ibm.com>
>
> Signed-off-by: Samuel Mendoza-Jonas <sam.mj at au1.ibm.com>
> ---
> discover/Makefile.am | 6 +
> discover/dm-snapshot.c | 569 +++++++++++++++++++++++++++++++++++++++++++++++++
> discover/dm-snapshot.h | 11 +
> 3 files changed, 586 insertions(+)
> create mode 100644 discover/dm-snapshot.c
> create mode 100644 discover/dm-snapshot.h
>
> diff --git a/discover/Makefile.am b/discover/Makefile.am
> index 7808110..a642c33 100644
> --- a/discover/Makefile.am
> +++ b/discover/Makefile.am
> @@ -24,6 +24,8 @@ discover_pb_discover_SOURCES = \
> discover/device-handler.h \
> discover/discover-server.c \
> discover/discover-server.h \
> + discover/dm-snapshot.c \
> + discover/dm-snapshot.h \
> discover/event.c \
> discover/event.h \
> discover/params.c \
> @@ -58,6 +60,10 @@ discover_pb_discover_LDADD = \
> $(core_lib) \
> $(UDEV_LIBS)
>
> +discover_pb_discover_LDFLAGS = \
> + $(AM_LDFLAGS) \
> + -ldevmapper
> +
> discover_pb_discover_CPPFLAGS = \
> $(AM_CPPFLAGS) \
> -DLOCAL_STATE_DIR='"$(localstatedir)"' \
> diff --git a/discover/dm-snapshot.c b/discover/dm-snapshot.c
> new file mode 100644
> index 0000000..4a752cd
> --- /dev/null
> +++ b/discover/dm-snapshot.c
> @@ -0,0 +1,569 @@
Copyright?
> +#include <talloc/talloc.h>
> +#include <types/types.h>
> +#include <log/log.h>
> +#include <errno.h>
> +#include <string.h>
> +
> +#include "libdevmapper.h"
> +#include "dm-snapshot.h"
> +
> +struct dm_target {
> + unsigned int start_sector;
> + unsigned int end_sector;
> + char *ttype;
> + char *params;
> +};
> +
> +/* Return the number of sectors on a block device. Zero represents an error */
> +static unsigned int get_block_sectors(struct discover_device *device)
> +{
> + const char *tmp;
> + unsigned int sectors;
> +
> + tmp = discover_device_get_param(device, "ID_PART_ENTRY_SIZE");
> + if (!tmp) {
> + pb_debug("Could not retrieve ID_PART_ENTRY_SIZE for %s\n",
> + device->device_path);
> + return 0;
> + }
> +
> + errno = 0;
> + sectors = strtoul(tmp, NULL, 0);
> + if (errno) {
> + pb_debug("Error reading sector count for %s: %s\n",
> + device->device_path, strerror(errno));
> + sectors = 0;
> + }
> +
> + return sectors;
> +}
> +
> +/* Petitboot's libdm isn't compiled with --enable-udev_sync, so we set
> + * empty cookie and flags unconditionally */
> +static inline int set_cookie(struct dm_task *task, uint32_t *cookie)
> +{
> + *cookie = 0;
> + return dm_task_set_cookie(task, cookie, 0);
> +}
> +
> +static char *dm_device_status(void *ctx, const char *dm_name)
> +{
> + char *params, *metadata = NULL, *target_type = NULL;
> + uint64_t start, length;
> + struct dm_task *task;
> + struct dm_info info;
> + void *target = NULL;
> +
> + task = dm_task_create(DM_DEVICE_STATUS);
> + if (!task) {
> + pb_log("%s: Error creating task\n", __func__);
> + return NULL;
> + }
> +
> + if (!dm_task_set_name(task, dm_name)) {
> + pb_log("No dm-device named '%s'\n", dm_name);
> + goto out;
> + }
> +
> + if (!dm_task_run(task)) {
> + pb_log("Unable to remove device '%s'\n", dm_name);
> + goto out;
> + }
> +
> + if (!dm_task_get_info(task, &info) || !info.exists) {
> + pb_log("Error retrieving info%s",
> + info.exists ? "\n" : ": info does not exist\n");
> + goto out;
> + }
> +
> + target = dm_get_next_target(task, target, &start, &length,
> + &target_type, ¶ms);
> +
Also as discussed, don't need target at all.
> + metadata = talloc_asprintf(ctx, "%" PRIu64 " %" PRIu64 " %s %s\n",
> + start, length, target_type, params);
> +out:
> + dm_task_destroy(task);
> + return metadata;
> +}
> +
> +static bool dm_merge_complete(void *ctx, const char *dm_name)
> +{
> + long long unsigned int sectors, meta_sectors;
> + char *metadata;
> +
> + metadata = dm_device_status(ctx, dm_name);
> + if (!metadata) {
> + /* Return true so that callers move on */
> + return true;
> + }
> +
> + /* Merge is complete when metadata sectors are the only sectors
> + * allocated - see Documentation/device-mapper/snapshot.txt */
> + sscanf(metadata, "%*u %*u %*s %llu/%*u %llu",
> + §ors, &meta_sectors);
> +
> + talloc_free(metadata);
> + return sectors == meta_sectors;
> +}
> +
> +/* Resume or suspend dm device */
> +static int dm_set_device_active(const char *dm_name, bool active)
> +{
> + struct dm_task *task;
> + uint32_t cookie = 0;
> + int rc = -1;
> +
> + if (active)
> + task = dm_task_create(DM_DEVICE_RESUME);
> + else
> + task = dm_task_create(DM_DEVICE_SUSPEND);
> +
> + if (!task) {
> + pb_log("%s: Could not create dm_task\n", __func__);
> + return rc;
> + }
> +
> + if (!dm_task_set_name(task, dm_name)) {
> + pb_log("No dm-device named '%s'\n", dm_name);
> + goto out;
> + }
> +
> + if (!set_cookie(task, &cookie))
> + goto out;
> +
> + if (!dm_task_run(task)) {
> + pb_log("Unable to %s device '%s'\n",
> + active ? "resume" : "suspend", dm_name);
> + goto out;
> + }
> +
> + rc = 0;
> +
> + /* Wait for /dev/mapper/ entries to be updated */
> + dm_udev_wait(cookie);
> +
> +out:
> + dm_task_destroy(task);
> + return rc;
> +}
> +
> +/* Run a DM_DEVICE_CREATE task with provided table (ttype and params) and pass
> + * back the result */
> +static int dm_run_create_task(struct dm_task *task, const struct dm_target *target)
> +{
> + uint32_t cookie = 0;
> + int rc;
> +
> + pb_debug("%s: %u %u '%s' '%s'\n", __func__,
> + target->start_sector, target->end_sector,
> + target->ttype, target->params);
> +
> + if (!(rc = dm_task_add_target(task,
> + target->start_sector, target->end_sector,
> + target->ttype, target->params)))
> + return rc;
> +
Personally I'd rather the rc = func(...) before the if statement, but as
developer/maintainer, your call :).
> + if (!(rc = dm_task_set_add_node(task, DM_ADD_NODE_ON_CREATE)))
> + return rc;
> +
> + if (!(rc = set_cookie(task, &cookie)))
> + return rc;
> +
> + if (!(rc = dm_task_run(task))) {
> + pb_log("Error executing dm-task\n");
> + return rc;
> + }
> +
> + /* Wait for /dev/mapper/ entry to appear */
> + dm_udev_wait(cookie);
> +
> + return rc;
> +}
> +
> +static int dm_create_base(struct discover_device *device)
> +{
> + struct dm_target target;
> + struct dm_task *task;
> + char *name = NULL;
> + int rc = -1;
> +
> + if (!device->ramdisk)
> + return rc;
> +
> + target.start_sector = 0;
> + target.end_sector = device->ramdisk->sectors;
> +
> + target.ttype = talloc_asprintf(device, "linear");
> + target.params = talloc_asprintf(device, "%s 0",
> + device->device_path);
> + if (!target.ttype || !target.params) {
> + pb_log("Failed to allocate map parameters\n");
> + goto err1;
> + }
> +
> + task = dm_task_create(DM_DEVICE_CREATE);
> + if (!task) {
> + pb_log("Error creating new dm-task\n");
> + goto err1;
> + }
> +
> + name = talloc_asprintf(device, "%s-base", device->device->id);
Check allocation?
> + if (!dm_task_set_name(task, name))
> + goto err2;
> +
> + if (!dm_run_create_task(task, &target))
> + goto err2;
> +
> + device->ramdisk->base = talloc_asprintf(device, "/dev/mapper/%s-base",
> + device->device->id);
> + if (!device->ramdisk->base) {
> + pb_log("Failed to track new device /dev/mapper/%s-base\n",
> + device->device->id);
> + goto err2;
> + }
> +
> + rc = 0;
> +
> +err2:
> + dm_task_destroy(task);
> +err1:
> + talloc_free(name);
> + talloc_free(target.params);
> + talloc_free(target.ttype);
> + return rc;
> +}
> +
> +static int dm_create_origin(struct discover_device *device)
> +{
> + struct dm_target target;
> + struct dm_task *task;
> + char *name = NULL;
> + int rc = -1;
> +
> + if (!device->ramdisk || !device->ramdisk->base)
> + return -1;
> +
> + target.start_sector = 0;
> + target.end_sector = device->ramdisk->sectors;
> +
> + target.ttype = talloc_asprintf(device, "snapshot-origin");
> + target.params = talloc_asprintf(device, "%s", device->ramdisk->base);
> + if (!target.ttype || !target.params) {
> + pb_log("Failed to allocate map parameters\n");
> + goto err1;
> + }
> +
> + task = dm_task_create(DM_DEVICE_CREATE);
> + if (!task) {
> + pb_log("Error creating new dm-task\n");
> + goto err1;
> + }
> +
> + name = talloc_asprintf(device, "%s-origin", device->device->id);
Check allocation?
> + if (!dm_task_set_name(task, name))
> + goto err2;
> +
> + if (!dm_run_create_task(task, &target))
> + goto err2;
> +
> + device->ramdisk->origin = talloc_asprintf(device,
> + "/dev/mapper/%s-origin",
> + device->device->id);
> + if (!device->ramdisk->origin) {
> + pb_log("Failed to track new device /dev/mapper/%s-origin\n",
> + device->device->id);
> + goto err2;
> + }
> +
> + rc = 0;
> +
> +err2:
> + dm_task_destroy(task);
> +err1:
> + talloc_free(name);
> + talloc_free(target.params);
> + talloc_free(target.ttype);
> + return rc;
> +}
> +
> +static int dm_create_snapshot(struct discover_device *device)
> +{
> + struct dm_target target;
> + struct dm_task *task;
> + int rc = -1;
> +
> + target.start_sector = 0;
> + target.end_sector = device->ramdisk->sectors;
> +
> + pb_debug("Creating a snapshot for %s, named %s\n",
> + device->device_path, device->device->id);
> +
> + target.ttype = talloc_asprintf(device, "snapshot");
> + target.params = talloc_asprintf(device, "%s %s P 8",
> + device->ramdisk->base, device->ramdisk->path);
> + if (!target.ttype || !target.params) {
> + pb_log("Failed to allocate snapshot parameters\n");
> + goto err1;
> + }
> +
> + task = dm_task_create(DM_DEVICE_CREATE);
> + if (!task) {
> + pb_log("Error creating new dm-task\n");
> + goto err1;
> + }
> +
> + if (!dm_task_set_name(task, device->device->id))
> + goto err2;
> +
> + if (!dm_run_create_task(task, &target)) {
> + pb_log("Failed to create snapshot\n");
> + goto err2;
> + }
> +
> + device->ramdisk->snapshot = talloc_asprintf(device, "/dev/mapper/%s",
> + device->device->id);
> + if (!device->ramdisk->snapshot) {
> + pb_log("Failed to track new device /dev/mapper/%s\n",
> + device->device->id);
> + goto err2;
> + }
> +
> + rc = 0;
> +
> +err2:
> + dm_task_destroy(task);
> +err1:
> + talloc_free(target.params);
> + talloc_free(target.ttype);
> + return rc;
> +}
> +
> +int dm_init_snapshot(struct device_handler *handler,
> + struct discover_device *device)
> +{
> + struct ramdisk_device *ramdisk;
> +
> + ramdisk = device_handler_get_ramdisk(handler);
> + if (!ramdisk) {
> + pb_log("No ramdisk available for snapshot %s\n",
> + device->device->id);
> + return -1;
> + }
> +
> + ramdisk->sectors = get_block_sectors(device);
> + if (!ramdisk->sectors) {
> + pb_log("Error retreiving sectors for %s\n",
> + device->device->id);
> + return -1;
> + }
> +
> + device->ramdisk = ramdisk;
> +
> + /* Create linear map */
> + if (dm_create_base(device)) {
> + pb_log("Error creating linear base\n");
> + goto err;
> + }
> +
> + /* Create snapshot-origin */
> + if (dm_create_origin(device)) {
> + pb_log("Error creating snapshot-origin\n");
> + goto err;
> + }
> +
> + if (dm_set_device_active(device->ramdisk->origin, false)) {
> + pb_log("Failed to suspend origin\n");
> + }
> +
> + /* Create snapshot */
> + if (dm_create_snapshot(device)) {
> + pb_log("Error creating snapshot\n");
> + goto err;
> + }
> +
> + if (dm_set_device_active(device->ramdisk->origin, true)) {
> + pb_log("Failed to resume origin\n");
> + goto err;
> + }
> +
> + pb_log("Snapshot successfully created for %s\n", device->device->id);
> +
> + return 0;
> +
> +err:
> + pb_log("Error creating snapshot devices for %s\n", device->device->id);
> + dm_destroy_snapshot(device);
> + device_handler_release_ramdisk(device);
> + return -1;
> +}
> +
> +/* Destroy specific dm device */
> +static int dm_destroy_device(const char *dm_name)
> +{
> + struct dm_task *task;
> + uint32_t cookie = 0;
> + int rc = -1;
> +
> + task = dm_task_create(DM_DEVICE_REMOVE);
> + if (!task) {
> + pb_log("%s: could not create dm_task\n", __func__);
> + return -1;
> + }
> +
> + if (!dm_task_set_name(task, dm_name)) {
> + pb_log("No dm device named '%s'\n", dm_name);
> + goto out;
> + }
> +
> + if (!set_cookie(task, &cookie))
> + goto out;
> +
> + if (!dm_task_run(task)) {
> + pb_log("Unable to remove device '%s'\n", dm_name);
> + goto out;
> + }
> +
> + rc = 0;
> +
> + /* Wait for /dev/mapper/ entries to be removed */
> + dm_udev_wait(cookie);
> +
> +out:
> + dm_task_destroy(task);
> + return rc;
> +}
> +
> +/* Destroy all dm devices related to a discover_device's snapshot */
> +int dm_destroy_snapshot(struct discover_device *device)
> +{
> + int rc = -1;
> +
> + if (!device->ramdisk)
> + return 0;
> +
> + if (device->mounted) {
> + pb_log("Can not remove snapshot: %s is mounted\n",
> + device->device->id);
> + return -1;
> + }
> +
> + /* Clean up dm devices in order */
> + if (device->ramdisk->snapshot)
> + if (dm_destroy_device(device->ramdisk->snapshot))
> + goto out;
> +
> + if (device->ramdisk->origin)
> + if (dm_destroy_device(device->ramdisk->origin))
> + goto out;
> +
> + if (device->ramdisk->base)
> + if (dm_destroy_device(device->ramdisk->base))
> + goto out;
> +
> + device_handler_release_ramdisk(device);
> + rc = 0;
> +out:
> + return rc;
> +}
> +
> +static int dm_reload_snapshot(struct discover_device *device, bool merge)
> +{
> + struct dm_target target;
> + struct dm_task *task;
> + int rc = -1;
> +
> + target.start_sector = 0;
> + target.end_sector = device->ramdisk->sectors;
> +
> + if (merge) {
> + target.ttype = talloc_asprintf(device, "snapshot-merge");
> + target.params = talloc_asprintf(device, "%s %s P 8",
> + device->ramdisk->base, device->ramdisk->path);
> + } else {
> + target.ttype = talloc_asprintf(device, "snapshot-origin");
> + target.params = talloc_asprintf(device, "%s",
> + device->ramdisk->base);
> + }
> + if (!target.ttype || !target.params) {
> + pb_log("%s: failed to allocate parameters\n", __func__);
> + return rc;
> + }
> +
> + task = dm_task_create(DM_DEVICE_RELOAD);
> + if (!task) {
> + pb_log("%s: Error creating task\n", __func__);
> + return rc;
> + }
> +
> + if (!dm_task_set_name(task, device->ramdisk->origin)) {
> + pb_log("No dm-device named '%s'\n", device->ramdisk->origin);
> + goto err;
> + }
> +
> + if (!(rc = dm_task_add_target(task,
> + target.start_sector, target.end_sector,
> + target.ttype, target.params))) {
> + pb_log("%s: Failed to set target\n", __func__);
> + goto err;
> + }
> +
> + if (!dm_task_run(task)) {
> + pb_log("Failed to reload %s\n", device->ramdisk->origin);
> + goto err;
> + }
> +
> + rc = 0;
> +err:
> + talloc_free(target.ttype);
> + talloc_free(target.params);
> + dm_task_destroy(task);
> + return rc;
> +}
> +
> +int dm_merge_snapshot(struct discover_device *device)
> +{
> + if (device->mounted) {
> + pb_log("%s: %s still mounted\n", __func__, device->device->id);
> + return -1;
> + }
> +
> + /* Suspend origin device */
> + if (dm_set_device_active(device->ramdisk->origin, false)) {
> + pb_log("%s: failed to suspend %s\n",
> + __func__, device->ramdisk->origin);
> + return -1;
> + }
> +
> + /* Destroy snapshot */
> + if (dm_destroy_device(device->ramdisk->snapshot)) {
> + /* The state of the snapshot is unknown, but try to
> + * resume to allow the snapshot to be remounted */
> + dm_set_device_active(device->ramdisk->origin, true);
> + return -1;
> + }
> + talloc_free(device->ramdisk->snapshot);
> + device->ramdisk->snapshot = NULL;
> +
> + /* Reload origin device for merging */
> + dm_reload_snapshot(device, true);
> +
> + /* Resume origin device */
> + dm_set_device_active(device->ramdisk->origin, true);
> +
> + /* Block until merge complete */
> + while (!dm_merge_complete(device, device->ramdisk->origin));
Perhaps not busy loop quite so much, sleep a bit if you can detect that
there's some writing to do.
> +
> + /* Suspend origin device */
> + dm_set_device_active(device->ramdisk->origin, false);
> +
> + /* Reload origin device */
> + dm_reload_snapshot(device, false);
> +
> + /* Re-create snapshot */
> + if (dm_create_snapshot(device))
> + return -1;
> +
> + /* Resume origin device */
> + return dm_set_device_active(device->ramdisk->origin, true);
> +
> +}
> diff --git a/discover/dm-snapshot.h b/discover/dm-snapshot.h
> new file mode 100644
> index 0000000..37b616a
> --- /dev/null
> +++ b/discover/dm-snapshot.h
> @@ -0,0 +1,11 @@
Copyright?
> +#ifndef _DM_SNAPSHOT_H
> +#define _DM_SNAPSHOT_H
> +
> +#include "device-handler.h"
> +
> +int dm_init_snapshot(struct device_handler *handler,
> + struct discover_device *device);
> +int dm_destroy_snapshot(struct discover_device *device);
> +int dm_merge_snapshot(struct discover_device *device);
> +
> +#endif /* _DM_SNAPSHOT_H */
More information about the Petitboot
mailing list