From: Adhemerval Zanella <adhemerval.zanella@linaro.org>
To: "H.J. Lu" <hjl.tools@gmail.com>
Cc: GNU C Library <libc-alpha@sourceware.org>,
Florian Weimer <fweimer@redhat.com>,
Noah Goldstein <goldstein.w.n@gmail.com>
Subject: Re: [PATCH v9] Add static tests for __clone_internal
Date: Wed, 14 Jul 2021 10:42:06 -0300 [thread overview]
Message-ID: <9b763fff-d71f-78f2-6ae8-59ab2e75dd3b@linaro.org> (raw)
In-Reply-To: <CAMe9rOo9GOCML6oDEFw_Zn1NENagkQFPnz=z26p4EdJPkcm=Ew@mail.gmail.com>
On 14/07/2021 10:32, H.J. Lu wrote:
> On Wed, Jul 14, 2021 at 6:18 AM Adhemerval Zanella
> <adhemerval.zanella@linaro.org> wrote:
>>
>>
>>
>> On 13/07/2021 18:12, H.J. Lu wrote:
>>>
>>> Here is the v9 patch. OK for master?
>>>
>>> Thanks.
>>>
>>
>> I think you forgot the attach the patch.
>
> Oops. Here is the patch.
>
LGTM, thanks.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> From 1aa6b571479b0a2fb54e8455f45875b4f6b4b2d8 Mon Sep 17 00:00:00 2001
> From: "H.J. Lu" <hjl.tools@gmail.com>
> Date: Fri, 14 May 2021 15:23:46 -0700
> Subject: [PATCH v9] Add static tests for __clone_internal
>
> ---
> sysdeps/unix/sysv/linux/Makefile | 9 ++
> .../sysv/linux/tst-align-clone-internal.c | 68 +++++++++
> sysdeps/unix/sysv/linux/tst-clone2-internal.c | 126 +++++++++++++++++
> sysdeps/unix/sysv/linux/tst-clone3-internal.c | 99 +++++++++++++
> .../unix/sysv/linux/tst-getpid1-internal.c | 133 ++++++++++++++++++
> .../sysv/linux/tst-misalign-clone-internal.c | 74 ++++++++++
> 6 files changed, 509 insertions(+)
> create mode 100644 sysdeps/unix/sysv/linux/tst-align-clone-internal.c
> create mode 100644 sysdeps/unix/sysv/linux/tst-clone2-internal.c
> create mode 100644 sysdeps/unix/sysv/linux/tst-clone3-internal.c
> create mode 100644 sysdeps/unix/sysv/linux/tst-getpid1-internal.c
> create mode 100644 sysdeps/unix/sysv/linux/tst-misalign-clone-internal.c
>
> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
> index ed0c0d27f4..cceb16be05 100644
> --- a/sysdeps/unix/sysv/linux/Makefile
> +++ b/sysdeps/unix/sysv/linux/Makefile
> @@ -139,6 +139,15 @@ tests-time64 += \
> tst-sigtimedwait-time64 \
> tst-timerfd-time64 \
>
> +tests-clone-internal = \
> + tst-align-clone-internal \
> + tst-clone2-internal \
> + tst-clone3-internal \
> + tst-getpid1-internal \
> + tst-misalign-clone-internal
> +tests-internal += $(tests-clone-internal)
> +tests-static += $(tests-clone-internal)
> +
> CFLAGS-tst-sigcontext-get_pc.c = -fasynchronous-unwind-tables
>
> # Generate the list of SYS_* macros for the system calls (__NR_*,
Ok.
> diff --git a/sysdeps/unix/sysv/linux/tst-align-clone-internal.c b/sysdeps/unix/sysv/linux/tst-align-clone-internal.c
> new file mode 100644
> index 0000000000..f3f5b46378
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-align-clone-internal.c
> @@ -0,0 +1,68 @@
> +/* Verify that the clone child stack is properly aligned.
> + Copyright (C) 2021 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <sched.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +#include <tst-stack-align.h>
> +#include <clone_internal.h>
> +#include <support/xunistd.h>
> +#include <support/check.h>
> +
> +static int
> +f (void *arg)
> +{
> + puts ("in f");
> +
> + return TEST_STACK_ALIGN () ? 1 : 0;
> +}
> +
> +static int
> +do_test (void)
> +{
> + puts ("in main");
> +
> + if (TEST_STACK_ALIGN ())
> + FAIL_EXIT1 ("stack alignment failed");
> +
> +#ifdef __ia64__
> +# define STACK_SIZE 256 * 1024
> +#else
> +# define STACK_SIZE 128 * 1024
> +#endif
> + char st[STACK_SIZE] __attribute__ ((aligned));
> + struct clone_args clone_args =
> + {
> + .stack = (uintptr_t) st,
> + .stack_size = sizeof (st),
> + };
> + pid_t p = __clone_internal (&clone_args, f, 0);
> + TEST_VERIFY (p != -1);
> +
> + int e;
> + xwaitpid (p, &e, __WCLONE);
> + TEST_VERIFY (WIFEXITED (e));
> + TEST_COMPARE (WEXITSTATUS (e), 0);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
Ok.
> diff --git a/sysdeps/unix/sysv/linux/tst-clone2-internal.c b/sysdeps/unix/sysv/linux/tst-clone2-internal.c
> new file mode 100644
> index 0000000000..fd3a55158c
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-clone2-internal.c
> @@ -0,0 +1,126 @@
> +/* Test if CLONE_VM does not change pthread pid/tid field (BZ #19957)
> + Copyright (C) 2021 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <sched.h>
> +#include <signal.h>
> +#include <string.h>
> +#include <stdio.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <stddef.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdlib.h>
> +#include <errno.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <sys/syscall.h>
> +#include <clone_internal.h>
> +#include <support/xunistd.h>
> +#include <support/check.h>
> +
> +static int sig;
> +static int pipefd[2];
> +
> +static int
> +f (void *a)
> +{
> + close (pipefd[0]);
> +
> + pid_t ppid = getppid ();
> + pid_t pid = getpid ();
> + pid_t tid = gettid ();
> +
> + if (write (pipefd[1], &ppid, sizeof ppid) != sizeof (ppid))
> + FAIL_EXIT1 ("write ppid failed\n");
> + if (write (pipefd[1], &pid, sizeof pid) != sizeof (pid))
> + FAIL_EXIT1 ("write pid failed\n");
> + if (write (pipefd[1], &tid, sizeof tid) != sizeof (tid))
> + FAIL_EXIT1 ("write tid failed\n");
> +
> + return 0;
> +}
> +
> +
> +static int
> +do_test (void)
> +{
> + sig = SIGRTMIN;
> + sigset_t ss;
> + sigemptyset (&ss);
> + sigaddset (&ss, sig);
> + if (sigprocmask (SIG_BLOCK, &ss, NULL) != 0)
> + FAIL_EXIT1 ("sigprocmask failed: %m");
> +
> + if (pipe2 (pipefd, O_CLOEXEC))
> + FAIL_EXIT1 ("pipe failed: %m");
> +
> +#ifdef __ia64__
> +# define STACK_SIZE 256 * 1024
> +#else
> +# define STACK_SIZE 128 * 1024
> +#endif
> + char st[STACK_SIZE] __attribute__ ((aligned));
> + struct clone_args clone_args =
> + {
> + .stack = (uintptr_t) st,
> + .stack_size = sizeof (st),
> + };
> + pid_t p = __clone_internal (&clone_args, f, 0);
> +
> + close (pipefd[1]);
> +
> + if (p == -1)
> + FAIL_EXIT1("clone failed: %m");
> +
> + pid_t ppid, pid, tid;
> + if (read (pipefd[0], &ppid, sizeof pid) != sizeof pid)
> + {
> + kill (p, SIGKILL);
> + FAIL_EXIT1 ("read ppid failed: %m");
> + }
> + if (read (pipefd[0], &pid, sizeof pid) != sizeof pid)
> + {
> + kill (p, SIGKILL);
> + FAIL_EXIT1 ("read pid failed: %m");
> + }
> + if (read (pipefd[0], &tid, sizeof tid) != sizeof tid)
> + {
> + kill (p, SIGKILL);
> + FAIL_EXIT1 ("read tid failed: %m");
> + }
> +
> + close (pipefd[0]);
> +
> + pid_t own_pid = getpid ();
> + pid_t own_tid = syscall (__NR_gettid);
> +
> + /* Some sanity checks for clone syscall: returned ppid should be current
> + pid and both returned tid/pid should be different from current one. */
> + if ((ppid != own_pid) || (pid == own_pid) || (tid == own_tid))
> + FAIL_RET ("ppid=%i pid=%i tid=%i | own_pid=%i own_tid=%i",
> + (int)ppid, (int)pid, (int)tid, (int)own_pid, (int)own_tid);
> +
> + int e;
> + xwaitpid (p, &e, __WCLONE);
> + TEST_VERIFY (WIFEXITED (e));
> + TEST_COMPARE (WEXITSTATUS (e), 0);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
Ok.
> diff --git a/sysdeps/unix/sysv/linux/tst-clone3-internal.c b/sysdeps/unix/sysv/linux/tst-clone3-internal.c
> new file mode 100644
> index 0000000000..2bdbc571e6
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-clone3-internal.c
> @@ -0,0 +1,99 @@
> +/* Check if clone (CLONE_THREAD) does not call exit_group (BZ #21512)
> + Copyright (C) 2021 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <string.h>
> +#include <sched.h>
> +#include <signal.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <sys/syscall.h>
> +#include <sys/wait.h>
> +#include <sys/types.h>
> +#include <linux/futex.h>
> +#include <support/check.h>
> +#include <stdatomic.h>
> +#include <clone_internal.h>
> +
> +/* Test if clone call with CLONE_THREAD does not call exit_group. The 'f'
> + function returns '1', which will be used by clone thread to call the
> + 'exit' syscall directly. If _exit is used instead, exit_group will be
> + used and thus the thread group will finish with return value of '1'
> + (where '2' from main thread is expected.). */
> +
> +static int
> +f (void *a)
> +{
> + return 1;
> +}
> +
> +/* Futex wait for TID argument, similar to pthread_join internal
> + implementation. */
> +#define wait_tid(ctid_ptr, ctid_val) \
> + do { \
> + __typeof (*(ctid_ptr)) __tid; \
> + /* We need acquire MO here so that we synchronize with the \
> + kernel's store to 0 when the clone terminates. */ \
> + while ((__tid = atomic_load_explicit (ctid_ptr, \
> + memory_order_acquire)) != 0) \
> + futex_wait (ctid_ptr, ctid_val); \
> + } while (0)
> +
> +static inline int
> +futex_wait (int *futexp, int val)
> +{
> +#ifdef __NR_futex
> + return syscall (__NR_futex, futexp, FUTEX_WAIT, val);
> +#else
> + return syscall (__NR_futex_time64, futexp, FUTEX_WAIT, val);
> +#endif
> +}
> +
> +static int
> +do_test (void)
> +{
> + char st[1024] __attribute__ ((aligned));
> + int clone_flags = CLONE_THREAD;
> + /* Minimum required flags to used along with CLONE_THREAD. */
> + clone_flags |= CLONE_VM | CLONE_SIGHAND;
> + /* We will used ctid to call on futex to wait for thread exit. */
> + clone_flags |= CLONE_CHILD_CLEARTID;
> + /* Initialize with a known value. ctid is set to zero by the kernel after the
> + cloned thread has exited. */
> +#define CTID_INIT_VAL 1
> + pid_t ctid = CTID_INIT_VAL;
> + pid_t tid;
> +
> + struct clone_args clone_args =
> + {
> + .flags = clone_flags & ~CSIGNAL,
> + .exit_signal = clone_flags & CSIGNAL,
> + .stack = (uintptr_t) st,
> + .stack_size = sizeof (st),
> + .child_tid = (uintptr_t) &ctid,
> + };
> + tid = __clone_internal (&clone_args, f, NULL);
> + if (tid == -1)
> + FAIL_EXIT1 ("clone failed: %m");
> +
> + wait_tid (&ctid, CTID_INIT_VAL);
> +
> + return 2;
> +}
> +
> +#define EXPECTED_STATUS 2
> +#include <support/test-driver.c>
Ok.
> diff --git a/sysdeps/unix/sysv/linux/tst-getpid1-internal.c b/sysdeps/unix/sysv/linux/tst-getpid1-internal.c
> new file mode 100644
> index 0000000000..ee69e52401
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-getpid1-internal.c
> @@ -0,0 +1,133 @@
> +/* Verify that the parent pid is unchanged by __clone_internal.
> + Copyright (C) 2021 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <sched.h>
> +#include <signal.h>
> +#include <string.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +#include <clone_internal.h>
> +#include <support/xunistd.h>
> +
> +#ifndef TEST_CLONE_FLAGS
> +#define TEST_CLONE_FLAGS 0
> +#endif
> +
> +static int sig;
> +
> +static int
> +f (void *a)
> +{
> + puts ("in f");
> + union sigval sival;
> + sival.sival_int = getpid ();
> + printf ("pid = %d\n", sival.sival_int);
> + if (sigqueue (getppid (), sig, sival) != 0)
> + return 1;
> + return 0;
> +}
> +
> +
> +static int
> +do_test (void)
> +{
> + int mypid = getpid ();
> +
> + sig = SIGRTMIN;
> + sigset_t ss;
> + sigemptyset (&ss);
> + sigaddset (&ss, sig);
> + if (sigprocmask (SIG_BLOCK, &ss, NULL) != 0)
> + {
> + printf ("sigprocmask failed: %m\n");
> + return 1;
> + }
> +
> +#ifdef __ia64__
> +# define STACK_SIZE 256 * 1024
> +#else
> +# define STACK_SIZE 128 * 1024
> +#endif
> + char st[STACK_SIZE] __attribute__ ((aligned));
> + struct clone_args clone_args =
> + {
> + .flags = TEST_CLONE_FLAGS & ~CSIGNAL,
> + .exit_signal = TEST_CLONE_FLAGS & CSIGNAL,
> + .stack = (uintptr_t) st,
> + .stack_size = sizeof (st),
> + };
> + pid_t p = __clone_internal (&clone_args, f, 0);
> + if (p == -1)
> + {
> + printf("clone failed: %m\n");
> + return 1;
> + }
> + printf ("new thread: %d\n", (int) p);
> +
> + siginfo_t si;
> + do
> + if (sigwaitinfo (&ss, &si) < 0)
> + {
> + printf("sigwaitinfo failed: %m\n");
> + kill (p, SIGKILL);
> + return 1;
> + }
> + while (si.si_signo != sig || si.si_code != SI_QUEUE);
> +
> + int e;
> + xwaitpid (p, &e, __WCLONE);
> + if (!WIFEXITED (e))
> + {
> + if (WIFSIGNALED (e))
> + printf ("died from signal %s\n", strsignal (WTERMSIG (e)));
> + else
> + puts ("did not terminate correctly");
> + return 1;
> + }
> + if (WEXITSTATUS (e) != 0)
> + {
> + printf ("exit code %d\n", WEXITSTATUS (e));
> + return 1;
> + }
> +
> + if (si.si_int != (int) p)
> + {
> + printf ("expected PID %d, got si_int %d\n", (int) p, si.si_int);
> + kill (p, SIGKILL);
> + return 1;
> + }
> +
> + if (si.si_pid != p)
> + {
> + printf ("expected PID %d, got si_pid %d\n", (int) p, (int) si.si_pid);
> + kill (p, SIGKILL);
> + return 1;
> + }
> +
> + if (getpid () != mypid)
> + {
> + puts ("my PID changed");
> + return 1;
> + }
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
Ok.
> diff --git a/sysdeps/unix/sysv/linux/tst-misalign-clone-internal.c b/sysdeps/unix/sysv/linux/tst-misalign-clone-internal.c
> new file mode 100644
> index 0000000000..5e34160238
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-misalign-clone-internal.c
> @@ -0,0 +1,74 @@
> +/* Verify that __clone_internal properly aligns the child stack.
> + Copyright (C) 2021 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <sched.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +#include <libc-pointer-arith.h>
> +#include <tst-stack-align.h>
> +#include <clone_internal.h>
> +#include <support/xunistd.h>
> +#include <support/check.h>
> +
> +static int
> +check_stack_alignment (void *arg)
> +{
> + puts ("in f");
> +
> + return TEST_STACK_ALIGN () ? 1 : 0;
> +}
> +
> +static int
> +do_test (void)
> +{
> + puts ("in do_test");
> +
> + if (TEST_STACK_ALIGN ())
> + FAIL_EXIT1 ("stack isn't aligned\n");
> +
> +#ifdef __ia64__
> +# define STACK_SIZE (256 * 1024)
> +#else
> +# define STACK_SIZE (128 * 1024)
> +#endif
> + char st[STACK_SIZE + 1];
> + /* NB: Align child stack to 1 byte. */
> + char *stack = PTR_ALIGN_UP (&st[0], 2) + 1;
> + struct clone_args clone_args =
> + {
> + .stack = (uintptr_t) stack,
> + .stack_size = STACK_SIZE,
> + };
> + pid_t p = __clone_internal (&clone_args, check_stack_alignment, 0);
> +
> + /* Clone must not fail. */
> + TEST_VERIFY_EXIT (p != -1);
> +
> + int e;
> + xwaitpid (p, &e, __WCLONE);
> + TEST_VERIFY (WIFEXITED (e));
> + TEST_COMPARE (WEXITSTATUS (e), 0);
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> --
> 2.31.1
Ok.
prev parent reply other threads:[~2021-07-14 13:42 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-06-01 14:55 [PATCH v8 0/3] Add an internal wrapper for clone, clone2 and clone3 H.J. Lu
2021-06-01 14:55 ` [PATCH v8 1/3] " H.J. Lu
2021-06-04 12:20 ` H.J. Lu
2021-06-18 18:20 ` PING^1 " H.J. Lu
2021-07-13 18:54 ` Adhemerval Zanella
2021-07-13 19:06 ` Adhemerval Zanella
2021-07-13 19:49 ` [PATCH v9] " H.J. Lu
2021-07-14 13:17 ` Adhemerval Zanella
2021-06-01 14:55 ` [PATCH v8 2/3] x86-64: Add the clone3 wrapper H.J. Lu
2021-07-13 19:12 ` Adhemerval Zanella
2021-06-01 14:55 ` [PATCH v8 3/3] Add static tests for __clone_internal H.J. Lu
2021-07-13 19:32 ` Adhemerval Zanella
2021-07-13 21:12 ` [PATCH v9] " H.J. Lu
2021-07-14 13:18 ` Adhemerval Zanella
2021-07-14 13:32 ` H.J. Lu
2021-07-14 13:42 ` Adhemerval Zanella [this message]
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=9b763fff-d71f-78f2-6ae8-59ab2e75dd3b@linaro.org \
--to=adhemerval.zanella@linaro.org \
--cc=fweimer@redhat.com \
--cc=goldstein.w.n@gmail.com \
--cc=hjl.tools@gmail.com \
--cc=libc-alpha@sourceware.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 read-only IMAP folder(s) and NNTP newsgroup(s).