From: Jeff Layton <jeff.layton@primarydata.com>
To: "J. Bruce Fields" <bfields@fieldses.org>
Cc: linux-nfs@vger.kernel.org
Subject: Re: [PATCH v4 001/100] nfsd: close potential race between delegation break and laundromat
Date: Thu, 10 Jul 2014 12:34:02 -0400 [thread overview]
Message-ID: <20140710123402.23a33daf@tlielax.poochiereds.net> (raw)
In-Reply-To: <20140710121625.69c8c08f@tlielax.poochiereds.net>
On Thu, 10 Jul 2014 12:16:25 -0400
Jeff Layton <jlayton@primarydata.com> wrote:
> On Thu, 10 Jul 2014 11:59:05 -0400
> "J. Bruce Fields" <bfields@fieldses.org> wrote:
>
> > On Tue, Jul 08, 2014 at 02:02:49PM -0400, Jeff Layton wrote:
> > > Bruce says:
> > >
> > > There's also a preexisting expire_client/laundromat vs break race:
> > >
> > > - expire_client/laundromat adds a delegation to its local
> > > reaplist using the same dl_recall_lru field that a delegation
> > > uses to track its position on the recall lru and drops the
> > > state lock.
> > >
> > > - a concurrent break_lease adds the delegation to the lru.
> > >
> > > - expire/client/laundromat then walks it reaplist and sees the
> > > lru head as just another delegation on the list....
> > >
> > > Fix this race by checking the dl_time under the state_lock. If we find
> > > that it's not 0, then we know that it has already been queued to the LRU
> > > list and that we shouldn't queue it again.
> > >
> > > In the case of destroy_client, we must also ensure that we don't hit
> > > similar races by ensuring that we don't move any delegations to the
> > > reaplist with a dl_time of 0. Just bump the dl_time by one before we
> > > drop the state_lock. We're destroying the delegations anyway, so a 1s
> > > difference there won't matter.
> > >
> > > The fault injection code also requires a bit of surgery here:
> > >
> > > First, in the case of nfsd_forget_client_delegations, we must prevent
> > > the same sort of race vs. the delegation break callback. For that, we
> > > just increment the dl_time to ensure that a delegation callback can't
> > > race in while we're working on it.
> > >
> > > We can't do that for nfsd_recall_client_delegations, as we need to have
> > > it actually queue the delegation, and that won't happen if we increment
> > > the dl_time. The state lock is held over that function, so we don't need
> > > to worry about these sorts of races there.
> > >
> > > There is one other potential bug nfsd_recall_client_delegations though.
> > > Entries on the victims list are not dequeued before calling
> > > nfsd_break_one_deleg. That's a potential list corruptor, so ensure that
> > > we do that there.
> > >
> > > Reported-by: "J. Bruce Fields" <bfields@fieldses.org>
> > > Signed-off-by: Jeff Layton <jlayton@primarydata.com>
> > > ---
> > > fs/nfsd/nfs4state.c | 40 +++++++++++++++++++++++++++++++++-------
> > > 1 file changed, 33 insertions(+), 7 deletions(-)
> > >
> > > diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
> > > index 763aeeb67ccf..633b34fd6c92 100644
> > > --- a/fs/nfsd/nfs4state.c
> > > +++ b/fs/nfsd/nfs4state.c
> > > @@ -1287,6 +1287,8 @@ destroy_client(struct nfs4_client *clp)
> > > while (!list_empty(&clp->cl_delegations)) {
> > > dp = list_entry(clp->cl_delegations.next, struct nfs4_delegation, dl_perclnt);
> > > list_del_init(&dp->dl_perclnt);
> > > + /* Ensure that deleg break won't try to requeue it */
> > > + ++dp->dl_time;
> > > list_move(&dp->dl_recall_lru, &reaplist);
> > > }
> > > spin_unlock(&state_lock);
> > > @@ -2933,10 +2935,14 @@ static void nfsd_break_one_deleg(struct nfs4_delegation *dp)
> > > * it's safe to take a reference: */
> > > atomic_inc(&dp->dl_count);
> > >
> > > - list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru);
> > > -
> > > - /* Only place dl_time is set; protected by i_lock: */
> > > - dp->dl_time = get_seconds();
> > > + /*
> > > + * If the dl_time != 0, then we know that it has already been
> > > + * queued for a lease break. Don't queue it again.
> > > + */
> > > + if (dp->dl_time == 0) {
> >
> > Any reason not to just make that
> >
> > if (dp->dl_time)
> > return;
> >
> > ?
> >
> > I don't know if it matters right now, but it might also be useful to
> > know that no more work will get queued once dl_time is set.
> >
> > --b.
> >
>
> No, that makes a lot of sense. I'll respin and do that instead...
>
>
Actually, now that I look, there are a few reasons not to do that...
First, we want to ensure that block_delegations gets called even if the
dl_time is non-zero. So fine, we can move that up above the dl_time
check...
Second, it'll complicate patch #4 in this series. We have to take a
reference to the delegation in the delegation break. The dl_time though
must be checked while holding the state_lock. So, if the dl_time ends
up being non-zero at that point, we'll have to put the client reference
and take care not to do the actual recall job. It's doable, but it'll
make the code more complex. I'm not sure that's worth the extra
complexity...
> > > + list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru);
> > > + dp->dl_time = get_seconds();
> > > + }
> > >
> > > block_delegations(&dp->dl_fh);
> > >
> > > @@ -5081,8 +5087,23 @@ static u64 nfsd_find_all_delegations(struct nfs4_client *clp, u64 max,
> > >
> > > lockdep_assert_held(&state_lock);
> > > list_for_each_entry_safe(dp, next, &clp->cl_delegations, dl_perclnt) {
> > > - if (victims)
> > > + if (victims) {
> > > + /*
> > > + * It's not safe to mess with delegations that have a
> > > + * non-zero dl_time. They might have already been broken
> > > + * and could be processed by the laundromat outside of
> > > + * the state_lock. Just leave them be.
> > > + */
> > > + if (dp->dl_time != 0)
> > > + continue;
> > > +
> > > + /*
> > > + * Increment dl_time to ensure that delegation breaks
> > > + * don't monkey with it now that we are.
> > > + */
> > > + ++dp->dl_time;
> > > list_move(&dp->dl_recall_lru, victims);
> > > + }
> > > if (++count == max)
> > > break;
> > > }
> > > @@ -5107,14 +5128,19 @@ u64 nfsd_forget_client_delegations(struct nfs4_client *clp, u64 max)
> > >
> > > u64 nfsd_recall_client_delegations(struct nfs4_client *clp, u64 max)
> > > {
> > > - struct nfs4_delegation *dp, *next;
> > > + struct nfs4_delegation *dp;
> > > LIST_HEAD(victims);
> > > u64 count;
> > >
> > > spin_lock(&state_lock);
> > > count = nfsd_find_all_delegations(clp, max, &victims);
> > > - list_for_each_entry_safe(dp, next, &victims, dl_recall_lru)
> > > + while (!list_empty(&victims)) {
> > > + dp = list_first_entry(&victims, struct nfs4_delegation,
> > > + dl_recall_lru);
> > > + list_del_init(&dp->dl_recall_lru);
> > > + dp->dl_time = 0;
> > > nfsd_break_one_deleg(dp);
> > > + }
> > > spin_unlock(&state_lock);
> > >
> > > return count;
> > > --
> > > 1.9.3
> > >
>
>
--
Jeff Layton <jlayton@primarydata.com>
next prev parent reply other threads:[~2014-07-10 16:34 UTC|newest]
Thread overview: 144+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-07-08 18:02 [PATCH v4 000/101] nfsd: eliminate the client_mutex Jeff Layton
2014-07-08 18:02 ` [PATCH v4 001/100] nfsd: close potential race between delegation break and laundromat Jeff Layton
2014-07-10 15:59 ` J. Bruce Fields
2014-07-10 16:16 ` Jeff Layton
2014-07-10 16:34 ` Jeff Layton [this message]
2014-07-10 17:41 ` J. Bruce Fields
2014-07-08 18:02 ` [PATCH v4 002/100] nfsd: reduce some spinlocking in put_client_renew Jeff Layton
2014-07-10 11:18 ` Christoph Hellwig
2014-07-08 18:02 ` [PATCH v4 003/100] nfsd: Ensure stateids remain unique until they are freed Jeff Layton
2014-07-10 11:23 ` Christoph Hellwig
2014-07-10 12:43 ` Jeff Layton
2014-07-14 16:45 ` Jeff Layton
2014-07-15 7:50 ` Christoph Hellwig
2014-07-08 18:02 ` [PATCH v4 004/100] nfsd: Avoid taking state_lock while holding inode lock in nfsd_break_one_deleg Jeff Layton
2014-07-08 18:02 ` [PATCH v4 005/100] nfsd: Move the delegation reference counter into the struct nfs4_stid Jeff Layton
2014-07-10 11:28 ` Christoph Hellwig
2014-07-08 18:02 ` [PATCH v4 006/100] nfsd4: use cl_lock to synchronize all stateid idr calls Jeff Layton
2014-07-10 11:32 ` Christoph Hellwig
2014-07-10 12:45 ` Jeff Layton
2014-07-08 18:02 ` [PATCH v4 007/100] nfsd: Add fine grained protection for the nfs4_file->fi_stateids list Jeff Layton
2014-07-10 11:33 ` Christoph Hellwig
2014-07-08 18:02 ` [PATCH v4 008/100] nfsd: Add a mutex to protect the NFSv4.0 open owner replay cache Jeff Layton
2014-07-08 18:02 ` [PATCH v4 009/100] nfsd: Add locking to the nfs4_file->fi_fds[] array Jeff Layton
2014-07-10 10:32 ` Christoph Hellwig
2014-07-08 18:02 ` [PATCH v4 010/100] nfsd: clean up helper __release_lock_stateid Jeff Layton
2014-07-08 18:02 ` [PATCH v4 011/100] nfsd: refactor nfs4_file_get_access and nfs4_file_put_access Jeff Layton
2014-07-10 7:59 ` Christoph Hellwig
2014-07-10 11:32 ` Jeff Layton
2014-07-10 11:35 ` Christoph Hellwig
2014-07-10 12:49 ` Jeff Layton
2014-07-10 13:01 ` Christoph Hellwig
2014-07-08 18:03 ` [PATCH v4 012/100] nfsd: remove nfs4_file_put_fd Jeff Layton
2014-07-10 8:03 ` Christoph Hellwig
2014-07-10 11:49 ` Jeff Layton
2014-07-10 12:05 ` Christoph Hellwig
2014-07-10 13:15 ` Jeff Layton
2014-07-08 18:03 ` [PATCH v4 013/100] nfsd: shrink st_access_bmap and st_deny_bmap Jeff Layton
2014-07-10 8:04 ` Christoph Hellwig
2014-07-10 10:50 ` Christoph Hellwig
2014-07-10 12:36 ` Jeff Layton
2014-07-08 18:03 ` [PATCH v4 014/100] nfsd: set stateid access and deny bits in nfs4_get_vfs_file Jeff Layton
2014-07-10 8:34 ` Christoph Hellwig
2014-07-10 15:05 ` Jeff Layton
2014-07-08 18:03 ` [PATCH v4 015/100] nfsd: clean up reset_union_bmap_deny Jeff Layton
2014-07-10 10:31 ` Christoph Hellwig
2014-07-10 12:19 ` Jeff Layton
2014-07-10 12:43 ` Christoph Hellwig
2014-07-10 13:16 ` Christoph Hellwig
2014-07-10 13:21 ` Jeff Layton
2014-07-10 16:20 ` J. Bruce Fields
2014-07-10 16:37 ` Jeff Layton
2014-07-08 18:03 ` [PATCH v4 016/100] nfsd: always hold the fi_lock when bumping fi_access refcounts Jeff Layton
2014-07-10 8:51 ` Christoph Hellwig
2014-07-10 12:20 ` Jeff Layton
2014-07-10 12:43 ` Christoph Hellwig
2014-07-08 18:03 ` [PATCH v4 017/100] nfsd: make deny mode enforcement more efficient and close races in it Jeff Layton
2014-07-10 10:49 ` Christoph Hellwig
2014-07-10 12:36 ` Jeff Layton
2014-07-10 12:45 ` Christoph Hellwig
2014-07-08 18:03 ` [PATCH v4 018/100] nfsd: cleanup and rename nfs4_check_open Jeff Layton
2014-07-10 10:51 ` Christoph Hellwig
2014-07-08 18:03 ` [PATCH v4 019/100] locks: add file_has_lease to prevent delegation break races Jeff Layton
2014-07-08 18:03 ` [PATCH v4 020/100] nfsd: nfs4_alloc_init_lease should take a nfs4_file arg Jeff Layton
2014-07-08 18:03 ` [PATCH v4 021/100] nfsd: Protect the nfs4_file delegation fields using the fi_lock Jeff Layton
2014-07-08 18:03 ` [PATCH v4 022/100] nfsd: Simplify stateid management Jeff Layton
2014-07-08 18:03 ` [PATCH v4 023/100] nfsd: Fix delegation revocation Jeff Layton
2014-07-08 18:03 ` [PATCH v4 024/100] nfsd: Add reference counting to the lock and open stateids Jeff Layton
2014-07-08 18:03 ` [PATCH v4 025/100] nfsd: Add a struct nfs4_file field to struct nfs4_stid Jeff Layton
2014-07-08 18:03 ` [PATCH v4 026/100] nfsd: Replace nfs4_ol_stateid->st_file with the st_stid.sc_file Jeff Layton
2014-07-08 18:03 ` [PATCH v4 027/100] nfsd: Ensure atomicity of stateid destruction and idr tree removal Jeff Layton
2014-07-08 18:03 ` [PATCH v4 028/100] nfsd: Cleanup the freeing of stateids Jeff Layton
2014-07-08 18:03 ` [PATCH v4 029/100] nfsd: do filp_close in sc_free callback for lock stateids Jeff Layton
2014-07-08 18:03 ` [PATCH v4 030/100] nfsd: Add locking to protect the state owner lists Jeff Layton
2014-07-08 18:03 ` [PATCH v4 031/100] nfsd: clean up races in lock stateid searching and creation Jeff Layton
2014-07-08 18:03 ` [PATCH v4 032/100] nfsd: Convert delegation counter to an atomic_long_t type Jeff Layton
2014-07-08 18:03 ` [PATCH v4 033/100] nfsd: Slight cleanup of find_stateid() Jeff Layton
2014-07-08 18:03 ` [PATCH v4 034/100] nfsd: ensure atomicity in nfsd4_free_stateid and nfsd4_validate_stateid Jeff Layton
2014-07-08 18:03 ` [PATCH v4 035/100] nfsd: Add reference counting to lock stateids Jeff Layton
2014-07-08 18:03 ` [PATCH v4 036/100] nfsd: nfsd4_locku() must reference the lock stateid Jeff Layton
2014-07-08 18:03 ` [PATCH v4 037/100] nfsd: Ensure that nfs4_open_delegation() references the delegation stateid Jeff Layton
2014-07-08 18:03 ` [PATCH v4 038/100] nfsd: nfsd4_process_open2() must reference " Jeff Layton
2014-07-08 18:03 ` [PATCH v4 039/100] nfsd: nfsd4_process_open2() must reference the open stateid Jeff Layton
2014-07-08 18:03 ` [PATCH v4 040/100] nfsd: Prepare nfsd4_close() for open stateid referencing Jeff Layton
2014-07-08 18:03 ` [PATCH v4 041/100] nfsd: nfsd4_open_confirm() must reference the open stateid Jeff Layton
2014-07-08 18:03 ` [PATCH v4 042/100] nfsd: Add reference counting to nfs4_preprocess_confirmed_seqid_op Jeff Layton
2014-07-08 18:03 ` [PATCH v4 043/100] nfsd: Migrate the stateid reference into nfs4_preprocess_seqid_op Jeff Layton
2014-07-08 18:03 ` [PATCH v4 044/100] nfsd: Migrate the stateid reference into nfs4_lookup_stateid() Jeff Layton
2014-07-08 18:03 ` [PATCH v4 045/100] nfsd: Migrate the stateid reference into nfs4_find_stateid_by_type() Jeff Layton
2014-07-08 18:03 ` [PATCH v4 046/100] nfsd: Add reference counting to state owners Jeff Layton
2014-07-08 18:03 ` [PATCH v4 047/100] nfsd: Keep a reference to the open stateid for the NFSv4.0 replay cache Jeff Layton
2014-07-08 18:03 ` [PATCH v4 048/100] nfsd: clean up lockowner refcounting when finding them Jeff Layton
2014-07-08 18:03 ` [PATCH v4 049/100] nfsd: add an operation for unhashing a stateowner Jeff Layton
2014-07-08 18:03 ` [PATCH v4 050/100] nfsd: Make lock stateid take a reference to the lockowner Jeff Layton
2014-07-08 18:03 ` [PATCH v4 051/100] nfsd: clean up refcounting for lockowners Jeff Layton
2014-07-08 18:03 ` [PATCH v4 052/100] nfsd: make openstateids hold references to their openowners Jeff Layton
2014-07-08 18:03 ` [PATCH v4 053/100] nfsd: don't allow CLOSE to proceed until refcount on stateid drops Jeff Layton
2014-07-08 18:03 ` [PATCH v4 054/100] nfsd: Protect adding/removing open state owners using client_lock Jeff Layton
2014-07-08 18:03 ` [PATCH v4 055/100] nfsd: Protect adding/removing lock " Jeff Layton
2014-07-08 18:03 ` [PATCH v4 056/100] nfsd: Move the open owner hash table into struct nfs4_client Jeff Layton
2014-07-08 18:03 ` [PATCH v4 057/100] nfsd: clean up and reorganize release_lockowner Jeff Layton
2014-07-08 18:03 ` [PATCH v4 058/100] nfsd: add locking to stateowner release Jeff Layton
2014-07-08 18:03 ` [PATCH v4 059/100] nfsd: optimize destroy_lockowner cl_lock thrashing Jeff Layton
2014-07-08 18:03 ` [PATCH v4 060/100] nfsd: close potential race in nfsd4_free_stateid Jeff Layton
2014-07-08 18:03 ` [PATCH v4 061/100] nfsd: reduce cl_lock thrashing in release_openowner Jeff Layton
2014-07-08 18:03 ` [PATCH v4 062/100] nfsd: don't thrash the cl_lock while freeing an open stateid Jeff Layton
2014-07-08 18:03 ` [PATCH v4 063/100] nfsd: Ensure struct nfs4_client is unhashed before we try to destroy it Jeff Layton
2014-07-08 18:03 ` [PATCH v4 064/100] nfsd: Ensure that the laundromat unhashes the client before releasing locks Jeff Layton
2014-07-08 18:03 ` [PATCH v4 065/100] nfsd: Don't require client_lock in free_client Jeff Layton
2014-07-08 18:03 ` [PATCH v4 066/100] nfsd: Move create_client() call outside the lock Jeff Layton
2014-07-08 18:03 ` [PATCH v4 067/100] nfsd: Protect unconfirmed client creation using client_lock Jeff Layton
2014-07-08 18:03 ` [PATCH v4 068/100] nfsd: Protect session creation and client confirm " Jeff Layton
2014-07-08 18:03 ` [PATCH v4 069/100] nfsd: Protect nfsd4_destroy_clientid " Jeff Layton
2014-07-08 18:03 ` [PATCH v4 070/100] nfsd: Ensure lookup_clientid() takes client_lock Jeff Layton
2014-07-08 18:03 ` [PATCH v4 071/100] nfsd: Add lockdep assertions to document the nfs4_client/session locking Jeff Layton
2014-07-08 18:04 ` [PATCH v4 072/100] nfsd: protect the close_lru list and oo_last_closed_stid with client_lock Jeff Layton
2014-07-08 18:04 ` [PATCH v4 073/100] nfsd: ensure that clp->cl_revoked list is protected by clp->cl_lock Jeff Layton
2014-07-08 18:04 ` [PATCH v4 074/100] nfsd: move unhash_client_locked call into mark_client_expired_locked Jeff Layton
2014-07-08 18:04 ` [PATCH v4 075/100] nfsd: don't destroy client if mark_client_expired_locked fails Jeff Layton
2014-07-08 18:04 ` [PATCH v4 076/100] nfsd: don't destroy clients that are busy Jeff Layton
2014-07-08 18:04 ` [PATCH v4 077/100] nfsd: protect clid and verifier generation with client_lock Jeff Layton
2014-07-08 18:04 ` [PATCH v4 078/100] nfsd: abstract out the get and set routines into the fault injection ops Jeff Layton
2014-07-08 18:04 ` [PATCH v4 079/100] nfsd: add a forget_clients "get" routine with proper locking Jeff Layton
2014-07-08 18:04 ` [PATCH v4 080/100] nfsd: add a forget_client set_clnt routine Jeff Layton
2014-07-08 18:04 ` [PATCH v4 081/100] nfsd: add nfsd_inject_forget_clients Jeff Layton
2014-07-08 18:04 ` [PATCH v4 082/100] nfsd: add a list_head arg to nfsd_foreach_client_lock Jeff Layton
2014-07-08 18:04 ` [PATCH v4 083/100] nfsd: add more granular locking to forget_locks fault injector Jeff Layton
2014-07-08 18:04 ` [PATCH v4 084/100] nfsd: add more granular locking to forget_openowners " Jeff Layton
2014-07-08 18:04 ` [PATCH v4 085/100] nfsd: add more granular locking to *_delegations fault injectors Jeff Layton
2014-07-08 18:04 ` [PATCH v4 086/100] nfsd: remove old fault injection infrastructure Jeff Layton
2014-07-08 18:04 ` [PATCH v4 087/100] nfsd: Remove nfs4_lock_state(): nfs4_preprocess_stateid_op() Jeff Layton
2014-07-08 18:04 ` [PATCH v4 088/100] nfsd: Remove nfs4_lock_state(): nfsd4_test_stateid/nfsd4_free_stateid Jeff Layton
2014-07-08 18:04 ` [PATCH v4 089/100] nfsd: Remove nfs4_lock_state(): nfsd4_release_lockowner Jeff Layton
2014-07-08 18:04 ` [PATCH v4 090/100] nfsd: Remove nfs4_lock_state(): nfsd4_lock/locku/lockt() Jeff Layton
2014-07-08 18:04 ` [PATCH v4 091/100] nfsd: Remove nfs4_lock_state(): nfsd4_open_downgrade + nfsd4_close Jeff Layton
2014-07-08 18:04 ` [PATCH v4 092/100] nfsd: Remove nfs4_lock_state(): nfsd4_delegreturn() Jeff Layton
2014-07-08 18:04 ` [PATCH v4 093/100] nfsd: Remove nfs4_lock_state(): nfsd4_open and nfsd4_open_confirm Jeff Layton
2014-07-08 18:04 ` [PATCH v4 094/100] nfsd: Remove nfs4_lock_state(): exchange_id, create/destroy_session() Jeff Layton
2014-07-08 18:04 ` [PATCH v4 095/100] nfsd: Remove nfs4_lock_state(): setclientid, setclientid_confirm, renew Jeff Layton
2014-07-08 18:04 ` [PATCH v4 096/100] nfsd: Remove nfs4_lock_state(): reclaim_complete() Jeff Layton
2014-07-08 18:04 ` [PATCH v4 097/100] nfsd: remove nfs4_lock_state: nfs4_laundromat Jeff Layton
2014-07-08 18:04 ` [PATCH v4 098/100] nfsd: remove nfs4_lock_state: nfs4_state_shutdown_net Jeff Layton
2014-07-08 18:04 ` [PATCH v4 099/100] nfsd: remove the client_mutex and the nfs4_lock/unlock_state wrappers Jeff Layton
2014-07-08 18:04 ` [PATCH v4 100/100] nfsd: add some comments to the nfsd4 object definitions Jeff Layton
2014-07-10 7:41 ` Christoph Hellwig
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=20140710123402.23a33daf@tlielax.poochiereds.net \
--to=jeff.layton@primarydata.com \
--cc=bfields@fieldses.org \
--cc=linux-nfs@vger.kernel.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