From: David Howells <dhowells@redhat.com>
To: Christian Brauner <christian@brauner.io>,
Matthew Wilcox <willy@infradead.org>,
Christoph Hellwig <hch@infradead.org>
Cc: David Howells <dhowells@redhat.com>,
Paulo Alcantara <pc@manguebit.org>, Jens Axboe <axboe@kernel.dk>,
Leon Romanovsky <leon@kernel.org>,
Steve French <sfrench@samba.org>,
ChenXiaoSong <chenxiaosong@chenxiaosong.com>,
Marc Dionne <marc.dionne@auristor.com>,
Eric Van Hensbergen <ericvh@kernel.org>,
Dominique Martinet <asmadeus@codewreck.org>,
Ilya Dryomov <idryomov@gmail.com>,
Trond Myklebust <trondmy@kernel.org>,
netfs@lists.linux.dev, linux-afs@lists.infradead.org,
linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org,
ceph-devel@vger.kernel.org, v9fs@lists.linux.dev,
linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-mm@kvack.org
Subject: [PATCH v2 04/21] netfs: Bulk load the readahead-provided folios up front
Date: Mon, 18 May 2026 23:29:36 +0100 [thread overview]
Message-ID: <20260518222959.488126-5-dhowells@redhat.com> (raw)
In-Reply-To: <20260518222959.488126-1-dhowells@redhat.com>
Load all the folios by the VM for readahead up front into the folio queue.
With the number of folios provided by the VM, the folio queue can be fully
allocated first and then the loading happen in one go inside the RCU read
lock. The folio refs acquired from readahead are dropped in bulk once the
first subrequest is dispatched as it's quite a slow operation. The
collector waits for NETFS_RREQ_NEED_PUT_RA_REFS to be cleared so that it
doesn't unlock folios before the xarray has been scanned for them.
This simplifies the buffer handling later and isn't noticeably slower as
the xarray doesn't need to be modified and the folios are all already
pre-locked.
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: netfs@lists.linux.dev
cc: linux-mm@kvack.org
cc: linux-fsdevel@vger.kernel.org
---
fs/netfs/buffered_read.c | 97 +++++++++++++++++++++-------------
fs/netfs/internal.h | 1 +
fs/netfs/misc.c | 19 +++++++
fs/netfs/read_collect.c | 7 +++
fs/netfs/rolling_buffer.c | 75 ++++++++++++++++++++++++++
include/linux/netfs.h | 1 +
include/linux/rolling_buffer.h | 3 ++
include/trace/events/netfs.h | 3 ++
8 files changed, 169 insertions(+), 37 deletions(-)
diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c
index 8f96bc0f6c03..146a2cf64af0 100644
--- a/fs/netfs/buffered_read.c
+++ b/fs/netfs/buffered_read.c
@@ -54,6 +54,42 @@ static void netfs_rreq_expand(struct netfs_io_request *rreq,
}
}
+/*
+ * Drop the folio refs acquired from the readahead API.
+ */
+static void netfs_bulk_drop_ra_refs(struct netfs_io_request *rreq)
+{
+ struct folio_batch fbatch;
+ struct folio *folio;
+ pgoff_t nr_pages = DIV_ROUND_UP(rreq->len, PAGE_SIZE);
+ pgoff_t first = rreq->start / PAGE_SIZE;
+ XA_STATE(xas, &rreq->mapping->i_pages, first);
+
+ folio_batch_init(&fbatch);
+
+ rcu_read_lock();
+
+ xas_for_each(&xas, folio, first + nr_pages - 1) {
+ if (xas_retry(&xas, folio))
+ continue;
+
+ if (!folio_batch_add(&fbatch, folio))
+ folio_batch_release(&fbatch);
+ }
+
+ rcu_read_unlock();
+ folio_batch_release(&fbatch);
+ trace_netfs_rreq(rreq, netfs_rreq_trace_ra_put_ref);
+ clear_bit_unlock(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags);
+ wake_up(&rreq->waitq);
+}
+
+static void netfs_maybe_bulk_drop_ra_refs(struct netfs_io_request *rreq)
+{
+ if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags))
+ netfs_bulk_drop_ra_refs(rreq);
+}
+
/*
* Begin an operation, and fetch the stored zero point value from the cookie if
* available.
@@ -74,12 +110,8 @@ static int netfs_begin_cache_read(struct netfs_io_request *rreq, struct netfs_in
*
* Returns the limited size if successful and -ENOMEM if insufficient memory
* available.
- *
- * [!] NOTE: This must be run in the same thread as ->issue_read() was called
- * in as we access the readahead_control struct.
*/
-static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq,
- struct readahead_control *ractl)
+static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq)
{
struct netfs_io_request *rreq = subreq->rreq;
size_t rsize = subreq->len;
@@ -87,28 +119,6 @@ static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq,
if (subreq->source == NETFS_DOWNLOAD_FROM_SERVER)
rsize = umin(rsize, rreq->io_streams[0].sreq_max_len);
- if (ractl) {
- /* If we don't have sufficient folios in the rolling buffer,
- * extract a folioq's worth from the readahead region at a time
- * into the buffer. Note that this acquires a ref on each page
- * that we will need to release later - but we don't want to do
- * that until after we've started the I/O.
- */
- struct folio_batch put_batch;
-
- folio_batch_init(&put_batch);
- while (rreq->submitted < subreq->start + rsize) {
- ssize_t added;
-
- added = rolling_buffer_load_from_ra(&rreq->buffer, ractl,
- &put_batch);
- if (added < 0)
- return added;
- rreq->submitted += added;
- }
- folio_batch_release(&put_batch);
- }
-
subreq->len = rsize;
if (unlikely(rreq->io_streams[0].sreq_max_segs)) {
size_t limit = netfs_limit_iter(&rreq->buffer.iter, 0, rsize,
@@ -204,8 +214,7 @@ static void netfs_issue_read(struct netfs_io_request *rreq,
* slicing up the region to be read according to available cache blocks and
* network rsize.
*/
-static void netfs_read_to_pagecache(struct netfs_io_request *rreq,
- struct readahead_control *ractl)
+static void netfs_read_to_pagecache(struct netfs_io_request *rreq)
{
struct fscache_occupancy _occ = {
.query_from = rreq->start,
@@ -335,7 +344,7 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq,
trace_netfs_sreq(subreq, netfs_sreq_trace_prepare);
}
- slice = netfs_prepare_read_iterator(subreq, ractl);
+ slice = netfs_prepare_read_iterator(subreq);
if (slice < 0) {
ret = slice;
netfs_cancel_read(subreq, ret);
@@ -350,6 +359,7 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq,
trace_netfs_sreq(subreq, netfs_sreq_trace_submit);
netfs_issue_read(rreq, subreq);
+ netfs_maybe_bulk_drop_ra_refs(rreq);
if (test_bit(NETFS_RREQ_PAUSE, &rreq->flags))
netfs_wait_for_paused_read(rreq);
@@ -388,6 +398,7 @@ void netfs_readahead(struct readahead_control *ractl)
struct netfs_io_request *rreq;
struct netfs_inode *ictx = netfs_inode(ractl->mapping->host);
unsigned long long start = readahead_pos(ractl);
+ ssize_t added;
size_t size = readahead_length(ractl);
int ret;
@@ -408,11 +419,23 @@ void netfs_readahead(struct readahead_control *ractl)
netfs_rreq_expand(rreq, ractl);
- rreq->submitted = rreq->start;
- if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST) < 0)
+ /* Load the folios to be read into a bvecq chain. Note that this
+ * acquires a ref on each folio that we will need to release later -
+ * but we don't want to do that until after we've started the I/O.
+ */
+ added = rolling_buffer_bulk_load_from_ra(&rreq->buffer, ractl, rreq->debug_id);
+ if (added < 0) {
+ ret = added;
goto cleanup_free;
- netfs_read_to_pagecache(rreq, ractl);
+ }
+ __set_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags);
+
+ rreq->submitted = rreq->start + added;
+ rreq->cleaned_to = rreq->start;
+ rreq->front_folio_order = folio_order(rreq->buffer.tail->vec.folios[0]);
+ netfs_read_to_pagecache(rreq);
+ netfs_maybe_bulk_drop_ra_refs(rreq);
return netfs_put_request(rreq, netfs_rreq_trace_put_return);
cleanup_free:
@@ -505,7 +528,7 @@ static int netfs_read_gaps(struct file *file, struct folio *folio)
iov_iter_bvec(&rreq->buffer.iter, ITER_DEST, bvec, i, rreq->len);
rreq->submitted = rreq->start + flen;
- netfs_read_to_pagecache(rreq, NULL);
+ netfs_read_to_pagecache(rreq);
ret = netfs_wait_for_read(rreq);
if (ret >= 0) {
@@ -580,7 +603,7 @@ int netfs_read_folio(struct file *file, struct folio *folio)
if (ret < 0)
goto discard;
- netfs_read_to_pagecache(rreq, NULL);
+ netfs_read_to_pagecache(rreq);
ret = netfs_wait_for_read(rreq);
netfs_put_request(rreq, netfs_rreq_trace_put_return);
return ret < 0 ? ret : 0;
@@ -737,7 +760,7 @@ int netfs_write_begin(struct netfs_inode *ctx,
if (ret < 0)
goto error_put;
- netfs_read_to_pagecache(rreq, NULL);
+ netfs_read_to_pagecache(rreq);
ret = netfs_wait_for_read(rreq);
netfs_put_request(rreq, netfs_rreq_trace_put_return);
if (ret < 0)
@@ -802,7 +825,7 @@ int netfs_prefetch_for_write(struct file *file, struct folio *folio,
if (ret < 0)
goto error_put;
- netfs_read_to_pagecache(rreq, NULL);
+ netfs_read_to_pagecache(rreq);
ret = netfs_wait_for_read(rreq);
netfs_put_request(rreq, netfs_rreq_trace_put_return);
return ret < 0 ? ret : 0;
diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h
index d82f2116f8e0..4b0f9304b970 100644
--- a/fs/netfs/internal.h
+++ b/fs/netfs/internal.h
@@ -80,6 +80,7 @@ ssize_t netfs_wait_for_read(struct netfs_io_request *rreq);
ssize_t netfs_wait_for_write(struct netfs_io_request *rreq);
void netfs_wait_for_paused_read(struct netfs_io_request *rreq);
void netfs_wait_for_paused_write(struct netfs_io_request *rreq);
+void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq);
/*
* objects.c
diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c
index 5d554512ed23..f5c1c463f4ff 100644
--- a/fs/netfs/misc.c
+++ b/fs/netfs/misc.c
@@ -563,3 +563,22 @@ void netfs_wait_for_paused_write(struct netfs_io_request *rreq)
{
return netfs_wait_for_pause(rreq, netfs_write_collection);
}
+
+/*
+ * Wait for the readahead-acquired refs to be put.
+ */
+void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq)
+{
+ DEFINE_WAIT(myself);
+
+ for (;;) {
+ trace_netfs_rreq(rreq, netfs_rreq_trace_wait_put_ra_refs);
+ prepare_to_wait(&rreq->waitq, &myself, TASK_UNINTERRUPTIBLE);
+ if (!test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags))
+ break;
+ schedule();
+ }
+
+ trace_netfs_rreq(rreq, netfs_rreq_trace_waited_put_ra_refs);
+ finish_wait(&rreq->waitq, &myself);
+}
diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c
index 23660a590124..edf7cea7e2f9 100644
--- a/fs/netfs/read_collect.c
+++ b/fs/netfs/read_collect.c
@@ -118,6 +118,13 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
slot = 0;
}
+ /* We have to wait for readahead refs to have been released before we
+ * can unlock any folios as the ref-dropper walks i_pages and the only
+ * thing preventing these folios from being removed is the folio lock.
+ */
+ if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags))
+ netfs_wait_for_put_ra_refs(rreq);
+
for (;;) {
struct folio *folio;
unsigned long long fpos, fend;
diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c
index a17fbf9853a4..576b425a227d 100644
--- a/fs/netfs/rolling_buffer.c
+++ b/fs/netfs/rolling_buffer.c
@@ -149,6 +149,81 @@ ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll,
return size;
}
+/*
+ * Decant the entire list of folios to read into a rolling buffer.
+ */
+ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll,
+ struct readahead_control *ractl,
+ unsigned int rreq_id)
+{
+ XA_STATE(xas, &ractl->mapping->i_pages, ractl->_index);
+ struct folio_queue *fq;
+ struct folio *folio;
+ ssize_t loaded = 0;
+ int nr, slot = 0, npages = 0;
+
+ /* First allocate all the folioqs we're going to need to avoid having
+ * to deal with ENOMEM later.
+ */
+ nr = ractl->_nr_folios;
+ do {
+ fq = netfs_folioq_alloc(rreq_id, GFP_KERNEL,
+ netfs_trace_folioq_make_space);
+ if (!fq) {
+ rolling_buffer_clear(roll);
+ return -ENOMEM;
+ }
+ fq->prev = roll->head;
+ if (!roll->tail)
+ roll->tail = fq;
+ else
+ roll->head->next = fq;
+ roll->head = fq;
+
+ nr -= folioq_nr_slots(fq);
+ } while (nr > 0);
+
+ rcu_read_lock();
+
+ fq = roll->tail;
+ xas_for_each(&xas, folio, ractl->_index + ractl->_nr_pages - 1) {
+ unsigned int order;
+
+ if (xas_retry(&xas, folio))
+ continue;
+ VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
+
+ order = folio_order(folio);
+ fq->orders[slot] = order;
+ fq->vec.folios[slot] = folio;
+ loaded += PAGE_SIZE << order;
+ npages += 1 << order;
+ trace_netfs_folio(folio, netfs_folio_trace_read);
+
+ slot++;
+ if (slot >= folioq_nr_slots(fq)) {
+ fq->vec.nr = slot;
+ fq = fq->next;
+ if (!fq) {
+ WARN_ON_ONCE(npages < readahead_count(ractl));
+ break;
+ }
+ slot = 0;
+ }
+ }
+
+ rcu_read_unlock();
+
+ if (fq)
+ fq->vec.nr = slot;
+
+ WRITE_ONCE(roll->iter.count, loaded);
+ iov_iter_folio_queue(&roll->iter, ITER_DEST, roll->tail, 0, 0, loaded);
+ ractl->_index += npages;
+ ractl->_nr_pages -= npages;
+ return loaded;
+}
+
/*
* Append a folio to the rolling buffer.
*/
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index d175c63ff659..f7f55b7621f3 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -285,6 +285,7 @@ struct netfs_io_request {
#define NETFS_RREQ_FOLIO_COPY_TO_CACHE 14 /* Copy current folio to cache from read */
#define NETFS_RREQ_UPLOAD_TO_SERVER 15 /* Need to write to the server */
#define NETFS_RREQ_USE_IO_ITER 16 /* Use ->io_iter rather than ->i_pages */
+#define NETFS_RREQ_NEED_PUT_RA_REFS 17 /* Need to put the folio refs RA gave us */
#define NETFS_RREQ_USE_PGPRIV2 31 /* [DEPRECATED] Use PG_private_2 to mark
* write to cache on read */
const struct netfs_request_ops *netfs_ops;
diff --git a/include/linux/rolling_buffer.h b/include/linux/rolling_buffer.h
index ac15b1ffdd83..b35ef43f325f 100644
--- a/include/linux/rolling_buffer.h
+++ b/include/linux/rolling_buffer.h
@@ -48,6 +48,9 @@ int rolling_buffer_make_space(struct rolling_buffer *roll);
ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll,
struct readahead_control *ractl,
struct folio_batch *put_batch);
+ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll,
+ struct readahead_control *ractl,
+ unsigned int rreq_id);
ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio,
unsigned int flags);
struct folio_queue *rolling_buffer_delete_spent(struct rolling_buffer *roll);
diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h
index 63ed1d771bd8..83266835b7ad 100644
--- a/include/trace/events/netfs.h
+++ b/include/trace/events/netfs.h
@@ -64,6 +64,7 @@
EM(netfs_rreq_trace_intr, "INTR ") \
EM(netfs_rreq_trace_inval_cache, "INVL-CA") \
EM(netfs_rreq_trace_ki_complete, "KI-CMPL") \
+ EM(netfs_rreq_trace_ra_put_ref, "RA-PUT ") \
EM(netfs_rreq_trace_recollect, "RECLLCT") \
EM(netfs_rreq_trace_redirty, "REDIRTY") \
EM(netfs_rreq_trace_resubmit, "RESUBMT") \
@@ -77,9 +78,11 @@
EM(netfs_rreq_trace_unpause, "UNPAUSE") \
EM(netfs_rreq_trace_wait_ip, "WAIT-IP") \
EM(netfs_rreq_trace_wait_pause, "--PAUSED--") \
+ EM(netfs_rreq_trace_wait_put_ra_refs, "WAIT-P-RA") \
EM(netfs_rreq_trace_wait_quiesce, "WAIT-QUIESCE") \
EM(netfs_rreq_trace_waited_ip, "DONE-IP") \
EM(netfs_rreq_trace_waited_pause, "--UNPAUSED--") \
+ EM(netfs_rreq_trace_waited_put_ra_refs, "DONE-P-RA") \
EM(netfs_rreq_trace_waited_quiesce, "DONE-QUIESCE") \
EM(netfs_rreq_trace_wake_ip, "WAKE-IP") \
EM(netfs_rreq_trace_wake_queue, "WAKE-Q ") \
next prev parent reply other threads:[~2026-05-18 22:30 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-18 22:29 [PATCH v2 00/21] netfs: Keep track of folios in a segmented bio_vec[] chain David Howells
2026-05-18 22:29 ` [PATCH v2 01/21] cachefiles: Don't rely on backing fs storage map for most use cases David Howells
2026-05-18 22:29 ` [PATCH v2 02/21] netfs: Add the cache object ID to netfs_read/write tracepoints David Howells
2026-05-18 22:29 ` [PATCH v2 03/21] mm: Make readahead store folio count in readahead_control David Howells
2026-05-18 22:29 ` David Howells [this message]
2026-05-18 22:29 ` [PATCH v2 05/21] Add a function to kmap one page of a multipage bio_vec David Howells
2026-05-25 6:13 ` Christoph Hellwig
2026-06-04 14:25 ` David Howells
2026-05-18 22:29 ` [PATCH v2 06/21] iov_iter: Make iov_iter_get_pages*() wrap iov_iter_extract_pages() David Howells
2026-05-25 6:13 ` Christoph Hellwig
2026-06-04 14:26 ` David Howells
2026-05-18 22:29 ` [PATCH v2 07/21] iov_iter: Add a segmented queue of bio_vec[] David Howells
2026-05-18 22:29 ` [PATCH v2 08/21] netfs: Add some tools for managing bvecq chains David Howells
2026-05-18 22:29 ` [PATCH v2 09/21] netfs: Add a function to extract from an iter into a bvecq David Howells
2026-05-18 22:29 ` [PATCH v2 10/21] afs: Use a bvecq to hold dir content rather than folioq David Howells
2026-05-18 22:29 ` [PATCH v2 11/21] cifs: Use a bvecq for buffering instead of a folioq David Howells
2026-05-18 22:29 ` [PATCH v2 12/21] cifs: Support ITER_BVECQ in smb_extract_iter_to_rdma() David Howells
2026-05-19 8:18 ` Stefan Metzmacher
2026-05-18 22:29 ` [PATCH v2 13/21] netfs: Switch to using bvecq rather than folio_queue and rolling_buffer David Howells
2026-05-18 22:29 ` [PATCH v2 14/21] cifs: Remove support for ITER_FOLIOQ from smb_extract_iter_to_rdma() David Howells
2026-05-19 8:19 ` Stefan Metzmacher
2026-05-18 22:29 ` [PATCH v2 15/21] netfs: Remove netfs_alloc/free_folioq_buffer() David Howells
2026-05-18 22:29 ` [PATCH v2 16/21] netfs: Remove netfs_extract_user_iter() David Howells
2026-05-18 22:29 ` [PATCH v2 17/21] iov_iter: Remove ITER_FOLIOQ David Howells
2026-05-18 22:29 ` [PATCH v2 18/21] netfs: Remove folio_queue and rolling_buffer David Howells
2026-05-18 22:29 ` [PATCH v2 19/21] netfs: Check for too much data being read David Howells
2026-05-18 22:29 ` [PATCH v2 20/21] netfs: Limit the minimum trigger for progress reporting David Howells
2026-05-18 22:29 ` [PATCH v2 21/21] netfs: Combine prepare and issue ops and grab the buffers on request David Howells
2026-05-19 8:15 ` [PATCH v2 00/21] netfs: Keep track of folios in a segmented bio_vec[] chain David Laight
2026-05-19 8:56 ` David Howells
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=20260518222959.488126-5-dhowells@redhat.com \
--to=dhowells@redhat.com \
--cc=asmadeus@codewreck.org \
--cc=axboe@kernel.dk \
--cc=ceph-devel@vger.kernel.org \
--cc=chenxiaosong@chenxiaosong.com \
--cc=christian@brauner.io \
--cc=ericvh@kernel.org \
--cc=hch@infradead.org \
--cc=idryomov@gmail.com \
--cc=leon@kernel.org \
--cc=linux-afs@lists.infradead.org \
--cc=linux-cifs@vger.kernel.org \
--cc=linux-erofs@lists.ozlabs.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-nfs@vger.kernel.org \
--cc=marc.dionne@auristor.com \
--cc=netfs@lists.linux.dev \
--cc=pc@manguebit.org \
--cc=sfrench@samba.org \
--cc=trondmy@kernel.org \
--cc=v9fs@lists.linux.dev \
--cc=willy@infradead.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