[dm-devel] [PATCH] announcing the dm-update target

Mikulas Patocka mpatocka at redhat.com
Tue Nov 23 21:02:47 UTC 2021


Hi

I announce the first version of the dm-update target. This target will 
perform background update of read-only system partition on embedded 
systems while the system is running.

It performs similar functionality that is described in this presentation: 
https://linuxplumbersconf.org/event/11/contributions/1049/attachments/826/1562/2021%20LPC_%20dm-snapshot%20in%20user%20space.pdf 
(except that dm-update works entirely in kernel space)

See the file Documentation/admin-guide/device-mapper/update.rst at the end 
of this patch for instructions how to use it.

I'd like to ask Akilesh Kailash and David Anderson if they are interested 
in using this in Android. I am willing to extend the update target, so 
that it will handle Android update format.

Mikulas


Index: linux-2.6/drivers/md/Kconfig
===================================================================
--- linux-2.6.orig/drivers/md/Kconfig
+++ linux-2.6/drivers/md/Kconfig
@@ -652,4 +652,14 @@ config DM_AUDIT
 	  Enables audit logging of several security relevant events in the
 	  particular device-mapper targets, especially the integrity target.
 
+config DM_UPDATE
+	tristate "Update target support"
+	depends on BLK_DEV_DM
+	select DM_BUFIO
+	select CRYPTO
+	help
+	  The dm-update target allows transparent updates for embedded devices
+
+	  If unsure, say N.
+
 endif # MD
Index: linux-2.6/drivers/md/Makefile
===================================================================
--- linux-2.6.orig/drivers/md/Makefile
+++ linux-2.6/drivers/md/Makefile
@@ -83,6 +83,7 @@ obj-$(CONFIG_DM_LOG_WRITES)	+= dm-log-wr
 obj-$(CONFIG_DM_INTEGRITY)	+= dm-integrity.o
 obj-$(CONFIG_DM_ZONED)		+= dm-zoned.o
 obj-$(CONFIG_DM_WRITECACHE)	+= dm-writecache.o
+obj-$(CONFIG_DM_UPDATE)		+= dm-update.o
 
 ifeq ($(CONFIG_DM_INIT),y)
 dm-mod-objs			+= dm-init.o
