[PATCH 2/3] discover: Support creation of device-mapper devices

Samuel Mendoza-Jonas sam.mj at au1.ibm.com
Wed May 13 17:14:06 AEST 2015


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.

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 @@
+#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, &params);
+
+	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",
+	       &sectors, &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;
+
+	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);
+	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);
+	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));
+
+	/* 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 @@
+#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 */
-- 
2.1.0



More information about the Petitboot mailing list