@@ -62,6 +62,8 @@ tags
/src/attr_replace_test
/src/attr-list-by-handle-cursor-test
/src/bstat
+/src/btrfs_encoded_read
+/src/btrfs_encoded_write
/src/bulkstat_null_ocount
/src/bulkstat_unlink_test
/src/bulkstat_unlink_test_modified
@@ -34,7 +34,8 @@ LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
attr_replace_test swapon mkswap t_attr_corruption t_open_tmpfiles \
fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail \
detached_mounts_propagation ext4_resize t_readdir_3 splice2pipe \
- uuid_ioctl t_snapshot_deleted_subvolume fiemap-fault min_dio_alignment
+ uuid_ioctl t_snapshot_deleted_subvolume fiemap-fault min_dio_alignment \
+ btrfs_encoded_read btrfs_encoded_write
EXTRA_EXECS = dmerror fill2attr fill2fs fill2fs_check scaleread.sh \
btrfs_crc32c_forged_name.py popdir.pl popattr.py \
new file mode 100644
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+#include <linux/btrfs.h>
+#include "config.h"
+
+#ifdef HAVE_LIBURING_H
+#include <liburing.h>
+#endif
+
+#define BTRFS_MAX_COMPRESSED 131072
+#define QUEUE_DEPTH 1
+
+static int encoded_read_ioctl(const char *filename, long long offset)
+{
+ int ret, fd;
+ char buf[BTRFS_MAX_COMPRESSED];
+ struct iovec iov;
+ struct btrfs_ioctl_encoded_io_args enc;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open failed for %s\n", filename);
+ return 1;
+ }
+
+ iov.iov_base = buf;
+ iov.iov_len = sizeof(buf);
+
+ enc.iov = &iov;
+ enc.iovcnt = 1;
+ enc.offset = offset;
+ enc.flags = 0;
+
+ ret = ioctl(fd, BTRFS_IOC_ENCODED_READ, &enc);
+
+ if (ret < 0) {
+ printf("%i\n", -errno);
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+
+ printf("%i\n", ret);
+ printf("%llu\n", enc.len);
+ printf("%llu\n", enc.unencoded_len);
+ printf("%llu\n", enc.unencoded_offset);
+ printf("%u\n", enc.compression);
+ printf("%u\n", enc.encryption);
+
+ fwrite(buf, ret, 1, stdout);
+
+ return 0;
+}
+
+static int encoded_read_io_uring(const char *filename, long long offset)
+{
+#ifdef HAVE_LIBURING_H
+ int ret, fd;
+ char buf[BTRFS_MAX_COMPRESSED];
+ struct iovec iov;
+ struct btrfs_ioctl_encoded_io_args enc;
+ struct io_uring ring;
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+
+ io_uring_queue_init(QUEUE_DEPTH, &ring, 0);
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "open failed for %s\n", filename);
+ ret = 1;
+ goto out_uring;
+ }
+
+ iov.iov_base = buf;
+ iov.iov_len = sizeof(buf);
+
+ enc.iov = &iov;
+ enc.iovcnt = 1;
+ enc.offset = offset;
+ enc.flags = 0;
+
+ sqe = io_uring_get_sqe(&ring);
+ if (!sqe) {
+ fprintf(stderr, "io_uring_get_sqe failed\n");
+ ret = 1;
+ goto out_close;
+ }
+
+ io_uring_prep_rw(IORING_OP_URING_CMD, sqe, fd, &enc, sizeof(enc), 0);
+ sqe->cmd_op = BTRFS_IOC_ENCODED_READ;
+
+ io_uring_submit(&ring);
+
+ ret = io_uring_wait_cqe(&ring, &cqe);
+ if (ret < 0) {
+ fprintf(stderr, "io_uring_wait_cqe returned %i\n", ret);
+ ret = 1;
+ goto out_close;
+ }
+
+ io_uring_cqe_seen(&ring, cqe);
+
+ if (cqe->res < 0) {
+ printf("%i\n", cqe->res);
+ ret = 0;
+ goto out_close;
+ }
+
+ printf("%i\n", cqe->res);
+ printf("%llu\n", enc.len);
+ printf("%llu\n", enc.unencoded_len);
+ printf("%llu\n", enc.unencoded_offset);
+ printf("%u\n", enc.compression);
+ printf("%u\n", enc.encryption);
+
+ fwrite(buf, cqe->res, 1, stdout);
+
+ ret = 0;
+
+out_close:
+ close(fd);
+
+out_uring:
+ io_uring_queue_exit(&ring);
+
+ return ret;
+#else
+ fprintf(stderr, "liburing not linked in\n");
+ return 1;
+#endif
+}
+
+static void usage()
+{
+ fprintf(stderr, "Usage: btrfs_encoded_read ioctl|io_uring filename offset\n");
+}
+
+int main(int argc, char *argv[])
+{
+ const char *filename;
+ long long offset;
+
+ if (argc != 4) {
+ usage();
+ return 1;
+ }
+
+ filename = argv[2];
+
+ offset = atoll(argv[3]);
+ if (offset == 0 && errno != 0) {
+ usage();
+ return 1;
+ }
+
+ if (!strcmp(argv[1], "ioctl")) {
+ return encoded_read_ioctl(filename, offset);
+ } else if (!strcmp(argv[1], "io_uring")) {
+ return encoded_read_io_uring(filename, offset);
+ } else {
+ usage();
+ return 1;
+ }
+}
new file mode 100644
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+#include <linux/btrfs.h>
+#include "config.h"
+
+#ifdef HAVE_LIBURING_H
+#include <liburing.h>
+#endif
+
+#define BTRFS_MAX_COMPRESSED 131072
+#define QUEUE_DEPTH 1
+
+static int encoded_write_ioctl(const char *filename, long long offset,
+ long long len, long long unencoded_len,
+ long long unencoded_offset, int compression,
+ char *buf, size_t size)
+{
+ int ret, fd;
+ struct iovec iov;
+ struct btrfs_ioctl_encoded_io_args enc;
+
+ fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+ if (fd < 0) {
+ fprintf(stderr, "open failed for %s\n", filename);
+ return 1;
+ }
+
+ iov.iov_base = buf;
+ iov.iov_len = size;
+
+ memset(&enc, 0, sizeof(enc));
+ enc.iov = &iov;
+ enc.iovcnt = 1;
+ enc.offset = offset;
+ enc.len = len;
+ enc.unencoded_len = unencoded_len;
+ enc.unencoded_offset = unencoded_offset;
+ enc.compression = compression;
+
+ ret = ioctl(fd, BTRFS_IOC_ENCODED_WRITE, &enc);
+
+ if (ret < 0) {
+ printf("%i\n", -errno);
+ close(fd);
+ return 0;
+ }
+
+ printf("%i\n", ret);
+
+ close(fd);
+
+ return 0;
+}
+
+static int encoded_write_io_uring(const char *filename, long long offset,
+ long long len, long long unencoded_len,
+ long long unencoded_offset, int compression,
+ char *buf, size_t size)
+{
+#ifdef HAVE_LIBURING_H
+ int ret, fd;
+ struct iovec iov;
+ struct btrfs_ioctl_encoded_io_args enc;
+ struct io_uring ring;
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+
+ io_uring_queue_init(QUEUE_DEPTH, &ring, 0);
+
+ fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+ if (fd < 0) {
+ fprintf(stderr, "open failed for %s\n", filename);
+ ret = 1;
+ goto out_uring;
+ }
+
+ iov.iov_base = buf;
+ iov.iov_len = size;
+
+ memset(&enc, 0, sizeof(enc));
+ enc.iov = &iov;
+ enc.iovcnt = 1;
+ enc.offset = offset;
+ enc.len = len;
+ enc.unencoded_len = unencoded_len;
+ enc.unencoded_offset = unencoded_offset;
+ enc.compression = compression;
+
+ sqe = io_uring_get_sqe(&ring);
+ if (!sqe) {
+ fprintf(stderr, "io_uring_get_sqe failed\n");
+ ret = 1;
+ goto out_close;
+ }
+
+ io_uring_prep_rw(IORING_OP_URING_CMD, sqe, fd, &enc, sizeof(enc), 0);
+ sqe->cmd_op = BTRFS_IOC_ENCODED_WRITE;
+
+ io_uring_submit(&ring);
+
+ ret = io_uring_wait_cqe(&ring, &cqe);
+ if (ret < 0) {
+ fprintf(stderr, "io_uring_wait_cqe returned %i\n", ret);
+ ret = 1;
+ goto out_close;
+ }
+
+ io_uring_cqe_seen(&ring, cqe);
+
+ if (cqe->res < 0) {
+ printf("%i\n", cqe->res);
+ ret = 0;
+ goto out_close;
+ }
+
+ printf("%i\n", cqe->res);
+
+ ret = 0;
+
+out_close:
+ close(fd);
+
+out_uring:
+ io_uring_queue_exit(&ring);
+
+ return ret;
+#else
+ fprintf(stderr, "liburing not linked in\n");
+ return 1;
+#endif
+}
+
+static void usage()
+{
+ fprintf(stderr, "Usage: btrfs_encoded_write ioctl|io_uring filename offset len unencoded_len unencoded_offset compression\n");
+}
+
+int main(int argc, char *argv[])
+{
+ const char *filename;
+ long long offset, len, unencoded_len, unencoded_offset;
+ int compression;
+ char buf[BTRFS_MAX_COMPRESSED];
+ size_t size;
+
+ if (argc != 8) {
+ usage();
+ return 1;
+ }
+
+ filename = argv[2];
+
+ offset = atoll(argv[3]);
+ if (offset == 0 && errno != 0) {
+ usage();
+ return 1;
+ }
+
+ len = atoll(argv[4]);
+ if (len == 0 && errno != 0) {
+ usage();
+ return 1;
+ }
+
+ unencoded_len = atoll(argv[5]);
+ if (unencoded_len == 0 && errno != 0) {
+ usage();
+ return 1;
+ }
+
+ unencoded_offset = atoll(argv[6]);
+ if (unencoded_offset == 0 && errno != 0) {
+ usage();
+ return 1;
+ }
+
+ compression = atoi(argv[7]);
+ if (compression == 0 && errno != 0) {
+ usage();
+ return 1;
+ }
+
+ size = fread(buf, 1, BTRFS_MAX_COMPRESSED, stdin);
+
+ if (!strcmp(argv[1], "ioctl")) {
+ return encoded_write_ioctl(filename, offset, len, unencoded_len,
+ unencoded_offset, compression, buf,
+ size);
+ } else if (!strcmp(argv[1], "io_uring")) {
+ return encoded_write_io_uring(filename, offset, len,
+ unencoded_len, unencoded_offset,
+ compression, buf, size);
+ } else {
+ usage();
+ return 1;
+ }
+}
new file mode 100755
@@ -0,0 +1,220 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2024 Meta Platforms, Inc. All Rights Reserved.
+#
+# FS QA Test No. btrfs/333
+#
+# Test btrfs encoded reads
+
+. ./common/preamble
+_begin_fstest auto quick compress rw
+
+. ./common/filter
+. ./common/btrfs
+
+_supported_fs btrfs
+
+do_encoded_read() {
+ local fn=$1
+ local type=$2
+ local exp_ret=$3
+ local exp_len=$4
+ local exp_unencoded_len=$5
+ local exp_unencoded_offset=$6
+ local exp_compression=$7
+ local exp_md5=$8
+
+ local tmpfile=`mktemp`
+
+ echo "running btrfs_encoded_read $type $fn 0 > $tmpfile" >>$seqres.full
+ src/btrfs_encoded_read $type $fn 0 > $tmpfile
+
+ if [[ $? -ne 0 ]]; then
+ echo "btrfs_encoded_read failed" >>$seqres.full
+ rm $tmpfile
+ return 1
+ fi
+
+ exec {FD}< $tmpfile
+
+ read -u ${FD} ret
+
+ if [[ $ret == -95 && $type -eq "io_uring" ]]; then
+ echo "btrfs io_uring encoded read failed with -EOPNOTSUPP, skipping" >>$seqres.full
+ exec {FD}<&-
+ return 1
+ fi
+
+ if [[ $ret -lt 0 ]]; then
+ if [[ $ret == -1 ]]; then
+ echo "btrfs encoded read failed with -EPERM; are you running as root?" >>$seqres.full
+ else
+ echo "btrfs encoded read failed (errno $ret)" >>$seqres.full
+ fi
+ exec {FD}<&-
+ return 1
+ fi
+
+ local status=0
+
+ if [[ $ret -ne $exp_ret ]]; then
+ echo "$fn: btrfs encoded read returned $ret, expected $exp_ret" >>$seqres.full
+ status=1
+ fi
+
+ read -u ${FD} len
+ read -u ${FD} unencoded_len
+ read -u ${FD} unencoded_offset
+ read -u ${FD} compression
+ read -u ${FD} encryption
+
+ local filesize=`stat -c%s $tmpfile`
+ local datafile=`mktemp`
+
+ dd if=$tmpfile of=$datafile bs=1 count=$ret skip=$(($filesize-$ret)) status=none
+
+ exec {FD}<&-
+ rm $tmpfile
+
+ local md5=`md5sum $datafile | cut -d ' ' -f 1`
+ rm $datafile
+
+ if [[ $len -ne $exp_len ]]; then
+ echo "$fn: btrfs encoded read had len of $len, expected $exp_len" >>$seqres.full
+ status=1
+ fi
+
+ if [[ $unencoded_len -ne $exp_unencoded_len ]]; then
+ echo "$fn: btrfs encoded read had unencoded_len of $unencoded_len, expected $exp_unencoded_len" >>$seqres.full
+ status=1
+ fi
+
+ if [[ $unencoded_offset -ne $exp_unencoded_offset ]]; then
+ echo "$fn: btrfs encoded read had unencoded_offset of $unencoded_offset, expected $exp_unencoded_offset" >>$seqres.full
+ status=1
+ fi
+
+ if [[ $compression -ne $exp_compression ]]; then
+ echo "$fn: btrfs encoded read had compression of $compression, expected $exp_compression" >>$seqres.full
+ status=1
+ fi
+
+ if [[ $encryption -ne 0 ]]; then
+ echo "$fn: btrfs encoded read had encryption of $encryption, expected 0" >>$seqres.full
+ status=1
+ fi
+
+ if [[ $md5 != $exp_md5 ]]; then
+ echo "$fn: data returned had hash of $md5, expected $exp_md5" >>$seqres.full
+ status=1
+ fi
+
+ return $status
+}
+
+do_encoded_write() {
+ local fn=$1
+ local exp_ret=$2
+ local len=$3
+ local unencoded_len=$4
+ local unencoded_offset=$5
+ local compression=$6
+ local data_file=$7
+
+ local tmpfile=`mktemp`
+
+ echo "running btrfs_encoded_write ioctl $fn 0 $len $unencoded_len $unencoded_offset $compression < $data_file > $tmpfile" >>$seqres.full
+ src/btrfs_encoded_write ioctl $fn 0 $len $unencoded_len $unencoded_offset $compression < $data_file > $tmpfile
+
+ if [[ $? -ne 0 ]]; then
+ echo "btrfs_encoded_write failed" >>$seqres.full
+ rm $tmpfile
+ return 1
+ fi
+
+ exec {FD}< $tmpfile
+
+ read -u ${FD} ret
+
+ if [[ $ret -lt 0 ]]; then
+ if [[ $ret == -1 ]]; then
+ echo "btrfs encoded write failed with -EPERM; are you running as root?" >>$seqres.full
+ else
+ echo "btrfs encoded write failed (errno $ret)" >>$seqres.full
+ fi
+ exec {FD}<&-
+ return 1
+ fi
+
+ exec {FD}<&-
+ rm $tmpfile
+
+ return 0
+}
+
+test_file() {
+ local size=$1
+ local len=$2
+ local unencoded_len=$3
+ local unencoded_offset=$4
+ local compression=$5
+
+ local tmpfile=`mktemp -p $SCRATCH_MNT`
+ local randfile=`mktemp`
+
+ dd if=/dev/urandom of=$randfile bs=1 count=$size status=none
+ local md5=`md5sum $randfile | cut -d ' ' -f 1`
+
+ do_encoded_write $tmpfile $size $len $unencoded_len $unencoded_offset \
+ $compression $randfile || _fail "encoded write ioctl failed"
+
+ rm $randfile
+
+ do_encoded_read $tmpfile ioctl $size $len $unencoded_len \
+ $unencoded_offset $compression $md5 || _fail "encoded read ioctl failed"
+ do_encoded_read $tmpfile io_uring $size $len $unencoded_len \
+ $unencoded_offset $compression $md5 || _fail "encoded read io_uring failed"
+
+ rm $tmpfile
+}
+
+_scratch_mkfs >> $seqres.full 2>&1 || _fail "mkfs failed"
+sector_size=$(_scratch_btrfs_sectorsize)
+
+if [[ $sector_size -ne 4096 && $sector_size -ne 65536 ]]; then
+ _notrun "sector size $sector_size not supported by this test"
+fi
+
+_scratch_mount "-o max_inline=2048"
+
+if [[ $sector_size -eq 4096 ]]; then
+ test_file 40960 97966 98304 0 1 # zlib
+ test_file 40960 97966 98304 0 2 # zstd
+ test_file 40960 97966 98304 0 3 # lzo 4k
+ test_file 40960 97966 110592 4096 1 # bookended zlib
+ test_file 40960 97966 110592 4096 2 # bookended zstd
+ test_file 40960 97966 110592 4096 3 # bookended lzo 4k
+elif [[ $sector_size -eq 65536 ]]; then
+ test_file 65536 97966 131072 0 1 # zlib
+ test_file 65536 97966 131072 0 2 # zstd
+ test_file 65536 97966 131072 0 7 # lzo 64k
+ # can't test bookended extents on 64k, as max is only 2 sectors long
+fi
+
+# btrfs won't create inline files unless PAGE_SIZE == sector size
+if [[ "$(_get_page_size)" -eq $sector_size ]]; then
+ test_file 892 1931 1931 0 1 # inline zlib
+ test_file 892 1931 1931 0 2 # inline zstd
+
+ if [[ $sector_size -eq 4096 ]]; then
+ test_file 892 1931 1931 0 3 # inline lzo 4k
+ elif [[ $sector_size -eq 65536 ]]; then
+ test_file 892 1931 1931 0 7 # inline lzo 64k
+ fi
+fi
+
+_scratch_unmount
+
+echo Silence is golden
+status=0
+exit
new file mode 100644
@@ -0,0 +1,2 @@
+QA output created by 333
+Silence is golden
Add btrfs/333 and its helper programs btrfs_encoded_read and btrfs_encoded_write, in order to test encoded reads. We use the BTRFS_IOC_ENCODED_WRITE ioctl to write random data into a compressed extent, then use the BTRFS_IOC_ENCODED_READ ioctl to check that it matches what we've written. If the new io_uring interface for encoded reads is supported, we also check that that matches the ioctl. Note that what we write isn't valid compressed data, so any non-encoded reads on these files will fail. Signed-off-by: Mark Harmstone <maharmstone@fb.com> --- .gitignore | 2 + src/Makefile | 3 +- src/btrfs_encoded_read.c | 175 ++++++++++++++++++++++++++++++ src/btrfs_encoded_write.c | 206 +++++++++++++++++++++++++++++++++++ tests/btrfs/333 | 220 ++++++++++++++++++++++++++++++++++++++ tests/btrfs/333.out | 2 + 6 files changed, 607 insertions(+), 1 deletion(-) create mode 100644 src/btrfs_encoded_read.c create mode 100644 src/btrfs_encoded_write.c create mode 100755 tests/btrfs/333 create mode 100644 tests/btrfs/333.out