Index: linux-2.6/drivers/md/dm-update.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/md/dm-update.c
@@ -0,0 +1,751 @@
+#include <linux/device-mapper.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/dm-io.h>
+#include <linux/crypto.h>
+
+#include "dm-update.h"
+
+#define DM_MSG_PREFIX "update"
+
+#define N_BUFFERS		16
+
+#define B_EMPTY			0
+#define B_LOADING		1
+#define B_VALID			2
+
+struct dm_update_buffer {
+	int status;
+	char *compressed_chunk;
+	char *decompressed_chunk;
+	uint64_t src;
+	struct update_entry *e;
+	struct bio_list waiting_bios;
+	struct work_struct work;
+	struct dm_update *u;
+};
+
+struct dm_update {
+	struct dm_dev *system_dev;
+	struct dm_dev *update_dev;
+	struct dm_target *ti;
+	struct dm_io_client *dm_io;
+	unsigned update_lbs;
+	unsigned char update_lbs_bits;
+	struct update_superblock *sb;
+	struct crypto_comp *cc;
+	struct update_entry *entries;
+
+	struct mutex mutex;
+	struct workqueue_struct *decompress_wq;
+	struct bio_list waiting_bios;
+
+	bool no_bg_update;
+	struct workqueue_struct *bg_wq;
+	struct work_struct bg_work;
+	char *bg_compressed_chunk;
+	char *bg_decompressed_chunk;
+	size_t bg_index;
+
+	unsigned buffer_replacement;
+	struct dm_update_buffer buffer[N_BUFFERS];
+};
+
+static int update_rw(struct dm_update *u, bool system_dev, int req_op, sector_t sector, sector_t n_sectors, void *ptr)
+{
+	struct dm_io_region region;
+	struct dm_io_request req;
+
+	region.bdev = system_dev ? u->system_dev->bdev : u->update_dev->bdev;
+	region.sector = sector;
+	region.count = n_sectors;
+
+	req.bi_op = req_op;
+	req.bi_op_flags = REQ_SYNC;
+	req.mem.type = DM_IO_VMA;
+	req.mem.ptr.vma = ptr;
+	req.client = u->dm_io;
+	req.notify.fn = NULL;
+	req.notify.context = NULL;
+
+	return dm_io(&req, 1, &region, NULL);
+}
+
+static int update_decompress(struct dm_update *u, void *src, size_t src_size, void *dst, size_t dst_size)
+{
+	int r;
+	if (!u->cc) {
+		if (unlikely(src_size > dst_size))
+			return -EOVERFLOW;
+		memcpy(dst, src, src_size);
+	} else {
+		unsigned dst_int;
+		if (unlikely(src_size != (unsigned)src_size) ||
+		    unlikely(dst_size != (unsigned)dst_size))
+			return -EOVERFLOW;
+		dst_int = dst_size;
+		r = crypto_comp_decompress(u->cc, src, src_size, dst, &dst_int);
+		if (unlikely(r))
+			return r;
+	}
+	return 0;
+}
+
+static void update_fill_from_buffer(struct dm_update *u, struct dm_update_buffer *b, struct bio *bio)
+{
+	struct bio_vec bv;
+	struct bvec_iter iter;
+
+	struct update_entry *e = bio->bi_private;
+	size_t data_offset = (size_t)le32_to_cpu(e->offset) << u->sb->block_bits;
+	size_t bio_offset = (bio->bi_iter.bi_sector & ((1 << (u->sb->block_bits - SECTOR_SHIFT)) - 1)) << SECTOR_SHIFT;
+	const char *data = b->decompressed_chunk + data_offset + bio_offset;
+
+	bio_for_each_segment(bv, bio, iter) {
+		char *addr = kmap_local_page(bv.bv_page);
+		memcpy(addr + bv.bv_offset, data, bv.bv_len);
+		flush_dcache_page(bv.bv_page);
+		kunmap_local(addr);
+		data += bv.bv_len;
+	}
+
+	bio_endio(bio);
+}
+
+static void update_process_bio(struct dm_update *u, struct bio *bio)
+{
+	struct update_entry *e;
+	struct dm_update_buffer *b;
+	uint64_t src;
+	int i;
+
+	e = bio->bi_private;
+
+	src = le32_to_cpu(e->src_lo) + ((uint64_t)le16_to_cpu(e->src_hi) << 32);
+	for (i = 0; i < N_BUFFERS; i++) {
+		b = &u->buffer[i];
+		if (b->status == B_EMPTY)
+			continue;
+		if (b->src == src) {
+			if (b->status == B_LOADING) {
+				bio_list_add(&b->waiting_bios, bio);
+			} else {
+				update_fill_from_buffer(u, b, bio);
+			}
+			return;
+		}
+	}
+	for (i = 0; i < N_BUFFERS; i++) {
+		b = &u->buffer[i];
+		if (b->status == B_EMPTY) {
+replace_buffer:
+			bio_list_add(&b->waiting_bios, bio);
+			b->status = B_LOADING;
+			b->src = src;
+			b->e = e;
+			queue_work(u->decompress_wq, &b->work);
+			return;
+		}
+	}
+	for (i = 0; i < N_BUFFERS; i++) {
+		b = &u->buffer[u->buffer_replacement];
+		u->buffer_replacement = (u->buffer_replacement + 1) % N_BUFFERS;
+		if (b->status == B_VALID)
+			goto replace_buffer;
+	}
+	bio_list_add(&u->waiting_bios, bio);
+}
+
+static void dm_update_get_locations(struct dm_update *u, struct update_entry *e, uint64_t *src, sector_t *sector, sector_t *n_sectors, size_t *front_pad, size_t *compressed_length)
+{
+	uint64_t next_src;
+	*src = le32_to_cpu(e->src_lo) + ((uint64_t)le16_to_cpu(e->src_hi) << 32);
+	do {
+		e++;
+		next_src = le32_to_cpu(e->src_lo) + ((uint64_t)le16_to_cpu(e->src_hi) << 32);
+	} while (next_src == *src);
+
+	*compressed_length = next_src - *src;
+	*front_pad = *src & (u->update_lbs - 1);
+	*sector = *src >> u->update_lbs_bits << (u->update_lbs_bits - SECTOR_SHIFT);
+	*n_sectors = round_up(*front_pad + *compressed_length, u->update_lbs) >> SECTOR_SHIFT;
+}
+
+static void dm_update_buffer_work(struct work_struct *w)
+{
+	struct dm_update_buffer *b = container_of(w, struct dm_update_buffer, work);
+	struct dm_update *u = b->u;
+	uint64_t src;
+	size_t front_pad, compressed_length;
+	sector_t sector, n_sectors;
+	struct bio *bio, *waiting_bios;
+	int r;
+
+	dm_update_get_locations(u, b->e, &src, &sector, &n_sectors, &front_pad, &compressed_length);
+
+	r = update_rw(u, false, REQ_OP_READ, sector, n_sectors, b->compressed_chunk);
+	if (unlikely(r))
+		goto io_error;
+
+	r = update_decompress(u, b->compressed_chunk + front_pad, compressed_length, b->decompressed_chunk, le32_to_cpu(u->sb->chunk_blocks) << u->sb->block_bits);
+	if (unlikely(r))
+		goto io_error;
+
+io_error:
+	mutex_lock(&u->mutex);
+	b->status = likely(!r) ? B_VALID : B_EMPTY;
+	while ((bio = bio_list_pop(&b->waiting_bios))) {
+		if (unlikely(r)) {
+			bio->bi_status = errno_to_blk_status(r);
+			bio_endio(bio);
+		} else {
+			update_fill_from_buffer(u, b, bio);
+		}
+	}
+
+	waiting_bios = bio_list_get(&u->waiting_bios);
+	while (waiting_bios != NULL) {
+		bio = waiting_bios;
+		waiting_bios = bio->bi_next;
+		bio->bi_next = NULL;
+		update_process_bio(u, bio);
+	}
+
+	mutex_unlock(&u->mutex);
+}
+
+static int update_map(struct dm_target *ti, struct bio *bio)
+{
+	struct dm_update *u = ti->private;
+	sector_t block;
+	size_t first, last, half;
+	struct update_entry *e;
+
+	if (bio_data_dir(bio) == WRITE)
+		return DM_MAPIO_KILL;
+
+	block = bio->bi_iter.bi_sector >> (u->sb->block_bits - SECTOR_SHIFT);
+
+	first = 0;
+	last = le64_to_cpu(u->sb->dir_n) - 1;
+	while (first < last) {
+		sector_t test_block;
+		half = first / 2 + last / 2 + (first & last & 1);
+		e = &u->entries[half];
+		test_block = le32_to_cpu(e->dest_lo) + ((uint64_t)le16_to_cpu(e->dest_hi) << 32);
+		if (test_block == block)
+			goto found;
+		if (test_block < block) {
+			first = half + 1;
+		} else {
+			last = half;
+		}
+	}
+
+	bio_set_dev(bio, u->system_dev->bdev);
+	return DM_MAPIO_REMAPPED;
+
+found:
+	bio->bi_private = e;
+
+	mutex_lock(&u->mutex);
+	update_process_bio(u, bio);
+	mutex_unlock(&u->mutex);
+
+	return DM_MAPIO_SUBMITTED;
+}
+
+static void update_status(struct dm_target *ti, status_type_t type, unsigned status_flags, char *result, unsigned maxlen)
+{
+	struct dm_update *u = ti->private;
+	unsigned extra_args;
+	unsigned sz = 0;
+
+	switch (type) {
+		case STATUSTYPE_INFO:
+			DMEMIT("%zu %llu", READ_ONCE(u->bg_index), (unsigned long long)(le64_to_cpu(u->sb->dir_n) - 1));
+			break;
+		case STATUSTYPE_TABLE:
+			DMEMIT("%s %s ", u->system_dev->name, u->update_dev->name);
+
+			extra_args = 0;
+			if (u->no_bg_update)
+				extra_args++;
+
+			DMEMIT("%u", extra_args);
+			if (u->no_bg_update)
+				DMEMIT(" no_bg_update");
+			break;
+		case STATUSTYPE_IMA:
+			DMEMIT_TARGET_NAME_VERSION(ti->type);
+			DMEMIT(",update_system_device=%s", u->system_dev->name);
+			DMEMIT(",update_update_device=%s", u->update_dev->name);
+			DMEMIT(";");
+			break;
+	}
+}
+
+static int update_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data)
+{
+	struct dm_update *u = ti->private;
+
+	return fn(ti, u->system_dev, 0, ti->len, data);
+}
+
+static void update_background_work(struct work_struct *w)
+{
+	struct dm_update *u = container_of(w, struct dm_update, bg_work);
+	uint64_t src;
+	size_t front_pad, compressed_length;
+	sector_t sector, n_sectors;
+	int r;
+
+	if (u->bg_index >= le64_to_cpu(u->sb->dir_n) - 1)
+		return;
+
+	dm_update_get_locations(u, &u->entries[u->bg_index], &src, &sector, &n_sectors, &front_pad, &compressed_length);
+
+	r = update_rw(u, false, REQ_OP_READ, sector, n_sectors, u->bg_compressed_chunk);
+	if (unlikely(r)) {
+		DMERR("error reading update device (%d), aborting backgroup update", r);
+		return;
+	}
+
+	r = update_decompress(u, u->bg_compressed_chunk + front_pad, compressed_length, u->bg_decompressed_chunk, le32_to_cpu(u->sb->chunk_blocks) << u->sb->block_bits);
+	if (unlikely(r)) {
+		DMERR("error decompressing update data (%d), aborting backgroup update", r);
+		return;
+	}
+
+	while (u->bg_index < le64_to_cpu(u->sb->dir_n) - 1) {
+		uint64_t s, dest;
+		size_t offset;
+		struct update_entry *e = &u->entries[u->bg_index];
+		s = le32_to_cpu(e->src_lo) + ((uint64_t)le16_to_cpu(e->src_hi) << 32);
+		if (s != src)
+			break;
+
+		dest = le32_to_cpu(e->dest_lo) + ((uint64_t)le16_to_cpu(e->dest_hi) << 32);
+		offset = (size_t)le32_to_cpu(e->offset) << u->sb->block_bits;
+
+		r = update_rw(u, true, REQ_OP_WRITE, dest << (u->sb->block_bits - SECTOR_SHIFT), 1UL << (u->sb->block_bits - SECTOR_SHIFT), u->bg_decompressed_chunk + offset);
+		if (unlikely(r)) {
+			DMERR("error writing to the system device (%d), aborting backgroup update", r);
+			return;
+		}
+
+		if (unlikely(u->bg_index == le64_to_cpu(u->sb->dir_n) - 2)) {
+			r = update_rw(u, true, REQ_OP_WRITE | REQ_PREFLUSH, 0, 0, NULL);
+			if (unlikely(r)) {
+				DMERR("error synchronizing the system device (%d), aborting backgroup update", r);
+				return;
+			}
+		}
+
+		WRITE_ONCE(u->bg_index, u->bg_index + 1);
+	}
+
+	queue_work(u->bg_wq, &u->bg_work);
+}
+
+static void update_presuspend(struct dm_target *ti)
+{
+	struct dm_update *u = ti->private;
+	cancel_work_sync(&u->bg_work);
+}
+
+static void update_resume(struct dm_target *ti)
+{
+	struct dm_update *u = ti->private;
+	if (u->no_bg_update)
+		return;
+	queue_work(u->bg_wq, &u->bg_work);
+}
+
+static void update_dtr(struct dm_target *ti)
+{
+	struct dm_update *u = ti->private;
+	int i;
+
+	if (u->bg_wq)
+		destroy_workqueue(u->bg_wq);
+	if (u->decompress_wq)
+		destroy_workqueue(u->decompress_wq);
+
+	vfree(u->bg_compressed_chunk);
+	vfree(u->bg_decompressed_chunk);
+
+	for (i = 0; i < N_BUFFERS; i++) {
+		vfree(u->buffer[i].compressed_chunk);
+		vfree(u->buffer[i].decompressed_chunk);
+	}
+	vfree(u->sb);
+	vfree(u->entries);
+	if (u->dm_io)
+		dm_io_client_destroy(u->dm_io);
+	if (u->system_dev)
+		dm_put_device(ti, u->system_dev);
+	if (u->update_dev)
+		dm_put_device(ti, u->update_dev);
+	if (u->cc)
+		crypto_free_comp(u->cc);
+
+	mutex_init(&u->mutex);
+
+	kfree(u);
+}
+
+static char *validate_sb(struct dm_update *u)
+{
+	struct update_superblock *sb = u->sb;
+
+	if (sb->block_bits < SECTOR_SHIFT || sb->block_bits >= 32)
+		return "Invalid superblock: block_bits";
+
+	if (!le32_to_cpu(sb->chunk_blocks))
+		return "Invalid superblock: chunk_blocks is zero";
+
+	if (le32_to_cpu(u->sb->chunk_blocks) << u->sb->block_bits >> u->sb->block_bits != le32_to_cpu(u->sb->chunk_blocks))
+		return "Invalid superblock: too large chunk_blocks";
+
+	if ((int)(le32_to_cpu(u->sb->chunk_blocks) << u->sb->block_bits) < 0)
+		return "Invalid superblock: too large chunk_blocks";
+
+	if (le64_to_cpu(sb->dir_n) < 1)
+		return "Invalid superblock: zero dir_n";
+
+	if ((size_t)le64_to_cpu(sb->dir_n) * sizeof(struct update_entry) / sizeof(struct update_entry) != le64_to_cpu(sb->dir_n))
+		return "Invalid superblock: overflow in dir_n";
+
+	return NULL;
+}
+
+static char *validate_metadata(struct dm_update *u)
+{
+	struct update_superblock *sb = u->sb;
+	size_t i;
+	size_t n = le64_to_cpu(sb->dir_n) - 1;
+
+	for (i = 0; i < n; i++) {
+		struct update_entry *e1 = &u->entries[i];
+		struct update_entry *e2 = &u->entries[i + 1];
+		uint64_t dest1, dest2;
+
+		if (le32_to_cpu(e1->offset) >= le32_to_cpu(sb->chunk_blocks))
+			return "Invalid metadata: offset is too high";
+
+		dest1 = le32_to_cpu(e1->dest_lo) + ((uint64_t)le16_to_cpu(e1->dest_hi) << 32);
+		dest2 = le32_to_cpu(e2->dest_lo) + ((uint64_t)le16_to_cpu(e2->dest_hi) << 32);
+
+		if (dest1 >= dest2)
+			return "Invalid metadata: destination is not monotonic";
+	}
+
+	return NULL;
+}
+
+static int update_ctr(struct dm_target *ti, unsigned argc, char **argv)
+{
+	struct dm_update *u;
+	struct dm_arg_set as;
+	const char *string;
+	unsigned opt_params;
+	char *err;
+	int r;
+	uint64_t compressed_dir_size;
+	void *compressed_dir = NULL;
+	size_t dst_len;
+	int i;
+	size_t o;
+	sector_t max_compressed_sectors;
+
+	static const struct dm_arg _args[] = {
+		{0, 1, "Invalid number of feature args"},
+	};
+
+	as.argc = argc;
+	as.argv = argv;
+
+	u = kzalloc(sizeof(struct dm_update), GFP_KERNEL);
+	if (!u) {
+		ti->error = "Cannot allocate dm_update structure";
+		return -ENOMEM;
+	}
+
+	ti->private = u;
+	u->ti = ti;
+
+	mutex_init(&u->mutex);
+	bio_list_init(&u->waiting_bios);
+	INIT_WORK(&u->bg_work, update_background_work);
+
+	string = dm_shift_arg(&as);
+	if (!string)
+		goto bad_arguments;
+
+	r = dm_get_device(ti, string, FMODE_READ | FMODE_WRITE, &u->system_dev);
+	if (r) {
+		ti->error = "System device lookup failed";
+		goto bad;
+	}
+
+	string = dm_shift_arg(&as);
+	if (!string)
+		goto bad_arguments;
+
+	r = dm_get_device(ti, string, FMODE_READ | FMODE_WRITE, &u->update_dev);
+	if (r) {
+		ti->error = "Update device lookup failed";
+		goto bad;
+	}
+
+	r = dm_read_arg_group(_args, &as, &opt_params, &ti->error);
+	if (r)
+		goto bad;
+
+	while (opt_params) {
+		string = dm_shift_arg(&as), opt_params--;
+		if (!strcasecmp(string, "no_bg_update")) {
+			u->no_bg_update = true;
+		} else {
+			r = -EINVAL;
+			ti->error = "Invalid optional argument";
+			goto bad;
+		}
+	}
+
+	u->update_lbs = bdev_logical_block_size(u->update_dev->bdev);
+	u->update_lbs_bits = __ffs(u->update_lbs);
+
+	u->decompress_wq = alloc_workqueue("dm-update", WQ_CPU_INTENSIVE | WQ_MEM_RECLAIM, 0);
+	if (!u->decompress_wq) {
+		ti->error = "Cannot allocate workqueue";
+		r = -ENOMEM;
+		goto bad;
+	}
+
+	if (!u->no_bg_update) {
+		u->bg_wq = alloc_workqueue("dm-update-background", WQ_CPU_INTENSIVE | WQ_UNBOUND, 1);
+		if (!u->bg_wq) {
+			ti->error = "Cannot allocate workqueue";
+			r = -ENOMEM;
+			goto bad;
+		}
+	}
+
+	u->dm_io = dm_io_client_create();
+	if (IS_ERR(u->dm_io)) {
+		r = PTR_ERR(u->dm_io);
+		u->dm_io = NULL;
+		ti->error = "Unable to allocate dm-io client";
+		goto bad;
+	}
+
+	u->sb = vmalloc(u->update_lbs);
+	if (!u->sb) {
+		r = -ENOMEM;
+		ti->error = "Cannot allocate superblock";
+		goto bad;
+	}
+
+	r = update_rw(u, false, REQ_OP_READ, 0, u->update_lbs >> SECTOR_SHIFT, u->sb);
+	if (r) {
+		ti->error = "Cannot read superblock";
+		goto bad;
+	}
+
+	if (memcmp(u->sb->magic, UPDATE_MAGIC, 8)) {
+		r = -EINVAL;
+		ti->error = "Invalid magic in the superblock";
+		//printk("%02x %02x %02x %02x %02x %02x %02x %02x\n", u->sb->magic[0], u->sb->magic[1], u->sb->magic[2], u->sb->magic[3], u->sb->magic[4], u->sb->magic[5], u->sb->magic[6], u->sb->magic[7]);
+		goto bad;
+	}
+
+	if (u->sb->version != UPDATE_VERSION) {
+		r = -EINVAL;
+		ti->error = "Invalid version in the superblock";
+		goto bad;
+	}
+
+	if ((err = validate_sb(u))) {
+		r = -EINVAL;
+		ti->error = err;
+		goto bad;
+	}
+
+	r = dm_set_target_max_io_len(ti, (sector_t)1 << (u->sb->block_bits - SECTOR_SHIFT));
+	if (r) {
+		ti->error = "Invalid block size in the superblock";
+		goto bad;
+	}
+
+	if (!memchr(u->sb->compression, 0, sizeof u->sb->compression)) {
+		r = -EINVAL;
+		ti->error = "Invalid compression algorithm in the superblock";
+		goto bad;
+	}
+	if (strcmp(u->sb->compression, "none")) {
+		u->cc = crypto_alloc_comp(u->sb->compression, 0, 0);
+		if (!u->cc)
+			u->cc = ERR_PTR(-ENOMEM);
+		if (IS_ERR(u->cc)) {
+			r = PTR_ERR(u->cc);
+			u->cc = NULL;
+			ti->error = "Unsupported compression method";
+			goto bad;
+		}
+	}
+
+	compressed_dir_size = roundup((le64_to_cpu(u->sb->dir_offset) & (u->update_lbs - 1)) + le64_to_cpu(u->sb->dir_compressed_size), u->update_lbs);
+	if (compressed_dir_size != (size_t)compressed_dir_size) {
+		r = -EOVERFLOW;
+		ti->error = "Compressed directory is too large for 32-bit system";
+		goto bad;
+	}
+
+	compressed_dir = vmalloc(compressed_dir_size);
+	if (!compressed_dir) {
+		r = -ENOMEM;
+		ti->error = "Cannot allocate compressed directory";
+		goto bad;
+	}
+
+	r = update_rw(u, false, REQ_OP_READ, round_down(le64_to_cpu(u->sb->dir_offset), u->update_lbs) >> SECTOR_SHIFT, compressed_dir_size >> SECTOR_SHIFT, compressed_dir);
+	if (r) {
+		ti->error = "Cannot read compressed directory";
+		goto bad;
+	}
+
+	dst_len = le64_to_cpu(u->sb->dir_n) * sizeof(struct update_entry);
+	u->entries = vmalloc(dst_len);
+	if (!u->entries) {
+		r = -ENOMEM;
+		ti->error = "Cannot allocate decompressed directory";
+		goto bad;
+	}
+
+	r = update_decompress(u, compressed_dir + (le64_to_cpu(u->sb->dir_offset) & (u->update_lbs - 1)), le64_to_cpu(u->sb->dir_compressed_size), (void *)u->entries, dst_len);
+	if (r) {
+		ti->error = "Cannot decompress directory";
+		goto bad;
+	}
+
+	if (dst_len != le64_to_cpu(u->sb->dir_n) * sizeof(struct update_entry)) {
+		r = -EINVAL;
+		ti->error = "Non-matching length of compressed directory";
+		goto bad;
+	}
+
+	vfree(compressed_dir);
+	compressed_dir = NULL;
+
+	if ((err = validate_metadata(u))) {
+		r = -EINVAL;
+		ti->error = err;
+		goto bad;
+	}
+
+	o = 0;
+	max_compressed_sectors = 1;
+	while (o < le64_to_cpu(u->sb->dir_n) - 1) {
+		struct update_entry *e = &u->entries[o];
+		uint64_t src, s;
+		size_t front_pad, compressed_length;
+		sector_t sector, n_sectors;
+		dm_update_get_locations(u, e, &src, &sector, &n_sectors, &front_pad, &compressed_length);
+		if (n_sectors > max_compressed_sectors)
+			max_compressed_sectors = n_sectors;
+		do {
+			o++;
+			if (o >= le64_to_cpu(u->sb->dir_n) - 1)
+				break;
+			e = &u->entries[o];
+			s = le32_to_cpu(e->src_lo) + ((uint64_t)le16_to_cpu(e->src_hi) << 32);
+		} while (s == src);
+	}
+
+	for (i = 0; i < N_BUFFERS; i++) {
+		struct dm_update_buffer *b = &u->buffer[i];
+		b->decompressed_chunk = vmalloc(le32_to_cpu(u->sb->chunk_blocks) << u->sb->block_bits);
+		if (!b->decompressed_chunk) {
+			r = -ENOMEM;
+			ti->error = "Cannot allocate buffers";
+			goto bad;
+		}
+		//memset(b->decompressed_chunk, 0xfe, le32_to_cpu(u->sb->chunk_blocks) << u->sb->block_bits);
+		b->compressed_chunk = vmalloc(max_compressed_sectors << SECTOR_SHIFT);
+		if (!b->compressed_chunk) {
+			r = -ENOMEM;
+			ti->error = "Cannot allocate buffers";
+			goto bad;
+		}
+		//memset(b->compressed_chunk, 0xfd, max_compressed_sectors << SECTOR_SHIFT);
+		bio_list_init(&b->waiting_bios);
+		INIT_WORK(&b->work, dm_update_buffer_work);
+		b->u = u;
+	}
+
+	if (!u->no_bg_update) {
+		u->bg_decompressed_chunk = vmalloc(le32_to_cpu(u->sb->chunk_blocks) << u->sb->block_bits);
+		if (!u->bg_decompressed_chunk) {
+			r = -ENOMEM;
+			ti->error = "Cannot allocate buffers";
+			goto bad;
+		}
+		u->bg_compressed_chunk = vmalloc(max_compressed_sectors << SECTOR_SHIFT);
+		if (!u->bg_compressed_chunk) {
+			r = -ENOMEM;
+			ti->error = "Cannot allocate buffers";
+			goto bad;
+		}
+	}
+
+	return 0;
+
+bad_arguments:
+	ti->error = "Not enough arguments";
+	r = -EINVAL;
+bad:
+	if (compressed_dir)
+		vfree(compressed_dir);
+	update_dtr(ti);
+	return r;
+}
+
+static struct target_type update_target = {
+	.name			= "update",
+	.version		= {1, 0, 0},
+	.module			= THIS_MODULE,
+	.ctr			= update_ctr,
+	.dtr			= update_dtr,
+	.map			= update_map,
+	.status			= update_status,
+	.iterate_devices	= update_iterate_devices,
+	.presuspend		= update_presuspend,
+	.resume			= update_resume,
+};
+
+static int __init dm_update_init(void)
+{
+	int r;
+
+	r = dm_register_target(&update_target);
+	if (r < 0) {
+		DMERR("register failed %d", r);
+		return r;
+	}
+
+	return 0;
+}
+
+static void __exit dm_update_exit(void)
+{
+	dm_unregister_target(&update_target);
+}
+
+module_init(dm_update_init);
+module_exit(dm_update_exit);
+
+MODULE_DESCRIPTION(DM_NAME " update target");
+MODULE_AUTHOR("Mikulas Patocka <dm-devel at redhat.com>");
+MODULE_LICENSE("GPL");
Index: linux-2.6/drivers/md/dm-update.h
===================================================================
--- /dev/null
+++ linux-2.6/drivers/md/dm-update.h
@@ -0,0 +1,23 @@
+#define UPDATE_MAGIC	"update\0"
+#define UPDATE_VERSION	0
+
+struct update_superblock {
+	char magic[8];
+	uint8_t version;
+	uint8_t block_bits;
+	uint16_t pad1;
+	__le32 chunk_blocks;
+	char compression[16];
+	__le64 dir_offset;
+	__le64 dir_compressed_size;
+	__le64 dir_n;
+	__le64 pad2;
+};
+
+struct update_entry {
+	__le32 dest_lo;
+	__le16 dest_hi;
+	__le16 src_hi;
+	__le32 src_lo;
+	__le32 offset;
+};
Index: linux-2.6/Documentation/admin-guide/device-mapper/update.rst
===================================================================
--- /dev/null
+++ linux-2.6/Documentation/admin-guide/device-mapper/update.rst
@@ -0,0 +1,54 @@
+=============
+Update target
+=============
+
+Embedded devices typically have a read-only partition that stores the operating
+system image. The purpose of the dm-update target is to transparently update
+this partition while the system is running.
+
+How to use the dm-update target:
+1. We have files "old.img" and "new.img" that contain the old and new system
+   image.
+
+2. We calculate the difference between these images with the update-diff
+   utility - it could be downloaded from
+   http://people.redhat.com/~mpatocka/dm-update/
+
+   ./update-diff old.img new.img -o diff.img -a zstd
+
+   This command calculates the difference between old.img and new.img,
+   compresses it with the "zstd" algorithm and stores it in the file diff.img.
+
+3. The file diff.img is delivered to the embedded system.
+
+4. The embedded system is rebooted.
+
+5. On next boot, we load the dm-update target (assume that /dev/sda1 is the
+   system partition and diff.img is the downloaded difference file):
+   # losetup /dev/loop0 diff.img
+   # dmsetup create system --table "0 `blockdev --getsize /dev/sda1` update
+     /dev/sda1 /dev/loop0 0"
+
+6. The update target will present the system image as if the update already
+   happened - if you read from blocks that are modified, it will transparently
+   load the data from "diff.img" and decompress it.
+
+7. On background, the update target will copy the contents of the file
+   "diff.img" to the partition /dev/sda1
+
+8. When the copying finishes (it can be detected by dmsetup status - if the
+   first two numbers are equal, the copying finished), you can replace the
+   update target with a linear target. After replacement, you can delete the
+   loop device /dev/loop0 and the file diff.img. The partition /dev/sda1 is now
+   equal to the file "new.img".
+
+Constructor parameters:
+1. the system partition
+2. the device that contains the difference between old and new image
+3. the number of optional arguments
+	no_bg_update
+		don't perform background update of the system partition
+
+Status:
+1. the number of blocks that were updated so far
+2. the total number of blocks in the difference file




More information about the dm-devel mailing list