From: Eugene Syromyatnikov <evgsyr-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
To: strace-devel-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
Cc: dm-devel-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org,
mpatocka-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org
Subject: [PATCH 21/21] dm: rewrite structure decoding
Date: Sun, 9 Oct 2016 16:31:42 +0300 [thread overview]
Message-ID: <20161009133142.GA4692@obsidian> (raw)
In-Reply-To: <20161005.192828.566127461630656590.yamato-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Rewrite structure decoding in attempt to make it more in line with how
structures and arrays are decoded in strace.
* Replace single structure retrieval with on-demand retrieval. It
allows limiting amount of memory being allocated (suppose ioctl with
data_size = -1)
* Check for abbrev in structure decoders itself. It allows
distinguishing cases when we want to decode some additional data from
cases when we are not.
---
dm.c | 363 +++++++++++++++++++++++++++++++++++++++---------------------------
1 file changed, 217 insertions(+), 146 deletions(-)
diff --git a/dm.c b/dm.c
index ff9e8ad..d846233 100644
--- a/dm.c
+++ b/dm.c
@@ -101,165 +101,254 @@ dm_decode_flags(const struct dm_ioctl *ioc)
}
static void
-dm_decode_dm_target_spec(struct tcb *tcp, const struct dm_ioctl *ioc,
- const char *extra, uint32_t extra_size)
+dm_decode_dm_target_spec(struct tcb *tcp, unsigned long addr,
+ const struct dm_ioctl *ioc)
{
static const uint32_t target_spec_size =
sizeof(struct dm_target_spec);
uint32_t i;
uint32_t offset = ioc->data_start;
+ if (abbrev(tcp)) {
+ if (ioc->target_count)
+ tprints(", ...");
+
+ return;
+ }
+
for (i = 0; i < ioc->target_count; i++) {
- if (offset + target_spec_size >= offset &&
- offset + target_spec_size < extra_size) {
- uint32_t new_offset;
- const struct dm_target_spec *s =
- (const struct dm_target_spec *) (extra + offset);
- tprintf(", {sector_start=%" PRIu64 ", length=%" PRIu64,
- (uint64_t) s->sector_start,
- (uint64_t) s->length);
- if (!entering(tcp))
- tprintf(", status=%" PRId32, s->status);
- tprints(", target_type=");
- print_quoted_string(s->target_type, DM_MAX_TYPE_NAME,
- QUOTE_0_TERMINATED);
- tprints(", string=");
- print_quoted_string((const char *) (s + 1), extra_size -
- (offset + target_spec_size),
- QUOTE_0_TERMINATED);
- tprintf("}");
- if (entering(tcp))
- new_offset = offset + s->next;
- else
- new_offset = ioc->data_start + s->next;
- if (new_offset <= offset + target_spec_size)
- goto misplaced;
- offset = new_offset;
- } else {
-misplaced:
- tprints(", /* misplaced struct dm_target_spec */ ...");
+ struct dm_target_spec s;
+ uint32_t new_offset;
+
+ if ((offset + target_spec_size) <= offset ||
+ (offset + target_spec_size) > ioc->data_size)
+ goto misplaced;
+
+ tprints(", ");
+
+ if (i >= max_strlen) {
+ tprints("...");
break;
}
+
+ if (umove_or_printaddr(tcp, addr + offset, &s))
+ break;
+
+ tprintf("{sector_start=%" PRI__u64 ", length=%" PRI__u64,
+ s.sector_start, s.length);
+
+ if (!entering(tcp))
+ tprintf(", status=%" PRId32, s.status);
+
+ tprints(", target_type=");
+ print_quoted_string(s.target_type, DM_MAX_TYPE_NAME,
+ QUOTE_0_TERMINATED);
+
+ tprints(", string=");
+ printstr_ex(tcp, addr + offset + target_spec_size,
+ ioc->data_size - (offset + target_spec_size),
+ QUOTE_0_TERMINATED);
+ tprintf("}");
+
+ if (entering(tcp))
+ new_offset = offset + s.next;
+ else
+ new_offset = ioc->data_start + s.next;
+
+ if (new_offset <= offset + target_spec_size)
+ goto misplaced;
+
+ offset = new_offset;
}
+
+ return;
+
+misplaced:
+ tprints(", /* misplaced struct dm_target_spec */ ...");
+}
+
+bool
+dm_print_dev(struct tcb *tcp, void *dev_ptr, size_t dev_size, void *dummy)
+{
+ uint64_t *dev = (uint64_t *) dev_ptr;
+
+ tprintf("makedev(%u, %u)", major(*dev), minor(*dev));
+
+ return 1;
}
static void
-dm_decode_dm_target_deps(const struct dm_ioctl *ioc, const char *extra,
- uint32_t extra_size)
+dm_decode_dm_target_deps(struct tcb *tcp, unsigned long addr,
+ const struct dm_ioctl *ioc)
{
static const uint32_t target_deps_dev_offs =
offsetof(struct dm_target_deps, dev);
+ uint64_t dev_buf;
+ struct dm_target_deps s;
uint32_t offset = ioc->data_start;
+ uint32_t space;
- if (offset + target_deps_dev_offs >= offset &&
- offset + target_deps_dev_offs <= extra_size) {
- uint32_t i;
- uint32_t space = (extra_size - offset - target_deps_dev_offs) /
- sizeof(__u64);
- const struct dm_target_deps *s =
- (const struct dm_target_deps *) (extra + offset);
-
- if (s->count > space)
- goto misplaced;
- tprints(", deps={");
- for (i = 0; i < s->count; i++) {
- tprintf("%smakedev(%u, %u)", i ? ", " : "",
- major(s->dev[i]), minor(s->dev[i]));
- }
- tprints("}");
- } else {
- misplaced:
- tprints(", /* misplaced struct dm_target_deps */ ...");
+ if (abbrev(tcp)) {
+ tprints(", ...");
+ return;
}
+
+ tprints(", ");
+
+ if (offset + target_deps_dev_offs <= offset ||
+ offset + target_deps_dev_offs > ioc->data_size)
+ goto misplaced;
+
+ if (umove_or_printaddr(tcp, addr + offset, &s))
+ return;
+
+ space = (ioc->data_size - offset - target_deps_dev_offs) / sizeof(__u64);
+
+ if (s.count > space)
+ goto misplaced;
+
+ tprintf("{count=%u, deps=", s.count);
+
+ print_array(tcp, addr + offset + target_deps_dev_offs, s.count,
+ &dev_buf, sizeof(dev_buf), umoven_or_printaddr,
+ dm_print_dev, NULL);
+
+ tprints("}");
+
+ return;
+
+misplaced:
+ tprints("/* misplaced struct dm_target_deps */ ...");
}
static void
-dm_decode_dm_name_list(const struct dm_ioctl *ioc, const char *extra,
- uint32_t extra_size)
+dm_decode_dm_name_list(struct tcb *tcp, unsigned long addr,
+ const struct dm_ioctl *ioc)
{
static const uint32_t name_list_name_offs =
offsetof(struct dm_name_list, name);
+ struct dm_name_list s;
uint32_t offset = ioc->data_start;
+ uint32_t count;
- while (1) {
- if (offset + name_list_name_offs >= offset &&
- offset + name_list_name_offs < extra_size) {
- const struct dm_name_list *s =
- (const struct dm_name_list *) (extra + offset);
+ if (abbrev(tcp)) {
+ tprints(", ...");
+ return;
+ }
- if (!s->dev)
- break;
- tprintf(", {dev=makedev(%u, %u), name=", major(s->dev),
- minor(s->dev));
- print_quoted_string(s->name, extra_size - (offset +
- name_list_name_offs),
- QUOTE_0_TERMINATED);
- tprints("}");
- if (!s->next)
- break;
- if (offset + s->next <= offset + name_list_name_offs)
- goto misplaced;
- offset = offset + s->next;
- } else {
- misplaced:
- tprints(", /* misplaced struct dm_name_list */ ...");
+ for (count = 0;; count++) {
+ if (offset + name_list_name_offs <= offset ||
+ offset + name_list_name_offs > ioc->data_size)
+ goto misplaced;
+
+ tprints(", ");
+
+ if (count >= max_strlen) {
+ tprints("...");
+ break;
+ }
+
+ if (umove_or_printaddr(tcp, addr + offset, &s))
+ break;
+ if (!count && !s.dev) {
+ tprints("/* no devices present */");
break;
}
+
+ tprintf("{dev=makedev(%u, %u), name=", major(s.dev),
+ minor(s.dev));
+ printstr_ex(tcp, addr + offset + name_list_name_offs,
+ ioc->data_size - (offset + name_list_name_offs),
+ QUOTE_0_TERMINATED);
+ tprints("}");
+
+ if (!s.next)
+ break;
+ if (offset + s.next <= offset + name_list_name_offs)
+ goto misplaced;
+ offset = offset + s.next;
}
+
+ return;
+
+misplaced:
+ tprints(", /* misplaced struct dm_name_list */ ...");
}
static void
-dm_decode_dm_target_versions(const struct dm_ioctl *ioc, const char *extra,
- uint32_t extra_size)
+dm_decode_dm_target_versions(struct tcb *tcp, unsigned long addr,
+ const struct dm_ioctl *ioc)
{
static const uint32_t target_vers_name_offs =
offsetof(struct dm_target_versions, name);
+ struct dm_target_versions s;
uint32_t offset = ioc->data_start;
+ uint32_t count;
- while (1) {
- if (offset + target_vers_name_offs >= offset &&
- offset + target_vers_name_offs < extra_size) {
- const struct dm_target_versions *s =
- (const struct dm_target_versions *)(extra + offset);
+ if (abbrev(tcp)) {
+ tprints(", ...");
+ return;
+ }
- tprints(", {name=");
- print_quoted_string(s->name, extra_size - (offset +
- target_vers_name_offs),
- QUOTE_0_TERMINATED);
- tprintf(", version=%" PRIu32 ".%" PRIu32 ".%" PRIu32 "}",
- s->version[0], s->version[1], s->version[2]);
- if (!s->next)
- break;
- if (offset + s->next <= offset + target_vers_name_offs)
- goto misplaced;
- offset = offset + s->next;
- } else {
- misplaced:
- tprints(", /* misplaced struct dm_target_versions */ "
- "...");
+ for (count = 0;; count++) {
+ if (offset + target_vers_name_offs <= offset ||
+ offset + target_vers_name_offs > ioc->data_size)
+ goto misplaced;
+
+ tprints(", ");
+
+ if (count >= max_strlen) {
+ tprints("...");
break;
}
+
+ if (umove_or_printaddr(tcp, addr + offset, &s))
+ break;
+
+ tprints("{name=");
+ printstr_ex(tcp, addr + offset + target_vers_name_offs,
+ ioc->data_size - (offset + target_vers_name_offs),
+ QUOTE_0_TERMINATED);
+ tprintf(", version=%" PRIu32 ".%" PRIu32 ".%" PRIu32 "}",
+ s.version[0], s.version[1], s.version[2]);
+
+ if (!s.next)
+ break;
+ if (offset + s.next <= offset + target_vers_name_offs)
+ goto misplaced;
+ offset = offset + s.next;
}
+
+ return;
+
+misplaced:
+ tprints(", /* misplaced struct dm_target_versions */ ...");
}
static void
-dm_decode_dm_target_msg(const struct dm_ioctl *ioc, const char *extra,
- uint32_t extra_size)
+dm_decode_dm_target_msg(struct tcb *tcp, unsigned long addr,
+ const struct dm_ioctl *ioc)
{
static const uint32_t target_msg_message_offs =
offsetof(struct dm_target_msg, message);
uint32_t offset = ioc->data_start;
- if (offset + target_msg_message_offs >= offset &&
- offset + target_msg_message_offs < extra_size) {
- const struct dm_target_msg *s =
- (const struct dm_target_msg *) (extra + offset);
+ if (abbrev(tcp)) {
+ tprints(", ...");
+ return;
+ }
+
+ if (offset + target_msg_message_offs > offset &&
+ offset + target_msg_message_offs <= ioc->data_size) {
+ struct dm_target_msg s;
- tprintf(", {sector=%" PRIu64 ", message=",
- (uint64_t) s->sector);
- print_quoted_string(s->message, extra_size -
- target_msg_message_offs,
- QUOTE_0_TERMINATED);
+ if (umove_or_printaddr(tcp, addr + offset, &s))
+ return;
+
+ tprintf(", {sector=%" PRI__u64 ", message=", s.sector);
+ printstr_ex(tcp, addr + offset + target_msg_message_offs,
+ ioc->data_size - offset - target_msg_message_offs,
+ QUOTE_0_TERMINATED);
tprints("}");
} else {
tprints(", /* misplaced struct dm_target_msg */");
@@ -267,15 +356,20 @@ dm_decode_dm_target_msg(const struct dm_ioctl *ioc, const char *extra,
}
static void
-dm_decode_string(const struct dm_ioctl *ioc, const char *extra,
- uint32_t extra_size)
+dm_decode_string(struct tcb *tcp, unsigned long addr,
+ const struct dm_ioctl *ioc)
{
uint32_t offset = ioc->data_start;
- if (offset < extra_size) {
+ if (abbrev(tcp)) {
+ tprints(", ...");
+ return;
+ }
+
+ if (offset < ioc->data_size) {
tprints(", string=");
- print_quoted_string(extra + offset, extra_size - offset,
- QUOTE_0_TERMINATED);
+ printstr_ex(tcp, addr + offset, ioc->data_size - offset,
+ QUOTE_0_TERMINATED);
} else {
tprints(", /* misplaced string */");
}
@@ -301,11 +395,9 @@ dm_ioctl_has_params(const unsigned int code)
static int
dm_known_ioctl(struct tcb *tcp, const unsigned int code, long arg)
{
- struct dm_ioctl *ioc;
+ struct dm_ioctl *ioc = NULL;
struct dm_ioctl *entering_ioc = NULL;
bool ioc_changed = false;
- char *extra = NULL;
- uint32_t extra_size = 0;
ioc = malloc(sizeof(* ioc));
if (!ioc)
@@ -371,70 +463,49 @@ dm_known_ioctl(struct tcb *tcp, const unsigned int code, long arg)
dm_decode_values(tcp, code, ioc);
dm_decode_flags(ioc);
- if (dm_ioctl_has_params(code) && (ioc->data_size > sizeof(ioc))) {
- extra = malloc(ioc->data_size);
- if (extra) {
- extra_size = ioc->data_size;
- if (umoven(tcp, arg, extra_size, extra) < 0) {
- free(extra);
- extra = NULL;
- extra_size = 0;
- }
- }
- }
-
- if (abbrev(tcp)) {
- tprints(", ...");
- goto skip;
- }
-
switch (code) {
case DM_DEV_WAIT:
case DM_TABLE_STATUS:
if (entering(tcp) || syserror(tcp))
break;
- dm_decode_dm_target_spec(tcp, ioc, extra, extra_size);
+ dm_decode_dm_target_spec(tcp, arg, ioc);
break;
case DM_TABLE_LOAD:
if (!entering(tcp))
break;
- dm_decode_dm_target_spec(tcp, ioc, extra, extra_size);
+ dm_decode_dm_target_spec(tcp, arg, ioc);
break;
case DM_TABLE_DEPS:
if (entering(tcp) || syserror(tcp))
break;
- dm_decode_dm_target_deps(ioc, extra, extra_size);
+ dm_decode_dm_target_deps(tcp, arg, ioc);
break;
case DM_LIST_DEVICES:
if (entering(tcp) || syserror(tcp))
break;
- dm_decode_dm_name_list(ioc, extra, extra_size);
+ dm_decode_dm_name_list(tcp, arg, ioc);
break;
case DM_LIST_VERSIONS:
if (entering(tcp) || syserror(tcp))
break;
- dm_decode_dm_target_versions(ioc, extra, extra_size);
+ dm_decode_dm_target_versions(tcp, arg, ioc);
break;
case DM_TARGET_MSG:
- if (entering(tcp)) {
- dm_decode_dm_target_msg(ioc, extra,
- extra_size);
- } else if (!syserror(tcp) && ioc->flags & DM_DATA_OUT_FLAG) {
- dm_decode_string(ioc, extra, extra_size);
- }
+ if (entering(tcp))
+ dm_decode_dm_target_msg(tcp, arg, ioc);
+ else if (!syserror(tcp) && ioc->flags & DM_DATA_OUT_FLAG)
+ dm_decode_string(tcp, arg, ioc);
break;
case DM_DEV_RENAME:
case DM_DEV_SET_GEOMETRY:
if (!entering(tcp))
break;
- dm_decode_string(ioc, extra, extra_size);
+ dm_decode_string(tcp, arg, ioc);
break;
}
skip:
tprints("}");
- if (extra)
- free(extra);
if (exiting(tcp))
free(ioc);
return 1;
--
1.7.10.4
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, SlashDot.org! http://sdm.link/slashdot
next prev parent reply other threads:[~2016-10-09 13:31 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <alpine.LRH.2.02.1608221155410.17400@file01.intranet.prod.int.rdu2.redhat.com>
[not found] ` <20160822170920.GA5147@altlinux.org>
[not found] ` <alpine.LRH.2.02.1608231303510.7049@file01.intranet.prod.int.rdu2.redhat.com>
[not found] ` <20160824.233543.198328104871315294.yamato@redhat.com>
[not found] ` <20160824.233543.198328104871315294.yamato-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2016-08-25 12:27 ` [PATCH] Re: your dm patch for strace Mikulas Patocka
[not found] ` <alpine.LRH.2.02.1608250823130.24332-Hpncn10jQN4oNljnaZt3ZvA+iT7yCHsGwRM8/txMwJMAicBL8TP8PQ@public.gmane.org>
2016-09-12 17:10 ` Dmitry V. Levin
[not found] ` <20160912171029.GA5263-u2l5PoMzF/Vg9hUCZPvPmw@public.gmane.org>
2016-10-02 21:59 ` Mikulas Patocka
[not found] ` <alpine.LRH.2.02.1610021751280.29417-Hpncn10jQN4oNljnaZt3ZvA+iT7yCHsGwRM8/txMwJMAicBL8TP8PQ@public.gmane.org>
2016-10-05 10:28 ` Masatake YAMATO
[not found] ` <20161005.192828.566127461630656590.yamato-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
2016-10-09 13:27 ` [PATCH 00/21] Some possible additions to the DM ioctl " Eugene Syromyatnikov
2016-10-09 13:28 ` [PATCH 01/21] tests/ioctl_dm: Formatting Eugene Syromyatnikov
2016-10-09 13:28 ` [PATCH 02/21] dm: whitespace fixes Eugene Syromyatnikov
2016-10-09 13:28 ` [PATCH 03/21] tests: Working around bounds check Eugene Syromyatnikov
2016-10-09 13:28 ` [PATCH 04/21] dm: Minor output tweaks Eugene Syromyatnikov
2016-10-09 13:29 ` [PATCH 05/21] xlat: Add values for dm_flags Eugene Syromyatnikov
2016-10-09 13:29 ` [PATCH 06/21] dm: Some future-proofing by means of compile-time DM_VERSION_MAJOR check Eugene Syromyatnikov
2016-10-09 13:29 ` [PATCH 07/21] dm: Add definitions for ioctl commands not implemented initially Eugene Syromyatnikov
2016-10-09 13:30 ` [PATCH 08/21] dm: Use static constants for offset sizes Eugene Syromyatnikov
2016-10-09 13:30 ` [PATCH 09/21] dm: Remove char * cast Eugene Syromyatnikov
2016-10-09 13:30 ` [PATCH 10/21] dm: use => instead of , for splitting output structure from input Eugene Syromyatnikov
2016-10-09 13:30 ` [PATCH 11/21] dm: Compare entering field values with exiting ones Eugene Syromyatnikov
2016-10-09 13:30 ` [PATCH 12/21] dm: Add inttypes.h, include reorder Eugene Syromyatnikov
2016-10-09 13:30 ` [PATCH 13/21] dm: Move printing of dm_ioctl fields before allocation of extra data Eugene Syromyatnikov
2016-10-09 13:30 ` [PATCH 14/21] dm: replace abbrev branching with goto Eugene Syromyatnikov
2016-10-09 13:31 ` [PATCH 15/21] dm: Additional data_size/data_start checks Eugene Syromyatnikov
2016-10-09 13:31 ` [PATCH 16/21] dm: Add comment regarding intended fall-through in switch statement Eugene Syromyatnikov
2016-10-09 13:31 ` [PATCH 17/21] dm: Add data_size and data_offset fields to output Eugene Syromyatnikov
2016-10-09 13:31 ` [PATCH 18/21] tests/ioctl_dm: Allow passing size and data_start to init_s Eugene Syromyatnikov
2016-10-09 13:31 ` [PATCH 19/21] dm: Add check whether command uses parameters Eugene Syromyatnikov
2016-10-09 13:31 ` [PATCH 20/21] dm: Fix printing of version field Eugene Syromyatnikov
2016-10-09 13:31 ` Eugene Syromyatnikov [this message]
2016-10-10 10:27 ` [PATCH] Re: your dm patch for strace Mikulas Patocka
2016-10-11 22:38 ` [PATCH 0/9] Additional checks for strace DM ioctl decoder test Eugene Syromyatnikov
2016-10-19 20:31 ` [PATCH] device mapper ioctl Mikulas Patocka
[not found] ` <alpine.LRH.2.02.1610191626360.628-Hpncn10jQN4oNljnaZt3ZvA+iT7yCHsGwRM8/txMwJMAicBL8TP8PQ@public.gmane.org>
2016-11-10 21:01 ` Eugene Syromyatnikov
[not found] ` <CACGkJdts9AKHnXb+b6J2kfpDNZJZW12WaNaYukrA11PHtAydag-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2016-11-11 3:13 ` Masatake YAMATO
2016-10-11 22:38 ` [PATCH 1/9] util: Add support for QUOTE_0_TERMINATED in user_style to ptrintstr_ex Eugene Syromyatnikov
2016-10-11 22:38 ` [PATCH 2/9] tests: Add check for printing of overlength strings to ioctl_dm test Eugene Syromyatnikov
2016-10-11 22:38 ` [PATCH 3/9] tests: Add check for presence of HAVE_LINUX_DM_IOCTL_H macro definition " Eugene Syromyatnikov
2016-10-11 22:38 ` [PATCH 4/9] tests/ioctl_dm: whitespace Eugene Syromyatnikov
2016-10-11 22:38 ` [PATCH 5/9] dm: Fix comma printing for the case when dm_target_msg structure is inaccessible Eugene Syromyatnikov
2016-10-11 22:39 ` [PATCH 6/9] tests/ioctl_dm: overly long string printing checks Eugene Syromyatnikov
2016-10-11 22:39 ` [PATCH 7/9] tests: Some additional checks for ioctl_dm test Eugene Syromyatnikov
2016-10-11 22:39 ` [PATCH 8/9] tests: Add ioctl_dm to .gitignore Eugene Syromyatnikov
2016-10-11 22:39 ` [PATCH 9/9] tests: Add checks for abbreviated DM ioctl output Eugene Syromyatnikov
2016-10-08 17:45 ` [PATCH] Re: your dm patch for strace Eugene Syromyatnikov
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=20161009133142.GA4692@obsidian \
--to=evgsyr-re5jqeeqqe8avxtiumwx3w@public.gmane.org \
--cc=dm-devel-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org \
--cc=mpatocka-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org \
--cc=strace-devel-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.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 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.