From 79de6eb1a7a2d46b3a582fae4b66315246598c0c Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 13 Sep 2023 12:48:44 +0800 Subject: [PATCH 1/3] tree-wide: explicitly compare return value of fd_is_fs_type with 0 According to our coding style. --- src/creds/creds.c | 4 ++-- src/libsystemd/sd-journal/journal-file.c | 2 +- src/shared/btrfs-util.c | 18 +++++++++--------- src/shared/discover-image.c | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/creds/creds.c b/src/creds/creds.c index 0bc55a36d2..101e5abf9b 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -181,8 +181,8 @@ static int add_credentials_to_table(Table *t, bool encrypted) { if (r < 0) return log_error_errno(r, "Failed to determine backing file system of '%s': %m", de->d_name); - secure = r ? "secure" : "weak"; /* ramfs is not swappable, hence "secure", everything else is "weak" */ - secure_color = r ? ansi_highlight_green() : ansi_highlight_yellow4(); + secure = r > 0 ? "secure" : "weak"; /* ramfs is not swappable, hence "secure", everything else is "weak" */ + secure_color = r > 0 ? ansi_highlight_green() : ansi_highlight_yellow4(); } j = path_join(prefix, de->d_name); diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 95cf25bff0..3aa6985b37 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -3792,7 +3792,7 @@ static int journal_file_warn_btrfs(JournalFile *f) { r = fd_is_fs_type(f->fd, BTRFS_SUPER_MAGIC); if (r < 0) return log_ratelimit_warning_errno(r, JOURNAL_LOG_RATELIMIT, "Failed to determine if journal is on btrfs: %m"); - if (!r) + if (r == 0) return 0; r = read_attr_fd(f->fd, &attrs); diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index d91c69b624..e61e5f31f5 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -120,7 +120,7 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; - if (!r) + if (r == 0) return -ENOTTY; if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0) @@ -182,7 +182,7 @@ int btrfs_subvol_get_id_fd(int fd, uint64_t *ret) { r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; - if (!r) + if (r == 0) return -ENOTTY; if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args) < 0) @@ -302,7 +302,7 @@ int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *ret) { r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; - if (!r) + if (r == 0) return -ENOTTY; } @@ -393,7 +393,7 @@ int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *ret) { r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; - if (!r) + if (r == 0) return -ENOTTY; } @@ -610,7 +610,7 @@ int btrfs_quota_enable_fd(int fd, bool b) { r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; - if (!r) + if (r == 0) return -ENOTTY; return RET_NERRNO(ioctl(fd, BTRFS_IOC_QUOTA_CTL, &args)); @@ -644,7 +644,7 @@ int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; - if (!r) + if (r == 0) return -ENOTTY; } @@ -1071,7 +1071,7 @@ int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupi r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; - if (!r) + if (r == 0) return -ENOTTY; while (btrfs_ioctl_search_args_compare(&args) <= 0) { @@ -1575,7 +1575,7 @@ int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret) { r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; - if (!r) + if (r == 0) return -ENOTTY; } @@ -1822,7 +1822,7 @@ int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) { r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; - if (!r) + if (r == 0) return -ENOTTY; } diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index d93a1cc74c..f94fac1b41 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -298,7 +298,7 @@ static int image_make( r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC); if (r < 0) return r; - if (r) { + if (r > 0) { BtrfsSubvolInfo info; /* It's a btrfs subvolume */ From efb6a76a2a097132087ee30720421136cba9e708 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Wed, 13 Sep 2023 13:52:55 +0800 Subject: [PATCH 2/3] btrfs-util: introduce btrfs_get_file_physical_offset_fd 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 | 295 ++++++++++++++++++++++++++++++++++++++++ src/shared/btrfs-util.h | 2 + 2 files changed, 297 insertions(+) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index e61e5f31f5..1d80deaecc 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -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; +} diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index 22ff0c1115..cd80903190 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -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); From 2b344ea808dadb860396e7b95c5f0846db5d04b6 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Mon, 18 Sep 2023 20:31:59 +0800 Subject: [PATCH 3/3] test: introduce TEST-83-BTRFS The Ubuntu CIs are deny-listed because the shipped btrfs-progs is too old, i.e. doesn't support the recently-added 'filesystem mkswapfile' command. --- src/test/meson.build | 4 ++++ src/test/test-btrfs-physical-offset.c | 33 ++++++++++++++++++++++++++ test/TEST-83-BTRFS/Makefile | 1 + test/TEST-83-BTRFS/deny-list-ubuntu-ci | 0 test/TEST-83-BTRFS/test.sh | 25 +++++++++++++++++++ test/units/testsuite-83.service | 8 +++++++ test/units/testsuite-83.sh | 25 +++++++++++++++++++ 7 files changed, 96 insertions(+) create mode 100644 src/test/test-btrfs-physical-offset.c create mode 120000 test/TEST-83-BTRFS/Makefile create mode 100644 test/TEST-83-BTRFS/deny-list-ubuntu-ci create mode 100755 test/TEST-83-BTRFS/test.sh create mode 100644 test/units/testsuite-83.service create mode 100755 test/units/testsuite-83.sh diff --git a/src/test/meson.build b/src/test/meson.build index 6d39006d09..2b3fe83a9f 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -221,6 +221,10 @@ executables += [ 'sources' : files('test-btrfs.c'), 'type' : 'manual', }, + test_template + { + 'sources' : files('test-btrfs-physical-offset.c'), + 'type' : 'manual', + }, test_template + { 'sources' : files('test-cap-list.c') + generated_gperf_headers, diff --git a/src/test/test-btrfs-physical-offset.c b/src/test/test-btrfs-physical-offset.c new file mode 100644 index 0000000000..bcc6252381 --- /dev/null +++ b/src/test/test-btrfs-physical-offset.c @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "btrfs-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "log.h" +#include "memory-util.h" + +int main(int argc, char *argv[]) { + _cleanup_close_ int fd = -EBADF; + uint64_t offset; + int r; + + assert(argc == 2); + assert(!isempty(argv[1])); + + fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + log_error_errno(fd, "Failed to open '%s': %m", argv[1]); + return EXIT_FAILURE; + } + + r = btrfs_get_file_physical_offset_fd(fd, &offset); + if (r < 0) { + log_error_errno(r, "Failed to get physical offset of '%s': %m", argv[1]); + return EXIT_FAILURE; + } + + printf("%" PRIu64 "\n", offset / page_size()); + return EXIT_SUCCESS; +} diff --git a/test/TEST-83-BTRFS/Makefile b/test/TEST-83-BTRFS/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-83-BTRFS/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-83-BTRFS/deny-list-ubuntu-ci b/test/TEST-83-BTRFS/deny-list-ubuntu-ci new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/TEST-83-BTRFS/test.sh b/test/TEST-83-BTRFS/test.sh new file mode 100755 index 0000000000..5c32c517e1 --- /dev/null +++ b/test/TEST-83-BTRFS/test.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -e + +TEST_DESCRIPTION="test btrfs-util" + +TEST_NO_NSPAWN=1 +FSTYPE=btrfs +IMAGE_NAME="btrfs" +TEST_FORCE_NEWIMAGE=1 + +# shellcheck source=test/test-functions +. "${TEST_BASE_DIR:?}/test-functions" + +if ! command -v btrfs >/dev/null || ! command -v mkfs.btrfs >/dev/null; then + echo "TEST: $TEST_DESCRIPTION [SKIPPED]: btrfs not supported by host" >&2 + exit 0 +fi + +test_append_files() { + install_btrfs + image_install sync +} + +do_test "$@" diff --git a/test/units/testsuite-83.service b/test/units/testsuite-83.service new file mode 100644 index 0000000000..55ebb45730 --- /dev/null +++ b/test/units/testsuite-83.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-83-BTRFS + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-83.sh b/test/units/testsuite-83.sh new file mode 100755 index 0000000000..a722c79d4e --- /dev/null +++ b/test/units/testsuite-83.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +TEST_BTRFS_OFFSET=/usr/lib/systemd/tests/unit-tests/manual/test-btrfs-physical-offset + +SWAPFILE=/var/tmp/swapfile + +btrfs filesystem mkswapfile -s 10m "$SWAPFILE" +sync -f "$SWAPFILE" + +offset_btrfs_progs="$(btrfs inspect-internal map-swapfile -r "$SWAPFILE")" +echo "btrfs-progs: $offset_btrfs_progs" + +offset_btrfs_util="$("$TEST_BTRFS_OFFSET" "$SWAPFILE")" +echo "btrfs-util: $offset_btrfs_util" + +(( offset_btrfs_progs == offset_btrfs_util )) + +rm -f "$SWAPFILE" + +/usr/lib/systemd/tests/unit-tests/manual/test-btrfs + +touch /testok