struct btree_insert_multi {
    struct btree_iter   iter;
    struct bkey_i       *k;
};

static void multi_lock_write(struct btree_insert_multi *m, unsigned nr)
{
    struct btree *b = m[nr].iter.nodes[0];
    unsigned i;

    for (i = 0; i < nr; i++)
        if (b == m[i].iter.nodes[0])
            return; /* already locked */

    btree_node_lock_for_insert(b, &m[nr].iter);
}

static void multi_unlock_write(struct btree_insert_multi *m, unsigned nr)
{
    struct btree *b = m[nr].iter.nodes[0];
    unsigned i;

    for (i = 0; i < nr; i++)
        if (b == m[i].iter.nodes[0])
            return; /* already locked */

    btree_node_unlock_write(b, &m[nr].iter);
}

int bch_btree_insert_at_multi(struct btree_insert_multi *m, unsigned nr,
                  u64 *journal_seq, unsigned flags)
{
    struct cache_set *c = m[0].iter.c;
    struct journal_res res = { 0, 0 };
    struct btree_insert_multi *i;
    struct btree_iter *split;
    unsigned u64s = 0;

    for (i = m; i < m + nr; i++)
        u64s += jset_u64s(i->k->k.u64s);
retry:
    if (bch_journal_res_get(&c->journal, &res, u64s, u64s))
        return -EIO;

    for (i = m; i < m + nr; i++) {
        struct btree *b = i->iter.nodes[0];

        multi_lock_write(m, i - m);

        if (!__have_enough_space(b, i->k->k.u64s))
            goto split;
    }

    for (i = m; i < m + nr; i++)
        BUG_ON(!btree_insert_key(&i->iter, i->iter.nodes[0],
                     &i->iter.node_iters[0],
                     &keylist_single(i->k), NULL,
                     &res, journal_seq, flags));

    do {
        multi_unlock_write(m, --i - m);
    } while (i != m);

    bch_journal_res_put(&c->journal, &res);

    for (i = m; i < m + nr; i++)
        bch_btree_node_write_lazy(i->iter.nodes[0], &i->iter);

    return 0;
split:
    split = &i->iter;
    do {
        multi_unlock_write(m, i - m);
    } while (i-- != m);

    /*
    * XXX: Do we need to drop our journal res for the split?
    */
    bch_journal_res_put(&c->journal, &res);

    {
        struct btree *b = split->nodes[0];
        struct btree_split_state state;
        int ret;

        closure_init_stack(&state.stack_cl);
        bch_keylist_init(&state.parent_keys,
                 state.inline_keys,
                 ARRAY_SIZE(state.inline_keys));

        /*
        * XXX: figure out how far we might need to split,
        * instead of locking/reserving all the way to the root:
        */
        split->locks_want = BTREE_MAX_DEPTH;
        if (!bch_btree_iter_upgrade(split))
            return -EINTR;

        state.reserve = bch_btree_reserve_get(c, b, split, 0,
                        !(flags & BTREE_INSERT_NOFAIL));
        if (IS_ERR(state.reserve))
            return PTR_ERR(state.reserve);

        ret = btree_split(b, split, NULL, flags, &state);

        bch_btree_reserve_put(c, state.reserve);

        if (ret)
            return ret;
        goto retry;
    }
}

int bch_dirent_rename(struct cache_set *c,
              u64 src_dir, const struct qstr *src_name,
              u64 dst_dir, const struct qstr *dst_name,
              u64 *journal_seq, bool overwriting)
{
    struct btree_insert_multi trans[2];
    struct btree_iter *src_iter = &trans[0].iter;
    struct btree_iter *dst_iter = &trans[1].iter;
    struct bkey_s_c k;
    struct bkey_s_c_dirent src;
    struct bkey_i_dirent *dst;
    struct bkey_i delete;
    struct keylist keys;
    int ret;

    dst = dirent_create_key(&keys, 0, dst_name, 0);
    if (!dst)
        return -ENOMEM;

    bch_btree_iter_init_intent(src_iter, c, BTREE_ID_DIRENTS,
                   POS(src_dir, bch_dirent_hash(src_name)));
    bch_btree_iter_init_intent(dst_iter, c, BTREE_ID_DIRENTS,
                   POS(dst_dir, bch_dirent_hash(dst_name)));
    bch_btree_iter_link(src_iter, dst_iter);

    do {
        k = __dirent_find(src_iter, src_dir, src_name);
        if (IS_ERR(k.k)) {
            ret = PTR_ERR(k.k);
            goto err;
        }

        src = bkey_s_c_to_dirent(k);

        bkey_init(&delete.k);
        delete.k.p = src.k->p;
        delete.k.type = BCH_DIRENT_WHITEOUT;

        k = overwriting
            ? __dirent_find(dst_iter, dst_dir, dst_name)
            : __dirent_find_hole(dst_iter, dst_dir, dst_name);
        if (IS_ERR(k.k)) {
            ret = PTR_ERR(k.k);
            goto err;
        }

        dst->v.d_inum = src.v->d_inum;
        dst->v.d_type = src.v->d_type;
        dst->k.p = k.k->p;

        trans[0].k = &delete;
        trans[1].k = &dst->k_i;

        ret = bch_btree_insert_at_multi(trans, ARRAY_SIZE(trans),
                        journal_seq, 0);
        bch_btree_iter_unlock(src_iter);
        bch_btree_iter_unlock(dst_iter);
    } while (ret == -EINTR);

    bch_keylist_free(&keys);
    return 0;
err:
    ret = bch_btree_iter_unlock(src_iter) ?: ret;
    ret = bch_btree_iter_unlock(dst_iter) ?: ret;
    bch_keylist_free(&keys);
    return ret;
}