All of lore.kernel.org
 help / color / mirror / Atom feed
From: Max Reitz <mreitz@redhat.com>
To: Andrey Shinkevich <andrey.shinkevich@virtuozzo.com>,
	qemu-block@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>,
	Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>,
	qemu-devel@nongnu.org
Subject: Re: [PATCH v7 14/47] stream: Deal with filters
Date: Fri, 10 Jul 2020 17:24:19 +0200	[thread overview]
Message-ID: <63ad2152-3861-1f9f-1bb9-1c14fd68cfbd@redhat.com> (raw)
In-Reply-To: <ed502f80-f4a2-bfb8-7395-5842f8103991@virtuozzo.com>


[-- Attachment #1.1: Type: text/plain, Size: 10688 bytes --]

On 09.07.20 16:52, Andrey Shinkevich wrote:
> On 25.06.2020 18:21, Max Reitz wrote:
>> Because of the (not so recent anymore) changes that make the stream job
>> independent of the base node and instead track the node above it, we
>> have to split that "bottom" node into two cases: The bottom COW node,
>> and the node directly above the base node (which may be an R/W filter
>> or the bottom COW node).
>>
>> Signed-off-by: Max Reitz <mreitz@redhat.com>
>> ---
>>   qapi/block-core.json |  4 +++
>>   block/stream.c       | 63 ++++++++++++++++++++++++++++++++------------
>>   blockdev.c           |  4 ++-
>>   3 files changed, 53 insertions(+), 18 deletions(-)
>>
>> diff --git a/qapi/block-core.json b/qapi/block-core.json
>> index b20332e592..df87855429 100644
>> --- a/qapi/block-core.json
>> +++ b/qapi/block-core.json
>> @@ -2486,6 +2486,10 @@
>>   # On successful completion the image file is updated to drop the
>> backing file
>>   # and the BLOCK_JOB_COMPLETED event is emitted.
>>   #
>> +# In case @device is a filter node, block-stream modifies the first
>> non-filter
>> +# overlay node below it to point to base's backing node (or NULL if
>> @base was
>> +# not specified) instead of modifying @device itself.
>> +#
>>   # @job-id: identifier for the newly-created block job. If
>>   #          omitted, the device name will be used. (Since 2.7)
>>   #
>> diff --git a/block/stream.c b/block/stream.c
>> index aa2e7af98e..b9c1141656 100644
>> --- a/block/stream.c
>> +++ b/block/stream.c
>> @@ -31,7 +31,8 @@ enum {
>>     typedef struct StreamBlockJob {
>>       BlockJob common;
>> -    BlockDriverState *bottom;
>> +    BlockDriverState *base_overlay; /* COW overlay (stream from this) */
>> +    BlockDriverState *above_base;   /* Node directly above the base */
> 
> Keeping the base_overlay is enough to complete the stream job.

Depends on the definition.  If we decide it isn’t enough, then it isn’t
enough.

> The above_base may disappear during the job and we can't rely on it.

In this version of this series, it may not, because the chain is frozen.
 So the above_base cannot disappear.

We can discuss whether we should allow it to disappear, but I think not.

The problem is, we need something to set as the backing file after
streaming.  How do we figure out what that should be?  My proposal is we
keep above_base and use its immediate child.

If we don’t keep above_base, then we’re basically left guessing as to
what should be the backing file after the stream job.

>>       BlockdevOnError on_error;
>>       char *backing_file_str;
>>       bool bs_read_only;
>> @@ -53,7 +54,7 @@ static void stream_abort(Job *job)
>>         if (s->chain_frozen) {
>>           BlockJob *bjob = &s->common;
>> -        bdrv_unfreeze_backing_chain(blk_bs(bjob->blk), s->bottom);
>> +        bdrv_unfreeze_backing_chain(blk_bs(bjob->blk), s->above_base);
>>       }
>>   }
>>   @@ -62,14 +63,15 @@ static int stream_prepare(Job *job)
>>       StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
>>       BlockJob *bjob = &s->common;
>>       BlockDriverState *bs = blk_bs(bjob->blk);
>> -    BlockDriverState *base = backing_bs(s->bottom);
>> +    BlockDriverState *unfiltered_bs = bdrv_skip_filters(bs);
>> +    BlockDriverState *base = bdrv_filter_or_cow_bs(s->above_base);
> 
> The initial base node may be a top node for a concurrent commit job and
> 
> may disappear.
Then it would just be replaced by another node, though, so above_base
keeps a child.  The @base here is not necessarily the initial @base, and
that’s intentional.

> base = bdrv_filter_or_cow_bs(s->base_overlay) is more reliable.

But also wrong.  The point of keeping above_base around is to get its
child here to use that child as the new backing child of the top node.

>>       Error *local_err = NULL;
>>       int ret = 0;
>>   -    bdrv_unfreeze_backing_chain(bs, s->bottom);
>> +    bdrv_unfreeze_backing_chain(bs, s->above_base);
>>       s->chain_frozen = false;
>>   -    if (bs->backing) {
>> +    if (bdrv_cow_child(unfiltered_bs)) {
>>           const char *base_id = NULL, *base_fmt = NULL;
>>           if (base) {
>>               base_id = s->backing_file_str;
>> @@ -77,8 +79,8 @@ static int stream_prepare(Job *job)
>>                   base_fmt = base->drv->format_name;
>>               }
>>           }
>> -        bdrv_set_backing_hd(bs, base, &local_err);
>> -        ret = bdrv_change_backing_file(bs, base_id, base_fmt);
>> +        bdrv_set_backing_hd(unfiltered_bs, base, &local_err);
>> +        ret = bdrv_change_backing_file(unfiltered_bs, base_id,
>> base_fmt);
>>           if (local_err) {
>>               error_report_err(local_err);
>>               return -EPERM;
>> @@ -109,14 +111,15 @@ static int coroutine_fn stream_run(Job *job,
>> Error **errp)
>>       StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
>>       BlockBackend *blk = s->common.blk;
>>       BlockDriverState *bs = blk_bs(blk);
>> -    bool enable_cor = !backing_bs(s->bottom);
>> +    BlockDriverState *unfiltered_bs = bdrv_skip_filters(bs);
>> +    bool enable_cor = !bdrv_cow_child(s->base_overlay);
>>       int64_t len;
>>       int64_t offset = 0;
>>       uint64_t delay_ns = 0;
>>       int error = 0;
>>       int64_t n = 0; /* bytes */
>>   -    if (bs == s->bottom) {
>> +    if (unfiltered_bs == s->base_overlay) {
>>           /* Nothing to stream */
>>           return 0;
>>       }
>> @@ -150,13 +153,14 @@ static int coroutine_fn stream_run(Job *job,
>> Error **errp)
>>             copy = false;
>>   -        ret = bdrv_is_allocated(bs, offset, STREAM_CHUNK, &n);
>> +        ret = bdrv_is_allocated(unfiltered_bs, offset, STREAM_CHUNK,
>> &n);
>>           if (ret == 1) {
>>               /* Allocated in the top, no need to copy.  */
>>           } else if (ret >= 0) {
>>               /* Copy if allocated in the intermediate images.  Limit
>> to the
>>                * known-unallocated area [offset,
>> offset+n*BDRV_SECTOR_SIZE).  */
>> -            ret = bdrv_is_allocated_above(backing_bs(bs), s->bottom,
>> true,
>> +            ret = bdrv_is_allocated_above(bdrv_cow_bs(unfiltered_bs),
>> +                                          s->base_overlay, true,
>>                                             offset, n, &n);
>>               /* Finish early if end of backing file has been reached */
>>               if (ret == 0 && n == 0) {
>> @@ -223,9 +227,29 @@ void stream_start(const char *job_id,
>> BlockDriverState *bs,
>>       BlockDriverState *iter;
>>       bool bs_read_only;
>>       int basic_flags = BLK_PERM_CONSISTENT_READ |
>> BLK_PERM_WRITE_UNCHANGED;
>> -    BlockDriverState *bottom = bdrv_find_overlay(bs, base);
>> +    BlockDriverState *base_overlay = bdrv_find_overlay(bs, base);
>> +    BlockDriverState *above_base;
>>   -    if (bdrv_freeze_backing_chain(bs, bottom, errp) < 0) {
>> +    if (!base_overlay) {
>> +        error_setg(errp, "'%s' is not in the backing chain of '%s'",
>> +                   base->node_name, bs->node_name);
> 
> Sorry, I am not clear with the error message.
> 
> In this case, there is no an intermediate COW node but the base, if not
> NULL, is
> 
> in the backing chain of bs, isn't it?
> 
>> +        return;
>> +    }
>> +
>> +    /*
>> +     * Find the node directly above @base.  @base_overlay is a COW
>> overlay, so
>> +     * it must have a bdrv_cow_child(), but it is the immediate
>> overlay of
>> +     * @base, so between the two there can only be filters.
>> +     */
>> +    above_base = base_overlay;
>> +    if (bdrv_cow_bs(above_base) != base) {
>> +        above_base = bdrv_cow_bs(above_base);
>> +        while (bdrv_filter_bs(above_base) != base) {
>> +            above_base = bdrv_filter_bs(above_base);
>> +        }
>> +    }
>> +
>> +    if (bdrv_freeze_backing_chain(bs, above_base, errp) < 0) {
> 
> When a concurrent stream job tries to freeze or remove the above_base node,
> 
> we will encounter the frozen node error. The above_base node is a part
> of the
> 
> concurrent job frozen chain.

Correct.

>>           return;
>>       }
>>   @@ -255,14 +279,19 @@ void stream_start(const char *job_id,
>> BlockDriverState *bs,
>>        * and resizes. Reassign the base node pointer because the
>> backing BS of the
>>        * bottom node might change after the call to
>> bdrv_reopen_set_read_only()
>>        * due to parallel block jobs running.
>> +     * above_base node might change after the call to
> Yes, if not frozen.
>> +     * bdrv_reopen_set_read_only() due to parallel block jobs running.
>>        */
>> -    base = backing_bs(bottom);
>> -    for (iter = backing_bs(bs); iter && iter != base; iter =
>> backing_bs(iter)) {
>> +    base = bdrv_filter_or_cow_bs(above_base);
>> +    for (iter = bdrv_filter_or_cow_bs(bs); iter != base;
>> +         iter = bdrv_filter_or_cow_bs(iter))
>> +    {
>>           block_job_add_bdrv(&s->common, "intermediate node", iter, 0,
>>                              basic_flags, &error_abort);
>>       }
>>   -    s->bottom = bottom;
>> +    s->base_overlay = base_overlay;
>> +    s->above_base = above_base;
> 
> Generally, being the filter for a concurrent job, the above_base node
> may be deleted any time
> 
> and we will keep the dangling pointer. It may happen even earlier if
> above_base is not frozen.
> 
> If it is, as it here, we may get the frozen link error then.

I’m not sure what you mean here.  Freezing it was absolutely
intentional.  A dangling pointer would be a problem, but that’s why it’s
frozen, so it stays around and can’t be deleted any time.

Max


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

  parent reply	other threads:[~2020-07-10 15:25 UTC|newest]

Thread overview: 173+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-06-25 15:21 [PATCH v7 00/47] block: Deal with filters Max Reitz
2020-06-25 15:21 ` [PATCH v7 01/47] block: Add child access functions Max Reitz
2020-07-08 17:22   ` Andrey Shinkevich
2020-07-13  9:06   ` Vladimir Sementsov-Ogievskiy
2020-07-16 14:46     ` Max Reitz
2020-07-28 16:09     ` Christophe de Dinechin
2020-08-07  9:33       ` Vladimir Sementsov-Ogievskiy
2020-07-13  9:57   ` Vladimir Sementsov-Ogievskiy
2020-06-25 15:21 ` [PATCH v7 02/47] block: Add chain helper functions Max Reitz
2020-07-08 17:20   ` Andrey Shinkevich
2020-07-09  8:24     ` Max Reitz
2020-07-09  9:07       ` Andrey Shinkevich
2020-07-13 10:18   ` Vladimir Sementsov-Ogievskiy
2020-07-16 14:50     ` Max Reitz
2020-07-16 15:24       ` Vladimir Sementsov-Ogievskiy
2020-06-25 15:21 ` [PATCH v7 03/47] block: bdrv_cow_child() for bdrv_has_zero_init() Max Reitz
2020-07-08 17:23   ` Andrey Shinkevich
2020-08-07  9:37   ` Vladimir Sementsov-Ogievskiy
2020-06-25 15:21 ` [PATCH v7 04/47] block: bdrv_set_backing_hd() is about bs->backing Max Reitz
2020-07-08 17:24   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 05/47] block: Include filters when freezing backing chain Max Reitz
2020-07-08 17:25   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 06/47] block: Drop bdrv_is_encrypted() Max Reitz
2020-07-08 17:41   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 07/47] block: Add bdrv_supports_compressed_writes() Max Reitz
2020-07-08 17:48   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 08/47] throttle: Support compressed writes Max Reitz
2020-07-08 17:52   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 09/47] copy-on-read: " Max Reitz
2020-07-08 17:54   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 10/47] mirror-top: " Max Reitz
2020-07-08 17:58   ` Andrey Shinkevich
2020-08-18 10:27   ` Kevin Wolf
2020-08-19 15:35     ` Max Reitz
2020-08-19 16:00       ` Kevin Wolf
2020-06-25 15:21 ` [PATCH v7 11/47] backup-top: " Max Reitz
2020-07-08 17:59   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 12/47] block: Use bdrv_filter_(bs|child) where obvious Max Reitz
2020-07-08 18:24   ` Andrey Shinkevich
2020-07-09  8:59     ` Max Reitz
2020-07-09  9:11       ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 13/47] block: Use CAFs in block status functions Max Reitz
2020-07-08 19:13   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 14/47] stream: Deal with filters Max Reitz
2020-07-09 14:52   ` Andrey Shinkevich
2020-07-09 15:27     ` Andrey Shinkevich
2020-07-10 15:24     ` Max Reitz [this message]
2020-07-10 17:41       ` Andrey Shinkevich
2020-07-16 14:59         ` Max Reitz
2020-08-07 10:29           ` Vladimir Sementsov-Ogievskiy
2020-08-10  8:12             ` Max Reitz
2020-08-10 11:04               ` Vladimir Sementsov-Ogievskiy
2020-08-14 15:18                 ` Andrey Shinkevich
2020-08-18 20:45                 ` Andrey Shinkevich
2020-08-19 12:39                 ` Max Reitz
2020-08-19 13:18                   ` Vladimir Sementsov-Ogievskiy
2020-07-09 15:13   ` Andrey Shinkevich
2020-07-10 15:27     ` Max Reitz
2020-08-18 14:28   ` Kevin Wolf
2020-08-19 14:47     ` Max Reitz
2020-08-19 15:16       ` Kevin Wolf
2020-08-20  8:31         ` Max Reitz
2020-08-20  9:22           ` Max Reitz
2020-08-20 10:49             ` Vladimir Sementsov-Ogievskiy
2020-08-20 11:43               ` Max Reitz
2020-06-25 15:21 ` [PATCH v7 15/47] block: Use CAFs when working with backing chains Max Reitz
2020-07-10 15:28   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 16/47] block: Use bdrv_cow_child() in bdrv_co_truncate() Max Reitz
2020-07-10 15:54   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 17/47] block: Re-evaluate backing file handling in reopen Max Reitz
2020-07-10 19:42   ` Andrey Shinkevich
2020-07-16 15:04     ` Max Reitz
2020-06-25 15:21 ` [PATCH v7 18/47] block: Flush all children in generic code Max Reitz
2020-07-14 12:52   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 19/47] vmdk: Drop vmdk_co_flush() Max Reitz
2020-07-14 14:52   ` Andrey Shinkevich
2020-07-16 15:08     ` Max Reitz
2020-06-25 15:21 ` [PATCH v7 20/47] block: Iterate over children in refresh_limits Max Reitz
2020-07-14 18:37   ` Andrey Shinkevich
2020-07-16 15:14     ` Max Reitz
2020-06-25 15:21 ` [PATCH v7 21/47] block: Use CAFs in bdrv_refresh_filename() Max Reitz
2020-07-15 12:52   ` Andrey Shinkevich
2020-07-15 12:58     ` Andrey Shinkevich
2020-07-16 15:21     ` Max Reitz
2020-06-25 15:21 ` [PATCH v7 22/47] block: Use CAF in bdrv_co_rw_vmstate() Max Reitz
2020-07-15 13:39   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 23/47] block/snapshot: Fix fallback Max Reitz
2020-07-15 21:22   ` Andrey Shinkevich
2020-07-15 22:18     ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 24/47] block: Use CAFs for debug breakpoints Max Reitz
2020-07-15 21:43   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 25/47] block: Def. impl.s for get_allocated_file_size Max Reitz
2020-07-15 22:56   ` Andrey Shinkevich
2020-08-19 10:57   ` Kevin Wolf
2020-08-19 15:53     ` Max Reitz
2020-06-25 15:21 ` [PATCH v7 26/47] block: Improve get_allocated_file_size's default Max Reitz
2020-07-20 15:12   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 27/47] blkverify: Use bdrv_sum_allocated_file_size() Max Reitz
2020-07-20 15:10   ` Andrey Shinkevich
2020-08-19 10:46   ` Kevin Wolf
2020-08-19 15:50     ` Max Reitz
2020-06-25 15:21 ` [PATCH v7 28/47] block/null: Implement bdrv_get_allocated_file_size Max Reitz
2020-07-20 15:10   ` Andrey Shinkevich
2020-07-24  8:58     ` Max Reitz
2020-07-24  9:49       ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 29/47] blockdev: Use CAF in external_snapshot_prepare() Max Reitz
2020-07-20 16:08   ` Andrey Shinkevich
2020-07-24  9:23     ` Max Reitz
2020-07-24 10:37       ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 30/47] block: Report data child for query-blockstats Max Reitz
2020-07-21 11:48   ` Andrey Shinkevich
2020-06-25 15:21 ` [PATCH v7 31/47] block: Use child access functions for QAPI queries Max Reitz
2020-07-21 12:30   ` Andrey Shinkevich
2020-06-25 15:22 ` [PATCH v7 32/47] block-copy: Use CAF to find sync=top base Max Reitz
2020-07-21 12:42   ` Andrey Shinkevich
2020-06-25 15:22 ` [PATCH v7 33/47] mirror: Deal with filters Max Reitz
2020-07-22 18:31   ` Andrey Shinkevich
2020-07-24  9:49     ` Max Reitz
2020-07-24 10:27       ` Andrey Shinkevich
2020-08-19 16:50   ` Kevin Wolf
2020-08-20 10:28     ` Max Reitz
2020-06-25 15:22 ` [PATCH v7 34/47] backup: " Max Reitz
2020-07-23 15:51   ` Andrey Shinkevich
2020-07-24  9:55     ` Max Reitz
2020-06-25 15:22 ` [PATCH v7 35/47] commit: " Max Reitz
2020-07-23 17:15   ` Andrey Shinkevich
2020-07-24 10:36     ` Andrey Shinkevich
2020-08-19 17:58   ` Kevin Wolf
2020-08-20 11:27     ` Max Reitz
2020-08-20 13:47       ` Kevin Wolf
2020-06-25 15:22 ` [PATCH v7 36/47] nbd: Use CAF when looking for dirty bitmap Max Reitz
2020-07-23 17:21   ` Andrey Shinkevich
2020-06-25 15:22 ` [PATCH v7 37/47] qemu-img: Use child access functions Max Reitz
2020-07-24 15:51   ` Andrey Shinkevich
2020-08-21 15:29   ` Kevin Wolf
2020-08-24 12:42     ` Max Reitz
2020-06-25 15:22 ` [PATCH v7 38/47] block: Drop backing_bs() Max Reitz
2020-07-24 15:55   ` Andrey Shinkevich
2020-06-25 15:22 ` [PATCH v7 39/47] blockdev: Fix active commit choice Max Reitz
2020-08-21 15:50   ` Kevin Wolf
2020-08-24 13:18     ` Max Reitz
2020-08-24 14:07       ` Kevin Wolf
2020-08-24 14:41         ` Max Reitz
2020-08-24 15:06           ` Kevin Wolf
2020-06-25 15:22 ` [PATCH v7 40/47] block: Inline bdrv_co_block_status_from_*() Max Reitz
2020-07-24 18:00   ` Andrey Shinkevich
2020-06-25 15:22 ` [PATCH v7 41/47] block: Leave BDS.backing_file constant Max Reitz
2020-07-27 12:27   ` Andrey Shinkevich
2020-07-28 14:10     ` Max Reitz
2020-08-24 13:14   ` Kevin Wolf
2020-08-24 14:29     ` Max Reitz
2020-06-25 15:22 ` [PATCH v7 42/47] iotests: Test that qcow2's data-file is flushed Max Reitz
2020-07-27 13:28   ` Andrey Shinkevich
2020-06-25 15:22 ` [PATCH v7 43/47] iotests: Let complete_and_wait() work with commit Max Reitz
2020-07-27 13:35   ` Andrey Shinkevich
2020-06-25 15:22 ` [PATCH v7 44/47] iotests: Add filter commit test cases Max Reitz
2020-07-27 17:45   ` Andrey Shinkevich
2020-07-28 14:00     ` Max Reitz
2020-06-25 15:22 ` [PATCH v7 45/47] iotests: Add filter mirror " Max Reitz
2020-08-02 11:05   ` Andrey Shinkevich
2020-06-25 15:22 ` [PATCH v7 46/47] iotests: Add test for commit in sub directory Max Reitz
2020-08-02 12:13   ` Andrey Shinkevich
2020-06-25 15:22 ` [PATCH v7 47/47] iotests: Test committing to overridden backing Max Reitz
2020-08-02 11:43   ` Andrey Shinkevich
2020-07-08 17:20 ` [PATCH v7 00/47] block: Deal with filters Andrey Shinkevich
2020-07-08 17:32   ` Eric Blake
2020-07-08 19:46     ` Andrey Shinkevich
2020-07-08 20:37       ` Eric Blake
2020-07-09  8:19         ` Max Reitz
2020-07-08 20:47   ` Eric Blake
2020-07-09  8:20     ` Max Reitz
2020-07-09  9:04       ` Andrey Shinkevich
2020-08-24 15:15 ` Kevin Wolf

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=63ad2152-3861-1f9f-1bb9-1c14fd68cfbd@redhat.com \
    --to=mreitz@redhat.com \
    --cc=andrey.shinkevich@virtuozzo.com \
    --cc=kwolf@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=vsementsov@virtuozzo.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.