| // SPDX-License-Identifier: GPL-2.0 |
| #include "bcachefs.h" |
| #include "bkey_on_stack.h" |
| #include "btree_update.h" |
| #include "extents.h" |
| #include "inode.h" |
| #include "io.h" |
| #include "reflink.h" |
| |
| #include <linux/sched/signal.h> |
| |
| /* reflink pointers */ |
| |
| const char *bch2_reflink_p_invalid(const struct bch_fs *c, struct bkey_s_c k) |
| { |
| struct bkey_s_c_reflink_p p = bkey_s_c_to_reflink_p(k); |
| |
| if (bkey_val_bytes(p.k) != sizeof(*p.v)) |
| return "incorrect value size"; |
| |
| return NULL; |
| } |
| |
| void bch2_reflink_p_to_text(struct printbuf *out, struct bch_fs *c, |
| struct bkey_s_c k) |
| { |
| struct bkey_s_c_reflink_p p = bkey_s_c_to_reflink_p(k); |
| |
| pr_buf(out, "idx %llu", le64_to_cpu(p.v->idx)); |
| } |
| |
| enum merge_result bch2_reflink_p_merge(struct bch_fs *c, |
| struct bkey_s _l, struct bkey_s _r) |
| { |
| struct bkey_s_reflink_p l = bkey_s_to_reflink_p(_l); |
| struct bkey_s_reflink_p r = bkey_s_to_reflink_p(_r); |
| |
| if (le64_to_cpu(l.v->idx) + l.k->size != le64_to_cpu(r.v->idx)) |
| return BCH_MERGE_NOMERGE; |
| |
| if ((u64) l.k->size + r.k->size > KEY_SIZE_MAX) { |
| bch2_key_resize(l.k, KEY_SIZE_MAX); |
| bch2_cut_front_s(l.k->p, _r); |
| return BCH_MERGE_PARTIAL; |
| } |
| |
| bch2_key_resize(l.k, l.k->size + r.k->size); |
| |
| return BCH_MERGE_MERGE; |
| } |
| |
| /* indirect extents */ |
| |
| const char *bch2_reflink_v_invalid(const struct bch_fs *c, struct bkey_s_c k) |
| { |
| struct bkey_s_c_reflink_v r = bkey_s_c_to_reflink_v(k); |
| |
| if (bkey_val_bytes(r.k) < sizeof(*r.v)) |
| return "incorrect value size"; |
| |
| return bch2_bkey_ptrs_invalid(c, k); |
| } |
| |
| void bch2_reflink_v_to_text(struct printbuf *out, struct bch_fs *c, |
| struct bkey_s_c k) |
| { |
| struct bkey_s_c_reflink_v r = bkey_s_c_to_reflink_v(k); |
| |
| pr_buf(out, "refcount: %llu ", le64_to_cpu(r.v->refcount)); |
| |
| bch2_bkey_ptrs_to_text(out, c, k); |
| } |
| |
| static int bch2_make_extent_indirect(struct btree_trans *trans, |
| struct btree_iter *extent_iter, |
| struct bkey_i_extent *e) |
| { |
| struct bch_fs *c = trans->c; |
| struct btree_iter *reflink_iter; |
| struct bkey_s_c k; |
| struct bkey_i_reflink_v *r_v; |
| struct bkey_i_reflink_p *r_p; |
| int ret; |
| |
| for_each_btree_key(trans, reflink_iter, BTREE_ID_REFLINK, |
| POS(0, c->reflink_hint), |
| BTREE_ITER_INTENT|BTREE_ITER_SLOTS, k, ret) { |
| if (reflink_iter->pos.inode) { |
| bch2_btree_iter_set_pos(reflink_iter, POS_MIN); |
| continue; |
| } |
| |
| if (bkey_deleted(k.k) && e->k.size <= k.k->size) |
| break; |
| } |
| |
| if (ret) |
| goto err; |
| |
| /* rewind iter to start of hole, if necessary: */ |
| bch2_btree_iter_set_pos(reflink_iter, bkey_start_pos(k.k)); |
| |
| r_v = bch2_trans_kmalloc(trans, sizeof(*r_v) + bkey_val_bytes(&e->k)); |
| ret = PTR_ERR_OR_ZERO(r_v); |
| if (ret) |
| goto err; |
| |
| bkey_reflink_v_init(&r_v->k_i); |
| r_v->k.p = reflink_iter->pos; |
| bch2_key_resize(&r_v->k, e->k.size); |
| r_v->k.version = e->k.version; |
| |
| set_bkey_val_u64s(&r_v->k, bkey_val_u64s(&r_v->k) + |
| bkey_val_u64s(&e->k)); |
| r_v->v.refcount = 0; |
| memcpy(r_v->v.start, e->v.start, bkey_val_bytes(&e->k)); |
| |
| bch2_trans_update(trans, reflink_iter, &r_v->k_i); |
| |
| r_p = bch2_trans_kmalloc(trans, sizeof(*r_p)); |
| if (IS_ERR(r_p)) |
| return PTR_ERR(r_p); |
| |
| e->k.type = KEY_TYPE_reflink_p; |
| r_p = bkey_i_to_reflink_p(&e->k_i); |
| set_bkey_val_bytes(&r_p->k, sizeof(r_p->v)); |
| r_p->v.idx = cpu_to_le64(bkey_start_offset(&r_v->k)); |
| |
| bch2_trans_update(trans, extent_iter, &r_p->k_i); |
| err: |
| if (!IS_ERR(reflink_iter)) { |
| c->reflink_hint = reflink_iter->pos.offset; |
| bch2_trans_iter_put(trans, reflink_iter); |
| } |
| |
| return ret; |
| } |
| |
| static struct bkey_s_c get_next_src(struct btree_iter *iter, struct bpos end) |
| { |
| struct bkey_s_c k = bch2_btree_iter_peek(iter); |
| int ret; |
| |
| for_each_btree_key_continue(iter, 0, k, ret) { |
| if (bkey_cmp(iter->pos, end) >= 0) |
| return bkey_s_c_null; |
| |
| if (k.k->type == KEY_TYPE_extent || |
| k.k->type == KEY_TYPE_reflink_p) |
| break; |
| } |
| |
| return k; |
| } |
| |
| s64 bch2_remap_range(struct bch_fs *c, |
| struct bpos dst_start, struct bpos src_start, |
| u64 remap_sectors, u64 *journal_seq, |
| u64 new_i_size, s64 *i_sectors_delta) |
| { |
| struct btree_trans trans; |
| struct btree_iter *dst_iter, *src_iter; |
| struct bkey_s_c src_k; |
| BKEY_PADDED(k) new_dst; |
| struct bkey_on_stack new_src; |
| struct bpos dst_end = dst_start, src_end = src_start; |
| struct bpos dst_want, src_want; |
| u64 src_done, dst_done; |
| int ret = 0, ret2 = 0; |
| |
| if (!percpu_ref_tryget(&c->writes)) |
| return -EROFS; |
| |
| if (!(c->sb.features & (1ULL << BCH_FEATURE_REFLINK))) { |
| mutex_lock(&c->sb_lock); |
| if (!(c->sb.features & (1ULL << BCH_FEATURE_REFLINK))) { |
| c->disk_sb.sb->features[0] |= |
| cpu_to_le64(1ULL << BCH_FEATURE_REFLINK); |
| |
| bch2_write_super(c); |
| } |
| mutex_unlock(&c->sb_lock); |
| } |
| |
| dst_end.offset += remap_sectors; |
| src_end.offset += remap_sectors; |
| |
| bkey_on_stack_init(&new_src); |
| bch2_trans_init(&trans, c, BTREE_ITER_MAX, 4096); |
| |
| src_iter = bch2_trans_get_iter(&trans, BTREE_ID_EXTENTS, src_start, |
| BTREE_ITER_INTENT); |
| dst_iter = bch2_trans_get_iter(&trans, BTREE_ID_EXTENTS, dst_start, |
| BTREE_ITER_INTENT); |
| |
| while (1) { |
| bch2_trans_begin_updates(&trans); |
| trans.mem_top = 0; |
| |
| if (fatal_signal_pending(current)) { |
| ret = -EINTR; |
| goto err; |
| } |
| |
| src_k = get_next_src(src_iter, src_end); |
| ret = bkey_err(src_k); |
| if (ret) |
| goto btree_err; |
| |
| src_done = bpos_min(src_iter->pos, src_end).offset - |
| src_start.offset; |
| dst_want = POS(dst_start.inode, dst_start.offset + src_done); |
| |
| if (bkey_cmp(dst_iter->pos, dst_want) < 0) { |
| ret = bch2_fpunch_at(&trans, dst_iter, dst_want, |
| journal_seq, i_sectors_delta); |
| if (ret) |
| goto btree_err; |
| continue; |
| } |
| |
| BUG_ON(bkey_cmp(dst_iter->pos, dst_want)); |
| |
| if (!bkey_cmp(dst_iter->pos, dst_end)) |
| break; |
| |
| if (src_k.k->type == KEY_TYPE_extent) { |
| bkey_on_stack_reassemble(&new_src, c, src_k); |
| src_k = bkey_i_to_s_c(new_src.k); |
| |
| bch2_cut_front(src_iter->pos, new_src.k); |
| bch2_cut_back(src_end, new_src.k); |
| |
| ret = bch2_make_extent_indirect(&trans, src_iter, |
| bkey_i_to_extent(new_src.k)); |
| if (ret) |
| goto btree_err; |
| |
| BUG_ON(src_k.k->type != KEY_TYPE_reflink_p); |
| } |
| |
| if (src_k.k->type == KEY_TYPE_reflink_p) { |
| struct bkey_s_c_reflink_p src_p = |
| bkey_s_c_to_reflink_p(src_k); |
| struct bkey_i_reflink_p *dst_p = |
| bkey_reflink_p_init(&new_dst.k); |
| |
| u64 offset = le64_to_cpu(src_p.v->idx) + |
| (src_iter->pos.offset - |
| bkey_start_offset(src_k.k)); |
| |
| dst_p->v.idx = cpu_to_le64(offset); |
| } else { |
| BUG(); |
| } |
| |
| new_dst.k.k.p = dst_iter->pos; |
| bch2_key_resize(&new_dst.k.k, |
| min(src_k.k->p.offset - src_iter->pos.offset, |
| dst_end.offset - dst_iter->pos.offset)); |
| |
| ret = bch2_extent_update(&trans, dst_iter, &new_dst.k, |
| NULL, journal_seq, |
| new_i_size, i_sectors_delta); |
| if (ret) |
| goto btree_err; |
| |
| dst_done = dst_iter->pos.offset - dst_start.offset; |
| src_want = POS(src_start.inode, src_start.offset + dst_done); |
| bch2_btree_iter_set_pos(src_iter, src_want); |
| btree_err: |
| if (ret == -EINTR) |
| ret = 0; |
| if (ret) |
| goto err; |
| } |
| |
| BUG_ON(bkey_cmp(dst_iter->pos, dst_end)); |
| err: |
| BUG_ON(bkey_cmp(dst_iter->pos, dst_end) > 0); |
| |
| dst_done = dst_iter->pos.offset - dst_start.offset; |
| new_i_size = min(dst_iter->pos.offset << 9, new_i_size); |
| |
| bch2_trans_begin(&trans); |
| |
| do { |
| struct bch_inode_unpacked inode_u; |
| struct btree_iter *inode_iter; |
| |
| inode_iter = bch2_inode_peek(&trans, &inode_u, |
| dst_start.inode, BTREE_ITER_INTENT); |
| ret2 = PTR_ERR_OR_ZERO(inode_iter); |
| |
| if (!ret2 && |
| inode_u.bi_size < new_i_size) { |
| inode_u.bi_size = new_i_size; |
| ret2 = bch2_inode_write(&trans, inode_iter, &inode_u) ?: |
| bch2_trans_commit(&trans, NULL, journal_seq, |
| BTREE_INSERT_ATOMIC); |
| } |
| } while (ret2 == -EINTR); |
| |
| ret = bch2_trans_exit(&trans) ?: ret; |
| bkey_on_stack_exit(&new_src, c); |
| |
| percpu_ref_put(&c->writes); |
| |
| return dst_done ?: ret ?: ret2; |
| } |