[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, &params);
> +

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",
> +	       &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;
> +

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