From: "Daniel P. Berrangé" <berrange@redhat.com>
To: Christophe de Dinechin <dinechin@redhat.com>
Cc: "Eduardo Habkost" <ehabkost@redhat.com>,
"Michael S. Tsirkin" <mst@redhat.com>,
qemu-trivial@nongnu.org, "Michael Tokarev" <mjt@tls.msk.ru>,
qemu-devel@nongnu.org, "Laurent Vivier" <laurent@vivier.eu>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Gerd Hoffmann" <kraxel@redhat.com>,
"Cleber Rosa" <crosa@redhat.com>,
"Marc-André Lureau" <marcandre.lureau@redhat.com>,
"Dr. David Alan Gilbert" <dgilbert@redhat.com>,
"Richard Henderson" <rth@twiddle.net>
Subject: Re: [PATCH 09/10] spice: Put spice functions in a separate load module
Date: Mon, 29 Jun 2020 18:30:35 +0100 [thread overview]
Message-ID: <20200629173035.GA1298906@redhat.com> (raw)
In-Reply-To: <lyo8p1dc2a.fsf@redhat.com>
On Mon, Jun 29, 2020 at 07:19:41PM +0200, Christophe de Dinechin wrote:
>
> On 2020-06-26 at 19:35 CEST, Daniel P. Berrangé wrote...
> > On Fri, Jun 26, 2020 at 06:43:06PM +0200, Christophe de Dinechin wrote:
> >> Use the MODIFACE and MODIMPL macros to to redirect the highest-level
> >> qemu_spice functions into the spice-app.so load module when SPICE is
> >> compiled as a module.
> >>
> >> With these changes, the following shared libraries are no longer
> >> necessary in the top-level qemu binary:
> >>
> >> libspice-server.so.1 => /lib64/libspice-server.so.1 (HEX)
> >> libopus.so.0 => /lib64/libopus.so.0 (HEX)
> >> liblz4.so.1 => /lib64/liblz4.so.1 (HEX)
> >> libgstapp-1.0.so.0 => /lib64/libgstapp-1.0.so.0 (HEX)
> >> libgstvideo-1.0.so.0 => /lib64/libgstvideo-1.0.so.0 (HEX)
> >> libgstbase-1.0.so.0 => /lib64/libgstbase-1.0.so.0 (HEX)
> >> libgstreamer-1.0.so.0 => /lib64/libgstreamer-1.0.so.0 (HEX)
> >> libssl.so.1.1 => /lib64/libssl.so.1.1 (HEX)
> >> liborc-0.4.so.0 => /lib64/liborc-0.4.so.0 (HEX)
> >>
> >> Signed-off-by: Christophe de Dinechin <dinechin@redhat.com>
> >> ---
> >> include/ui/qemu-spice.h | 24 +++++++++++++++---------
> >> monitor/hmp-cmds.c | 6 ++++++
> >> softmmu/vl.c | 1 +
> >> ui/spice-core.c | 31 +++++++++++++++++++++----------
> >> ui/spice-display.c | 2 +-
> >> 5 files changed, 44 insertions(+), 20 deletions(-)
> >>
> >> diff --git a/include/ui/qemu-spice.h b/include/ui/qemu-spice.h
> >> index 8c23dfe717..0f7e139da5 100644
> >> --- a/include/ui/qemu-spice.h
> >> +++ b/include/ui/qemu-spice.h
> >> @@ -24,22 +24,28 @@
> >>
> >> #include <spice.h>
> >> #include "qemu/config-file.h"
> >> +#include "qemu/module.h"
> >>
> >> -extern int using_spice;
> >> +#define using_spice (qemu_is_using_spice())
> >>
> >> -void qemu_spice_init(void);
> >> +MODIFACE(bool, qemu_is_using_spice,(void));
> >> +MODIFACE(void, qemu_start_using_spice, (void));
> >> +MODIFACE(void, qemu_spice_init, (void));
> >> void qemu_spice_input_init(void);
> >> void qemu_spice_audio_init(void);
> >> -void qemu_spice_display_init(void);
> >> -int qemu_spice_display_add_client(int csock, int skipauth, int tls);
> >> +MODIFACE(void, qemu_spice_display_init, (void));
> >> +MODIFACE(int, qemu_spice_display_add_client, (int csock, int skipauth, int tls));
> >> int qemu_spice_add_interface(SpiceBaseInstance *sin);
> >> bool qemu_spice_have_display_interface(QemuConsole *con);
> >> int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con);
> >> -int qemu_spice_set_passwd(const char *passwd,
> >> - bool fail_if_connected, bool disconnect_if_connected);
> >> -int qemu_spice_set_pw_expire(time_t expires);
> >> -int qemu_spice_migrate_info(const char *hostname, int port, int tls_port,
> >> - const char *subject);
> >> +MODIFACE(int, qemu_spice_set_passwd, (const char *passwd,
> >> + bool fail_if_connected,
> >> + bool disconnect_if_connected));
> >> +MODIFACE(int, qemu_spice_set_pw_expire,(time_t expires));
> >> +MODIFACE(int, qemu_spice_migrate_info,(const char *hostname,
> >> + int port, int tls_port,
> >> + const char *subject));
> >> +MODIFACE(struct SpiceInfo *,qemu_spice_query, (Error **errp));
> >
> > This macro usage looks kind of unpleasant and its hard to understand
> > just what is going on, especially why some methods are changed but
> > others are not.
>
> The functions that are changed are the module interface between qemu and the
> DSO. For example, qemu_spice_init is called from vl.c, i.e. the main binary,
> but qemu_spice_audio_init is called from ui/spice-core.c and
> ui/spice-input.c, which are both in the DSO after the commit.
>
> The existing function-based interface in qemu-spice.h was not really
> carefully designed for modularization, so this list was determined by
> following the initialization path. It may not be the smallest possible cut.
> It may be neat to add a patch to reorder functions based on whether they are
> inside the DSO or exported from it.
>
> As for the macro syntax, I see it as somewhat transient. I wanted to propose
> a working and scalable mechanism before adding some nice syntactic sugar
> tooling to it. I expect the syntax to turn into something like:
>
> MODIFACE void qemu_spice_display_init (void);
> MODIIMPL void qemu_spice_display_init (void) { ... }
>
> But it feels a bit too early to do that. I prefer to experiment with a
> simple macro for now.
>
> >
> > IIUC, the goal is to turn all these into weak symbols so they don't
> > need to be resolved immediately at startup, and instead have an
> > impl of them provided dynamically at runtime.
>
> My very first approach was indeed to use weak symbols, but then I learned
> that ld.so no longer deals with weak symbols, apparently for security
> reasons. See LD_DYNAMIC_WEAK in ld.so(8).
>
> >
> > If so the more normal approach would be to have a struct defining
> > a set of callbacks, that can be registered. Or if there's a natural
> > fit with QOM, then a QOM interface that can then have a QOM object
> > impl registered as a singleton.
>
> That was my second attempt (after the weak symbols). I cleaned it up a bit
> and put it here: https://github.com/c3d/qemu/commits/spice-vtable.
>
> What made me switch to the approach in this series is the following
> considerations:
>
> - A vtable is useful if there can be multiple values for a method, e.g. to
> implement inheritance, or if you have multiple instances. This is not the
> case here.
IMHO a vtable is fine for singleton objects. It is a generic way to
remove tight coupling between caller(s) and an implementation. Just
having 1 implementation doesn't invalidate the model.
> - A vtable adds one level of indirection, which does not seem to be
> particularly useful or helpful for this particular use case.
>
> - Overloading QOM for that purpose looked more confusing than anything else.
> It looked like I was mixing unrelated concepts. Maybe that's just me.
>
> - The required change with a vtable ends up being more extensive. Instead of
> changing a single line to put an entry point in a DSO, you need to create
> the vtable, add functions to it, add a register function, etc. I was
> looking for an easier and more scalable way.
>
> - In particular, with a vtable, you cannot take advantage of the syntactic
> trick I used here, which is that foo(x) is a shortcut for (*foo)(x).
> So for a vtable, you need to manually write wrappers.
>
> This could be automated, of course, but so far I did not find any clear
> benefit, and many drawbacks to using the vtable approach. As a quantitative
> comparison point, the commit that does this same connection using the vtable
> approach is:
>
> include/ui/qemu-spice.h | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
> monitor/hmp-cmds.c | 6 +++++
> softmmu/vl.c | 10 +++++++
> ui/spice-core.c | 38 ++++++++++++++++++++++++---
> 4 files changed, 148 insertions(+), 10 deletions(-)
>
> as opposed to what is presented in this series:
>
> include/ui/qemu-spice.h | 24 +++++++++++++++---------
> monitor/hmp-cmds.c | 6 ++++++
> softmmu/vl.c | 1 +
> ui/spice-core.c | 31 +++++++++++++++++++++----------
> ui/spice-display.c | 2 +-
> 5 files changed, 44 insertions(+), 20 deletions(-)
I much prefer the code in the vtable patch version. The larger number of
lines of code doesn't bother me, because the code is really simple and
clear to read. IOW I prefer the clarity of the longer code, over the
shorter code with magic hidden behind it.
Regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
WARNING: multiple messages have this Message-ID (diff)
From: "Daniel P. Berrangé" <berrange@redhat.com>
To: Christophe de Dinechin <dinechin@redhat.com>
Cc: "Eduardo Habkost" <ehabkost@redhat.com>,
"Michael S. Tsirkin" <mst@redhat.com>,
qemu-trivial@nongnu.org, "Michael Tokarev" <mjt@tls.msk.ru>,
"Laurent Vivier" <laurent@vivier.eu>,
qemu-devel@nongnu.org,
"Marc-André Lureau" <marcandre.lureau@redhat.com>,
"Gerd Hoffmann" <kraxel@redhat.com>,
"Cleber Rosa" <crosa@redhat.com>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Dr. David Alan Gilbert" <dgilbert@redhat.com>,
"Richard Henderson" <rth@twiddle.net>
Subject: Re: [PATCH 09/10] spice: Put spice functions in a separate load module
Date: Mon, 29 Jun 2020 18:30:35 +0100 [thread overview]
Message-ID: <20200629173035.GA1298906@redhat.com> (raw)
In-Reply-To: <lyo8p1dc2a.fsf@redhat.com>
On Mon, Jun 29, 2020 at 07:19:41PM +0200, Christophe de Dinechin wrote:
>
> On 2020-06-26 at 19:35 CEST, Daniel P. Berrangé wrote...
> > On Fri, Jun 26, 2020 at 06:43:06PM +0200, Christophe de Dinechin wrote:
> >> Use the MODIFACE and MODIMPL macros to to redirect the highest-level
> >> qemu_spice functions into the spice-app.so load module when SPICE is
> >> compiled as a module.
> >>
> >> With these changes, the following shared libraries are no longer
> >> necessary in the top-level qemu binary:
> >>
> >> libspice-server.so.1 => /lib64/libspice-server.so.1 (HEX)
> >> libopus.so.0 => /lib64/libopus.so.0 (HEX)
> >> liblz4.so.1 => /lib64/liblz4.so.1 (HEX)
> >> libgstapp-1.0.so.0 => /lib64/libgstapp-1.0.so.0 (HEX)
> >> libgstvideo-1.0.so.0 => /lib64/libgstvideo-1.0.so.0 (HEX)
> >> libgstbase-1.0.so.0 => /lib64/libgstbase-1.0.so.0 (HEX)
> >> libgstreamer-1.0.so.0 => /lib64/libgstreamer-1.0.so.0 (HEX)
> >> libssl.so.1.1 => /lib64/libssl.so.1.1 (HEX)
> >> liborc-0.4.so.0 => /lib64/liborc-0.4.so.0 (HEX)
> >>
> >> Signed-off-by: Christophe de Dinechin <dinechin@redhat.com>
> >> ---
> >> include/ui/qemu-spice.h | 24 +++++++++++++++---------
> >> monitor/hmp-cmds.c | 6 ++++++
> >> softmmu/vl.c | 1 +
> >> ui/spice-core.c | 31 +++++++++++++++++++++----------
> >> ui/spice-display.c | 2 +-
> >> 5 files changed, 44 insertions(+), 20 deletions(-)
> >>
> >> diff --git a/include/ui/qemu-spice.h b/include/ui/qemu-spice.h
> >> index 8c23dfe717..0f7e139da5 100644
> >> --- a/include/ui/qemu-spice.h
> >> +++ b/include/ui/qemu-spice.h
> >> @@ -24,22 +24,28 @@
> >>
> >> #include <spice.h>
> >> #include "qemu/config-file.h"
> >> +#include "qemu/module.h"
> >>
> >> -extern int using_spice;
> >> +#define using_spice (qemu_is_using_spice())
> >>
> >> -void qemu_spice_init(void);
> >> +MODIFACE(bool, qemu_is_using_spice,(void));
> >> +MODIFACE(void, qemu_start_using_spice, (void));
> >> +MODIFACE(void, qemu_spice_init, (void));
> >> void qemu_spice_input_init(void);
> >> void qemu_spice_audio_init(void);
> >> -void qemu_spice_display_init(void);
> >> -int qemu_spice_display_add_client(int csock, int skipauth, int tls);
> >> +MODIFACE(void, qemu_spice_display_init, (void));
> >> +MODIFACE(int, qemu_spice_display_add_client, (int csock, int skipauth, int tls));
> >> int qemu_spice_add_interface(SpiceBaseInstance *sin);
> >> bool qemu_spice_have_display_interface(QemuConsole *con);
> >> int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con);
> >> -int qemu_spice_set_passwd(const char *passwd,
> >> - bool fail_if_connected, bool disconnect_if_connected);
> >> -int qemu_spice_set_pw_expire(time_t expires);
> >> -int qemu_spice_migrate_info(const char *hostname, int port, int tls_port,
> >> - const char *subject);
> >> +MODIFACE(int, qemu_spice_set_passwd, (const char *passwd,
> >> + bool fail_if_connected,
> >> + bool disconnect_if_connected));
> >> +MODIFACE(int, qemu_spice_set_pw_expire,(time_t expires));
> >> +MODIFACE(int, qemu_spice_migrate_info,(const char *hostname,
> >> + int port, int tls_port,
> >> + const char *subject));
> >> +MODIFACE(struct SpiceInfo *,qemu_spice_query, (Error **errp));
> >
> > This macro usage looks kind of unpleasant and its hard to understand
> > just what is going on, especially why some methods are changed but
> > others are not.
>
> The functions that are changed are the module interface between qemu and the
> DSO. For example, qemu_spice_init is called from vl.c, i.e. the main binary,
> but qemu_spice_audio_init is called from ui/spice-core.c and
> ui/spice-input.c, which are both in the DSO after the commit.
>
> The existing function-based interface in qemu-spice.h was not really
> carefully designed for modularization, so this list was determined by
> following the initialization path. It may not be the smallest possible cut.
> It may be neat to add a patch to reorder functions based on whether they are
> inside the DSO or exported from it.
>
> As for the macro syntax, I see it as somewhat transient. I wanted to propose
> a working and scalable mechanism before adding some nice syntactic sugar
> tooling to it. I expect the syntax to turn into something like:
>
> MODIFACE void qemu_spice_display_init (void);
> MODIIMPL void qemu_spice_display_init (void) { ... }
>
> But it feels a bit too early to do that. I prefer to experiment with a
> simple macro for now.
>
> >
> > IIUC, the goal is to turn all these into weak symbols so they don't
> > need to be resolved immediately at startup, and instead have an
> > impl of them provided dynamically at runtime.
>
> My very first approach was indeed to use weak symbols, but then I learned
> that ld.so no longer deals with weak symbols, apparently for security
> reasons. See LD_DYNAMIC_WEAK in ld.so(8).
>
> >
> > If so the more normal approach would be to have a struct defining
> > a set of callbacks, that can be registered. Or if there's a natural
> > fit with QOM, then a QOM interface that can then have a QOM object
> > impl registered as a singleton.
>
> That was my second attempt (after the weak symbols). I cleaned it up a bit
> and put it here: https://github.com/c3d/qemu/commits/spice-vtable.
>
> What made me switch to the approach in this series is the following
> considerations:
>
> - A vtable is useful if there can be multiple values for a method, e.g. to
> implement inheritance, or if you have multiple instances. This is not the
> case here.
IMHO a vtable is fine for singleton objects. It is a generic way to
remove tight coupling between caller(s) and an implementation. Just
having 1 implementation doesn't invalidate the model.
> - A vtable adds one level of indirection, which does not seem to be
> particularly useful or helpful for this particular use case.
>
> - Overloading QOM for that purpose looked more confusing than anything else.
> It looked like I was mixing unrelated concepts. Maybe that's just me.
>
> - The required change with a vtable ends up being more extensive. Instead of
> changing a single line to put an entry point in a DSO, you need to create
> the vtable, add functions to it, add a register function, etc. I was
> looking for an easier and more scalable way.
>
> - In particular, with a vtable, you cannot take advantage of the syntactic
> trick I used here, which is that foo(x) is a shortcut for (*foo)(x).
> So for a vtable, you need to manually write wrappers.
>
> This could be automated, of course, but so far I did not find any clear
> benefit, and many drawbacks to using the vtable approach. As a quantitative
> comparison point, the commit that does this same connection using the vtable
> approach is:
>
> include/ui/qemu-spice.h | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
> monitor/hmp-cmds.c | 6 +++++
> softmmu/vl.c | 10 +++++++
> ui/spice-core.c | 38 ++++++++++++++++++++++++---
> 4 files changed, 148 insertions(+), 10 deletions(-)
>
> as opposed to what is presented in this series:
>
> include/ui/qemu-spice.h | 24 +++++++++++++++---------
> monitor/hmp-cmds.c | 6 ++++++
> softmmu/vl.c | 1 +
> ui/spice-core.c | 31 +++++++++++++++++++++----------
> ui/spice-display.c | 2 +-
> 5 files changed, 44 insertions(+), 20 deletions(-)
I much prefer the code in the vtable patch version. The larger number of
lines of code doesn't bother me, because the code is really simple and
clear to read. IOW I prefer the clarity of the longer code, over the
shorter code with magic hidden behind it.
Regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
next prev parent reply other threads:[~2020-06-29 17:31 UTC|newest]
Thread overview: 66+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-06-26 16:42 [PATCH 00/10] RFC: Move SPICE to a load module Christophe de Dinechin
2020-06-26 16:42 ` Christophe de Dinechin
2020-06-26 16:42 ` [PATCH 01/10] modules: Provide macros making it easier to identify module exports Christophe de Dinechin
2020-06-26 16:42 ` Christophe de Dinechin
2020-06-29 10:13 ` Claudio Fontana
2020-06-29 10:13 ` Claudio Fontana
2020-06-30 13:48 ` Christophe de Dinechin
2020-06-30 13:48 ` Christophe de Dinechin
2020-06-30 9:38 ` Michael S. Tsirkin
2020-06-30 9:38 ` Michael S. Tsirkin
2020-06-30 13:39 ` Christophe de Dinechin
2020-06-30 13:39 ` Christophe de Dinechin
2020-06-26 16:42 ` [PATCH 02/10] minikconf: Pass variables for modules Christophe de Dinechin
2020-06-26 16:42 ` Christophe de Dinechin
2020-06-26 16:43 ` [PATCH 03/10] spice: Make spice a module configuration Christophe de Dinechin
2020-06-26 16:43 ` Christophe de Dinechin
2020-06-26 16:43 ` [PATCH 04/10] spice: Move all the spice-related code in spice-app.so Christophe de Dinechin
2020-06-26 16:43 ` Christophe de Dinechin
2020-06-26 17:20 ` Daniel P. Berrangé
2020-06-26 17:20 ` Daniel P. Berrangé
2020-06-29 9:22 ` Christophe de Dinechin
2020-06-29 9:22 ` Christophe de Dinechin
2020-06-29 9:47 ` Daniel P. Berrangé
2020-06-29 9:47 ` Daniel P. Berrangé
2020-06-29 23:37 ` Gerd Hoffmann
2020-06-29 23:37 ` Gerd Hoffmann
2020-06-26 16:43 ` [PATCH 05/10] build: Avoid build failure when building drivers as modules Christophe de Dinechin
2020-06-26 16:43 ` Christophe de Dinechin
2020-06-26 17:23 ` Daniel P. Berrangé
2020-06-26 17:23 ` Daniel P. Berrangé
2020-06-29 23:26 ` Gerd Hoffmann
2020-06-29 23:26 ` Gerd Hoffmann
2020-06-26 16:43 ` [PATCH 06/10] trivial: Remove extra trailing whitespace Christophe de Dinechin
2020-06-26 16:43 ` Christophe de Dinechin
2020-06-29 9:56 ` Philippe Mathieu-Daudé
2020-06-29 9:56 ` Philippe Mathieu-Daudé
2020-06-26 16:43 ` [PATCH 07/10] qxl - FIXME: Build as module Christophe de Dinechin
2020-06-26 16:43 ` Christophe de Dinechin
2020-06-26 16:43 ` [PATCH 08/10] build: Add SPICE_CFLAGS and SPICE_LIBS to relevant files Christophe de Dinechin
2020-06-26 16:43 ` Christophe de Dinechin
2020-06-26 17:26 ` Daniel P. Berrangé
2020-06-26 17:26 ` Daniel P. Berrangé
2020-06-29 9:27 ` Christophe de Dinechin
2020-06-29 9:27 ` Christophe de Dinechin
2020-06-29 23:08 ` Gerd Hoffmann
2020-06-29 23:08 ` Gerd Hoffmann
2020-06-30 13:56 ` Christophe de Dinechin
2020-06-30 13:56 ` Christophe de Dinechin
2020-06-26 16:43 ` [PATCH 09/10] spice: Put spice functions in a separate load module Christophe de Dinechin
2020-06-26 16:43 ` Christophe de Dinechin
2020-06-26 17:35 ` Daniel P. Berrangé
2020-06-26 17:35 ` Daniel P. Berrangé
2020-06-29 17:19 ` Christophe de Dinechin
2020-06-29 17:19 ` Christophe de Dinechin
2020-06-29 17:30 ` Daniel P. Berrangé [this message]
2020-06-29 17:30 ` Daniel P. Berrangé
2020-06-29 23:00 ` Gerd Hoffmann
2020-06-29 23:00 ` Gerd Hoffmann
2020-06-30 12:54 ` Christophe de Dinechin
2020-06-30 12:54 ` Christophe de Dinechin
2020-06-26 16:43 ` [PATCH 10/10] REMOVE: Instrumentation to show the module functions being replaced Christophe de Dinechin
2020-06-26 16:43 ` Christophe de Dinechin
2020-06-26 17:29 ` Daniel P. Berrangé
2020-06-26 17:29 ` Daniel P. Berrangé
2020-06-30 12:48 ` Christophe de Dinechin
2020-06-30 12:48 ` Christophe de Dinechin
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=20200629173035.GA1298906@redhat.com \
--to=berrange@redhat.com \
--cc=crosa@redhat.com \
--cc=dgilbert@redhat.com \
--cc=dinechin@redhat.com \
--cc=ehabkost@redhat.com \
--cc=kraxel@redhat.com \
--cc=laurent@vivier.eu \
--cc=marcandre.lureau@redhat.com \
--cc=mjt@tls.msk.ru \
--cc=mst@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=qemu-trivial@nongnu.org \
--cc=rth@twiddle.net \
/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.