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

Akilesh Kailash akailash at google.com
Wed Nov 24 00:07:21 UTC 2021


On Tue, Nov 23, 2021 at 1:03 PM Mikulas Patocka <mpatocka at redhat.com> wrote:
>
> 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.

Why not extend dm-snapshot; in that way you can have the existing
kernel COW format + support compression.
I understand this for read-only but if dm-snapshot supports it,
existing users  can have the
compression feature if required.

Also, I am curious what are the other real world use case here apart
from Android ?

> +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