All of lore.kernel.org
 help / color / mirror / Atom feed
From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
To: qemu-block@nongnu.org, qemu-devel@nongnu.org
Cc: kwolf@redhat.com, armbru@redhat.com, eblake@redhat.com,
	jsnow@redhat.com, famz@redhat.com, den@openvz.org,
	stefanha@redhat.com, pbonzini@redhat.com
Subject: [Qemu-devel] DROP THIS Re: [PATCH 11/22] qcow2-bitmap: add qcow2_store_persistent_bitmaps()
Date: Mon, 17 Oct 2016 20:58:35 +0300	[thread overview]
Message-ID: <5805114B.8070104@virtuozzo.com> (raw)
In-Reply-To: <58051104.6020905@virtuozzo.com>

Sorry, this was an accidental reply.

On 17.10.2016 20:57, Vladimir Sementsov-Ogievskiy wrote:
> On 30.09.2016 12:53, Vladimir Sementsov-Ogievskiy wrote:
>> Realize block bitmap stroing interface, to allow qcow2 images store
>> persistent bitmaps.
>>
>> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
>> ---
>>  block/qcow2-bitmap.c | 241 
>> +++++++++++++++++++++++++++++++++++++++++++++++++++
>>  block/qcow2.c        |   2 +
>>  block/qcow2.h        |   2 +
>>  3 files changed, 245 insertions(+)
>>
>> diff --git a/block/qcow2-bitmap.c b/block/qcow2-bitmap.c
>> index 81520cd..a5be25a 100644
>> --- a/block/qcow2-bitmap.c
>> +++ b/block/qcow2-bitmap.c
>> @@ -27,6 +27,7 @@
>>
>>  #include "qemu/osdep.h"
>>  #include "qapi/error.h"
>> +#include "qemu/cutils.h"
>>
>>  #include "block/block_int.h"
>>  #include "block/qcow2.h"
>> @@ -96,6 +97,15 @@ static inline void bitmap_table_to_cpu(uint64_t 
>> *bitmap_table, size_t size)
>>      }
>>  }
>>
>> +static inline void bitmap_table_to_be(uint64_t *bitmap_table, size_t 
>> size)
>> +{
>> +    size_t i;
>> +
>> +    for (i = 0; i < size; ++i) {
>> +        cpu_to_be64s(&bitmap_table[i]);
>> +    }
>> +}
>> +
>>  static inline int calc_dir_entry_size(size_t name_size, size_t 
>> extra_data_size)
>>  {
>>      return align_offset(sizeof(Qcow2BitmapDirEntry) +
>> @@ -564,3 +574,234 @@ out:
>>
>>      return ret;
>>  }
>> +
>> +/* store_bitmap_data()
>> + * Store bitmap to image, filling bitamp table accordingly.
>
>
>> + */
>> +static int store_bitmap_data(BlockDriverState *bs, BdrvDirtyBitmap 
>> *bitmap,
>> +                             uint64_t *bitmap_table, uint32_t 
>> bitmap_table_size)
>> +{
>> +    int ret;
>> +    BDRVQcow2State *s = bs->opaque;
>> +    uint64_t sector, dsc;
>> +    uint64_t bm_size = bdrv_dirty_bitmap_size(bitmap);
>> +    int cl_size = s->cluster_size;
>
>
>
>> +    uint8_t *buf = NULL;
>> +    uint32_t tb_size =
>> +            size_to_clusters(s,
>> +                bdrv_dirty_bitmap_serialization_size(bitmap, 0, 
>> bm_size));
>
>
>> +
>> +    BdrvDirtyBitmapIter *dbi;
>> +
>> +    if (tb_size != bitmap_table_size) {
>> +        return -EINVAL;
>> +    }
>> +
>> +    memset(bitmap_table, 0, bitmap_table_size * 
>> sizeof(bitmap_table[0]));
>
>
>> +
>> +    dbi = bdrv_dirty_iter_new(bitmap, 0);
>> +    buf = g_malloc(cl_size);
>> +    dsc = dirty_sectors_in_cluster(s, bitmap);
>> +
>> +    while ((sector = bdrv_dirty_iter_next(dbi)) != -1) {
>
>
>> +        uint64_t cluster = sector / dsc;
>> +        sector = cluster * dsc;
>
>
>
>> +        uint64_t end = MIN(bm_size, sector + dsc);
>> +        uint64_t write_size =
>> +            bdrv_dirty_bitmap_serialization_size(bitmap, sector, end 
>> - sector);
>> +
>> +        int64_t off = qcow2_alloc_clusters(bs, cl_size);
>> +        if (off < 0) {
>> +            ret = off;
>> +            goto finish;
>> +        }
>> +        bitmap_table[cluster] = off;
>> +
>> +        bdrv_dirty_bitmap_serialize_part(bitmap, buf, sector, end);
>
>
>
>> +        if (write_size < cl_size) {
>> +            memset(buf + write_size, 0, cl_size - write_size);
>> +        }
>> +
>
>
>> +        ret = bdrv_pwrite(bs->file, off, buf, cl_size);
>> +        if (ret < 0) {
>> +            goto finish;
>> +        }
>> +
>> +        if (end >= bm_size) {
>> +            break;
>> +        }
>> +
>> +        bdrv_set_dirty_iter(dbi, end);
>> +    }
>> +    ret = 0; /* writes */
>
> What is that comment supposed to mean?
>
>> +
>> +finish:
>> +    if (ret < 0) {
>> +        clear_bitmap_table(bs, bitmap_table, bitmap_table_size);
>> +    }
>> +    g_free(buf);
>> +    bdrv_dirty_iter_free(dbi);
>> +
>> +    return ret;
>
> In case you decide to keep BME_MAX_PHYS_SIZE, this function should check
> somewhere that the physical size of the bitmap does not exceed that 
> value.
>
>> +}
>> +
>> +/* store_bitmap()
>> + * Store bitmap to qcow2 and set bitmap_table. bitmap_table itself 
>> is not
>> + * stored to qcow2.
>
> First of all, there is no parameter called "bitmap_table", and second,
> yes, the bitmap table is written to the qcow2 file.
>
>> + */
>> +static int store_bitmap(BlockDriverState *bs,
>> +                        BdrvDirtyBitmap *bitmap,
>> +                        Qcow2BitmapDirEntry *entry)
>> +{
>> +    int ret;
>> +    BDRVQcow2State *s = bs->opaque;
>> +    uint64_t bm_size = bdrv_dirty_bitmap_size(bitmap);
>> +    const char *bm_name = bdrv_dirty_bitmap_name(bitmap);
>> +
>> +    uint64_t *tb;
>> +    int64_t tb_offset;
>> +    uint32_t tb_size =
>> +            size_to_clusters(s,
>> +                bdrv_dirty_bitmap_serialization_size(bitmap, 0, 
>> bm_size));
>
> As above, this variable should be of type uint64_t.
>
> Also, you have to check that it does not exceed BME_MAX_TABLE_SIZE.
>
>> +
>> +    tb = g_try_new(uint64_t, tb_size);
>> +    if (tb == NULL) {
>> +        return -ENOMEM;
>> +    }
>> +
>> +    ret = store_bitmap_data(bs, bitmap, tb, tb_size);
>> +    if (ret < 0) {
>> +        g_free(tb);
>> +        return ret;
>> +    }
>> +
>> +    tb_offset = qcow2_alloc_clusters(bs, tb_size * sizeof(tb[0]));
>
> If you don't limit tb_size, then this multiplication can overflow on 32
> bit machines.
>
>> +    if (tb_offset < 0) {
>> +        ret = tb_offset;
>> +        goto fail;
>> +    }
>> +
>
> There should be a metadata overlap check here.
>
>> +    bitmap_table_to_be(tb, tb_size);
>> +    ret = bdrv_pwrite(bs->file, tb_offset, tb, tb_size * 
>> sizeof(tb[0]));
>> +    if (ret < 0) {
>> +        goto fail;
>> +    }
>> +
>> +    g_free(tb);
>> +
>> +    entry->bitmap_table_offset = tb_offset;
>> +    entry->bitmap_table_size = tb_size;
>> +    entry->flags = bdrv_dirty_bitmap_granularity(bitmap) ? 
>> BME_FLAG_AUTO : 0;
>
> s/granularity/get_autoload/
>
>> +    entry->type = BT_DIRTY_TRACKING_BITMAP;
>> +    entry->granularity_bits = 
>> ctz32(bdrv_dirty_bitmap_granularity(bitmap));
>
> You should probably check somewhere that the resulting value for
> entry->granularity_bits is in the BME_{MIN,MAX}_GRANULARITY_BITS range.
>
>> +    entry->name_size = strlen(bm_name);
>
> And that this length does not exceed BME_MAX_NAME_SIZE.
>
>> +    entry->extra_data_size = 0;
>> +    memcpy(entry + 1, bm_name, entry->name_size);
>> +
>> +    return 0;
>> +
>> +fail:
>> +    clear_bitmap_table(bs, tb, tb_size);
>> +
>> +    if (tb_offset > 0) {
>> +        qcow2_free_clusters(bs, tb_offset, tb_size, 
>> QCOW2_DISCARD_ALWAYS);
>
> As before, I'd vote for QCOW2_DISCARD_OTHER.
>
>> +    }
>> +
>> +    g_free(tb);
>> +
>> +    return ret;
>> +}
>> +
>> +static Qcow2BitmapDirEntry *find_bitmap_by_name(uint8_t 
>> *bitmap_directory,
>> +                                                size_t size, const 
>> char *name)
>> +{
>> +    Qcow2BitmapDirEntry *e;
>> +
>> +    for_each_bitmap_dir_entry(e, bitmap_directory, size) {
>> +        if (strncmp((char *)(e + 1), name, e->name_size) == 0) {
>> +            return e;
>> +        }
>> +    }
>> +
>> +    return NULL;
>> +}
>> +
>> +void qcow2_store_persistent_bitmaps(BlockDriverState *bs, Error **errp)
>> +{
>> +    BdrvDirtyBitmap *bm;
>> +    BDRVQcow2State *s = bs->opaque;
>> +    uint32_t new_nb_bitmaps = s->nb_bitmaps;
>> +    uint64_t new_dir_size = s->bitmap_directory_size;
>> +    uint8_t *dir = NULL, *new_dir = NULL;
>> +    int ret;
>> +    Qcow2BitmapDirEntry *new_pos;
>> +
>> +    if (s->nb_bitmaps > 0) {
>> +        dir = directory_read(bs, s->bitmap_directory_offset,
>> +                             s->bitmap_directory_size, errp);
>> +        if (dir == NULL) {
>> +            goto out;
>> +        }
>> +    }
>> +
>> +    for (bm = bdrv_dirty_bitmap_next(bs, NULL); bm != NULL;
>> +            bm = bdrv_dirty_bitmap_next(bs, bm)) {
>> +        const char *name = bdrv_dirty_bitmap_name(bm);
>> +
>> +        if (!bdrv_dirty_bitmap_get_persistance(bm)) {
>> +            continue;
>> +        }
>> +
>> +        if (s->nb_bitmaps > 0 &&
>> +                find_bitmap_by_name(dir, s->bitmap_directory_size, 
>> name)) {
>> +            error_setg(errp,
>> +                       "Can't store bitmap '%s' to '%s', as it 
>> already exists",
>> +                       name, bdrv_get_device_or_node_name(bs));
>> +            goto out;
>> +        }
>> +
>> +        new_nb_bitmaps++;
>> +        new_dir_size += calc_dir_entry_size(strlen(name), 0);
>> +    }
>> +
>> +    if (s->nb_bitmaps == new_nb_bitmaps) {
>> +        /* No new bitmaps - nothing to do */
>> +        goto out;
>> +    }
>> +
>> +    new_dir = g_try_malloc0(new_dir_size);
>> +    if (new_dir == NULL) {
>> +        error_setg(errp, "Can't allocate space for bitmap directory.");
>> +        goto out;
>> +    }
>> +
>> +    memcpy(new_dir, dir, s->bitmap_directory_size);
>> +    new_pos = (Qcow2BitmapDirEntry *)(new_dir + 
>> s->bitmap_directory_size);
>> +
>> +    for (bm = bdrv_dirty_bitmap_next(bs, NULL); bm != NULL;
>> +            bm = bdrv_dirty_bitmap_next(bs, bm)) {
>> +        if (!bdrv_dirty_bitmap_get_persistance(bm)) {
>> +            continue;
>> +        }
>> +
>> +        ret = store_bitmap(bs, bm, new_pos);
>> +        if (ret < 0) {
>> +            error_setg_errno(errp, -ret, "Can't store bitmap '%s' to 
>> '%s'",
>> +                             bdrv_dirty_bitmap_name(bm),
>> +                             bdrv_get_device_or_node_name(bs));
>> +            goto out;
>> +        }
>> +        new_pos = next_dir_entry(new_pos);
>> +    }
>> +
>> +    ret = directory_update(bs, new_dir, new_dir_size, new_nb_bitmaps);
>> +    if (ret < 0) {
>> +        error_setg_errno(errp, -ret, "Can't update bitmap directory 
>> in '%s'",
>> +                         bdrv_get_device_or_node_name(bs));
>> +        goto out;
>> +    }
>> +
>> +out:
>> +    g_free(new_dir);
>> +    g_free(dir);
>
> This error path leaks all the bitmaps that have been written
> successfully (if any). I guess this is more or less fine if
> directory_update() failed (because you can't really tell the state of
> the image header after directory_update(), so better be safe) but it's
> not so fine if just some store_bitmap() failed.
>
> Max
>
>> +}
>> diff --git a/block/qcow2.c b/block/qcow2.c
>> index 02ec224..8238205 100644
>> --- a/block/qcow2.c
>> +++ b/block/qcow2.c
>> @@ -3493,6 +3493,8 @@ BlockDriver bdrv_qcow2 = {
>>
>>      .bdrv_detach_aio_context  = qcow2_detach_aio_context,
>>      .bdrv_attach_aio_context  = qcow2_attach_aio_context,
>> +
>> +    .bdrv_store_persistent_bitmaps = qcow2_store_persistent_bitmaps,
>>  };
>>
>>  static void bdrv_qcow2_init(void)
>> diff --git a/block/qcow2.h b/block/qcow2.h
>> index 482a29f..dfcf4c6 100644
>> --- a/block/qcow2.h
>> +++ b/block/qcow2.h
>> @@ -627,4 +627,6 @@ int qcow2_cache_get_empty(BlockDriverState *bs, 
>> Qcow2Cache *c, uint64_t offset,
>>      void **table);
>>  void qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c, void 
>> **table);
>>
>> +void qcow2_store_persistent_bitmaps(BlockDriverState *bs, Error 
>> **errp);
>> +
>>  #endif
>>
>
>
>


