Re: [Libguestfs] [PATCH nbdkit v2] common: Move shared bitmap code to a common library.

Eric Blake
Mon, 03 Dec 2018 21:00:22 -0000

On 12/2/18 10:33 AM, Richard W.M. Jones wrote:
> The cow and cache filters both use a bitmap mapping virtual disk
> blocks to status stored in the bitmap.  The implementation of the
> bitmaps is very similar because one was derived from the other when
> the filters were implemented.
>
> The main difference is the cow filter uses a simple bitmap (one bit
> per block), whereas the cache filter uses two bits per block.
>
> This commit abstracts the bitmap structure into a common library.  The
> block size and bits per block are configurable.
>
> This commit is refactoring.  However it also cures a memory leak
> (bitmap was not being freed).
> ---
>   Makefile.am               |   1 +
>   common/bitmap/Makefile.am |  44 ++++++++++++
>   common/bitmap/bitmap.c    |  74 ++++++++++++++++++++
>   common/bitmap/bitmap.h    | 141 ++++++++++++++++++++++++++++++++++++++

With git's orderfile directive, you can arrange to have all Makefile.am
appear before all .h before all .c, to make patches a little easier to
follow logically (what gets built, what interfaces does it need, and how
is it implemented).  'git config diff.orderFile /path/to/file'; see
qemu's scripts/git.orderfile for an example.


> +
> +int
> +bitmap_resize (struct bitmap *bm, uint64_t new_size)
> +{
> +  uint8_t *new_bitmap;
> +  const size_t old_bm_size = bm->size;
> +  uint64_t new_bm_size_u64;
> +  size_t new_bm_size;
> +
> +  new_bm_size_u64 = DIV_ROUND_UP (new_size, bm->blksize * 8 /
bm->bpb);

Can the computation of bm->blksize * 8 overflow? (blksize is 32-bit
unsigned; did initialization clamp block size to less than 512M?). But
appears to be unchanged in the refactor, and at most a pre-existing
issue.  If so, write bm->blksize * UINT64_C(8) / bm->bpb or similar.


> diff --git a/common/bitmap/bitmap.h b/common/bitmap/bitmap.h
> new file mode 100644
> index 0000000..2e1dd19
> --- /dev/null
> +++ b/common/bitmap/bitmap.h

> +
> +/* This is the bitmap structure. */
> +struct bitmap {
> +  unsigned blksize;             /* Block size. */
> +  unsigned bpb;                 /* Bits per block (1, 2, 4, 8 only). */
> +
> +  uint8_t *bitmap;              /* The bitmap. */
> +  size_t size;                  /* Size of bitmap in bytes. */
> +};
> +
> +static inline void
> +bitmap_init (struct bitmap *bm, unsigned blksize, unsigned bpb)
> +{
> +  assert (is_power_of_2 (blksize));
> +  assert (bpb >= 1);
> +  assert (bpb <= 8);
> +  assert (is_power_of_2 (bpb)); /* Only 1, 2, 4, 8 allowed. */
> +
> +  bm->blksize = blksize;

No validation that blksize is not too big for multiplying by 8 (for bpb
of 1).

> +  bm->bpb = bpb;
> +
> +  bm->bitmap = NULL;
> +  bm->size = 0;
> +}
> +
> +/* Only frees the bitmap itself, since it is assumed that the struct
> + * bitmap is statically allocated.
> + */
> +static inline void
> +bitmap_free (struct bitmap *bm)
> +{
> +  if (bm)
> +    free (bm->bitmap);
> +}
> +
> +/* Resize the bitmap to the virtual disk size in bytes.
> + * Returns -1 on error, setting nbdkit_error.
> + */
> +extern int bitmap_resize (struct bitmap *bm, uint64_t new_size);
> +
> +/* Return the bit(s) associated with the given block.
> + * If the request is out of range, returns the default value.
> + */
> +static inline unsigned
> +bitmap_get_blk (const struct bitmap *bm, uint64_t blk, unsigned default_)
> +{
> +  uint64_t blk_offset = blk / (8 / bm->bpb);
> +  unsigned blk_bit = bm->bpb * (blk % (8 / bm->bpb));

Would using << and >> instead of / and % aid the compiler, since we
know
we have powers of 2 based on initialization, but the compiler might not
see it locally?

> +/* Set the bit(s) associated with the given block.
> + * If out of range, it is ignored.
> + */
> +static inline void
> +bitmap_set_blk (const struct bitmap *bm, uint64_t blk, unsigned v)
> +{
> +  uint64_t blk_offset = blk / (8 / bm->bpb);
> +  unsigned blk_bit = bm->bpb * (blk % (8 / bm->bpb));
> +
> +  if (blk_offset >= bm->size) {
> +    nbdkit_debug ("bitmap_set: block number is out of range");
> +    return;
> +  }
> +
> +  bm->bitmap[blk_offset] |= v << blk_bit;

Do you want to mask out the existing bits first, or is this
intentionally only a saturating set (where you can turn bits on but not
off)?  Should there be a counterpart for clearing bits?

> +}
> +
> +/* As above bit works with virtual disk offset in bytes. */
> +static inline void
> +bitmap_set (const struct bitmap *bm, uint64_t offset, unsigned v)
> +{
> +  return bitmap_set_blk (bm, offset / bm->blksize, v);
> +}
> +
> +/* Iterate over blocks represented in the bitmap. */
> +#define bitmap_for(bm, /* uint64_t */ blknum)                           \
> +  for (blknum = 0; blknum < (bm)->size * (8 / (bm)->bpb);
++blknum)

For safety, you should probably:

for ((blknum) = 0; (blknum) < (bm)->size * (8 / (bm->bpb); ++(blknum))



--
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org