From: Andrew Cooper <andrew.cooper3@citrix.com>
To: Xen-devel <xen-devel@lists.xen.org>
Cc: Andrew Cooper <andrew.cooper3@citrix.com>,
Ian Jackson <Ian.Jackson@eu.citrix.com>,
Ian Campbell <Ian.Campbell@citrix.com>,
Ross Lagerwall <ross.lagerwall@citrix.com>
Subject: [PATCH 26/29] tools/libxl: Implement libxl__domain_restore() for v2 streams
Date: Wed, 10 Sep 2014 18:11:04 +0100 [thread overview]
Message-ID: <1410369067-1330-27-git-send-email-andrew.cooper3@citrix.com> (raw)
In-Reply-To: <1410369067-1330-1-git-send-email-andrew.cooper3@citrix.com>
TODO:
* Integrate with the json series
Signed-off-by: Ross Lagerwall <ross.lagerwall@citrix.com>
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
---
tools/libxl/libxl_create.c | 310 ++++++++++++++++++++++++++++++++++++++++--
tools/libxl/libxl_dom.c | 4 +-
tools/libxl/libxl_internal.h | 10 +-
3 files changed, 310 insertions(+), 14 deletions(-)
diff --git a/tools/libxl/libxl_create.c b/tools/libxl/libxl_create.c
index a5e185e..9661f78 100644
--- a/tools/libxl/libxl_create.c
+++ b/tools/libxl/libxl_create.c
@@ -19,6 +19,7 @@
#include "libxl_internal.h"
#include "libxl_arch.h"
+#include "libxl_saverestore.h"
#include <xc_dom.h>
#include <xenguest.h>
@@ -934,8 +935,6 @@ static void domcreate_bootloader_done(libxl__egc *egc,
libxl_domain_build_info *const info = &d_config->b_info;
const int restore_fd = dcs->restore_fd;
libxl__domain_build_state *const state = &dcs->build_state;
- libxl__srm_restore_autogen_callbacks *const callbacks =
- &dcs->shs.callbacks.restore.a;
if (rc) {
domcreate_rebuild_done(egc, dcs, rc);
@@ -975,7 +974,6 @@ static void domcreate_bootloader_done(libxl__egc *egc,
hvm = 1;
superpages = 1;
pae = libxl_defbool_val(info->u.hvm.pae);
- callbacks->toolstack_restore = libxl__toolstack_restore;
break;
case LIBXL_DOMAIN_TYPE_PV:
hvm = 0;
@@ -1105,12 +1103,16 @@ static void domcreate_rebuild_done(libxl__egc *egc,
goto error_out;
}
- store_libxl_entry(gc, domid, &d_config->b_info);
+ if (dcs->rebuild_callback) {
+ dcs->rebuild_callback(dcs);
+ } else {
+ store_libxl_entry(gc, domid, &d_config->b_info);
- libxl__multidev_begin(ao, &dcs->multidev);
- dcs->multidev.callback = domcreate_launch_dm;
- libxl__add_disks(egc, ao, domid, d_config, &dcs->multidev);
- libxl__multidev_prepared(egc, &dcs->multidev, 0);
+ libxl__multidev_begin(ao, &dcs->multidev);
+ dcs->multidev.callback = domcreate_launch_dm;
+ libxl__add_disks(egc, ao, domid, d_config, &dcs->multidev);
+ libxl__multidev_prepared(egc, &dcs->multidev, 0);
+ }
return;
@@ -1454,6 +1456,11 @@ static void domcreate_destruction_cb(libxl__egc *egc,
typedef struct {
libxl__domain_create_state dcs;
uint32_t *domid_out;
+ libxl__datacopier_state dc;
+ libxl__datacopier_state stream_dc;
+ int expected_len;
+ struct restore_hdr hdr;
+ struct restore_rec_hdr rechdr;
} libxl__app_domain_create_state;
static void domain_create_cb(libxl__egc *egc,
@@ -1517,6 +1524,293 @@ int libxl_domain_create_restore(libxl_ctx *ctx, libxl_domain_config *d_config,
params->checkpointed_stream, ao_how, aop_console_how);
}
+static void read_restore_rec_hdr(libxl__app_domain_create_state *cdcs);
+
+static void read_padding_cb(libxl__egc *egc, libxl__datacopier_state *dc,
+ int onwrite, int errnoval)
+{
+ libxl__app_domain_create_state *cdcs = CONTAINER_OF(dc, *cdcs, stream_dc);
+ STATE_AO_GC(cdcs->dcs.ao);
+ int ret = 0;
+
+ if (onwrite != 0 || errnoval != cdcs->expected_len) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ read_restore_rec_hdr(cdcs);
+
+out:
+ if (ret)
+ libxl__ao_complete(egc, ao, ret);
+}
+
+static void restore_write_dm_cb(libxl__egc *egc,
+ libxl__datacopier_state *dc, int onwrite, int errnoval)
+{
+ libxl__app_domain_create_state *cdcs = CONTAINER_OF(dc, *cdcs, dc);
+ STATE_AO_GC(cdcs->dcs.ao);
+ int ret = 0, padding;
+ libxl__datacopier_state *stream_dc = &cdcs->stream_dc;
+ struct restore_rec_hdr *rechdr = &cdcs->rechdr;
+
+ if (onwrite || errnoval) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ padding = ROUNDUP(rechdr->length - sizeof(struct restore_emulator_hdr),
+ REC_ALIGN_ORDER) - rechdr->length - sizeof(struct restore_emulator_hdr);
+ if (padding > 0) {
+ stream_dc->readwhat = "padding";
+ stream_dc->maxread = padding;
+ cdcs->expected_len = stream_dc->maxread;
+ stream_dc->callback = read_padding_cb;
+ stream_dc->used = 0;
+ stream_dc->readbuf = libxl__malloc(gc, padding);
+ libxl__datacopier_start(stream_dc);
+ } else {
+ read_restore_rec_hdr(cdcs);
+ }
+
+out:
+ if (ret)
+ libxl__ao_complete(egc, ao, ret);
+}
+
+static void read_restore_record_complete(libxl__egc *egc,
+ libxl__app_domain_create_state *cdcs,
+ void *buf)
+{
+ STATE_AO_GC(cdcs->dcs.ao);
+ int ret = 0;
+
+ /* convenience aliases */
+ libxl_ctx *ctx = CTX;
+ libxl__domain_create_state *dcs = &cdcs->dcs;
+ struct restore_rec_hdr *rechdr = &cdcs->rechdr;
+
+ LIBXL__LOG(ctx, LIBXL__LOG_DEBUG,
+ "Record: 0x%08x, length %u", rechdr->type, rechdr->length);
+ switch (rechdr->type) {
+ case REC_TYPE_END:
+ if (rechdr->length != 0) {
+ LIBXL__LOG(ctx, LIBXL__LOG_ERROR,
+ "Encountered END record with non-zero length");
+ ret = ERROR_FAIL;
+ } else {
+ /* complete restore */
+ store_libxl_entry(gc, dcs->guest_domid, &dcs->guest_config->b_info);
+
+ libxl__multidev_begin(ao, &dcs->multidev);
+ dcs->multidev.callback = domcreate_launch_dm;
+ libxl__add_disks(egc, ao, dcs->guest_domid, dcs->guest_config, &dcs->multidev);
+ libxl__multidev_prepared(egc, &dcs->multidev, 0);
+ }
+ break;
+ case REC_TYPE_DOMAIN_JSON:
+ /* XXX handle domain JSON */
+ break;
+ case REC_TYPE_LIBXC_CONTEXT:
+ initiate_domain_create(egc, &cdcs->dcs);
+ break;
+ case REC_TYPE_XENSTORE_DATA:
+ ret = libxl__toolstack_restore(cdcs->dcs.guest_domid, buf,
+ rechdr->length, &cdcs->dcs);
+ if (!ret)
+ read_restore_rec_hdr(cdcs);
+ break;
+ case REC_TYPE_EMULATOR_CONTEXT: {
+ struct restore_emulator_hdr *emuhdr = buf;
+ char path[256];
+ libxl__datacopier_state *dc = &cdcs->dc;
+
+ if (emuhdr->id == EMULATOR_UNKNOWN) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ sprintf(path, XC_DEVICE_MODEL_RESTORE_FILE".%u", cdcs->dcs.guest_domid);
+ memset(dc, 0, sizeof(*dc));
+ dc->ao = ao;
+ dc->readwhat = "save/migration stream";
+ dc->copywhat = "emulator context";
+ dc->writewhat = "qemu save file";
+ dc->readfd = cdcs->dcs.restore_fd;
+ dc->writefd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (dc->writefd == -1) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+ dc->maxsz = INT_MAX;
+ dc->maxread = rechdr->length - sizeof(*emuhdr);
+ dc->callback = restore_write_dm_cb;
+
+ ret = libxl__datacopier_start(dc);
+ if (ret)
+ goto out;
+ break;
+ }
+ default:
+ ret = ERROR_FAIL;
+ break;
+ }
+
+ free(buf);
+
+out:
+ if (ret)
+ libxl__ao_complete(egc, ao, ret);
+}
+
+static void read_restore_rec_body_cb(libxl__egc *egc,
+ libxl__datacopier_state *dc, int onwrite, int errnoval)
+{
+ libxl__app_domain_create_state *cdcs = CONTAINER_OF(dc, *cdcs, stream_dc);
+ STATE_AO_GC(cdcs->dcs.ao);
+ int ret = 0;
+
+ if (onwrite != 0 || errnoval != cdcs->expected_len) {
+ ret = ERROR_FAIL;
+ free(dc->readbuf);
+ goto out;
+ }
+
+ read_restore_record_complete(egc, cdcs, dc->readbuf);
+
+out:
+ if (ret)
+ libxl__ao_complete(egc, ao, ret);
+}
+
+static void read_restore_rec_hdr_cb(libxl__egc *egc,
+ libxl__datacopier_state *dc, int onwrite, int errnoval)
+{
+ libxl__app_domain_create_state *cdcs = CONTAINER_OF(dc, *cdcs, stream_dc);
+ STATE_AO_GC(cdcs->dcs.ao);
+ int ret = 0;
+
+ /* convenience aliases */
+ struct restore_rec_hdr *rechdr = &cdcs->rechdr;
+
+ if (onwrite != 0 || errnoval != cdcs->expected_len) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ if (rechdr->length > 0) {
+ dc->readwhat = "read record body";
+ if (rechdr->type == REC_TYPE_EMULATOR_CONTEXT)
+ dc->maxread = sizeof(struct restore_rec_hdr);
+ else
+ dc->maxread = ROUNDUP(rechdr->length, REC_ALIGN_ORDER);
+ cdcs->expected_len = dc->maxread;
+ dc->callback = read_restore_rec_body_cb;
+ dc->used = 0;
+ dc->readbuf = libxl__malloc(NOGC, dc->maxread);
+ libxl__datacopier_start(dc);
+ } else {
+ read_restore_record_complete(egc, cdcs, NULL);
+ }
+
+out:
+ if (ret)
+ libxl__ao_complete(egc, ao, ret);
+}
+
+static void read_restore_rec_hdr(libxl__app_domain_create_state *cdcs)
+{
+ STATE_AO_GC(cdcs->dcs.ao);
+
+ libxl__datacopier_state *dc = &cdcs->stream_dc;
+ dc->readwhat = "read record header";
+ dc->maxread = sizeof(cdcs->rechdr);
+ cdcs->expected_len = dc->maxread;
+ dc->used = 0;
+ dc->callback = read_restore_rec_hdr_cb;
+ dc->readbuf = &cdcs->rechdr;
+ dc->suppress_pollhup = 1;
+ libxl__datacopier_start(dc);
+}
+
+static void read_restore_hdr_cb(libxl__egc *egc,
+ libxl__datacopier_state *dc, int onwrite, int errnoval)
+{
+ libxl__app_domain_create_state *cdcs = CONTAINER_OF(dc, *cdcs, stream_dc);
+ STATE_AO_GC(cdcs->dcs.ao);
+ int ret = 0;
+
+ /* convenience aliases */
+ libxl_ctx *ctx = CTX;
+ struct restore_hdr *hdr = &cdcs->hdr;
+
+ if (onwrite != 0 || errnoval != sizeof(*hdr)) {
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ hdr->ident = be64toh(hdr->ident);
+ hdr->version = be32toh(hdr->version);
+ hdr->options = be32toh(hdr->options);
+
+ if (hdr->ident != RESTORE_STREAM_IDENT) {
+ LIBXL__LOG(ctx, LIBXL__LOG_ERROR,
+ "Invalid ident: Got 0x%016"PRIx64, hdr->ident);
+ ret = ERROR_FAIL;
+ goto out;
+ }
+ if (hdr->version != RESTORE_STREAM_VERSION) {
+ LIBXL__LOG(ctx, LIBXL__LOG_ERROR,
+ "Invalid Version: Expected %u, Got %u",
+ hdr->version, RESTORE_STREAM_VERSION);
+ ret = ERROR_FAIL;
+ goto out;
+ }
+ if (hdr->options & RESTORE_OPT_BIG_ENDIAN) {
+ LIBXL__LOG(ctx, LIBXL__LOG_ERROR,
+ "Unable to handle big endian streams");
+ ret = ERROR_FAIL;
+ goto out;
+ }
+
+ read_restore_rec_hdr(cdcs);
+
+out:
+ if (ret)
+ libxl__ao_complete(egc, ao, ret);
+}
+
+static void restore_rebuild_complete(libxl__domain_create_state *dcs)
+{
+ libxl__app_domain_create_state *cdcs = CONTAINER_OF(dcs, *cdcs, dcs);
+ STATE_AO_GC(cdcs->dcs.ao);
+
+ read_restore_rec_hdr(cdcs);
+}
+
+static void libxl__domain_restore(libxl__egc *egc,
+ libxl__app_domain_create_state *cdcs)
+{
+ libxl__datacopier_state *dc = &cdcs->stream_dc;
+
+ memset(dc, 0, sizeof(*dc));
+ dc->readwhat = "read header";
+ dc->copywhat = "";
+ dc->writewhat = "";
+ dc->ao = cdcs->dcs.ao;
+ dc->readfd = cdcs->dcs.restore_fd;
+ dc->maxread = sizeof(cdcs->hdr);
+ cdcs->expected_len = dc->maxread;
+ dc->maxsz = INT_MAX;
+ dc->used = 0;
+ dc->callback = read_restore_hdr_cb;
+ dc->writefd = -1;
+ dc->readbuf = &cdcs->hdr;
+
+ libxl__datacopier_start(dc);
+}
+
+
/*
* Local variables:
* mode: C
diff --git a/tools/libxl/libxl_dom.c b/tools/libxl/libxl_dom.c
index 2f74341..4160695 100644
--- a/tools/libxl/libxl_dom.c
+++ b/tools/libxl/libxl_dom.c
@@ -780,10 +780,8 @@ static inline char *restore_helper(libxl__gc *gc, uint32_t domid,
}
int libxl__toolstack_restore(uint32_t domid, const uint8_t *buf,
- uint32_t size, void *user)
+ uint32_t size, libxl__domain_create_state *dcs)
{
- libxl__save_helper_state *shs = user;
- libxl__domain_create_state *dcs = CONTAINER_OF(shs, *dcs, shs);
STATE_AO_GC(dcs->ao);
int i, ret;
const uint8_t *ptr = buf;
diff --git a/tools/libxl/libxl_internal.h b/tools/libxl/libxl_internal.h
index 537b523..3964009 100644
--- a/tools/libxl/libxl_internal.h
+++ b/tools/libxl/libxl_internal.h
@@ -991,8 +991,11 @@ _hidden int libxl__domain_rename(libxl__gc *gc, uint32_t domid,
const char *old_name, const char *new_name,
xs_transaction_t trans);
+typedef struct libxl__domain_create_state libxl__domain_create_state;
+
_hidden int libxl__toolstack_restore(uint32_t domid, const uint8_t *buf,
- uint32_t size, void *data);
+ uint32_t size,
+ libxl__domain_create_state *dcs);
_hidden int libxl__domain_resume_device_model(libxl__gc *gc, uint32_t domid);
_hidden const char *libxl__userdata_path(libxl__gc *gc, uint32_t domid,
@@ -2780,12 +2783,12 @@ _hidden int libxl__destroy_qdisk_backend(libxl__gc *gc, uint32_t domid);
/*----- Domain creation -----*/
-typedef struct libxl__domain_create_state libxl__domain_create_state;
-
typedef void libxl__domain_create_cb(libxl__egc *egc,
libxl__domain_create_state*,
int rc, uint32_t domid);
+typedef void libxl__domain_rebuild_cb(libxl__domain_create_state *dcs);
+
struct libxl__domain_create_state {
/* filled in by user */
libxl__ao *ao;
@@ -2806,6 +2809,7 @@ struct libxl__domain_create_state {
/* necessary if the domain creation failed and we have to destroy it */
libxl__domain_destroy_state dds;
libxl__multidev multidev;
+ libxl__domain_rebuild_cb *rebuild_callback;
};
/*----- Domain suspend (save) functions -----*/
--
1.7.10.4
next prev parent reply other threads:[~2014-09-10 17:11 UTC|newest]
Thread overview: 79+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-09-10 17:10 [PATCH v7 0/29] Migration Stream v2 Andrew Cooper
2014-09-10 17:10 ` [PATCH 01/29] tools/libxl: Fix stray blank line from debug logging Andrew Cooper
2014-09-11 10:18 ` Ian Campbell
2014-09-10 17:10 ` [PATCH 02/29] tools/[lib]xl: Correct use of init/dispose for libxl_domain_restore_params Andrew Cooper
2014-09-11 10:19 ` Ian Campbell
2014-09-10 17:10 ` [PATCH 03/29] tools/libxc: Implement writev_exact() in the same style as write_exact() Andrew Cooper
2014-09-11 10:19 ` Ian Campbell
2014-09-11 10:57 ` Ian Campbell
2014-09-11 10:59 ` Andrew Cooper
2014-09-10 17:10 ` [PATCH 04/29] libxc/bitops: Add or() to the available bitmap operations Andrew Cooper
2014-09-11 10:21 ` Ian Campbell
2014-09-10 17:10 ` [PATCH 05/29] libxc/progress: Repurpose the current progress reporting infrastructure Andrew Cooper
2014-09-11 10:32 ` Ian Campbell
2014-09-11 14:03 ` Andrew Cooper
2014-09-11 14:06 ` Ian Campbell
2014-09-10 17:10 ` [PATCH 06/29] docs: libxc migration stream specification Andrew Cooper
2014-09-10 17:10 ` [PATCH 07/29] docs: libxl " Andrew Cooper
2014-09-11 10:45 ` Ian Campbell
2014-09-11 10:56 ` Andrew Cooper
2014-09-11 11:03 ` Ian Campbell
2014-09-11 11:10 ` Andrew Cooper
2014-09-10 17:10 ` [PATCH 08/29] tools/python: Infrastructure relating to migration v2 streams Andrew Cooper
2014-09-10 17:10 ` [PATCH 09/29] [HACK] tools/libxc: save/restore v2 framework Andrew Cooper
2014-09-11 10:34 ` Ian Campbell
2014-09-11 10:37 ` Andrew Cooper
2014-09-11 11:01 ` Ian Campbell
2014-09-11 11:04 ` Andrew Cooper
2014-09-11 11:10 ` Ian Campbell
2014-09-14 10:23 ` Shriram Rajagopalan
2014-09-15 15:09 ` Andrew Cooper
2014-09-15 18:58 ` Konrad Rzeszutek Wilk
2014-09-16 11:44 ` Andrew Cooper
2014-09-16 19:54 ` Konrad Rzeszutek Wilk
2014-09-10 17:10 ` [PATCH 10/29] tools/libxc: C implementation of stream format Andrew Cooper
2014-09-11 10:48 ` Ian Campbell
2014-09-10 17:10 ` [PATCH 11/29] tools/libxc: noarch common code Andrew Cooper
2014-09-11 10:52 ` Ian Campbell
2014-09-10 17:10 ` [PATCH 12/29] tools/libxc: x86 " Andrew Cooper
2014-09-10 17:10 ` [PATCH 13/29] tools/libxc: x86 PV " Andrew Cooper
2014-09-10 17:10 ` [PATCH 14/29] tools/libxc: x86 PV save code Andrew Cooper
2014-09-10 17:10 ` [PATCH 15/29] tools/libxc: x86 PV restore code Andrew Cooper
2014-09-10 17:10 ` [PATCH 16/29] tools/libxc: x86 HVM save code Andrew Cooper
2014-09-10 17:10 ` [PATCH 17/29] tools/libxc: x86 HVM restore code Andrew Cooper
2014-09-10 17:10 ` [PATCH 18/29] tools/libxc: noarch save code Andrew Cooper
2014-09-10 17:10 ` [PATCH 19/29] tools/libxc: noarch restore code Andrew Cooper
2014-09-10 17:10 ` [PATCH 20/29] tools/libxl: Update datacopier to support sending data only Andrew Cooper
2014-09-11 11:56 ` Ian Campbell
2014-09-11 12:00 ` Andrew Cooper
2014-09-11 12:39 ` Ian Campbell
2014-09-11 13:03 ` Andrew Cooper
2014-09-11 13:04 ` Ian Campbell
2014-09-10 17:10 ` [PATCH 21/29] tools/libxl: Allow adding larger amounts of prefixdata to datacopier Andrew Cooper
2014-09-11 12:01 ` Ian Campbell
2014-09-11 12:17 ` Ross Lagerwall
2014-09-11 12:39 ` Ian Campbell
2014-09-10 17:11 ` [PATCH 22/29] tools/libxl: Allow limiting amount copied by datacopier Andrew Cooper
2014-09-11 12:02 ` Ian Campbell
2014-09-11 12:23 ` Ross Lagerwall
2014-09-11 12:40 ` Ian Campbell
2014-09-12 8:36 ` Wen Congyang
2014-09-19 7:45 ` Ross Lagerwall
2014-09-10 17:11 ` [PATCH 23/29] tools/libxl: Extend datacopier to support reading into a buffer Andrew Cooper
2014-09-11 12:03 ` Ian Campbell
2014-09-11 12:26 ` Ross Lagerwall
2014-09-11 12:41 ` Ian Campbell
2014-09-12 8:49 ` Wen Congyang
2014-09-19 7:48 ` Ross Lagerwall
2014-09-10 17:11 ` [PATCH 24/29] tools/libxl: Allow suppression of POLLHUP for datacopiers Andrew Cooper
2014-09-11 12:05 ` Ian Campbell
2014-09-10 17:11 ` [PATCH 25/29] tools/libxl: Stream v2 format Andrew Cooper
2014-09-11 12:06 ` Ian Campbell
2014-09-10 17:11 ` Andrew Cooper [this message]
2014-09-11 12:35 ` [PATCH 26/29] tools/libxl: Implement libxl__domain_restore() for v2 streams Ian Campbell
2014-09-11 13:01 ` Andrew Cooper
2014-09-10 17:11 ` [PATCH 27/29] [VERY RFC] tools/libxl: Support restoring legacy streams Andrew Cooper
2014-09-11 12:36 ` Ian Campbell
2014-09-10 17:11 ` [PATCH 28/29] tools/xl: Restore v2 streams using new interface Andrew Cooper
2014-09-10 17:11 ` [PATCH 29/29] tools/[lib]xl: Alter libxl_domain_suspend() to write a v2 stream Andrew Cooper
2014-09-11 11:50 ` [PATCH v7 0/29] Migration Stream v2 Ian Campbell
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=1410369067-1330-27-git-send-email-andrew.cooper3@citrix.com \
--to=andrew.cooper3@citrix.com \
--cc=Ian.Campbell@citrix.com \
--cc=Ian.Jackson@eu.citrix.com \
--cc=ross.lagerwall@citrix.com \
--cc=xen-devel@lists.xen.org \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).