From: "Michael Montalbo via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Patrick Steinhardt <ps@pks.im>, Michael Montalbo <mmontalbo@gmail.com>
Subject: [PATCH v3 0/3] t/lib-httpd: make CGI test helpers concurrency-safe
Date: Thu, 13 Aug 2026 01:05:33 +0000 [thread overview]
Message-ID: <pull.2171.v3.git.1786583137.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2171.git.1783479584.gitgitgadget@gmail.com>
The httpd tests share a handful of CGI helper scripts under t/lib-httpd. Two
of them keep state between requests in the shared HTTPD_ROOT_PATH, on the
assumption that the web server hands them one request at a time. It does
not: Apache serves requests concurrently, and a single Git operation can
open more than one request to the same endpoint at once. For example, a
partial fetch that receives a REF_DELTA against a missing promisor object
lazily fetches that base while the first response is still being served.
Under that overlap apply-one-time-script.sh fails. Two requests both pass
its "test -f one-time-script" check; one removes the marker; the other then
fails to exec it, emits an empty body, and the server answers HTTP 500. In
the field this is an occasional failure[1] of
t5616.47 tolerate server sending REF_DELTA against missing promisor objects
on the macOS CI runners, with
fatal: ... The requested URL returned error: 500 fatal: could not fetch from
promisor remote
I could not reproduce it against a live server, since the window is tiny and
timing-dependent, but the macOS CI error log names the exact failure and the
new test reproduces the helper's shell error.
http-429.sh keeps its "already returned 429 once" state with the same
non-atomic check-and-set. Its retry flow is mostly sequential, so it seems
less likely to fail, but it is the same latent race.
Each helper replaces a non-atomic "test -f" check and separate follow-up
action with a single atomic operation whose exit status decides the outcome:
apply-one-time-script.sh consumes its one-shot marker with "rm" (without
"-f"), and http-429.sh elects the first request with "mkdir".
* Patch 1 fixes apply-one-time-script.sh (the actual flake) and adds t5567,
which drives the helper directly with no web server so the overlap can be
forced deterministically.
* Patch 2 makes http-429.sh atomic.
* Patch 3 documents the atomic idioms next to where t/lib-httpd.sh installs
the CGI scripts, so the guidance is in front of anyone adding another
helper.
Changes since v2:
* Patch 1 now consumes the marker with a plain "rm" (without "-f") instead
of a rename. "rm" without "-f" already fails once the marker is gone,
which is the atomicity the helper needs. A new comment explains why the
helper discards the one-time script's stderr: a losing request can find
the marker already removed.
* Patch 3 is now specific to the lib-httpd CGI helpers and lives beside
their install site in t/lib-httpd.sh, rather than as a general section in
t/README.
* Reworded several helper comments and the patch 1 and 2 log messages for
clarity and to match the code; no behavior change.
[1]
https://github.com/gitgitgadget/git/actions/runs/28756172690/job/85263916762?pr=2169
Michael Montalbo (3):
t/lib-httpd: fix apply-one-time-script race under concurrent requests
t/lib-httpd: make http-429 first-request check atomic
t/lib-httpd: document writing concurrency-safe CGI helpers
t/lib-httpd.sh | 13 ++++
t/lib-httpd/apply-one-time-script.sh | 50 +++++++++++----
t/lib-httpd/http-429.sh | 30 +++++----
t/meson.build | 1 +
t/t5567-one-time-script.sh | 96 ++++++++++++++++++++++++++++
5 files changed, 164 insertions(+), 26 deletions(-)
create mode 100755 t/t5567-one-time-script.sh
base-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2171%2Fmmontalbo%2Fmm%2Flib-httpd-cgi-safe-proto-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2171/mmontalbo/mm/lib-httpd-cgi-safe-proto-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/2171
Range-diff vs v2:
1: 79b56402c0 ! 1: 862c4258e5 t/lib-httpd: fix apply-one-time-script race under concurrent requests
@@ Metadata
## Commit message ##
t/lib-httpd: fix apply-one-time-script race under concurrent requests
- apply-one-time-script.sh checks for the "one-time-script" marker, runs
- it, captures the git-http-backend response in the fixed-name files "out"
- and "out_modified", and removes the marker only after it has finished
- serving the modified response. Because the client receives the response
- body before that removal, it can start its next request while the marker
- still exists. Apache can then run this CGI for two requests at once: a
- partial fetch that receives a REF_DELTA against a missing promisor
- object lazily fetches that base while the first response is still in
- flight. The second request passes the marker check, the first request
- then removes the marker, and the second fails to exec the now-missing
- marker, emits no output, and the server answers HTTP 500:
+ apply-one-time-script.sh is a CGI helper that, when the file
+ "one-time-script" is present, runs it to rewrite the git-http-backend
+ response. If "one-time-script" generates a response that differs from
+ git-http-backend, the modified response is returned and
+ "one-time-script" is deleted. Requests after the deletion return normal
+ git-http-backend responses.
+
+ The deletion is not safe under concurrency. The helper serves the
+ modified body first and deletes "one-time-script" only afterward, so a
+ client can issue its next request while the file still exists. Apache
+ runs the CGI for both requests at once, for example when a partial fetch
+ lazily fetches a missing promisor base while the first response is still
+ in flight. Both requests find the file and try to run it; the first
+ deletes it; the second then fails to exec the now-missing file, produces
+ no output, and the server returns HTTP 500:
fatal: ... The requested URL returned error: 500
fatal: could not fetch <oid> from promisor remote
- This has been seen as a flaky failure of t5616.47 on the macOS CI
- runners.
-
- Claim the marker atomically with a rename, and only once the one-time
- script has succeeded and actually changed the response; give the scratch
- files per-request names. A request that loses the rename, or whose
- script fails or leaves the response unchanged, serves the unmodified
- body and keeps the marker for a later request. No path emits an empty
- body, so the HTTP 500 no longer occurs.
+ This is the flaky failure of t5616.47 on the macOS CI runners.
- Running the one-time script more than once is fine; the only thing to
- avoid is serving a second, racing request's modified output. Two
- requests can both find the marker and run the script before either
- renames it away, but the rename is atomic, so exactly one of them wins:
- it serves its modified body and consumes the marker. The loser's rename
- fails because the marker is already gone, so it discards the modified
- output it produced and serves the unmodified body instead. The rename,
- not running the script, is what is serialized.
+ Fix it by removing the file with "rm" only after the script has actually
+ changed the response. Because "rm" without "-f" fails once the file is
+ gone, exactly one request removes it and serves the modified body. Any
+ other request serves the unmodified body. Running the script more than
+ once is harmless; only its deletion is serialized, so exactly one
+ request's modified response is ever served. Per-request scratch file
+ names keep concurrent runs from overwriting each other, and no path
+ emits an empty response body.
- Add t5567 to lock this down. The overlap depends on timing, so a live
- httpd test such as t5616.47 (the real code path) passes almost every
- time even against the buggy helper; t5567 instead drives the helper
- directly with a fake git-http-backend and forces the overlap with FIFOs.
- Against the pre-fix helper it fails with the same shell error seen in
- the field:
+ t5616.47 exercises the real code path but, being timing-dependent,
+ passes against the buggy helper almost every time. Add t5567, which
+ drives the helper directly with a fake git-http-backend and forces the
+ overlap with FIFOs; against the pre-fix helper it fails with the same
+ shell error seen in the field:
./one-time-script: No such file or directory
@@ t/lib-httpd/apply-one-time-script.sh
- LC_ALL=C
- export LC_ALL
+#
-+# Apache can run this CGI for concurrent requests (for example a partial fetch
-+# that lazily fetches a missing object while the first response is still in
-+# flight), so the helper claims the marker atomically with a rename, and only
-+# once it has decided to modify the response. A request that loses the race
-+# finds the marker already gone and serves its response unchanged; no request
-+# is left emitting an empty body, which the server would report as HTTP 500.
-+# Scratch files are per-request ($$) so concurrent requests do not clobber each
-+# other.
++# Apache can run this CGI for several requests at the same time. For example, a
++# partial fetch lazily fetches a missing object while the first response is
++# still in flight. To stay correct, the helper removes the marker only after
++# the response changes, and only with "rm" (without "-f"). The "rm" fails for
++# every request except the one that removes the marker first. That request
++# serves the modified body. Every other request serves its response unchanged.
++# No request emits an empty body, which Apache would report as HTTP 500.
++#
++# A scratch file name includes the process ID ($$), so concurrent requests do
++# not overwrite each other's files.
+#
-+# The script may run more than once: the marker is consumed when the response
-+# actually changes (the rename after "cmp"), not when the script runs, so a
-+# request whose response is not the targeted one runs the script, sees no
-+# change, and leaves the marker for a later request. That is safe because the
++# The helper can run one-time-script more than once. It consumes the marker
++# when the response changes (the "rm" after "cmp"), not when it runs the
++# script. A request whose response is not the target runs the script, finds no
++# change, and leaves the marker for a later request. This is safe because the
+# scripts are stateless filters over the captured response.
- "$GIT_EXEC_PATH/git-http-backend" >out
@@ t/lib-httpd/apply-one-time-script.sh
+modified=out-modified.$$
+"$GIT_EXEC_PATH/git-http-backend" >"$out"
+
++# one-time-script can be gone here: a concurrent request may have consumed it
++# since the "test -f" above. Then "./one-time-script" fails, the exit status
++# selects the unmodified body, and "2>/dev/null" discards the expected
++# "no such file" message.
+if ./one-time-script "$out" 2>/dev/null >"$modified" &&
+ ! cmp -s "$out" "$modified" &&
-+ mv one-time-script one-time-script.$$ 2>/dev/null
++ rm one-time-script 2>/dev/null
+then
+ cat "$modified"
else
- "$GIT_EXEC_PATH/git-http-backend"
+ cat "$out"
fi
-+rm -f "$out" "$modified" one-time-script.$$
++rm -f "$out" "$modified"
## t/meson.build ##
@@ t/meson.build: integration_tests = [
2: 5f56f32a74 ! 2: 8ed22c02a1 t/lib-httpd: make http-429 first-request check atomic
@@ Metadata
## Commit message ##
t/lib-httpd: make http-429 first-request check atomic
- http-429.sh records "already returned 429 once" with a "test -f"
- followed by a "touch" of a shared state file. That check-then-act is not
- atomic: Apache can run this CGI for several requests at once, and two of
- them can both pass the "test -f" before either "touch"es, so both treat
- themselves as the first request. The retry flow that drives this
- endpoint is mostly sequential, so this has not been seen to fail, but
- the race is latent.
+ http-429.sh returns 429 to the first request for an endpoint and
+ forwards later ones to git-http-backend so the retry succeeds. It
+ remembers that it has already answered 429 by checking for a shared
+ state file with "test -f" and creating it with "touch".
- Decide whether this is the first request with a single atomic mkdir,
- which fails if the directory already exists, so exactly one of any
- concurrent requests is rate-limited and the rest are forwarded.
+ That "check-and-set" is not atomic. Apache runs the CGI for several
+ requests at once, so two of them can pass the "test -f" before either
+ "touch"es the file, and both then answer as the first request. The
+ retry flow is mostly sequential, so this has not been observed to fail,
+ but the race is latent. Replace the check and the "touch" with a single
+ atomic "mkdir", which fails if the directory already exists, so exactly
+ one of the concurrent requests is rate-limited and the rest are
+ forwarded.
- Skipping state for "permanent" is required for correctness, not just an
- optimization. The marker tells a later or concurrent request that a 429
- has already been served, so that it forwards to git-http-backend instead
- of rate-limiting. Since "permanent" must return 429 to every request,
- that marker must never become visible to another such request.
+ The "permanent" mode needs one extra step, for correctness rather than
+ tidiness. The marker means "429 already served, now forward", so it must
+ never be visible to a request that must itself return 429. Since
+ "permanent" returns 429 to every request, it must leave no marker. The
+ original did not manage this. It ran the "touch" unconditionally and
+ removed the file with "rm -f" in the "permanent" case, and that
+ "create-then-remove" has the same racy window: a concurrent "permanent"
+ request can see the marker before the "rm -f" and be wrongly forwarded.
+ Skipping the "mkdir" entirely for "permanent" (the "!= permanent" guard)
+ leaves no marker at all, so every "permanent" request rate-limits.
- The original did not achieve this by staying stateless: its "touch" of
- the marker ran unconditionally, and the "permanent" case removed it
- afterward with "rm -f". That create-then-remove leaves a window in which
- a concurrent "permanent" request sees the marker and is forwarded. It is
- the same class of check-then-act race this patch removes from the
- first-request check, latent for the same reason: the flow is mostly
- sequential. This version fuses the check and the mark into one atomic
- mkdir and, rather than recreate the pattern as mkdir-then-rmdir, skips
- the mkdir for "permanent" with a "!= permanent" guard. No marker is ever
- created, so there is no window and every "permanent" request
- rate-limits.
-
- There is no accompanying regression test. The check and the set are
- adjacent commands with no external step in between to synchronize on, so
- the overlap cannot be forced deterministically, only reproduced
- probabilistically; the fix is preventive.
+ There is no regression test. The check and the set are adjacent commands
+ with nothing in between to synchronize on, so the overlap cannot be
+ forced deterministically, only reproduced by chance; the fix is
+ preventive.
Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
## t/lib-httpd/http-429.sh ##
+@@
+ # Script to return HTTP 429 Too Many Requests responses for testing retry logic.
+ # Usage: /http_429/<test-context>/<retry-after-value>/<repo-path>
+ #
+-# The test-context is a unique identifier for each test to isolate state files.
++# The test-context is a unique identifier for each test to isolate state directories.
+ # The retry-after-value can be:
+ # - A number (e.g., "1", "2", "100") - sets Retry-After header to that many seconds
+ # - "none" - no Retry-After header
@@ t/lib-httpd/http-429.sh: repo_path="${remaining#*/}" # Get rest (repo path)
# The repo name is the first component before any "/"
repo_name="${repo_path%%/*}"
@@ t/lib-httpd/http-429.sh: repo_path="${remaining#*/}" # Get rest (repo path)
-# Use current directory (HTTPD_ROOT_PATH) for state file
-# Create a safe filename from test_context, retry_after and repo_name
-# This ensures all requests for the same test context share the same state file
-+# Use current directory (HTTPD_ROOT_PATH) for state.
-+# Create a safe name from test_context, retry_after and repo_name so that all
-+# requests for the same test context share the same state.
++# Store state in the current directory (HTTPD_ROOT_PATH). Build a safe name
++# from test_context, retry_after, and repo_name, so that all requests for one
++# test context share the same state.
safe_name=$(echo "${test_context}-${retry_after}-${repo_name}" | tr '/' '_' | tr -cd 'a-zA-Z0-9_-')
-state_file="http-429-state-${safe_name}"
+state="http-429-state-${safe_name}"
-# Check if this is the first call (no state file exists)
-if test -f "$state_file"
-+# This endpoint returns 429 to the first request and forwards later ones to
-+# git-http-backend, so the retry succeeds. Apache can run this CGI for several
-+# requests at once, so a single atomic "mkdir" elects that first request: the
-+# one whose mkdir succeeds returns 429 and leaves the directory behind as the
-+# "already rate-limited" marker; every later request finds the directory (mkdir
-+# fails) and is forwarded.
++# This endpoint returns 429 to the first request. It forwards every later
++# request to git-http-backend, so the retry succeeds. Apache can run this CGI
++# for several requests at the same time. A single atomic "mkdir" selects the
++# first request, because only one "mkdir" succeeds. That request returns 429
++# and leaves the directory as the "already rate-limited" marker. Every later
++# "mkdir" fails, so the endpoint forwards those requests.
+#
-+# "permanent" is the exception: it must return 429 to every request and never
-+# succeed, so it skips the mkdir and records no state. A leftover directory
-+# would make its own later requests find the marker and be forwarded, which is
-+# exactly what "permanent" must not do.
++# "permanent" is the exception. It must return 429 to every request, so it
++# skips the "mkdir" and records no state. A leftover directory would let a
++# later "permanent" request find the marker. The endpoint would forward that
++# request, which "permanent" must not allow.
+if test "$retry_after" != permanent && ! mkdir "$state" 2>/dev/null
then
# Already returned 429 once, forward to git-http-backend
3: f158e1f92e ! 3: 374d148f43 t/README: document writing concurrency-safe helpers
@@ Metadata
Author: Michael Montalbo <mmontalbo@gmail.com>
## Commit message ##
- t/README: document writing concurrency-safe helpers
+ t/lib-httpd: document writing concurrency-safe CGI helpers
- The apply-one-time-script.sh and http-429.sh fixes addressed the same
- underlying problem: a test helper assuming it has exclusive access to a
- file when the web server can run it for several requests at once. The
- atomic idioms that avoid this are not specific to CGI or to HTTP, so
- document them generally, alongside the other guidance for writing tests,
- and leave a pointer from the lib-httpd helper list rather than a local
- comment. The note covers the anti-pattern (a "test -f" then a separate
- act) and the two safe operations (mkdir to elect a winner, rename to
- consume a one-shot marker), citing Git's own lockfile machinery and
- make_symlink() as precedent.
+ The apply-one-time-script.sh and http-429.sh fixes share a root cause: a
+ CGI helper assumed it had a file to itself, when Apache can run the
+ helper for several requests at once. Document the atomic idioms that
+ avoid this next to where lib-httpd.sh installs the CGI scripts, so the
+ advice is in front of anyone adding another one.
- Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
+ The note describes the anti-pattern, a "test -f" check followed by a
+ separate action, and the two atomic alternatives these helpers now use:
+
+ - "mkdir", which fails if the directory exists, to elect the first
+ request (http-429.sh); and
+ - "rm" without "-f", which fails once the file is gone, to consume a
+ one-shot marker (apply-one-time-script.sh).
- ## t/README ##
-@@ t/README: from the test harness library. At the end of the script, call
- 'test_done'.
-
-
-+Writing concurrency-safe helpers
-+--------------------------------
-+
-+Some test code runs concurrently: a test may background work with '&',
-+and the helper scripts installed for the web server (in t/lib-httpd) are
-+run once per request, so the same script can execute for several
-+requests at once. Such code cannot assume it has exclusive access to a
-+file.
-+
-+When exactly one of several concurrent processes needs to "win" a
-+decision, a single atomic filesystem operation can make it, rather than
-+a check followed by a separate action. A "test -f X" then "touch X"
-+(or "rm X") races: two processes can both pass the check before either
-+acts. Two atomic operations avoid this:
-+
-+ - "mkdir dir", which fails if the directory already exists, so that
-+ exactly one caller wins, electing a first or only request (see
-+ t/lib-httpd/http-429.sh).
-+
-+ - "mv src dst" (rename), which fails if the source is gone, so that
-+ exactly one caller consumes it, claiming a planted one-shot marker
-+ (see t/lib-httpd/apply-one-time-script.sh).
-+
-+A "$$" suffix on per-request scratch files keeps concurrent invocations
-+from clobbering each other's fixed-name files.
-+
-+This is a standard shell locking idiom, and the same reasoning behind
-+Git's own lockfile machinery, which creates its lock with O_CREAT|O_EXCL,
-+and make_symlink() in t/test-lib.sh, which uses an mkdir lock: an atomic
-+operation whose failure indicates that another process got there first.
-+
-+
- Test harness library
- --------------------
-
+ Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
## t/lib-httpd.sh ##
@@ t/lib-httpd.sh: prepare_httpd() {
mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH"
cp "$TEST_PATH"/proxy-passwd "$HTTPD_ROOT_PATH"
-+ # The web server can run any of these CGI scripts for two requests at
-+ # once; a helper that keeps state between requests must do so with an
-+ # atomic operation. See "Writing concurrency-safe helpers" in t/README.
++ # Apache runs each of these CGI scripts once per request. Apache can run one
++ # script for several requests at the same time. A helper that keeps state
++ # between requests must update that state with one atomic operation. A check
++ # and then a separate action is not safe: two requests can both pass the
++ # check before either one acts. Test the exit status of one atomic operation
++ # instead:
++ # - "mkdir dir" fails if the directory exists, so only one request
++ # succeeds. http-429.sh selects the first request this way.
++ # - "rm marker" (without "-f") fails if the marker is gone, so only one
++ # request consumes it. apply-one-time-script.sh claims its one-shot
++ # marker this way.
++ # A scratch file name includes the process ID ($$), so concurrent requests
++ # do not overwrite each other's files.
install_script incomplete-length-upload-pack-v2-http.sh
install_script incomplete-body-upload-pack-v2-http.sh
install_script error-no-report.sh
--
gitgitgadget
next prev parent reply other threads:[~2026-08-13 1:05 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-08 2:59 [PATCH 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Michael Montalbo via GitGitGadget
2026-07-08 2:59 ` [PATCH 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests Michael Montalbo via GitGitGadget
2026-07-08 19:54 ` Junio C Hamano
2026-07-09 17:26 ` Michael Montalbo
2026-07-08 2:59 ` [PATCH 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-07-08 19:58 ` Junio C Hamano
2026-07-08 20:02 ` Junio C Hamano
2026-07-09 18:10 ` Michael Montalbo
2026-07-08 2:59 ` [PATCH 3/3] t/README: document writing concurrency-safe helpers Michael Montalbo via GitGitGadget
2026-07-08 19:59 ` Junio C Hamano
2026-07-10 17:30 ` [PATCH v2 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Michael Montalbo via GitGitGadget
2026-07-10 17:30 ` [PATCH v2 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests Michael Montalbo via GitGitGadget
2026-08-04 8:03 ` Patrick Steinhardt
2026-08-07 16:29 ` Michael Montalbo
2026-07-10 17:30 ` [PATCH v2 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-07-10 17:30 ` [PATCH v2 3/3] t/README: document writing concurrency-safe helpers Michael Montalbo via GitGitGadget
2026-08-04 8:03 ` Patrick Steinhardt
2026-08-07 16:51 ` Michael Montalbo
2026-08-10 6:06 ` Patrick Steinhardt
2026-08-02 3:02 ` [PATCH v2 0/3] t/lib-httpd: make CGI test helpers concurrency-safe Michael Montalbo
2026-08-03 21:55 ` Junio C Hamano
2026-08-13 1:05 ` Michael Montalbo via GitGitGadget [this message]
2026-08-13 1:05 ` [PATCH v3 1/3] t/lib-httpd: fix apply-one-time-script race under concurrent requests Michael Montalbo via GitGitGadget
2026-08-13 1:05 ` [PATCH v3 2/3] t/lib-httpd: make http-429 first-request check atomic Michael Montalbo via GitGitGadget
2026-08-13 1:05 ` [PATCH v3 3/3] t/lib-httpd: document writing concurrency-safe CGI helpers Michael Montalbo via GitGitGadget
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=pull.2171.v3.git.1786583137.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=mmontalbo@gmail.com \
--cc=ps@pks.im \
/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.