Path: blob/main/sys/contrib/openzfs/tests/zfs-tests/cmd/clonefile.c
109066 views
/*1* SPDX-License-Identifier: MIT2*3* Copyright (c) 2023, Rob Norris <[email protected]>4*5* Permission is hereby granted, free of charge, to any person obtaining a copy6* of this software and associated documentation files (the "Software"), to7* deal in the Software without restriction, including without limitation the8* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or9* sell copies of the Software, and to permit persons to whom the Software is10* furnished to do so, subject to the following conditions:11*12* The above copyright notice and this permission notice shall be included in13* all copies or substantial portions of the Software.14*15* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR16* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,17* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE18* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER19* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING20* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS21* IN THE SOFTWARE.22*/2324/*25* This program is to test the availability and behaviour of copy_file_range,26* FICLONE, FICLONERANGE and FIDEDUPERANGE in the Linux kernel. It should27* compile and run even if these features aren't exposed through the libc.28*/2930#include <sys/ioctl.h>31#include <sys/types.h>32#include <sys/stat.h>33#include <fcntl.h>34#include <stdint.h>35#include <unistd.h>36#include <sys/syscall.h>37#include <stdlib.h>38#include <limits.h>39#include <stdio.h>40#include <string.h>41#include <errno.h>4243#ifndef __NR_copy_file_range44#if defined(__x86_64__)45#define __NR_copy_file_range (326)46#elif defined(__i386__)47#define __NR_copy_file_range (377)48#elif defined(__s390__)49#define __NR_copy_file_range (375)50#elif defined(__arm__)51#define __NR_copy_file_range (391)52#elif defined(__aarch64__)53#define __NR_copy_file_range (285)54#elif defined(__powerpc__)55#define __NR_copy_file_range (379)56#else57#error "no definition of __NR_copy_file_range for this platform"58#endif59#endif /* __NR_copy_file_range */6061#if defined(_GNU_SOURCE) && defined(__linux__)62_Static_assert(sizeof (loff_t) == sizeof (off_t),63"loff_t and off_t must be the same size");64#endif6566ssize_t67copy_file_range(int, off_t *, int, off_t *, size_t, unsigned int)68__attribute__((weak));6970static inline ssize_t71cf_copy_file_range(int sfd, off_t *soff, int dfd, off_t *doff,72size_t len, unsigned int flags)73{74if (copy_file_range)75return (copy_file_range(sfd, soff, dfd, doff, len, flags));76return (77syscall(__NR_copy_file_range, sfd, soff, dfd, doff, len, flags));78}7980/* Define missing FICLONE */81#ifdef FICLONE82#define CF_FICLONE FICLONE83#else84#define CF_FICLONE _IOW(0x94, 9, int)85#endif8687/* Define missing FICLONERANGE and support structs */88#ifdef FICLONERANGE89#define CF_FICLONERANGE FICLONERANGE90typedef struct file_clone_range cf_file_clone_range_t;91#else92typedef struct {93int64_t src_fd;94uint64_t src_offset;95uint64_t src_length;96uint64_t dest_offset;97} cf_file_clone_range_t;98#define CF_FICLONERANGE _IOW(0x94, 13, cf_file_clone_range_t)99#endif100101/* Define missing FIDEDUPERANGE and support structs */102#ifdef FIDEDUPERANGE103#define CF_FIDEDUPERANGE FIDEDUPERANGE104#define CF_FILE_DEDUPE_RANGE_SAME FILE_DEDUPE_RANGE_SAME105#define CF_FILE_DEDUPE_RANGE_DIFFERS FILE_DEDUPE_RANGE_DIFFERS106typedef struct file_dedupe_range_info cf_file_dedupe_range_info_t;107typedef struct file_dedupe_range cf_file_dedupe_range_t;108#else109typedef struct {110int64_t dest_fd;111uint64_t dest_offset;112uint64_t bytes_deduped;113int32_t status;114uint32_t reserved;115} cf_file_dedupe_range_info_t;116typedef struct {117uint64_t src_offset;118uint64_t src_length;119uint16_t dest_count;120uint16_t reserved1;121uint32_t reserved2;122cf_file_dedupe_range_info_t info[0];123} cf_file_dedupe_range_t;124#define CF_FIDEDUPERANGE _IOWR(0x94, 54, cf_file_dedupe_range_t)125#define CF_FILE_DEDUPE_RANGE_SAME (0)126#define CF_FILE_DEDUPE_RANGE_DIFFERS (1)127#endif128129typedef enum {130CF_MODE_NONE,131CF_MODE_CLONE,132CF_MODE_CLONERANGE,133CF_MODE_COPYFILERANGE,134CF_MODE_DEDUPERANGE,135} cf_mode_t;136137static int138usage(void)139{140printf(141"usage:\n"142" FICLONE:\n"143" clonefile -c <src> <dst>\n"144" FICLONERANGE:\n"145" clonefile -r <src> <dst> <soff> <doff> <len>\n"146" copy_file_range:\n"147" clonefile -f <src> <dst> [<soff> <doff> <len | \"all\">]\n"148" FIDEDUPERANGE:\n"149" clonefile -d <src> <dst> <soff> <doff> <len>\n");150return (1);151}152153int do_clone(int sfd, int dfd);154int do_clonerange(int sfd, int dfd, off_t soff, off_t doff, size_t len);155int do_copyfilerange(int sfd, int dfd, off_t soff, off_t doff, size_t len);156int do_deduperange(int sfd, int dfd, off_t soff, off_t doff, size_t len);157158int quiet = 0;159160int161main(int argc, char **argv)162{163cf_mode_t mode = CF_MODE_NONE;164165int c;166while ((c = getopt(argc, argv, "crfdq")) != -1) {167switch (c) {168case 'c':169mode = CF_MODE_CLONE;170break;171case 'r':172mode = CF_MODE_CLONERANGE;173break;174case 'f':175mode = CF_MODE_COPYFILERANGE;176break;177case 'd':178mode = CF_MODE_DEDUPERANGE;179break;180case 'q':181quiet = 1;182break;183}184}185186switch (mode) {187case CF_MODE_NONE:188return (usage());189case CF_MODE_CLONE:190if ((argc-optind) != 2)191return (usage());192break;193case CF_MODE_CLONERANGE:194case CF_MODE_DEDUPERANGE:195if ((argc-optind) != 5)196return (usage());197break;198case CF_MODE_COPYFILERANGE:199if ((argc-optind) != 2 && (argc-optind) != 5)200return (usage());201break;202default:203abort();204}205206off_t soff = 0, doff = 0;207size_t len = SSIZE_MAX;208unsigned long long len2;209if ((argc-optind) == 5) {210soff = strtoull(argv[optind+2], NULL, 10);211if (soff == ULLONG_MAX) {212fprintf(stderr, "invalid source offset");213return (1);214}215doff = strtoull(argv[optind+3], NULL, 10);216if (doff == ULLONG_MAX) {217fprintf(stderr, "invalid dest offset");218return (1);219}220if (mode == CF_MODE_COPYFILERANGE &&221strcmp(argv[optind+4], "all") == 0) {222len = SSIZE_MAX;223} else {224len2 = strtoull(argv[optind+4], NULL, 10);225if (len2 == ULLONG_MAX) {226fprintf(stderr, "invalid length");227return (1);228}229if (len2 < SSIZE_MAX)230len = (size_t)len2;231}232}233234int sfd = open(argv[optind], O_RDONLY);235if (sfd < 0) {236fprintf(stderr, "open: %s: %s\n",237argv[optind], strerror(errno));238return (1);239}240241int dfd = open(argv[optind+1], O_WRONLY|O_CREAT,242S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);243if (dfd < 0) {244fprintf(stderr, "open: %s: %s\n",245argv[optind+1], strerror(errno));246close(sfd);247return (1);248}249250int err;251switch (mode) {252case CF_MODE_CLONE:253err = do_clone(sfd, dfd);254break;255case CF_MODE_CLONERANGE:256err = do_clonerange(sfd, dfd, soff, doff, len);257break;258case CF_MODE_COPYFILERANGE:259err = do_copyfilerange(sfd, dfd, soff, doff, len);260break;261case CF_MODE_DEDUPERANGE:262err = do_deduperange(sfd, dfd, soff, doff, len);263break;264default:265abort();266}267268if (!quiet) {269off_t spos = lseek(sfd, 0, SEEK_CUR);270off_t slen = lseek(sfd, 0, SEEK_END);271off_t dpos = lseek(dfd, 0, SEEK_CUR);272off_t dlen = lseek(dfd, 0, SEEK_END);273274fprintf(stderr, "file offsets: src=%jd/%jd; dst=%jd/%jd\n",275spos, slen, dpos, dlen);276}277278close(dfd);279close(sfd);280281return (err == 0 ? 0 : 1);282}283284int285do_clone(int sfd, int dfd)286{287if (!quiet)288fprintf(stderr, "using FICLONE\n");289int err = ioctl(dfd, CF_FICLONE, sfd);290if (err < 0) {291fprintf(stderr, "ioctl(FICLONE): %s\n", strerror(errno));292return (err);293}294return (0);295}296297int298do_clonerange(int sfd, int dfd, off_t soff, off_t doff, size_t len)299{300if (!quiet)301fprintf(stderr, "using FICLONERANGE\n");302cf_file_clone_range_t fcr = {303.src_fd = sfd,304.src_offset = soff,305.src_length = len,306.dest_offset = doff,307};308int err = ioctl(dfd, CF_FICLONERANGE, &fcr);309if (err < 0) {310fprintf(stderr, "ioctl(FICLONERANGE): %s\n", strerror(errno));311return (err);312}313return (0);314}315316int317do_copyfilerange(int sfd, int dfd, off_t soff, off_t doff, size_t len)318{319if (!quiet)320fprintf(stderr, "using copy_file_range\n");321ssize_t copied = cf_copy_file_range(sfd, &soff, dfd, &doff, len, 0);322if (copied < 0) {323fprintf(stderr, "copy_file_range: %s\n", strerror(errno));324return (1);325}326if (len == SSIZE_MAX) {327struct stat sb;328329if (fstat(sfd, &sb) < 0) {330fprintf(stderr, "fstat(sfd): %s\n", strerror(errno));331return (1);332}333len = sb.st_size;334}335if (copied != len) {336fprintf(stderr, "copy_file_range: copied less than requested: "337"requested=%zu; copied=%zd\n", len, copied);338return (1);339}340return (0);341}342343int344do_deduperange(int sfd, int dfd, off_t soff, off_t doff, size_t len)345{346if (!quiet)347fprintf(stderr, "using FIDEDUPERANGE\n");348349char buf[sizeof (cf_file_dedupe_range_t)+350sizeof (cf_file_dedupe_range_info_t)] = {0};351cf_file_dedupe_range_t *fdr = (cf_file_dedupe_range_t *)&buf[0];352cf_file_dedupe_range_info_t *fdri =353(cf_file_dedupe_range_info_t *)354&buf[sizeof (cf_file_dedupe_range_t)];355356fdr->src_offset = soff;357fdr->src_length = len;358fdr->dest_count = 1;359360fdri->dest_fd = dfd;361fdri->dest_offset = doff;362363int err = ioctl(sfd, CF_FIDEDUPERANGE, fdr);364if (err != 0)365fprintf(stderr, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno));366367if (fdri->status < 0) {368fprintf(stderr, "dedup failed: %s\n", strerror(-fdri->status));369err = -1;370} else if (fdri->status == CF_FILE_DEDUPE_RANGE_DIFFERS) {371fprintf(stderr, "dedup failed: range differs\n");372err = -1;373}374375return (err);376}377378379