aboutsummaryrefslogtreecommitdiffstats
path: root/tests/zfs-tests/cmd
diff options
context:
space:
mode:
authorRob Norris <[email protected]>2023-07-11 20:46:33 +1000
committerBrian Behlendorf <[email protected]>2023-07-24 16:37:58 -0700
commit48d0e9465de9571a5268f65b7446c693410e0220 (patch)
treea620d532b78681faf9f61ed779e41ea08996cad1 /tests/zfs-tests/cmd
parent6b0a4be5fec60eb774c5393a0093150c608b7496 (diff)
zts: block cloning tests
Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Kay Pedersen <[email protected]> Signed-off-by: Rob Norris <[email protected]> Sponsored-By: OpenDrives Inc. Sponsored-By: Klara Inc. Closes #15050 Closes #405 Closes #13349
Diffstat (limited to 'tests/zfs-tests/cmd')
-rw-r--r--tests/zfs-tests/cmd/.gitignore1
-rw-r--r--tests/zfs-tests/cmd/Makefile.am1
-rw-r--r--tests/zfs-tests/cmd/clonefile.c333
3 files changed, 335 insertions, 0 deletions
diff --git a/tests/zfs-tests/cmd/.gitignore b/tests/zfs-tests/cmd/.gitignore
index f68f58072..5f53b6871 100644
--- a/tests/zfs-tests/cmd/.gitignore
+++ b/tests/zfs-tests/cmd/.gitignore
@@ -1,6 +1,7 @@
/badsend
/btree_test
/chg_usr_exec
+/clonefile
/devname2devid
/dir_rd_update
/draid
diff --git a/tests/zfs-tests/cmd/Makefile.am b/tests/zfs-tests/cmd/Makefile.am
index 066abb6ce..9bdb3c209 100644
--- a/tests/zfs-tests/cmd/Makefile.am
+++ b/tests/zfs-tests/cmd/Makefile.am
@@ -119,6 +119,7 @@ scripts_zfs_tests_bin_PROGRAMS += %D%/renameat2
scripts_zfs_tests_bin_PROGRAMS += %D%/xattrtest
scripts_zfs_tests_bin_PROGRAMS += %D%/zed_fd_spill-zedlet
scripts_zfs_tests_bin_PROGRAMS += %D%/idmap_util
+scripts_zfs_tests_bin_PROGRAMS += %D%/clonefile
%C%_idmap_util_LDADD = libspl.la
diff --git a/tests/zfs-tests/cmd/clonefile.c b/tests/zfs-tests/cmd/clonefile.c
new file mode 100644
index 000000000..a7e7277ae
--- /dev/null
+++ b/tests/zfs-tests/cmd/clonefile.c
@@ -0,0 +1,333 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2023, Rob Norris <[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/*
+ * This program is to test the availability and behaviour of copy_file_range,
+ * FICLONE, FICLONERANGE and FIDEDUPERANGE in the Linux kernel. It should
+ * compile and run even if these features aren't exposed through the libc.
+ */
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#ifndef __NR_copy_file_range
+#if defined(__x86_64__)
+#define __NR_copy_file_range (326)
+#elif defined(__i386__)
+#define __NR_copy_file_range (377)
+#elif defined(__s390__)
+#define __NR_copy_file_range (375)
+#elif defined(__arm__)
+#define __NR_copy_file_range (391)
+#elif defined(__aarch64__)
+#define __NR_copy_file_range (285)
+#elif defined(__powerpc__)
+#define __NR_copy_file_range (379)
+#else
+#error "no definition of __NR_copy_file_range for this platform"
+#endif
+#endif /* __NR_copy_file_range */
+
+ssize_t
+copy_file_range(int, loff_t *, int, loff_t *, size_t, unsigned int)
+ __attribute__((weak));
+
+static inline ssize_t
+cf_copy_file_range(int sfd, loff_t *soff, int dfd, loff_t *doff,
+ size_t len, unsigned int flags)
+{
+ if (copy_file_range)
+ return (copy_file_range(sfd, soff, dfd, doff, len, flags));
+ return (
+ syscall(__NR_copy_file_range, sfd, soff, dfd, doff, len, flags));
+}
+
+/* Define missing FICLONE */
+#ifdef FICLONE
+#define CF_FICLONE FICLONE
+#else
+#define CF_FICLONE _IOW(0x94, 9, int)
+#endif
+
+/* Define missing FICLONERANGE and support structs */
+#ifdef FICLONERANGE
+#define CF_FICLONERANGE FICLONERANGE
+typedef struct file_clone_range cf_file_clone_range_t;
+#else
+typedef struct {
+ int64_t src_fd;
+ uint64_t src_offset;
+ uint64_t src_length;
+ uint64_t dest_offset;
+} cf_file_clone_range_t;
+#define CF_FICLONERANGE _IOW(0x94, 13, cf_file_clone_range_t)
+#endif
+
+/* Define missing FIDEDUPERANGE and support structs */
+#ifdef FIDEDUPERANGE
+#define CF_FIDEDUPERANGE FIDEDUPERANGE
+#define CF_FILE_DEDUPE_RANGE_SAME FILE_DEDUPE_RANGE_SAME
+#define CF_FILE_DEDUPE_RANGE_DIFFERS FILE_DEDUPE_RANGE_DIFFERS
+typedef struct file_dedupe_range_info cf_file_dedupe_range_info_t;
+typedef struct file_dedupe_range cf_file_dedupe_range_t;
+#else
+typedef struct {
+ int64_t dest_fd;
+ uint64_t dest_offset;
+ uint64_t bytes_deduped;
+ int32_t status;
+ uint32_t reserved;
+} cf_file_dedupe_range_info_t;
+typedef struct {
+ uint64_t src_offset;
+ uint64_t src_length;
+ uint16_t dest_count;
+ uint16_t reserved1;
+ uint32_t reserved2;
+ cf_file_dedupe_range_info_t info[0];
+} cf_file_dedupe_range_t;
+#define CF_FIDEDUPERANGE _IOWR(0x94, 54, cf_file_dedupe_range_t)
+#define CF_FILE_DEDUPE_RANGE_SAME (0)
+#define CF_FILE_DEDUPE_RANGE_DIFFERS (1)
+#endif
+
+typedef enum {
+ CF_MODE_NONE,
+ CF_MODE_CLONE,
+ CF_MODE_CLONERANGE,
+ CF_MODE_COPYFILERANGE,
+ CF_MODE_DEDUPERANGE,
+} cf_mode_t;
+
+static int
+usage(void)
+{
+ printf(
+ "usage:\n"
+ " FICLONE:\n"
+ " clonefile -c <src> <dst>\n"
+ " FICLONERANGE:\n"
+ " clonefile -r <src> <dst> <soff> <doff> <len>\n"
+ " copy_file_range:\n"
+ " clonefile -f <src> <dst> <soff> <doff> <len>\n"
+ " FIDEDUPERANGE:\n"
+ " clonefile -d <src> <dst> <soff> <doff> <len>\n");
+ return (1);
+}
+
+int do_clone(int sfd, int dfd);
+int do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len);
+int do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len);
+int do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len);
+
+int quiet = 0;
+
+int
+main(int argc, char **argv)
+{
+ cf_mode_t mode = CF_MODE_NONE;
+
+ char c;
+ while ((c = getopt(argc, argv, "crfdq")) != -1) {
+ switch (c) {
+ case 'c':
+ mode = CF_MODE_CLONE;
+ break;
+ case 'r':
+ mode = CF_MODE_CLONERANGE;
+ break;
+ case 'f':
+ mode = CF_MODE_COPYFILERANGE;
+ break;
+ case 'd':
+ mode = CF_MODE_DEDUPERANGE;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ }
+ }
+
+ if (mode == CF_MODE_NONE || (argc-optind) < 2 ||
+ (mode != CF_MODE_CLONE && (argc-optind) < 5))
+ return (usage());
+
+ loff_t soff = 0, doff = 0;
+ size_t len = 0;
+ if (mode != CF_MODE_CLONE) {
+ soff = strtoull(argv[optind+2], NULL, 10);
+ if (soff == ULLONG_MAX) {
+ fprintf(stderr, "invalid source offset");
+ return (1);
+ }
+ doff = strtoull(argv[optind+3], NULL, 10);
+ if (doff == ULLONG_MAX) {
+ fprintf(stderr, "invalid dest offset");
+ return (1);
+ }
+ len = strtoull(argv[optind+4], NULL, 10);
+ if (len == ULLONG_MAX) {
+ fprintf(stderr, "invalid length");
+ return (1);
+ }
+ }
+
+ int sfd = open(argv[optind], O_RDONLY);
+ if (sfd < 0) {
+ fprintf(stderr, "open: %s: %s\n",
+ argv[optind], strerror(errno));
+ return (1);
+ }
+
+ int dfd = open(argv[optind+1], O_WRONLY|O_CREAT,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+ if (sfd < 0) {
+ fprintf(stderr, "open: %s: %s\n",
+ argv[optind+1], strerror(errno));
+ close(sfd);
+ return (1);
+ }
+
+ int err;
+ switch (mode) {
+ case CF_MODE_CLONE:
+ err = do_clone(sfd, dfd);
+ break;
+ case CF_MODE_CLONERANGE:
+ err = do_clonerange(sfd, dfd, soff, doff, len);
+ break;
+ case CF_MODE_COPYFILERANGE:
+ err = do_copyfilerange(sfd, dfd, soff, doff, len);
+ break;
+ case CF_MODE_DEDUPERANGE:
+ err = do_deduperange(sfd, dfd, soff, doff, len);
+ break;
+ default:
+ abort();
+ }
+
+ off_t spos = lseek(sfd, 0, SEEK_CUR);
+ off_t slen = lseek(sfd, 0, SEEK_END);
+ off_t dpos = lseek(dfd, 0, SEEK_CUR);
+ off_t dlen = lseek(dfd, 0, SEEK_END);
+
+ fprintf(stderr, "file offsets: src=%lu/%lu; dst=%lu/%lu\n", spos, slen,
+ dpos, dlen);
+
+ close(dfd);
+ close(sfd);
+
+ return (err == 0 ? 0 : 1);
+}
+
+int
+do_clone(int sfd, int dfd)
+{
+ fprintf(stderr, "using FICLONE\n");
+ int err = ioctl(dfd, CF_FICLONE, sfd);
+ if (err < 0) {
+ fprintf(stderr, "ioctl(FICLONE): %s\n", strerror(errno));
+ return (err);
+ }
+ return (0);
+}
+
+int
+do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len)
+{
+ fprintf(stderr, "using FICLONERANGE\n");
+ cf_file_clone_range_t fcr = {
+ .src_fd = sfd,
+ .src_offset = soff,
+ .src_length = len,
+ .dest_offset = doff,
+ };
+ int err = ioctl(dfd, CF_FICLONERANGE, &fcr);
+ if (err < 0) {
+ fprintf(stderr, "ioctl(FICLONERANGE): %s\n", strerror(errno));
+ return (err);
+ }
+ return (0);
+}
+
+int
+do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len)
+{
+ fprintf(stderr, "using copy_file_range\n");
+ ssize_t copied = cf_copy_file_range(sfd, &soff, dfd, &doff, len, 0);
+ if (copied < 0) {
+ fprintf(stderr, "copy_file_range: %s\n", strerror(errno));
+ return (1);
+ }
+ if (copied != len) {
+ fprintf(stderr, "copy_file_range: copied less than requested: "
+ "requested=%lu; copied=%lu\n", len, copied);
+ return (1);
+ }
+ return (0);
+}
+
+int
+do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len)
+{
+ fprintf(stderr, "using FIDEDUPERANGE\n");
+
+ char buf[sizeof (cf_file_dedupe_range_t)+
+ sizeof (cf_file_dedupe_range_info_t)] = {0};
+ cf_file_dedupe_range_t *fdr = (cf_file_dedupe_range_t *)&buf[0];
+ cf_file_dedupe_range_info_t *fdri =
+ (cf_file_dedupe_range_info_t *)
+ &buf[sizeof (cf_file_dedupe_range_t)];
+
+ fdr->src_offset = soff;
+ fdr->src_length = len;
+ fdr->dest_count = 1;
+
+ fdri->dest_fd = dfd;
+ fdri->dest_offset = doff;
+
+ int err = ioctl(sfd, CF_FIDEDUPERANGE, fdr);
+ if (err != 0)
+ fprintf(stderr, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno));
+
+ if (fdri->status < 0) {
+ fprintf(stderr, "dedup failed: %s\n", strerror(-fdri->status));
+ err = -1;
+ } else if (fdri->status == CF_FILE_DEDUPE_RANGE_DIFFERS) {
+ fprintf(stderr, "dedup failed: range differs\n");
+ err = -1;
+ }
+
+ return (err);
+}