-- 
Best regards,
Vladimir

  reply	other threads:[~2016-10-17 17:58 UTC|newest]

Thread overview: 100+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-09-30 10:53 [Qemu-devel] [PATCH v7 00/22] qcow2: persistent dirty bitmaps Vladimir Sementsov-Ogievskiy
2016-09-30 10:53 ` [Qemu-devel] [PATCH 01/22] hbitmap: improve dirty iter Vladimir Sementsov-Ogievskiy
2016-10-01 13:52   ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 02/22] tests: add hbitmap iter test Vladimir Sementsov-Ogievskiy
2016-10-01 14:02   ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 03/22] block: fix bdrv_dirty_bitmap_granularity signature Vladimir Sementsov-Ogievskiy
2016-09-30 10:53 ` [Qemu-devel] [PATCH 04/22] block/dirty-bitmap: add deserialize_ones func Vladimir Sementsov-Ogievskiy
2016-09-30 10:53 ` [Qemu-devel] [PATCH 05/22] qcow2-bitmap: structs and consts Vladimir Sementsov-Ogievskiy
2016-10-01 14:34   ` Max Reitz
2016-10-01 14:56     ` Max Reitz
2016-10-07 13:11     ` Vladimir Sementsov-Ogievskiy
2016-10-11 11:50     ` Vladimir Sementsov-Ogievskiy
2016-10-12 18:20       ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 06/22] qcow2: add dirty bitmaps extension Vladimir Sementsov-Ogievskiy
2016-10-01 14:46   ` Max Reitz
2016-10-11 12:09     ` Vladimir Sementsov-Ogievskiy
2016-10-12 18:21       ` Max Reitz
2016-10-13 12:18         ` Vladimir Sementsov-Ogievskiy
2016-09-30 10:53 ` [Qemu-devel] [PATCH 07/22] qcow2-bitmap: introduce auto-loading bitmaps Vladimir Sementsov-Ogievskiy
2016-10-01 16:26   ` Max Reitz
2016-10-14 18:44     ` Vladimir Sementsov-Ogievskiy
2016-10-15 17:03       ` Max Reitz
2016-10-15 17:22         ` Vladimir Sementsov-Ogievskiy
2016-10-20 12:22     ` Vladimir Sementsov-Ogievskiy
2016-10-21 19:49       ` Max Reitz
2016-10-07 19:25   ` Max Reitz
2016-10-21 11:59     ` Vladimir Sementsov-Ogievskiy
2016-10-21 19:56       ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 08/22] block/dirty-bitmap: add autoload field to BdrvDirtyBitmap Vladimir Sementsov-Ogievskiy
2016-10-07 17:05   ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 09/22] block: introduce persistent dirty bitmaps Vladimir Sementsov-Ogievskiy
2016-10-07 17:54   ` Max Reitz
2016-10-11 13:11     ` Vladimir Sementsov-Ogievskiy
2016-10-12 18:24       ` Max Reitz
2016-10-07 19:28   ` Max Reitz
2016-10-12 11:38     ` Vladimir Sementsov-Ogievskiy
2016-10-12 12:30       ` Vladimir Sementsov-Ogievskiy
2016-10-12 18:25         ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 10/22] block/dirty-bitmap: add bdrv_dirty_bitmap_next() Vladimir Sementsov-Ogievskiy
2016-10-07 18:11   ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 11/22] qcow2-bitmap: add qcow2_store_persistent_bitmaps() Vladimir Sementsov-Ogievskiy
2016-10-07 19:24   ` Max Reitz
2016-10-13 16:48     ` Vladimir Sementsov-Ogievskiy
2016-10-15 16:40       ` Max Reitz
2016-10-17 17:19     ` Vladimir Sementsov-Ogievskiy
2016-10-21 19:44       ` Max Reitz
2016-10-21 21:04         ` Eric Blake
2016-10-17 17:57   ` Vladimir Sementsov-Ogievskiy
2016-10-17 17:58     ` Vladimir Sementsov-Ogievskiy [this message]
2016-09-30 10:53 ` [Qemu-devel] [PATCH 12/22] qcow2-bitmap: add IN_USE flag Vladimir Sementsov-Ogievskiy
2016-10-07 19:44   ` Max Reitz
2016-10-21 15:34     ` Vladimir Sementsov-Ogievskiy
2016-10-21 19:58       ` Max Reitz
2016-10-24 10:32         ` Vladimir Sementsov-Ogievskiy
2016-10-24 11:35           ` Vladimir Sementsov-Ogievskiy
2016-10-24 17:08             ` Max Reitz
2016-10-24 17:18               ` Max Reitz
2016-10-25 10:53                 ` Vladimir Sementsov-Ogievskiy
2016-10-26  9:04                   ` Vladimir Sementsov-Ogievskiy
2016-10-26  9:21                     ` Vladimir Sementsov-Ogievskiy
2016-10-26 12:13                       ` Vladimir Sementsov-Ogievskiy
2016-10-26 13:02                         ` Vladimir Sementsov-Ogievskiy
2016-10-26 15:28                     ` Max Reitz
2016-11-07 16:12                   ` Vladimir Sementsov-Ogievskiy
2016-11-07 16:18                     ` Max Reitz
2016-10-24 16:54           ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 13/22] qcow2-bitmap: check constraints Vladimir Sementsov-Ogievskiy
2016-10-07 19:54   ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 14/22] qcow2: delete bitmaps on truncate Vladimir Sementsov-Ogievskiy
2016-10-07 19:58   ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 15/22] qcow2-bitmap: add autoclear bit Vladimir Sementsov-Ogievskiy
2016-10-07 20:11   ` Max Reitz
2016-10-24 14:25     ` Vladimir Sementsov-Ogievskiy
2016-10-24 17:21       ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 16/22] qmp: add persistent flag to block-dirty-bitmap-add Vladimir Sementsov-Ogievskiy
2016-10-07 19:52   ` Eric Blake
2016-10-24 14:44     ` Vladimir Sementsov-Ogievskiy
2016-10-10 16:08   ` Max Reitz
2016-10-24 15:12     ` Vladimir Sementsov-Ogievskiy
2016-10-24 17:30       ` Max Reitz
2016-10-25 11:05         ` Vladimir Sementsov-Ogievskiy
2016-09-30 10:53 ` [Qemu-devel] [PATCH 17/22] qmp: add autoload parameter " Vladimir Sementsov-Ogievskiy
2016-10-07 19:53   ` Eric Blake
2016-10-10 16:25   ` Max Reitz
2016-10-24 15:55     ` Vladimir Sementsov-Ogievskiy
2016-09-30 10:53 ` [Qemu-devel] [PATCH 18/22] qapi: add md5 checksum of last dirty bitmap level to query-block Vladimir Sementsov-Ogievskiy
2016-10-10 16:44   ` Max Reitz
2016-10-10 17:03     ` Max Reitz
2016-10-10 19:22       ` Eric Blake
2016-09-30 10:53 ` [Qemu-devel] [PATCH 19/22] iotests: test qcow2 persistent dirty bitmap Vladimir Sementsov-Ogievskiy
2016-10-10 17:04   ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 20/22] qcow2-dirty-bitmap: refcounts Vladimir Sementsov-Ogievskiy
2016-10-10 17:59   ` Max Reitz
2016-09-30 10:53 ` [Qemu-devel] [PATCH 21/22] specs/qcow2: fix bitmap granularity qemu-specific note Vladimir Sementsov-Ogievskiy
2016-10-07 20:18   ` Eric Blake
2016-11-09 16:43     ` Vladimir Sementsov-Ogievskiy
2016-09-30 10:53 ` [Qemu-devel] [PATCH 22/22] specs/qcow2: do not use wording 'bitmap header' Vladimir Sementsov-Ogievskiy
2016-10-07 20:20   ` Eric Blake
2016-10-01 13:37 ` [Qemu-devel] [PATCH v7 00/22] qcow2: persistent dirty bitmaps Max Reitz
2016-10-13 18:11   ` John Snow

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=5805114B.8070104@virtuozzo.com \
    --to=vsementsov@virtuozzo.com \
    --cc=armbru@redhat.com \
    --cc=den@openvz.org \
    --cc=eblake@redhat.com \
    --cc=famz@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.