]> git.proxmox.com Git - mirror_zfs.git/blob - tests/zfs-tests/cmd/clonefile.c
zts: block cloning tests
[mirror_zfs.git] / tests / zfs-tests / cmd / clonefile.c
1 /*
2 * SPDX-License-Identifier: MIT
3 *
4 * Copyright (c) 2023, Rob Norris <robn@despairlabs.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24
25 /*
26 * This program is to test the availability and behaviour of copy_file_range,
27 * FICLONE, FICLONERANGE and FIDEDUPERANGE in the Linux kernel. It should
28 * compile and run even if these features aren't exposed through the libc.
29 */
30
31 #include <sys/ioctl.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 #include <stdint.h>
36 #include <unistd.h>
37 #include <sys/syscall.h>
38 #include <stdlib.h>
39 #include <limits.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include <errno.h>
43
44 #ifndef __NR_copy_file_range
45 #if defined(__x86_64__)
46 #define __NR_copy_file_range (326)
47 #elif defined(__i386__)
48 #define __NR_copy_file_range (377)
49 #elif defined(__s390__)
50 #define __NR_copy_file_range (375)
51 #elif defined(__arm__)
52 #define __NR_copy_file_range (391)
53 #elif defined(__aarch64__)
54 #define __NR_copy_file_range (285)
55 #elif defined(__powerpc__)
56 #define __NR_copy_file_range (379)
57 #else
58 #error "no definition of __NR_copy_file_range for this platform"
59 #endif
60 #endif /* __NR_copy_file_range */
61
62 ssize_t
63 copy_file_range(int, loff_t *, int, loff_t *, size_t, unsigned int)
64 __attribute__((weak));
65
66 static inline ssize_t
67 cf_copy_file_range(int sfd, loff_t *soff, int dfd, loff_t *doff,
68 size_t len, unsigned int flags)
69 {
70 if (copy_file_range)
71 return (copy_file_range(sfd, soff, dfd, doff, len, flags));
72 return (
73 syscall(__NR_copy_file_range, sfd, soff, dfd, doff, len, flags));
74 }
75
76 /* Define missing FICLONE */
77 #ifdef FICLONE
78 #define CF_FICLONE FICLONE
79 #else
80 #define CF_FICLONE _IOW(0x94, 9, int)
81 #endif
82
83 /* Define missing FICLONERANGE and support structs */
84 #ifdef FICLONERANGE
85 #define CF_FICLONERANGE FICLONERANGE
86 typedef struct file_clone_range cf_file_clone_range_t;
87 #else
88 typedef struct {
89 int64_t src_fd;
90 uint64_t src_offset;
91 uint64_t src_length;
92 uint64_t dest_offset;
93 } cf_file_clone_range_t;
94 #define CF_FICLONERANGE _IOW(0x94, 13, cf_file_clone_range_t)
95 #endif
96
97 /* Define missing FIDEDUPERANGE and support structs */
98 #ifdef FIDEDUPERANGE
99 #define CF_FIDEDUPERANGE FIDEDUPERANGE
100 #define CF_FILE_DEDUPE_RANGE_SAME FILE_DEDUPE_RANGE_SAME
101 #define CF_FILE_DEDUPE_RANGE_DIFFERS FILE_DEDUPE_RANGE_DIFFERS
102 typedef struct file_dedupe_range_info cf_file_dedupe_range_info_t;
103 typedef struct file_dedupe_range cf_file_dedupe_range_t;
104 #else
105 typedef struct {
106 int64_t dest_fd;
107 uint64_t dest_offset;
108 uint64_t bytes_deduped;
109 int32_t status;
110 uint32_t reserved;
111 } cf_file_dedupe_range_info_t;
112 typedef struct {
113 uint64_t src_offset;
114 uint64_t src_length;
115 uint16_t dest_count;
116 uint16_t reserved1;
117 uint32_t reserved2;
118 cf_file_dedupe_range_info_t info[0];
119 } cf_file_dedupe_range_t;
120 #define CF_FIDEDUPERANGE _IOWR(0x94, 54, cf_file_dedupe_range_t)
121 #define CF_FILE_DEDUPE_RANGE_SAME (0)
122 #define CF_FILE_DEDUPE_RANGE_DIFFERS (1)
123 #endif
124
125 typedef enum {
126 CF_MODE_NONE,
127 CF_MODE_CLONE,
128 CF_MODE_CLONERANGE,
129 CF_MODE_COPYFILERANGE,
130 CF_MODE_DEDUPERANGE,
131 } cf_mode_t;
132
133 static int
134 usage(void)
135 {
136 printf(
137 "usage:\n"
138 " FICLONE:\n"
139 " clonefile -c <src> <dst>\n"
140 " FICLONERANGE:\n"
141 " clonefile -r <src> <dst> <soff> <doff> <len>\n"
142 " copy_file_range:\n"
143 " clonefile -f <src> <dst> <soff> <doff> <len>\n"
144 " FIDEDUPERANGE:\n"
145 " clonefile -d <src> <dst> <soff> <doff> <len>\n");
146 return (1);
147 }
148
149 int do_clone(int sfd, int dfd);
150 int do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len);
151 int do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len);
152 int do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len);
153
154 int quiet = 0;
155
156 int
157 main(int argc, char **argv)
158 {
159 cf_mode_t mode = CF_MODE_NONE;
160
161 char c;
162 while ((c = getopt(argc, argv, "crfdq")) != -1) {
163 switch (c) {
164 case 'c':
165 mode = CF_MODE_CLONE;
166 break;
167 case 'r':
168 mode = CF_MODE_CLONERANGE;
169 break;
170 case 'f':
171 mode = CF_MODE_COPYFILERANGE;
172 break;
173 case 'd':
174 mode = CF_MODE_DEDUPERANGE;
175 break;
176 case 'q':
177 quiet = 1;
178 break;
179 }
180 }
181
182 if (mode == CF_MODE_NONE || (argc-optind) < 2 ||
183 (mode != CF_MODE_CLONE && (argc-optind) < 5))
184 return (usage());
185
186 loff_t soff = 0, doff = 0;
187 size_t len = 0;
188 if (mode != CF_MODE_CLONE) {
189 soff = strtoull(argv[optind+2], NULL, 10);
190 if (soff == ULLONG_MAX) {
191 fprintf(stderr, "invalid source offset");
192 return (1);
193 }
194 doff = strtoull(argv[optind+3], NULL, 10);
195 if (doff == ULLONG_MAX) {
196 fprintf(stderr, "invalid dest offset");
197 return (1);
198 }
199 len = strtoull(argv[optind+4], NULL, 10);
200 if (len == ULLONG_MAX) {
201 fprintf(stderr, "invalid length");
202 return (1);
203 }
204 }
205
206 int sfd = open(argv[optind], O_RDONLY);
207 if (sfd < 0) {
208 fprintf(stderr, "open: %s: %s\n",
209 argv[optind], strerror(errno));
210 return (1);
211 }
212
213 int dfd = open(argv[optind+1], O_WRONLY|O_CREAT,
214 S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
215 if (sfd < 0) {
216 fprintf(stderr, "open: %s: %s\n",
217 argv[optind+1], strerror(errno));
218 close(sfd);
219 return (1);
220 }
221
222 int err;
223 switch (mode) {
224 case CF_MODE_CLONE:
225 err = do_clone(sfd, dfd);
226 break;
227 case CF_MODE_CLONERANGE:
228 err = do_clonerange(sfd, dfd, soff, doff, len);
229 break;
230 case CF_MODE_COPYFILERANGE:
231 err = do_copyfilerange(sfd, dfd, soff, doff, len);
232 break;
233 case CF_MODE_DEDUPERANGE:
234 err = do_deduperange(sfd, dfd, soff, doff, len);
235 break;
236 default:
237 abort();
238 }
239
240 off_t spos = lseek(sfd, 0, SEEK_CUR);
241 off_t slen = lseek(sfd, 0, SEEK_END);
242 off_t dpos = lseek(dfd, 0, SEEK_CUR);
243 off_t dlen = lseek(dfd, 0, SEEK_END);
244
245 fprintf(stderr, "file offsets: src=%lu/%lu; dst=%lu/%lu\n", spos, slen,
246 dpos, dlen);
247
248 close(dfd);
249 close(sfd);
250
251 return (err == 0 ? 0 : 1);
252 }
253
254 int
255 do_clone(int sfd, int dfd)
256 {
257 fprintf(stderr, "using FICLONE\n");
258 int err = ioctl(dfd, CF_FICLONE, sfd);
259 if (err < 0) {
260 fprintf(stderr, "ioctl(FICLONE): %s\n", strerror(errno));
261 return (err);
262 }
263 return (0);
264 }
265
266 int
267 do_clonerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len)
268 {
269 fprintf(stderr, "using FICLONERANGE\n");
270 cf_file_clone_range_t fcr = {
271 .src_fd = sfd,
272 .src_offset = soff,
273 .src_length = len,
274 .dest_offset = doff,
275 };
276 int err = ioctl(dfd, CF_FICLONERANGE, &fcr);
277 if (err < 0) {
278 fprintf(stderr, "ioctl(FICLONERANGE): %s\n", strerror(errno));
279 return (err);
280 }
281 return (0);
282 }
283
284 int
285 do_copyfilerange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len)
286 {
287 fprintf(stderr, "using copy_file_range\n");
288 ssize_t copied = cf_copy_file_range(sfd, &soff, dfd, &doff, len, 0);
289 if (copied < 0) {
290 fprintf(stderr, "copy_file_range: %s\n", strerror(errno));
291 return (1);
292 }
293 if (copied != len) {
294 fprintf(stderr, "copy_file_range: copied less than requested: "
295 "requested=%lu; copied=%lu\n", len, copied);
296 return (1);
297 }
298 return (0);
299 }
300
301 int
302 do_deduperange(int sfd, int dfd, loff_t soff, loff_t doff, size_t len)
303 {
304 fprintf(stderr, "using FIDEDUPERANGE\n");
305
306 char buf[sizeof (cf_file_dedupe_range_t)+
307 sizeof (cf_file_dedupe_range_info_t)] = {0};
308 cf_file_dedupe_range_t *fdr = (cf_file_dedupe_range_t *)&buf[0];
309 cf_file_dedupe_range_info_t *fdri =
310 (cf_file_dedupe_range_info_t *)
311 &buf[sizeof (cf_file_dedupe_range_t)];
312
313 fdr->src_offset = soff;
314 fdr->src_length = len;
315 fdr->dest_count = 1;
316
317 fdri->dest_fd = dfd;
318 fdri->dest_offset = doff;
319
320 int err = ioctl(sfd, CF_FIDEDUPERANGE, fdr);
321 if (err != 0)
322 fprintf(stderr, "ioctl(FIDEDUPERANGE): %s\n", strerror(errno));
323
324 if (fdri->status < 0) {
325 fprintf(stderr, "dedup failed: %s\n", strerror(-fdri->status));
326 err = -1;
327 } else if (fdri->status == CF_FILE_DEDUPE_RANGE_DIFFERS) {
328 fprintf(stderr, "dedup failed: range differs\n");
329 err = -1;
330 }
331
332 return (err);
333 }