From: Boris Burkov <boris@bur.io>
To: Qu Wenruo <wqu@suse.com>
Cc: linux-btrfs@vger.kernel.org
Subject: Re: [PATCH v4 1/6] btrfs: add delayed ordered extent support
Date: Thu, 24 Sep 2026 10:21:18 -0700 [thread overview]
Message-ID: <20260924172118.GA2146908@zen.localdomain> (raw)
In-Reply-To: <cc71ad3fcbd4564a7af97f8563500adac5730756.1789687750.git.wqu@suse.com>
On Fri, Sep 18, 2026 at 09:00:26AM +0930, Qu Wenruo wrote:
> A delayed ordered extent has the following features:
>
> - A new BTRFS_ORDERED_DELAYED flag
> And this new flag must be set along with the BTRFS_ORDERED_REGULAR flag.
>
> - No allocation of any on-disk space
> As a delayed ordered extent doesn't take any on-disk space yet, it
> won't release any reserved data/meta space either.
>
> - Zero or more real OEs can be added to the parent
> If a real OE is allocated, it must be inside the parent OE.
> And such real OE will go through the regular data/meta space
> reservation path.
>
> - Child OEs will not be added to the per-inode OE rb-tree nor
> per-root list
> Only the parent OE is added to the per-inode rb-tree and per-root
> list.
> So anything waiting for ordered extents should only work on the parent
> one.
>
> There is a special corner case for btrfs_wait_ordered_extents(), as
> delayed parent OEs have 0 disk_bytenr and disk_num_bytes, they will
> be considered out of the [0, U64_MAX] range.
> Thus we have to always wait for any delayed OEs of a root, no matter
> if a block group range is given or not.
>
> - When the parent OE finishes, all children OEs will also be finished
> And reserved space is all handled by the children OEs.
>
> - Any range not covered by a child OE will be manually cleaned up
>
> The above features allow us to use the existing ordered extent interfaces
> to allocate new real OEs, and wait for them properly.
>
> Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---
> fs/btrfs/inode.c | 76 +++++++++++++++++
> fs/btrfs/ordered-data.c | 178 ++++++++++++++++++++++++++++++----------
> fs/btrfs/ordered-data.h | 14 ++++
> 3 files changed, 224 insertions(+), 44 deletions(-)
>
> diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> index 79f2181dc631..2421fe49b43b 100644
> --- a/fs/btrfs/inode.c
> +++ b/fs/btrfs/inode.c
> @@ -3189,6 +3189,79 @@ static int insert_ordered_extent_file_extent(struct btrfs_trans_handle *trans,
> update_inode_bytes, oe->qgroup_rsv);
> }
>
> +static int finish_delayed_ordered(struct btrfs_ordered_extent *oe)
> +{
> + struct btrfs_inode *inode = oe->inode;
> + struct btrfs_fs_info *fs_info = inode->root->fs_info;
> + struct btrfs_ordered_extent *child;
> + struct btrfs_ordered_extent *tmp;
> + struct extent_state *cached = NULL;
> + const u32 nr_bits = oe->num_bytes >> fs_info->sectorsize_bits;
> + bool io_error = test_bit(BTRFS_ORDERED_IOERR, &oe->flags);
> + u64 cur = oe->file_offset;
> + int ret = 0;
> + int saved_ret = 0;
> +
> + /* Finish each child OE. */
> + list_for_each_entry_safe(child, tmp, &oe->child_list, child_list) {
> + list_del_init(&child->child_list);
> + refcount_inc(&child->refs);
> +
> + /* The range should have been marked in the bitmap. */
> + ASSERT(bitmap_test_range_all_set(oe->child_bitmap,
> + (child->file_offset - oe->file_offset) >> fs_info->sectorsize_bits,
> + child->num_bytes >> fs_info->sectorsize_bits));
> +
> + if (io_error)
> + set_bit(BTRFS_ORDERED_IOERR, &child->flags);
> +
> + ret = btrfs_finish_one_ordered(child);
> + if (ret && !saved_ret)
> + saved_ret = ret;
> + }
> +
> + /* For ranges that don't have a child OE, manually clean them up. */
> + while (cur < oe->file_offset + oe->num_bytes) {
> + const u32 cur_bit = (cur - oe->file_offset) >> fs_info->sectorsize_bits;
> + u32 first_zero;
> + u32 next_set;
> + u64 range_start;
> + u64 range_end;
> + u32 range_len;
> +
> + first_zero = find_next_zero_bit(oe->child_bitmap, nr_bits, cur_bit);
> + if (first_zero >= nr_bits)
> + break;
> + next_set = find_next_bit(oe->child_bitmap, nr_bits, first_zero);
> + ASSERT(next_set > first_zero);
> +
> + range_start = oe->file_offset + (first_zero << fs_info->sectorsize_bits);
> + range_len = (next_set - first_zero) << fs_info->sectorsize_bits;
> + range_end = range_start + range_len - 1;
> +
> + btrfs_lock_extent(&inode->io_tree, range_start, range_end, &cached);
> + /*
> + * The range has reserved data/metadata but no real OE, thus we have
> + * to manually release them.
> + */
> + btrfs_delalloc_release_space(inode, NULL, range_start, range_len, true);
> + /*
> + * Also need to remove/drop the pinned extent map range.
> + * Here we do not want the extent map to stay, as they do not represent
> + * any real extent on-disk.
> + */
> + btrfs_drop_extent_map_range(inode, range_start, range_end, false);
> + btrfs_clear_extent_bit(&inode->io_tree, range_start, range_end,
> + EXTENT_LOCKED | EXTENT_DELALLOC_NEW | EXTENT_DEFRAG |
> + EXTENT_DO_ACCOUNTING, &cached);
> + cur = range_end + 1;
> + }
> + btrfs_remove_ordered_extent(oe);
> + btrfs_put_ordered_extent(oe);
> + btrfs_put_ordered_extent(oe);
> + return saved_ret;
> +}
> +
> /*
> * As ordered data IO finishes, this gets called so we can finish
> * an ordered extent if the range of bytes in the file it covers are
> @@ -3211,6 +3284,9 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent)
> bool clear_reserved_extent = true;
> unsigned int clear_bits = 0;
>
> + if (test_bit(BTRFS_ORDERED_DELAYED, &ordered_extent->flags))
> + return finish_delayed_ordered(ordered_extent);
> +
I believe this needs to come after btrfs_lockdep_acquire() below and I
was able to get a unbalance release warning with lockdep enabled.
> start = ordered_extent->file_offset;
> end = start + ordered_extent->num_bytes - 1;
>
> diff --git a/fs/btrfs/ordered-data.c b/fs/btrfs/ordered-data.c
> index b32d4eabe0ab..b0469aad3486 100644
> --- a/fs/btrfs/ordered-data.c
> +++ b/fs/btrfs/ordered-data.c
> @@ -155,6 +155,7 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
> u64 qgroup_rsv = 0;
> const bool is_nocow = (flags &
> ((1U << BTRFS_ORDERED_NOCOW) | (1U << BTRFS_ORDERED_PREALLOC)));
> + const bool is_delayed = test_bit(BTRFS_ORDERED_DELAYED, &flags);
>
> /* Only one type flag can be set. */
> ASSERT(has_single_bit_set(flags & BTRFS_ORDERED_EXCLUSIVE_FLAGS),
> @@ -170,6 +171,17 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
> if (test_bit(BTRFS_ORDERED_ENCODED, &flags))
> ASSERT(test_bit(BTRFS_ORDERED_COMPRESSED, &flags));
>
> + /*
> + * DELAYED can only be set with REGULAR, no DIRECT/ENCODED, and should
> + * not exceed BTRFS_MAX_COMPRESSED size.
> + */
> + if (test_bit(BTRFS_ORDERED_DELAYED, &flags)) {
> + ASSERT(test_bit(BTRFS_ORDERED_REGULAR, &flags));
> + ASSERT(!test_bit(BTRFS_ORDERED_DIRECT, &flags));
> + ASSERT(!test_bit(BTRFS_ORDERED_ENCODED, &flags));
> + ASSERT(num_bytes <= BTRFS_MAX_COMPRESSED);
> + }
> +
> /*
> * For a NOCOW write we can free the qgroup reserve right now. For a COW
> * one we transfer the reserved space from the inode's iotree into the
> @@ -178,13 +190,16 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
> * completing the ordered extent, when running the data delayed ref it
> * creates, we free the reserved data with btrfs_qgroup_free_refroot().
> */
> - if (is_nocow)
> - ret = btrfs_qgroup_free_data(inode, NULL, file_offset, num_bytes, &qgroup_rsv);
> - else
> - ret = btrfs_qgroup_release_data(inode, file_offset, num_bytes, &qgroup_rsv);
> -
> - if (ret < 0)
> - return ERR_PTR(ret);
> + if (!is_delayed) {
> + if (is_nocow)
> + ret = btrfs_qgroup_free_data(inode, NULL, file_offset,
> + num_bytes, &qgroup_rsv);
> + else
> + ret = btrfs_qgroup_release_data(inode, file_offset,
> + num_bytes, &qgroup_rsv);
> + if (ret < 0)
> + return ERR_PTR(ret);
> + }
>
> entry = kmem_cache_zalloc(btrfs_ordered_extent_cache, GFP_NOFS);
> if (!entry) {
> @@ -216,19 +231,23 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
> INIT_LIST_HEAD(&entry->root_extent_list);
> INIT_LIST_HEAD(&entry->work_list);
> INIT_LIST_HEAD(&entry->bioc_list);
> + INIT_LIST_HEAD(&entry->child_list);
> init_completion(&entry->completion);
> + RB_CLEAR_NODE(&entry->rb_node);
>
> /*
> * We don't need the count_max_extents here, we can assume that all of
> * that work has been done at higher layers, so this is truly the
> * smallest the extent is going to get.
> */
> - spin_lock(&inode->lock);
> - btrfs_mod_outstanding_extents(inode, 1);
> - spin_unlock(&inode->lock);
> + if (!is_delayed) {
> + spin_lock(&inode->lock);
> + btrfs_mod_outstanding_extents(inode, 1);
> + spin_unlock(&inode->lock);
> + }
>
> out:
> - if (IS_ERR(entry) && !is_nocow)
> + if (IS_ERR(entry) && !is_nocow && !is_delayed)
> btrfs_qgroup_free_refroot(inode->root->fs_info,
> btrfs_root_id(inode->root),
> qgroup_rsv, BTRFS_QGROUP_RSV_DATA);
> @@ -236,12 +255,43 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
> return entry;
> }
>
> +static void add_child_oe(struct btrfs_ordered_extent *parent,
> + struct btrfs_ordered_extent *child)
> +{
> + struct btrfs_inode *inode = parent->inode;
> + struct btrfs_fs_info *fs_info = inode->root->fs_info;
> + const u32 start_bit = (child->file_offset - parent->file_offset) >>
> + fs_info->sectorsize_bits;
> + const u32 nr_bits = child->num_bytes >> fs_info->sectorsize_bits;
> +
> + lockdep_assert_held(&inode->ordered_tree_lock);
> + /* Basic flags check for parent and child. */
> + ASSERT(test_bit(BTRFS_ORDERED_DELAYED, &parent->flags));
> + ASSERT(!test_bit(BTRFS_ORDERED_DELAYED, &child->flags));
> +
> + /* Child should not belong to any parent yet. */
> + ASSERT(list_empty(&child->child_list));
> +
> + /* Child should be fully inside parent's range. */
> + ASSERT(child->file_offset >= parent->file_offset);
> + ASSERT(child->file_offset + child->num_bytes <=
> + parent->file_offset + parent->num_bytes);
> +
> + /* There should be no existing child in the range. */
> + ASSERT(bitmap_test_range_all_zero(parent->child_bitmap, start_bit, nr_bits));
> +
> + list_add_tail(&child->child_list, &parent->child_list);
> +
> + bitmap_set(parent->child_bitmap, start_bit, nr_bits);
> +}
> +
> static void insert_ordered_extent(struct btrfs_ordered_extent *entry)
> {
> struct btrfs_inode *inode = entry->inode;
> struct btrfs_root *root = inode->root;
> struct btrfs_fs_info *fs_info = root->fs_info;
> struct rb_node *node;
> + bool is_child = false;
>
> trace_btrfs_ordered_extent_add(inode, entry);
>
> @@ -254,17 +304,25 @@ static void insert_ordered_extent(struct btrfs_ordered_extent *entry)
> spin_lock(&inode->ordered_tree_lock);
> node = tree_insert(&inode->ordered_tree, entry->file_offset,
> &entry->rb_node);
> - if (unlikely(node)) {
> + if (node) {
> struct btrfs_ordered_extent *exist =
> rb_entry(node, struct btrfs_ordered_extent, rb_node);
>
> - btrfs_panic(fs_info, -EEXIST,
> + if (test_bit(BTRFS_ORDERED_DELAYED, &exist->flags)) {
> + add_child_oe(exist, entry);
> + is_child = true;
> + } else {
> + btrfs_panic(fs_info, -EEXIST,
> "overlapping ordered extents, existing oe file_offset %llu num_bytes %llu flags 0x%lx, new oe file_offset %llu num_bytes %llu flags 0x%lx",
> exist->file_offset, exist->num_bytes, exist->flags,
> entry->file_offset, entry->num_bytes, entry->flags);
> + }
> }
> spin_unlock(&inode->ordered_tree_lock);
>
> + /* Child OE shouldn't be added to per-root oe list. */
> + if (is_child)
> + return;
> spin_lock(&root->ordered_extent_lock);
> list_add_tail(&entry->root_extent_list,
> &root->ordered_extents);
> @@ -337,6 +395,20 @@ struct btrfs_ordered_extent *btrfs_alloc_ordered_extent(
> return entry;
> }
>
> +struct btrfs_ordered_extent *btrfs_alloc_delayed_ordered_extent(
> + struct btrfs_inode *inode, u64 file_offset, u32 length)
> +{
> + struct btrfs_ordered_extent *entry;
> +
> + entry = alloc_ordered_extent(inode, file_offset, length, length, 0, 0, 0,
> + (1UL << BTRFS_ORDERED_REGULAR) |
> + (1UL << BTRFS_ORDERED_DELAYED),
> + BTRFS_COMPRESS_NONE);
> + if (!IS_ERR(entry))
> + insert_ordered_extent(entry);
> + return entry;
> +}
> +
> /*
> * Add a struct btrfs_ordered_sum into the list of checksums to be inserted
> * when an ordered extent is finished. If the list covers more than one
> @@ -656,8 +728,9 @@ void btrfs_remove_ordered_extent(struct btrfs_ordered_extent *entry)
> struct btrfs_root *root = btrfs_inode->root;
> struct btrfs_fs_info *fs_info = root->fs_info;
> struct rb_node *node;
> - bool pending;
> + bool pending = false;
> bool freespace_inode;
> + const bool is_delayed = test_bit(BTRFS_ORDERED_DELAYED, &entry->flags);
>
> /*
> * If this is a free space inode the thread has not acquired the ordered
> @@ -666,33 +739,37 @@ void btrfs_remove_ordered_extent(struct btrfs_ordered_extent *entry)
> freespace_inode = btrfs_is_free_space_inode(btrfs_inode);
>
> btrfs_lockdep_acquire(fs_info, btrfs_trans_pending_ordered);
> - /* This is paired with alloc_ordered_extent(). */
> - spin_lock(&btrfs_inode->lock);
> - btrfs_mod_outstanding_extents(btrfs_inode, -1);
> - spin_unlock(&btrfs_inode->lock);
> - if (root != fs_info->tree_root) {
> - u64 release;
> + if (!is_delayed) {
> + /* This is paired with alloc_ordered_extent(). */
> + spin_lock(&btrfs_inode->lock);
> + btrfs_mod_outstanding_extents(btrfs_inode, -1);
> + spin_unlock(&btrfs_inode->lock);
>
> - if (test_bit(BTRFS_ORDERED_ENCODED, &entry->flags))
> - release = entry->disk_num_bytes;
> - else
> - release = entry->num_bytes;
> - btrfs_delalloc_release_metadata(btrfs_inode, release,
> + if (root != fs_info->tree_root) {
> + u64 release;
> +
> + if (test_bit(BTRFS_ORDERED_ENCODED, &entry->flags))
> + release = entry->disk_num_bytes;
> + else
> + release = entry->num_bytes;
> + btrfs_delalloc_release_metadata(btrfs_inode, release,
> test_bit(BTRFS_ORDERED_IOERR,
> &entry->flags));
> + }
> }
> -
> percpu_counter_add_batch(&fs_info->ordered_bytes, -entry->num_bytes,
> fs_info->delalloc_batch);
>
> spin_lock(&btrfs_inode->ordered_tree_lock);
> - node = &entry->rb_node;
> - rb_erase(node, &btrfs_inode->ordered_tree);
> - RB_CLEAR_NODE(node);
> - if (btrfs_inode->ordered_tree_last == node)
> - btrfs_inode->ordered_tree_last = NULL;
> - set_bit(BTRFS_ORDERED_COMPLETE, &entry->flags);
> - pending = test_and_clear_bit(BTRFS_ORDERED_PENDING, &entry->flags);
> + if (!RB_EMPTY_NODE(&entry->rb_node)) {
> + node = &entry->rb_node;
> + rb_erase(node, &btrfs_inode->ordered_tree);
> + RB_CLEAR_NODE(node);
> + if (btrfs_inode->ordered_tree_last == node)
> + btrfs_inode->ordered_tree_last = NULL;
> + set_bit(BTRFS_ORDERED_COMPLETE, &entry->flags);
> + pending = test_and_clear_bit(BTRFS_ORDERED_PENDING, &entry->flags);
> + }
> spin_unlock(&btrfs_inode->ordered_tree_lock);
>
> /*
> @@ -724,17 +801,20 @@ void btrfs_remove_ordered_extent(struct btrfs_ordered_extent *entry)
>
> btrfs_lockdep_release(fs_info, btrfs_trans_pending_ordered);
>
> - spin_lock(&root->ordered_extent_lock);
> - list_del_init(&entry->root_extent_list);
> - root->nr_ordered_extents--;
> -
> trace_btrfs_ordered_extent_remove(btrfs_inode, entry);
>
> - if (!root->nr_ordered_extents) {
> - spin_lock(&fs_info->ordered_root_lock);
> - BUG_ON(list_empty(&root->ordered_root));
> - list_del_init(&root->ordered_root);
> - spin_unlock(&fs_info->ordered_root_lock);
> + spin_lock(&root->ordered_extent_lock);
> + /* For child OEs, they are not added to per-root OEs. */
> + if (!list_empty(&entry->root_extent_list)) {
> + list_del_init(&entry->root_extent_list);
> + root->nr_ordered_extents--;
> +
> + if (!root->nr_ordered_extents) {
> + spin_lock(&fs_info->ordered_root_lock);
> + BUG_ON(list_empty(&root->ordered_root));
> + list_del_init(&root->ordered_root);
> + spin_unlock(&fs_info->ordered_root_lock);
> + }
> }
> spin_unlock(&root->ordered_extent_lock);
> wake_up(&entry->wait);
> @@ -783,8 +863,18 @@ u64 btrfs_wait_ordered_extents(struct btrfs_root *root, u64 nr,
> ordered = list_first_entry(&splice, struct btrfs_ordered_extent,
> root_extent_list);
>
> - if (range_end <= ordered->disk_bytenr ||
> - ordered->disk_bytenr + ordered->disk_num_bytes <= range_start) {
> + /*
> + * Delayed OEs have 0 disk_bytenr and 0 disk_num_bytes, thus
> + * they will be considered out of the [0, U64_MAX) range.
> + * And we do not know where they will really land until the
> + * writeback has finished.
> + *
> + * So here we must exclude delayed OEs from the block group
> + * range check, and always wait for them.
> + */
> + if (!test_bit(BTRFS_ORDERED_DELAYED, &ordered->flags) &&
> + (range_end <= ordered->disk_bytenr ||
> + ordered->disk_bytenr + ordered->disk_num_bytes <= range_start)) {
> list_move_tail(&ordered->root_extent_list, &skipped);
> cond_resched_lock(&root->ordered_extent_lock);
> continue;
> diff --git a/fs/btrfs/ordered-data.h b/fs/btrfs/ordered-data.h
> index 8d5d5ba1e02f..ce0d3199b533 100644
> --- a/fs/btrfs/ordered-data.h
> +++ b/fs/btrfs/ordered-data.h
> @@ -13,6 +13,7 @@
> #include <linux/rbtree.h>
> #include <linux/wait.h>
> #include "async-thread.h"
> +#include "compression.h"
>
> struct inode;
> struct page;
> @@ -87,6 +88,12 @@ enum {
> */
> BTRFS_ORDERED_DIRECT,
>
> + /*
> + * Extra bit for delayed OE, can only be set for REGULAR.
> + * Cannot be set with COMPRESSED/ENCODED/DIRECT.
> + */
> + BTRFS_ORDERED_DELAYED,
> +
> BTRFS_ORDERED_NR_FLAGS,
> };
> static_assert(BTRFS_ORDERED_NR_FLAGS <= BITS_PER_LONG);
> @@ -155,6 +162,11 @@ struct btrfs_ordered_extent {
> /* a per root list of all the pending ordered extents */
> struct list_head root_extent_list;
>
> + /* Child ordered extent list for delayed OE. */
> + struct list_head child_list;
> +
> + unsigned long child_bitmap[BITS_TO_LONGS(BTRFS_MAX_COMPRESSED / BTRFS_MIN_BLOCKSIZE)];
> +
> struct btrfs_work work;
>
> struct completion completion;
> @@ -192,6 +204,8 @@ struct btrfs_file_extent {
> struct btrfs_ordered_extent *btrfs_alloc_ordered_extent(
> struct btrfs_inode *inode, u64 file_offset,
> const struct btrfs_file_extent *file_extent, unsigned long flags);
> +struct btrfs_ordered_extent *btrfs_alloc_delayed_ordered_extent(
> + struct btrfs_inode *inode, u64 file_offset, u32 length);
> void btrfs_add_ordered_sum(struct btrfs_ordered_extent *entry,
> struct btrfs_ordered_sum *sum);
> struct btrfs_ordered_extent *btrfs_lookup_ordered_extent(struct btrfs_inode *inode,
> --
> 2.55.0
>
next prev parent reply other threads:[~2026-09-24 17:21 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-17 23:30 [PATCH v4 0/6] btrfs: delay compression to bbio submission time Qu Wenruo
2026-09-17 23:30 ` [PATCH v4 1/6] btrfs: add delayed ordered extent support Qu Wenruo
2026-09-24 17:21 ` Boris Burkov [this message]
2026-09-17 23:30 ` [PATCH v4 2/6] btrfs: add skeleton for delayed btrfs bio Qu Wenruo
2026-09-17 23:30 ` [PATCH v4 3/6] btrfs: introduce the skeleton of delayed bbio endio function Qu Wenruo
2026-09-17 23:30 ` [PATCH v4 4/6] btrfs: introduce compression for delayed bbio Qu Wenruo
2026-09-24 17:30 ` Boris Burkov
2026-09-24 22:14 ` Qu Wenruo
2026-09-17 23:30 ` [PATCH v4 5/6] btrfs: implement uncompressed fallback " Qu Wenruo
2026-09-24 17:36 ` Boris Burkov
2026-09-24 22:17 ` Qu Wenruo
2026-09-17 23:30 ` [PATCH v4 6/6] btrfs: enable experimental delayed compression support Qu Wenruo
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=20260924172118.GA2146908@zen.localdomain \
--to=boris@bur.io \
--cc=linux-btrfs@vger.kernel.org \
--cc=wqu@suse.com \
/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