Skip to content

Commit

Permalink
zts: block cloning tests
Browse files Browse the repository at this point in the history
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Kay Pedersen <mail@mkwg.de>
Signed-off-by: Rob Norris <rob.norris@klarasystems.com>
Sponsored-By: OpenDrives Inc.
Sponsored-By: Klara Inc.
Closes openzfs#15050
Closes openzfs#405
Closes openzfs#13349
  • Loading branch information
robn authored and behlendorf committed Jul 26, 2023
1 parent 2768dc0 commit 36d1a3e
Show file tree
Hide file tree
Showing 19 changed files with 1,016 additions and 0 deletions.
9 changes: 9 additions & 0 deletions tests/runfiles/linux.run
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ tags = ['functional', 'acl', 'posix-sa']
tests = ['atime_003_pos', 'root_relatime_on']
tags = ['functional', 'atime']

[tests/functional/block_cloning:Linux]
tests = ['block_cloning_copyfilerange', 'block_cloning_copyfilerange_partial',
'block_cloning_ficlone', 'block_cloning_ficlonerange',
'block_cloning_ficlonerange_partial',
'block_cloning_disabled_copyfilerange', 'block_cloning_disabled_ficlone',
'block_cloning_disabled_ficlonerange',
'block_cloning_copyfilerange_cross_dataset']
tags = ['functional', 'block_cloning']

[tests/functional/chattr:Linux]
tests = ['chattr_001_pos', 'chattr_002_neg']
tags = ['functional', 'chattr']
Expand Down
14 changes: 14 additions & 0 deletions tests/test-runner/bin/zts-report.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ ci_reason = 'CI runner doesn\'t have all requirements'
#
idmap_reason = 'Idmapped mount needs kernel 5.12+'

#
# copy_file_range() is not supported by all kernels
#
cfr_reason = 'Kernel copy_file_range support required'
cfr_cross_reason = 'copy_file_range(2) cross-filesystem needs kernel 5.3+'

#
# These tests are known to fail, thus we use this list to prevent these
# failures from failing the job as a whole; only unexpected failures
Expand Down Expand Up @@ -288,6 +294,14 @@ elif sys.platform.startswith('linux'):
'idmap_mount/idmap_mount_003': ['SKIP', idmap_reason],
'idmap_mount/idmap_mount_004': ['SKIP', idmap_reason],
'idmap_mount/idmap_mount_005': ['SKIP', idmap_reason],
'block_cloning/block_cloning_disabled_copyfilerange':
['SKIP', cfr_reason],
'block_cloning/block_cloning_copyfilerange':
['SKIP', cfr_reason],
'block_cloning/block_cloning_copyfilerange_partial':
['SKIP', cfr_reason],
'block_cloning/block_cloning_copyfilerange_cross_dataset':
['SKIP', cfr_cross_reason],
})


Expand Down
1 change: 1 addition & 0 deletions tests/zfs-tests/cmd/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/badsend
/btree_test
/chg_usr_exec
/clonefile
/devname2devid
/dir_rd_update
/draid
Expand Down
1 change: 1 addition & 0 deletions tests/zfs-tests/cmd/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
333 changes: 333 additions & 0 deletions tests/zfs-tests/cmd/clonefile.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
/*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2023, Rob Norris <robn@despairlabs.com>
*
* 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);
}
Loading

0 comments on commit 36d1a3e

Please sign in to comment.