mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-12-08 00:00:12 +03:00
Some checks failed
build / bcachefs-tools-deb (ubuntu-22.04) (push) Has been cancelled
build / bcachefs-tools-deb (ubuntu-24.04) (push) Has been cancelled
build / bcachefs-tools-rpm (push) Has been cancelled
build / bcachefs-tools-msrv (push) Has been cancelled
Nix Flake actions / nix-matrix (push) Has been cancelled
Nix Flake actions / ${{ matrix.name }} (${{ matrix.system }}) (push) Has been cancelled
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
913 lines
22 KiB
C
913 lines
22 KiB
C
#include <dirent.h>
|
|
#include <sys/xattr.h>
|
|
#include <linux/dcache.h>
|
|
#include <linux/sort.h>
|
|
#include <linux/xattr.h>
|
|
|
|
#include "posix_to_bcachefs.h"
|
|
#include "libbcachefs/alloc_foreground.h"
|
|
#include "libbcachefs/buckets.h"
|
|
#include "libbcachefs/io_misc.h"
|
|
#include "libbcachefs/io_read.h"
|
|
#include "libbcachefs/io_write.h"
|
|
#include "libbcachefs/namei.h"
|
|
#include "libbcachefs/str_hash.h"
|
|
#include "libbcachefs/xattr.h"
|
|
|
|
static int unlink_and_rm(struct bch_fs *c,
|
|
subvol_inum dir_inum,
|
|
struct bch_inode_unpacked *dir,
|
|
const char *child_name)
|
|
{
|
|
struct qstr child_name_q = QSTR_INIT(child_name, strlen(child_name));
|
|
|
|
struct bch_inode_unpacked child;
|
|
int ret = bch2_trans_commit_do(c, NULL, NULL,
|
|
BCH_TRANS_COMMIT_no_enospc,
|
|
bch2_unlink_trans(trans, dir_inum, dir, &child, &child_name_q, false));
|
|
bch_err_msg(c, ret, "unlinking %s", child_name);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!(child.bi_flags & BCH_INODE_unlinked))
|
|
return 0;
|
|
|
|
subvol_inum child_inum = dir_inum;
|
|
child_inum.inum = child.bi_inum;
|
|
|
|
ret = bch2_inode_rm(c, child_inum);
|
|
bch_err_msg(c, ret, "deleting %s", child_name);
|
|
return ret;
|
|
}
|
|
|
|
static void update_inode(struct bch_fs *c,
|
|
struct bch_inode_unpacked *inode)
|
|
{
|
|
struct bkey_inode_buf packed;
|
|
int ret;
|
|
|
|
bch2_inode_pack(&packed, inode);
|
|
packed.inode.k.p.snapshot = U32_MAX;
|
|
ret = bch2_btree_insert(c, BTREE_ID_inodes, &packed.inode.k_i,
|
|
NULL, 0, BTREE_ITER_cached);
|
|
if (ret)
|
|
die("error updating inode: %s", bch2_err_str(ret));
|
|
}
|
|
|
|
static int create_or_update_link(struct bch_fs *c,
|
|
subvol_inum dir_inum,
|
|
struct bch_inode_unpacked *dir,
|
|
const char *name, subvol_inum inum, mode_t mode)
|
|
{
|
|
struct bch_hash_info dir_hash = bch2_hash_info_init(c, dir);
|
|
|
|
struct qstr qstr = QSTR(name);
|
|
struct bch_inode_unpacked dir_u;
|
|
struct bch_inode_unpacked inode;
|
|
|
|
subvol_inum old_inum;
|
|
int ret = bch2_dirent_lookup(c, dir_inum, &dir_hash, &qstr, &old_inum);
|
|
if (bch2_err_matches(ret, ENOENT))
|
|
goto create;
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (subvol_inum_eq(inum, old_inum))
|
|
return 0;
|
|
|
|
ret = unlink_and_rm(c, dir_inum, dir, name);
|
|
if (ret)
|
|
return ret;
|
|
create:
|
|
ret = bch2_trans_commit_do(c, NULL, NULL, 0,
|
|
bch2_link_trans(trans,
|
|
dir_inum, &dir_u,
|
|
inum, &inode, &qstr));
|
|
bch_err_msg(c, ret, "error creating hardlink %s", name);
|
|
return ret;
|
|
}
|
|
|
|
static struct bch_inode_unpacked create_or_update_file(struct bch_fs *c,
|
|
subvol_inum dir_inum,
|
|
struct bch_inode_unpacked *dir,
|
|
const char *name,
|
|
uid_t uid, gid_t gid,
|
|
mode_t mode, dev_t rdev)
|
|
{
|
|
struct bch_hash_info dir_hash = bch2_hash_info_init(c, dir);
|
|
|
|
struct qstr qname = QSTR(name);
|
|
struct bch_inode_unpacked child_inode;
|
|
subvol_inum child_inum;
|
|
|
|
int ret = bch2_dirent_lookup(c, dir_inum, &dir_hash,
|
|
&qname, &child_inum);
|
|
if (!ret) {
|
|
/* Already exists, update */
|
|
|
|
ret = bch2_inode_find_by_inum(c, child_inum, &child_inode);
|
|
bch_err_fn(c, ret);
|
|
if (ret)
|
|
die("error looking up %s: %s", name, bch2_err_str(ret));
|
|
|
|
BUG_ON(mode_to_type(child_inode.bi_mode) !=
|
|
mode_to_type(mode));
|
|
|
|
child_inode.bi_mode = mode;
|
|
child_inode.bi_uid = uid;
|
|
child_inode.bi_gid = gid;
|
|
child_inode.bi_dev = rdev;
|
|
|
|
ret = bch2_trans_run(c, bch2_fsck_write_inode(trans, &child_inode));
|
|
if (ret)
|
|
die("error updating up %s: %s", name, bch2_err_str(ret));
|
|
} else {
|
|
bch2_inode_init_early(c, &child_inode);
|
|
|
|
int ret = bch2_trans_commit_do(c, NULL, NULL, 0,
|
|
bch2_create_trans(trans,
|
|
dir_inum, dir,
|
|
&child_inode, &qname,
|
|
uid, gid, mode, rdev, NULL, NULL,
|
|
(subvol_inum) {}, 0));
|
|
if (ret)
|
|
die("error creating %s: %s", name, bch2_err_str(ret));
|
|
}
|
|
|
|
return child_inode;
|
|
}
|
|
|
|
#define for_each_xattr_handler(handlers, handler) \
|
|
if (handlers) \
|
|
for ((handler) = *(handlers)++; \
|
|
(handler) != NULL; \
|
|
(handler) = *(handlers)++)
|
|
|
|
static const struct xattr_handler *xattr_resolve_name(char **name)
|
|
{
|
|
const struct xattr_handler * const *handlers = bch2_xattr_handlers;
|
|
const struct xattr_handler *handler;
|
|
|
|
for_each_xattr_handler(handlers, handler) {
|
|
char *n;
|
|
|
|
n = strcmp_prefix(*name, xattr_prefix(handler));
|
|
if (n) {
|
|
if (!handler->prefix ^ !*n) {
|
|
if (*n)
|
|
continue;
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
*name = n;
|
|
return handler;
|
|
}
|
|
}
|
|
return ERR_PTR(-EOPNOTSUPP);
|
|
}
|
|
|
|
static void copy_times(struct bch_fs *c, struct bch_inode_unpacked *dst,
|
|
struct stat *src)
|
|
{
|
|
dst->bi_atime = timespec_to_bch2_time(c, src->st_atim);
|
|
dst->bi_mtime = timespec_to_bch2_time(c, src->st_mtim);
|
|
dst->bi_ctime = timespec_to_bch2_time(c, src->st_ctim);
|
|
}
|
|
|
|
static void copy_xattrs(struct bch_fs *c, struct bch_inode_unpacked *dst,
|
|
char *src)
|
|
{
|
|
struct bch_hash_info hash_info = bch2_hash_info_init(c, dst);
|
|
|
|
char attrs[XATTR_LIST_MAX];
|
|
ssize_t attrs_size = llistxattr(src, attrs, sizeof(attrs));
|
|
if (attrs_size < 0)
|
|
die("listxattr error: %m");
|
|
|
|
char *next, *attr;
|
|
for (attr = attrs;
|
|
attr < attrs + attrs_size;
|
|
attr = next) {
|
|
next = attr + strlen(attr) + 1;
|
|
|
|
char val[XATTR_SIZE_MAX];
|
|
ssize_t val_size = lgetxattr(src, attr, val, sizeof(val));
|
|
|
|
if (val_size < 0)
|
|
die("error getting xattr val: %m");
|
|
|
|
const struct xattr_handler *h = xattr_resolve_name(&attr);
|
|
if (IS_ERR(h))
|
|
continue;
|
|
|
|
int ret = bch2_trans_commit_do(c, NULL, NULL, 0,
|
|
bch2_xattr_set(trans,
|
|
(subvol_inum) { 1, dst->bi_inum },
|
|
dst, &hash_info, attr,
|
|
val, val_size, h->flags, 0));
|
|
if (ret < 0)
|
|
die("error creating xattr: %s", bch2_err_str(ret));
|
|
}
|
|
}
|
|
|
|
#define WRITE_DATA_BUF (1 << 20)
|
|
|
|
static char src_buf[WRITE_DATA_BUF] __aligned(PAGE_SIZE);
|
|
static char dst_buf[WRITE_DATA_BUF] __aligned(PAGE_SIZE);
|
|
|
|
static void read_data_endio(struct bio *bio)
|
|
{
|
|
closure_put(bio->bi_private);
|
|
}
|
|
|
|
static void read_data(struct bch_fs *c,
|
|
subvol_inum inum,
|
|
struct bch_inode_unpacked *inode,
|
|
u64 offset, void *buf, size_t len)
|
|
{
|
|
BUG_ON(offset & (block_bytes(c) - 1));
|
|
BUG_ON(len & (block_bytes(c) - 1));
|
|
BUG_ON(len > WRITE_DATA_BUF);
|
|
|
|
struct closure cl;
|
|
closure_init_stack(&cl);
|
|
|
|
struct bch_read_bio rbio;
|
|
struct bio_vec bv[WRITE_DATA_BUF / PAGE_SIZE];
|
|
|
|
bio_init(&rbio.bio, NULL, bv, ARRAY_SIZE(bv), 0);
|
|
rbio.bio.bi_opf = REQ_OP_READ|REQ_SYNC;
|
|
rbio.bio.bi_iter.bi_sector = offset >> 9;
|
|
rbio.bio.bi_private = &cl;
|
|
bch2_bio_map(&rbio.bio, buf, len);
|
|
|
|
struct bch_io_opts opts;
|
|
bch2_inode_opts_get(&opts, c, inode);
|
|
|
|
rbio_init(&rbio.bio, c, opts, read_data_endio);
|
|
|
|
closure_get(&cl);
|
|
bch2_read(c, &rbio, inum);
|
|
closure_sync(&cl);
|
|
|
|
if (rbio.ret)
|
|
die("read error: %s", bch2_err_str(rbio.ret));
|
|
}
|
|
|
|
static void write_data(struct bch_fs *c,
|
|
struct bch_inode_unpacked *dst_inode,
|
|
u64 dst_offset, void *buf, size_t len)
|
|
{
|
|
struct bch_write_op op;
|
|
struct bio_vec bv[WRITE_DATA_BUF / PAGE_SIZE];
|
|
|
|
BUG_ON(dst_offset & (block_bytes(c) - 1));
|
|
BUG_ON(len & (block_bytes(c) - 1));
|
|
BUG_ON(len > WRITE_DATA_BUF);
|
|
|
|
bio_init(&op.wbio.bio, NULL, bv, ARRAY_SIZE(bv), 0);
|
|
bch2_bio_map(&op.wbio.bio, buf, len);
|
|
|
|
bch2_write_op_init(&op, c, bch2_opts_to_inode_opts(c->opts));
|
|
op.write_point = writepoint_hashed(0);
|
|
op.nr_replicas = 1;
|
|
op.subvol = 1;
|
|
op.pos = SPOS(dst_inode->bi_inum, dst_offset >> 9, U32_MAX);
|
|
op.flags |= BCH_WRITE_sync|BCH_WRITE_only_specified_devs;
|
|
|
|
int ret = bch2_disk_reservation_get(c, &op.res, len >> 9,
|
|
c->opts.data_replicas, 0);
|
|
if (ret)
|
|
die("error reserving space in new filesystem: %s", bch2_err_str(ret));
|
|
|
|
closure_call(&op.cl, bch2_write, NULL, NULL);
|
|
|
|
BUG_ON(!(op.flags & BCH_WRITE_submitted));
|
|
dst_inode->bi_sectors += op.i_sectors_delta;
|
|
|
|
if (op.error)
|
|
die("write error: %s", bch2_err_str(op.error));
|
|
}
|
|
|
|
static void copy_data(struct bch_fs *c,
|
|
struct bch_inode_unpacked *dst_inode,
|
|
int src_fd, u64 start, u64 end)
|
|
{
|
|
while (start < end) {
|
|
unsigned len = min_t(u64, end - start, sizeof(src_buf));
|
|
unsigned pad = round_up(len, block_bytes(c)) - len;
|
|
|
|
xpread(src_fd, src_buf, len, start);
|
|
memset(src_buf + len, 0, pad);
|
|
|
|
write_data(c, dst_inode, start, src_buf, len + pad);
|
|
start += len;
|
|
}
|
|
}
|
|
|
|
static void link_data(struct bch_fs *c, struct bch_inode_unpacked *dst,
|
|
u64 logical, u64 physical, u64 length)
|
|
{
|
|
struct bch_dev *ca = c->devs[0];
|
|
|
|
BUG_ON(logical & (block_bytes(c) - 1));
|
|
BUG_ON(physical & (block_bytes(c) - 1));
|
|
BUG_ON(length & (block_bytes(c) - 1));
|
|
|
|
logical >>= 9;
|
|
physical >>= 9;
|
|
length >>= 9;
|
|
|
|
BUG_ON(physical + length > bucket_to_sector(ca, ca->mi.nbuckets));
|
|
|
|
while (length) {
|
|
struct bkey_i_extent *e;
|
|
BKEY_PADDED_ONSTACK(k, BKEY_EXTENT_VAL_U64s_MAX) k;
|
|
u64 b = sector_to_bucket(ca, physical);
|
|
struct disk_reservation res;
|
|
unsigned sectors;
|
|
int ret;
|
|
|
|
sectors = min(ca->mi.bucket_size -
|
|
(physical & (ca->mi.bucket_size - 1)),
|
|
length);
|
|
|
|
e = bkey_extent_init(&k.k);
|
|
e->k.p.inode = dst->bi_inum;
|
|
e->k.p.offset = logical + sectors;
|
|
e->k.p.snapshot = U32_MAX;
|
|
e->k.size = sectors;
|
|
bch2_bkey_append_ptr(&e->k_i, (struct bch_extent_ptr) {
|
|
.offset = physical,
|
|
.dev = 0,
|
|
.gen = *bucket_gen(ca, b),
|
|
});
|
|
|
|
ret = bch2_disk_reservation_get(c, &res, sectors, 1,
|
|
BCH_DISK_RESERVATION_NOFAIL);
|
|
if (ret)
|
|
die("error reserving space in new filesystem: %s",
|
|
bch2_err_str(ret));
|
|
|
|
ret = bch2_btree_insert(c, BTREE_ID_extents, &e->k_i, &res, 0, 0);
|
|
if (ret)
|
|
die("btree insert error %s", bch2_err_str(ret));
|
|
|
|
bch2_disk_reservation_put(c, &res);
|
|
|
|
dst->bi_sectors += sectors;
|
|
logical += sectors;
|
|
physical += sectors;
|
|
length -= sectors;
|
|
}
|
|
}
|
|
|
|
static void copy_link(struct bch_fs *c,
|
|
subvol_inum dst_inum,
|
|
struct bch_inode_unpacked *dst,
|
|
char *src)
|
|
{
|
|
s64 i_sectors_delta = 0;
|
|
int ret = bch2_fpunch(c, dst_inum, 0, U64_MAX, &i_sectors_delta);
|
|
if (ret)
|
|
die("bch2_fpunch error: %s", bch2_err_str(ret));
|
|
|
|
dst->bi_sectors += i_sectors_delta;
|
|
|
|
ret = readlink(src, src_buf, sizeof(src_buf));
|
|
if (ret < 0)
|
|
die("readlink error: %m");
|
|
|
|
for (unsigned i = ret; i < round_up(ret, block_bytes(c)); i++)
|
|
src_buf[i] = 0;
|
|
|
|
write_data(c, dst, 0, src_buf, round_up(ret, block_bytes(c)));
|
|
}
|
|
|
|
static void link_file_data(struct bch_fs *c,
|
|
struct copy_fs_state *s,
|
|
struct bch_inode_unpacked *dst,
|
|
int src_fd, char *src_path, u64 src_size)
|
|
{
|
|
struct fiemap_iter iter;
|
|
struct fiemap_extent e;
|
|
|
|
fiemap_for_each(src_fd, iter, e)
|
|
if (e.fe_flags & FIEMAP_EXTENT_UNKNOWN) {
|
|
fsync(src_fd);
|
|
break;
|
|
}
|
|
fiemap_iter_exit(&iter);
|
|
|
|
fiemap_for_each(src_fd, iter, e) {
|
|
s->total_input += e.fe_length;
|
|
|
|
u64 src_max = roundup(src_size, block_bytes(c));
|
|
|
|
e.fe_length = min(e.fe_length, src_max - e.fe_logical);
|
|
|
|
unsigned visible_len = min(src_size - e.fe_logical, e.fe_length);
|
|
|
|
if ((e.fe_logical & (block_bytes(c) - 1)) ||
|
|
(e.fe_length & (block_bytes(c) - 1)))
|
|
die("Unaligned extent in %s - can't handle", src_path);
|
|
|
|
if (BCH_MIGRATE_copy == s->type || (e.fe_flags & (FIEMAP_EXTENT_UNKNOWN|
|
|
FIEMAP_EXTENT_ENCODED|
|
|
FIEMAP_EXTENT_NOT_ALIGNED|
|
|
FIEMAP_EXTENT_DATA_INLINE))) {
|
|
copy_data(c, dst, src_fd, e.fe_logical,
|
|
e.fe_logical + visible_len);
|
|
s->total_wrote += visible_len;
|
|
continue;
|
|
}
|
|
|
|
/* If the data is in bcachefs's superblock region, copy it: */
|
|
if (e.fe_physical < s->reserve_start) {
|
|
copy_data(c, dst, src_fd, e.fe_logical,
|
|
e.fe_logical + visible_len);
|
|
s->total_wrote += visible_len;
|
|
continue;
|
|
}
|
|
|
|
if ((e.fe_physical & (block_bytes(c) - 1)))
|
|
die("Unaligned extent in %s - can't handle", src_path);
|
|
|
|
range_add(&s->extents, e.fe_physical, e.fe_length);
|
|
link_data(c, dst, e.fe_logical, e.fe_physical, e.fe_length);
|
|
s->total_linked += e.fe_length;
|
|
}
|
|
fiemap_iter_exit(&iter);
|
|
}
|
|
|
|
static struct range align_range(struct range r, unsigned bs)
|
|
{
|
|
r.start = round_down(r.start, bs);
|
|
r.end = round_up(r.end, bs);
|
|
return r;
|
|
}
|
|
|
|
struct range seek_data(int fd, u64 i_size, loff_t o)
|
|
{
|
|
s64 s = lseek(fd, o, SEEK_DATA);
|
|
if (s < 0 && errno == ENXIO)
|
|
return (struct range) {};
|
|
if (s < 0)
|
|
die("lseek error: %m");
|
|
|
|
s64 e = lseek(fd, s, SEEK_HOLE);
|
|
if (e < 0 && errno == ENXIO)
|
|
e = i_size;
|
|
if (e < 0)
|
|
die("lseek error: %m");
|
|
|
|
return (struct range) { s, e };
|
|
}
|
|
|
|
static struct range seek_data_aligned(int fd, u64 i_size, loff_t o, unsigned bs)
|
|
{
|
|
struct range r = align_range(seek_data(fd, i_size, o), bs);
|
|
if (!r.end)
|
|
return r;
|
|
|
|
while (true) {
|
|
struct range n = align_range(seek_data(fd, i_size, r.end), bs);
|
|
if (!n.end || r.end < n.start)
|
|
break;
|
|
|
|
r.end = n.end;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
struct range seek_mismatch(const char *buf1, const char *buf2,
|
|
unsigned o, unsigned len)
|
|
{
|
|
while (o < len && buf1[o] == buf2[o])
|
|
o++;
|
|
|
|
if (o == len)
|
|
return (struct range) {};
|
|
|
|
unsigned s = o;
|
|
while (o < len && buf1[o] != buf2[o])
|
|
o++;
|
|
|
|
return (struct range) { s, o };
|
|
}
|
|
|
|
static struct range seek_mismatch_aligned(const char *buf1, const char *buf2,
|
|
unsigned offset, unsigned len,
|
|
unsigned bs)
|
|
{
|
|
struct range r = align_range(seek_mismatch(buf1, buf2, offset, len), bs);
|
|
if (r.end)
|
|
while (true) {
|
|
struct range n = align_range(seek_mismatch(buf1, buf2, r.end, len), bs);
|
|
if (!n.end || r.end < n.start)
|
|
break;
|
|
|
|
r.end = n.end;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static void copy_sync_file_range(struct bch_fs *c,
|
|
struct copy_fs_state *s,
|
|
subvol_inum dst_inum,
|
|
struct bch_inode_unpacked *dst,
|
|
int src_fd, u64 src_size,
|
|
struct range r)
|
|
{
|
|
while (r.start != r.end) {
|
|
BUG_ON(r.start > r.end);
|
|
|
|
unsigned b = min(r.end - r.start, WRITE_DATA_BUF);
|
|
|
|
memset(src_buf, 0, b);
|
|
xpread(src_fd, src_buf, min(b, src_size - r.start), r.start);
|
|
|
|
read_data(c, dst_inum, dst, r.start, dst_buf, b);
|
|
|
|
struct range m = {};
|
|
while ((m = seek_mismatch_aligned(src_buf, dst_buf,
|
|
m.end, b, c->opts.block_size)).end) {
|
|
write_data(c, dst, r.start + m.start,
|
|
src_buf + m.start, m.end - m.start);
|
|
s->total_wrote += m.end - m.start;
|
|
}
|
|
|
|
r.start += b;
|
|
}
|
|
}
|
|
|
|
static void copy_sync_file_data(struct bch_fs *c,
|
|
struct copy_fs_state *s,
|
|
subvol_inum dst_inum,
|
|
struct bch_inode_unpacked *dst,
|
|
int src_fd, u64 src_size)
|
|
{
|
|
s64 i_sectors_delta = 0;
|
|
|
|
struct range next, prev = {};
|
|
|
|
while ((next = seek_data_aligned(src_fd, src_size, prev.end, c->opts.block_size)).end) {
|
|
if (next.start) {
|
|
BUG_ON(prev.end >= next.start);
|
|
|
|
int ret = bch2_fpunch(c, dst_inum, prev.end >> 9, next.start >> 9, &i_sectors_delta);
|
|
if (ret)
|
|
die("bch2_fpunch error: %s", bch2_err_str(ret));
|
|
}
|
|
|
|
copy_sync_file_range(c, s, dst_inum, dst, src_fd, src_size, next);
|
|
|
|
s->total_input += next.end - next.start;
|
|
|
|
prev = next;
|
|
}
|
|
|
|
/* end of file, truncate remaining */
|
|
int ret = bch2_fpunch(c, dst_inum, prev.end >> 9, U64_MAX, &i_sectors_delta);
|
|
if (ret)
|
|
die("bch2_fpunch error: %s", bch2_err_str(ret));
|
|
}
|
|
|
|
static int dirent_cmp(const void *_l, const void *_r)
|
|
{
|
|
const struct dirent *l = _l;
|
|
const struct dirent *r = _r;
|
|
|
|
return cmp_int(l->d_type, r->d_type) ?:
|
|
strcmp(l->d_name, r->d_name);
|
|
}
|
|
|
|
typedef DARRAY(struct dirent) dirents;
|
|
|
|
struct readdir_out {
|
|
struct dir_context ctx;
|
|
dirents *dirents;
|
|
};
|
|
|
|
static int readdir_actor(struct dir_context *ctx, const char *name, int name_len,
|
|
loff_t pos, u64 inum, unsigned type)
|
|
{
|
|
struct readdir_out *out = container_of(ctx, struct readdir_out, ctx);
|
|
|
|
struct dirent d = {
|
|
.d_ino = inum,
|
|
.d_type = type,
|
|
};
|
|
memcpy(d.d_name, name, name_len);
|
|
d.d_name[name_len] = '\0';
|
|
|
|
return darray_push(out->dirents, d);
|
|
}
|
|
|
|
static int simple_readdir(struct bch_fs *c,
|
|
subvol_inum dir_inum,
|
|
struct bch_inode_unpacked *dir,
|
|
dirents *dirents)
|
|
{
|
|
darray_init(dirents);
|
|
|
|
struct bch_hash_info hash_info = bch2_hash_info_init(c, dir);
|
|
struct readdir_out dst_dirents = { .ctx.actor = readdir_actor, .dirents = dirents };
|
|
|
|
int ret = bch2_readdir(c, dir_inum, &hash_info, &dst_dirents.ctx);
|
|
bch_err_fn(c, ret);
|
|
if (ret) {
|
|
darray_exit(dirents);
|
|
return ret;
|
|
}
|
|
|
|
sort(dirents->data, dirents->nr, sizeof(dirents->data[0]), dirent_cmp, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int recursive_remove(struct bch_fs *c,
|
|
subvol_inum dir_inum,
|
|
struct bch_inode_unpacked *dir,
|
|
struct dirent *d)
|
|
{
|
|
subvol_inum child_inum = dir_inum;
|
|
child_inum.inum = d->d_ino;
|
|
|
|
struct bch_inode_unpacked child;
|
|
int ret = bch2_inode_find_by_inum(c, child_inum, &child);
|
|
bch_err_msg(c, ret, "looking up inode for %s", d->d_name);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (S_ISDIR(child.bi_mode)) {
|
|
dirents child_dirents;
|
|
ret = simple_readdir(c, child_inum, &child, &child_dirents);
|
|
if (ret)
|
|
return ret;
|
|
|
|
darray_for_each(child_dirents, i) {
|
|
ret = recursive_remove(c, child_inum, &child, i);
|
|
if (ret) {
|
|
darray_exit(&child_dirents);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
darray_exit(&child_dirents);
|
|
}
|
|
|
|
return unlink_and_rm(c, dir_inum, dir, d->d_name);
|
|
}
|
|
|
|
static int delete_non_matching_dirents(struct bch_fs *c,
|
|
struct copy_fs_state *s,
|
|
subvol_inum dst_dir_inum,
|
|
struct bch_inode_unpacked *dst_dir,
|
|
dirents src_dirents)
|
|
{
|
|
/* Assumes single subvolume */
|
|
|
|
dirents dst_dirents;
|
|
int ret = simple_readdir(c, dst_dir_inum, dst_dir, &dst_dirents);
|
|
if (ret)
|
|
return ret;
|
|
|
|
struct dirent *src_d = src_dirents.data;
|
|
darray_for_each(dst_dirents, dst_d) {
|
|
while (src_d < &darray_top(src_dirents) &&
|
|
dirent_cmp(src_d, dst_d) < 0)
|
|
src_d++;
|
|
|
|
if (src_d == &darray_top(src_dirents) ||
|
|
dirent_cmp(src_d, dst_d)) {
|
|
if (subvol_inum_eq(dst_dir_inum, BCACHEFS_ROOT_SUBVOL_INUM) &&
|
|
!strcmp(dst_d->d_name, "lost+found"))
|
|
continue;
|
|
|
|
if (s->verbosity > 1)
|
|
printf("deleting %s\n", dst_d->d_name);
|
|
|
|
ret = recursive_remove(c, dst_dir_inum, dst_dir, dst_d);
|
|
if (ret)
|
|
goto err;
|
|
}
|
|
}
|
|
err:
|
|
darray_exit(&dst_dirents);
|
|
return ret;
|
|
}
|
|
|
|
static int copy_dir(struct bch_fs *c,
|
|
struct copy_fs_state *s,
|
|
struct bch_inode_unpacked *dst,
|
|
int src_fd, const char *src_path)
|
|
{
|
|
lseek(src_fd, 0, SEEK_SET);
|
|
|
|
DIR *dir = fdopendir(src_fd);
|
|
struct dirent *d;
|
|
dirents dirents = {};
|
|
|
|
while ((errno = 0), (d = readdir(dir)))
|
|
darray_push(&dirents, *d);
|
|
|
|
if (errno)
|
|
die("readdir error: %m");
|
|
|
|
sort(dirents.data, dirents.nr, sizeof(dirents.data[0]), dirent_cmp, NULL);
|
|
|
|
subvol_inum dir_inum = { 1, dst->bi_inum };
|
|
int ret = delete_non_matching_dirents(c, s, dir_inum, dst, dirents);
|
|
if (ret)
|
|
goto err;
|
|
|
|
darray_for_each(dirents, d) {
|
|
struct bch_inode_unpacked inode;
|
|
int fd;
|
|
|
|
if (fchdir(src_fd))
|
|
die("fchdir error: %m");
|
|
|
|
struct stat stat =
|
|
xfstatat(src_fd, d->d_name, AT_SYMLINK_NOFOLLOW);
|
|
|
|
if (!strcmp(d->d_name, ".") ||
|
|
!strcmp(d->d_name, "..") ||
|
|
!strcmp(d->d_name, "lost+found"))
|
|
continue;
|
|
|
|
if (BCH_MIGRATE_migrate == s->type && stat.st_ino == s->bcachefs_inum)
|
|
continue;
|
|
|
|
s->total_files++;
|
|
|
|
char *child_path = mprintf("%s/%s", src_path, d->d_name);
|
|
|
|
if (s->type == BCH_MIGRATE_migrate && stat.st_dev != s->dev)
|
|
die("%s does not have correct st_dev!", child_path);
|
|
|
|
u64 *dst_inum_p = S_ISREG(stat.st_mode)
|
|
? genradix_ptr_alloc(&s->hardlinks, stat.st_ino, GFP_KERNEL)
|
|
: NULL;
|
|
|
|
subvol_inum dst_dir_inum = { 1, dst->bi_inum };
|
|
|
|
if (dst_inum_p && *dst_inum_p) {
|
|
ret = create_or_update_link(c, dst_dir_inum, dst, d->d_name,
|
|
(subvol_inum) { 1, *dst_inum_p }, S_IFREG);
|
|
if (ret)
|
|
goto err;
|
|
goto next;
|
|
}
|
|
|
|
inode = create_or_update_file(c, dst_dir_inum, dst, d->d_name,
|
|
stat.st_uid, stat.st_gid,
|
|
stat.st_mode, stat.st_rdev);
|
|
|
|
subvol_inum dst_child_inum = { 1, inode.bi_inum };
|
|
|
|
if (dst_inum_p)
|
|
*dst_inum_p = inode.bi_inum;
|
|
|
|
copy_xattrs(c, &inode, d->d_name);
|
|
|
|
switch (mode_to_type(stat.st_mode)) {
|
|
case DT_DIR:
|
|
fd = xopen(d->d_name, O_RDONLY|O_NOATIME);
|
|
ret = copy_dir(c, s, &inode, fd, child_path);
|
|
if (ret)
|
|
goto err;
|
|
break;
|
|
case DT_REG:
|
|
inode.bi_size = stat.st_size;
|
|
|
|
fd = xopen(d->d_name, O_RDONLY|O_NOATIME);
|
|
if (s->type == BCH_MIGRATE_migrate)
|
|
link_file_data(c, s, &inode,
|
|
fd, child_path, stat.st_size);
|
|
else
|
|
copy_sync_file_data(c, s, dst_child_inum, &inode,
|
|
fd, stat.st_size);
|
|
xclose(fd);
|
|
break;
|
|
case DT_LNK:
|
|
inode.bi_size = stat.st_size;
|
|
|
|
copy_link(c, dst_child_inum, &inode, d->d_name);
|
|
break;
|
|
case DT_FIFO:
|
|
case DT_CHR:
|
|
case DT_BLK:
|
|
case DT_SOCK:
|
|
case DT_WHT:
|
|
/* nothing else to copy for these: */
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
|
|
copy_times(c, &inode, &stat);
|
|
update_inode(c, &inode);
|
|
next:
|
|
free(child_path);
|
|
}
|
|
err:
|
|
darray_exit(&dirents);
|
|
closedir(dir);
|
|
return ret;
|
|
}
|
|
|
|
static void reserve_old_fs_space(struct bch_fs *c,
|
|
struct bch_inode_unpacked *root_inode,
|
|
ranges *extents,
|
|
u64 reserve_start)
|
|
{
|
|
struct bch_dev *ca = c->devs[0];
|
|
struct bch_inode_unpacked dst;
|
|
struct hole_iter iter;
|
|
struct range i;
|
|
|
|
subvol_inum root_inum = { 1, root_inode->bi_inum };
|
|
dst = create_or_update_file(c, root_inum, root_inode,
|
|
"old_migrated_filesystem",
|
|
0, 0, S_IFREG|0400, 0);
|
|
dst.bi_size = bucket_to_sector(ca, ca->mi.nbuckets) << 9;
|
|
|
|
ranges_sort_merge(extents);
|
|
|
|
for_each_hole(iter, *extents, bucket_to_sector(ca, ca->mi.nbuckets) << 9, i) {
|
|
if (i.end <= reserve_start)
|
|
continue;
|
|
|
|
u64 start = max(i.start, reserve_start);
|
|
|
|
link_data(c, &dst, start, start, i.end - start);
|
|
}
|
|
|
|
update_inode(c, &dst);
|
|
}
|
|
|
|
int copy_fs(struct bch_fs *c, struct copy_fs_state *s,
|
|
int src_fd, const char *src_path)
|
|
{
|
|
if (!S_ISDIR(xfstat(src_fd).st_mode))
|
|
die("%s is not a directory", src_path);
|
|
|
|
if (s->type == BCH_MIGRATE_migrate)
|
|
syncfs(src_fd);
|
|
|
|
struct bch_inode_unpacked root_inode;
|
|
int ret = bch2_inode_find_by_inum(c, (subvol_inum) { 1, BCACHEFS_ROOT_INO },
|
|
&root_inode);
|
|
bch_err_msg(c, ret, "looking up root directory");
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (fchdir(src_fd))
|
|
die("fchdir error: %m");
|
|
|
|
struct stat stat = xfstat(src_fd);
|
|
copy_times(c, &root_inode, &stat);
|
|
copy_xattrs(c, &root_inode, ".");
|
|
|
|
/* now, copy: */
|
|
ret = copy_dir(c, s, &root_inode, dup(src_fd), src_path);
|
|
bch_err_msg(c, ret, "copying filesystem");
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (s->type == BCH_MIGRATE_migrate)
|
|
reserve_old_fs_space(c, &root_inode, &s->extents, s->reserve_start);
|
|
|
|
update_inode(c, &root_inode);
|
|
|
|
darray_exit(&s->extents);
|
|
genradix_free(&s->hardlinks);
|
|
|
|
CLASS(printbuf, buf)();
|
|
printbuf_tabstop_push(&buf, 24);
|
|
printbuf_tabstop_push(&buf, 16);
|
|
prt_printf(&buf, "Total files:\t%llu\r\n", s->total_files);
|
|
prt_str_indented(&buf, "Total input:\t");
|
|
prt_human_readable_u64(&buf, s->total_input);
|
|
prt_printf(&buf, "\r\n");
|
|
|
|
if (s->total_wrote) {
|
|
prt_str_indented(&buf, "Wrote:\t");
|
|
prt_human_readable_u64(&buf, s->total_wrote);
|
|
prt_printf(&buf, "\r\n");
|
|
}
|
|
|
|
if (s->total_linked) {
|
|
prt_str(&buf, "Linked:\t");
|
|
prt_human_readable_u64(&buf, s->total_linked);
|
|
prt_printf(&buf, "\r\n");
|
|
}
|
|
|
|
prt_newline(&buf);
|
|
|
|
fputs(buf.buf, stdout);
|
|
return 0;
|
|
}
|