]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
btrfs-util: introduce btrfs_get_file_physical_offset_fd
authorMike Yuan <me@yhndnzj.com>
Wed, 13 Sep 2023 05:52:55 +0000 (13:52 +0800)
committerMike Yuan <me@yhndnzj.com>
Tue, 19 Sep 2023 13:45:27 +0000 (21:45 +0800)
This calculates the physical offset of a file on btrfs,
similar to what FIEMAP does on other filesystems.

The implementation should generally be kept in sync with
btrfs-progs' inspect-internal map-swapfile command:
https://github.com/kdave/btrfs-progs/blob/92d04d4780886a9850716e5529f1dace97779931/cmds/inspect.c#L1516

Preparation for #25130

src/shared/btrfs-util.c
src/shared/btrfs-util.h

index e61e5f31f581a43b7a964af556b35e0dc95754de..1d80deaecc8fb67c46d665f3325ae7c5f7371253 100644 (file)
@@ -1871,3 +1871,298 @@ int btrfs_forget_device(const char *path) {
 
         return RET_NERRNO(ioctl(control_fd, BTRFS_IOC_FORGET_DEV, &args));
 }
+
+typedef struct BtrfsStripe {
+        uint64_t devid;
+        uint64_t offset;
+} BtrfsStripe;
+
+typedef struct BtrfsChunk {
+        uint64_t offset;
+        uint64_t length;
+        uint64_t type;
+
+        BtrfsStripe *stripes;
+        uint16_t n_stripes;
+        uint64_t stripe_len;
+} BtrfsChunk;
+
+typedef struct BtrfsChunkTree {
+        BtrfsChunk **chunks;
+        size_t n_chunks;
+} BtrfsChunkTree;
+
+static BtrfsChunk* btrfs_chunk_free(BtrfsChunk *chunk) {
+        if (!chunk)
+                return NULL;
+
+        free(chunk->stripes);
+
+        return mfree(chunk);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(BtrfsChunk*, btrfs_chunk_free);
+
+static void btrfs_chunk_tree_done(BtrfsChunkTree *tree) {
+        assert(tree);
+
+        FOREACH_ARRAY(i, tree->chunks, tree->n_chunks)
+                btrfs_chunk_free(*i);
+}
+
+static int btrfs_read_chunk_tree_fd(int fd, BtrfsChunkTree *ret) {
+
+        struct btrfs_ioctl_search_args search_args = {
+                .key.tree_id = BTRFS_CHUNK_TREE_OBJECTID,
+
+                .key.min_type = BTRFS_CHUNK_ITEM_KEY,
+                .key.max_type = BTRFS_CHUNK_ITEM_KEY,
+
+                .key.min_objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID,
+                .key.max_objectid = BTRFS_FIRST_CHUNK_TREE_OBJECTID,
+
+                .key.min_offset = 0,
+                .key.max_offset = UINT64_MAX,
+
+                .key.min_transid = 0,
+                .key.max_transid = UINT64_MAX,
+        };
+
+        _cleanup_(btrfs_chunk_tree_done) BtrfsChunkTree tree = {};
+
+        assert(fd >= 0);
+        assert(ret);
+
+        while (btrfs_ioctl_search_args_compare(&search_args) <= 0) {
+                const struct btrfs_ioctl_search_header *sh;
+                unsigned i;
+
+                search_args.key.nr_items = 256;
+
+                if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search_args) < 0)
+                        return -errno;
+
+                if (search_args.key.nr_items == 0)
+                        break;
+
+                FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, search_args) {
+                        _cleanup_(btrfs_chunk_freep) BtrfsChunk *chunk = NULL;
+                        const struct btrfs_chunk *item;
+
+                        btrfs_ioctl_search_args_set(&search_args, sh);
+
+                        if (sh->objectid != BTRFS_FIRST_CHUNK_TREE_OBJECTID)
+                                continue;
+                        if (sh->type != BTRFS_CHUNK_ITEM_KEY)
+                                continue;
+
+                        chunk = new(BtrfsChunk, 1);
+                        if (!chunk)
+                                return -ENOMEM;
+
+                        item = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh);
+
+                        *chunk = (BtrfsChunk) {
+                                .offset = sh->offset,
+                                .length = le64toh(item->length),
+                                .type = le64toh(item->type),
+                                .n_stripes = le16toh(item->num_stripes),
+                                .stripe_len = le64toh(item->stripe_len),
+                        };
+
+                        chunk->stripes = new(BtrfsStripe, chunk->n_stripes);
+                        if (!chunk->stripes)
+                                return -ENOMEM;
+
+                        for (size_t j = 0; j < chunk->n_stripes; j++) {
+                                const struct btrfs_stripe *stripe = &item->stripe + j;
+
+                                chunk->stripes[j] = (BtrfsStripe) {
+                                        .devid = le64toh(stripe->devid),
+                                        .offset = le64toh(stripe->offset),
+                                };
+                        }
+
+                        if (!GREEDY_REALLOC(tree.chunks, tree.n_chunks + 1))
+                                return -ENOMEM;
+
+                        tree.chunks[tree.n_chunks++] = TAKE_PTR(chunk);
+                }
+
+                if (!btrfs_ioctl_search_args_inc(&search_args))
+                        break;
+        }
+
+        *ret = TAKE_STRUCT(tree);
+        return 0;
+}
+
+static BtrfsChunk* btrfs_find_chunk_from_logical_address(const BtrfsChunkTree *tree, uint64_t logical) {
+        size_t min_index, max_index;
+
+        assert(tree);
+        assert(tree->chunks || tree->n_chunks == 0);
+
+        if (tree->n_chunks == 0)
+                return NULL;
+
+        /* bisection */
+        min_index = 0;
+        max_index = tree->n_chunks - 1;
+
+        while (min_index <= max_index) {
+                size_t mid = (min_index + max_index) / 2;
+
+                if (logical < tree->chunks[mid]->offset) {
+                        if (mid < 1)
+                                return NULL;
+
+                        max_index = mid - 1;
+                } else if (logical >= tree->chunks[mid]->offset + tree->chunks[mid]->length)
+                        min_index = mid + 1;
+                else
+                        return tree->chunks[mid];
+        }
+
+        return NULL;
+}
+
+static int btrfs_is_nocow_fd(int fd) {
+        struct statfs sfs;
+        unsigned flags;
+
+        assert(fd >= 0);
+
+        if (fstatfs(fd, &sfs) < 0)
+                return -errno;
+
+        if (!is_fs_type(&sfs, BTRFS_SUPER_MAGIC))
+                return -ENOTTY;
+
+        if (ioctl(fd, FS_IOC_GETFLAGS, &flags) < 0)
+                return -errno;
+
+        return FLAGS_SET(flags, FS_NOCOW_FL) && !FLAGS_SET(flags, FS_COMPR_FL);
+}
+
+int btrfs_get_file_physical_offset_fd(int fd, uint64_t *ret) {
+
+        struct btrfs_ioctl_search_args search_args = {
+                .key.min_type = BTRFS_EXTENT_DATA_KEY,
+                .key.max_type = BTRFS_EXTENT_DATA_KEY,
+
+                .key.min_offset = 0,
+                .key.max_offset = UINT64_MAX,
+
+                .key.min_transid = 0,
+                .key.max_transid = UINT64_MAX,
+        };
+
+        _cleanup_(btrfs_chunk_tree_done) BtrfsChunkTree tree = {};
+        uint64_t subvol_id;
+        struct stat st;
+        int r;
+
+        assert(fd >= 0);
+        assert(ret);
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        r = stat_verify_regular(&st);
+        if (r < 0)
+                return r;
+
+        r = btrfs_is_nocow_fd(fd);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Cannot get physical address for btrfs extent: CoW enabled");
+
+        r = btrfs_subvol_get_id_fd(fd, &subvol_id);
+        if (r < 0)
+                return r;
+
+        r = btrfs_read_chunk_tree_fd(fd, &tree);
+        if (r < 0)
+                return r;
+
+        search_args.key.tree_id = subvol_id;
+        search_args.key.min_objectid = search_args.key.max_objectid = st.st_ino;
+
+        while (btrfs_ioctl_search_args_compare(&search_args) <= 0) {
+                const struct btrfs_ioctl_search_header *sh;
+                unsigned i;
+
+                search_args.key.nr_items = 256;
+
+                if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search_args) < 0)
+                        return -errno;
+
+                if (search_args.key.nr_items == 0)
+                        break;
+
+                FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, search_args) {
+                        const struct btrfs_file_extent_item *item;
+                        uint64_t logical_offset;
+                        BtrfsChunk *chunk;
+
+                        btrfs_ioctl_search_args_set(&search_args, sh);
+
+                        if (sh->type != BTRFS_EXTENT_DATA_KEY)
+                                continue;
+
+                        if (sh->objectid != st.st_ino)
+                                continue;
+
+                        item = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh);
+
+                        if (!IN_SET(item->type, BTRFS_FILE_EXTENT_REG, BTRFS_FILE_EXTENT_PREALLOC))
+                                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Cannot get physical address for btrfs extent: invalid type %" PRIu8,
+                                                       item->type);
+
+                        if (item->compression != 0 || item->encryption != 0 || item->other_encoding != 0)
+                                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Cannot get physical address for btrfs extent: has incompatible property");
+
+                        logical_offset = le64toh(item->disk_bytenr);
+                        if (logical_offset == 0)
+                                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Cannot get physical address for btrfs extent: failed to get logical offset");
+
+                        chunk = btrfs_find_chunk_from_logical_address(&tree, logical_offset);
+                        if (!chunk)
+                                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Cannot get physical address for btrfs extent: no matching chunk found");
+
+                        if ((chunk->type & BTRFS_BLOCK_GROUP_PROFILE_MASK) != 0)
+                                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Cannot get physical address for btrfs extent: unsupported profile");
+
+                        uint64_t relative_chunk, relative_stripe, stripe_nr;
+                        uint16_t stripe_index;
+
+                        assert(logical_offset >= chunk->offset);
+                        assert(chunk->n_stripes > 0);
+                        assert(chunk->stripe_len > 0);
+
+                        relative_chunk = logical_offset - chunk->offset;
+                        stripe_nr = relative_chunk / chunk->stripe_len;
+                        relative_stripe = relative_chunk - stripe_nr * chunk->stripe_len;
+                        stripe_index = stripe_nr % chunk->n_stripes;
+
+                        *ret = chunk->stripes[stripe_index].offset +
+                                stripe_nr / chunk->n_stripes * chunk->stripe_len +
+                                relative_stripe;
+
+                        return 0;
+                }
+
+                if (!btrfs_ioctl_search_args_inc(&search_args))
+                        break;
+        }
+
+        return -ENODATA;
+}
index 22ff0c111535ac8bd57ac202010a5fabeebeab1a..cd80903190df35579731b396a9226a2887700bc6 100644 (file)
@@ -145,3 +145,5 @@ static inline bool btrfs_might_be_subvol(const struct stat *st) {
 }
 
 int btrfs_forget_device(const char *path);
+
+int btrfs_get_file_physical_offset_fd(int fd, uint64_t *ret);