generic: test MADV_POPULATE_READ with IO errors
[xfstests-dev.git] / src / cloner.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  *  Tiny program to perform file (range) clones using raw Btrfs and CIFS ioctls.
4  *  Copyright (C) 2014 SUSE Linux Products GmbH. All Rights Reserved.
5  */
6
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <sys/ioctl.h>
10 #include <sys/vfs.h>
11 #include <stdint.h>
12 #include <stdbool.h>
13 #include <fcntl.h>
14 #include <unistd.h>
15 #include <stdlib.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <linux/magic.h>
20 #ifdef HAVE_BTRFS_IOCTL_H
21 #include <btrfs/ioctl.h>
22 #else
23
24 struct btrfs_ioctl_clone_range_args {
25         int64_t src_fd;
26         uint64_t src_offset;
27         uint64_t src_length;
28         uint64_t dest_offset;
29 };
30
31 #define BTRFS_IOCTL_MAGIC 0x94
32 #define BTRFS_IOC_CLONE       _IOW(BTRFS_IOCTL_MAGIC, 9, int)
33 #define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \
34                                    struct btrfs_ioctl_clone_range_args)
35 #endif
36
37 #ifdef HAVE_CIFS_IOCTL_H
38 #include <cifs/ioctl.h>
39 #else
40
41 #define CIFS_IOCTL_MAGIC 0xCF
42 #define CIFS_IOC_COPYCHUNK_FILE _IOW(CIFS_IOCTL_MAGIC, 3, int)
43
44 #endif
45
46 #ifndef BTRFS_SUPER_MAGIC
47 #define BTRFS_SUPER_MAGIC    0x9123683E
48 #endif
49 #ifndef CIFS_MAGIC_NUMBER
50 #define CIFS_MAGIC_NUMBER    0xFE534D42
51 #endif
52
53 static void
54 usage(char *name, const char *msg)
55 {
56         printf("Fatal: %s\n"
57                "Usage:\n"
58                "%s [options] <src_file> <dest_file>\n"
59                "\tA full file clone is performed by default, "
60                "unless any of the following are specified (Btrfs only):\n"
61                "\t-s <offset>:  source file offset (default = 0)\n"
62                "\t-d <offset>:  destination file offset (default = 0)\n"
63                "\t-l <length>:  length of clone (default = 0)\n\n"
64                "\tBoth Btrfs and CIFS are supported. On Btrfs, a COW clone "
65                "is attempted. On CIFS, a server-side copy is requested.\n",
66                msg, name);
67         _exit(1);
68 }
69
70 static int
71 clone_file_btrfs(int src_fd, int dst_fd)
72 {
73         int ret = ioctl(dst_fd, BTRFS_IOC_CLONE, src_fd);
74         if (ret != 0)
75                 ret = errno;
76         return ret;
77 }
78
79 static int
80 clone_file_cifs(int src_fd, int dst_fd)
81 {
82         int ret = ioctl(dst_fd, CIFS_IOC_COPYCHUNK_FILE, src_fd);
83         if (ret != 0)
84                 ret = errno;
85         return ret;
86 }
87
88 static int
89 clone_file(unsigned int fs_type, int src_fd, int dst_fd)
90 {
91         switch (fs_type) {
92         case BTRFS_SUPER_MAGIC:
93                 return clone_file_btrfs(src_fd, dst_fd);
94                 break;
95         case CIFS_MAGIC_NUMBER:
96                 return clone_file_cifs(src_fd, dst_fd);
97                 break;
98         default:
99                 return ENOTSUP;
100                 break;
101         }
102 }
103
104 static int
105 clone_file_range_btrfs(int src_fd, int dst_fd, uint64_t src_off,
106                        uint64_t dst_off, uint64_t len)
107 {
108         struct btrfs_ioctl_clone_range_args cr_args;
109         int ret;
110
111         memset(&cr_args, 0, sizeof(cr_args));
112         cr_args.src_fd = src_fd;
113         cr_args.src_offset = src_off;
114         cr_args.src_length = len;
115         cr_args.dest_offset = dst_off;
116         ret = ioctl(dst_fd, BTRFS_IOC_CLONE_RANGE, &cr_args);
117         if (ret != 0)
118                 ret = errno;
119         return ret;
120 }
121
122 static int
123 clone_file_range(unsigned int fs_type, int src_fd, int dst_fd, uint64_t src_off,
124                  uint64_t dst_off, uint64_t len)
125 {
126         switch (fs_type) {
127         case BTRFS_SUPER_MAGIC:
128                 return clone_file_range_btrfs(src_fd, dst_fd, src_off, dst_off,
129                                               len);
130                 break;
131         case CIFS_MAGIC_NUMBER: /* only supports full file server-side copies */
132         default:
133                 return ENOTSUP;
134                 break;
135         }
136 }
137
138 static int
139 cloner_check_fs_support(int src_fd, int dest_fd, unsigned int *fs_type)
140 {
141         int ret;
142         struct statfs sfs;
143
144         ret = fstatfs(src_fd, &sfs);
145         if (ret != 0) {
146                 printf("failed to stat source FS\n");
147                 return errno;
148         }
149
150         if ((sfs.f_type != BTRFS_SUPER_MAGIC)
151          && (sfs.f_type != CIFS_MAGIC_NUMBER)) {
152                 printf("unsupported source FS 0x%x\n",
153                        (unsigned int)sfs.f_type);
154                 return ENOTSUP;
155         }
156
157         *fs_type = (unsigned int)sfs.f_type;
158
159         ret = fstatfs(dest_fd, &sfs);
160         if (ret != 0) {
161                 printf("failed to stat destination FS\n");
162                 return errno;
163         }
164
165         if (sfs.f_type != *fs_type) {
166                 printf("dest FS type 0x%x does not match source 0x%x\n",
167                        (unsigned int)sfs.f_type, *fs_type);
168                 return ENOTSUP;
169         }
170
171         return 0;
172 }
173
174 int
175 main(int argc, char **argv)
176 {
177         bool full_file = true;
178         uint64_t src_off = 0;
179         uint64_t dst_off = 0;
180         uint64_t len = 0;
181         char *src_file;
182         int src_fd;
183         char *dst_file;
184         int dst_fd;
185         int ret;
186         int opt;
187         unsigned int fs_type = 0;
188
189         while ((opt = getopt(argc, argv, "s:d:l:")) != -1) {
190                 char *sval_end;
191                 switch (opt) {
192                 case 's':
193                         errno = 0;
194                         src_off = strtoull(optarg, &sval_end, 10);
195                         if ((errno) || (*sval_end != '\0'))
196                                 usage(argv[0], "invalid source offset");
197                         full_file = false;
198                         break;
199                 case 'd':
200                         errno = 0;
201                         dst_off = strtoull(optarg, &sval_end, 10);
202                         if ((errno) || (*sval_end != '\0'))
203                                 usage(argv[0], "invalid destination offset");
204                         full_file = false;
205                         break;
206                 case 'l':
207                         errno = 0;
208                         len = strtoull(optarg, &sval_end, 10);
209                         if ((errno) || (*sval_end != '\0'))
210                                 usage(argv[0], "invalid length");
211                         full_file = false;
212                         break;
213                 default:
214                         usage(argv[0], "invalid argument");
215                 }
216         }
217
218         /* should be exactly two args left */
219         if (optind != argc - 2)
220                 usage(argv[0], "src_file and dst_file arguments are madatory");
221
222         src_file = (char *)strdup(argv[optind++]);
223         if (src_file == NULL) {
224                 ret = ENOMEM;
225                 printf("no memory\n");
226                 goto err_out;
227         }
228         dst_file = (char *)strdup(argv[optind++]);
229         if (dst_file == NULL) {
230                 ret = ENOMEM;
231                 printf("no memory\n");
232                 goto err_src_free;
233         }
234
235         src_fd = open(src_file, O_RDONLY);
236         if (src_fd == -1) {
237                 ret = errno;
238                 printf("failed to open %s: %s\n", src_file, strerror(errno));
239                 goto err_dst_free;
240         }
241         dst_fd = open(dst_file, O_CREAT | O_WRONLY, 0644);
242         if (dst_fd == -1) {
243                 ret = errno;
244                 printf("failed to open %s: %s\n", dst_file, strerror(errno));
245                 goto err_src_close;
246         }
247
248         ret = cloner_check_fs_support(src_fd, dst_fd, &fs_type);
249         if (ret != 0) {
250                 goto err_dst_close;
251         }
252
253         if (full_file) {
254                 ret = clone_file(fs_type, src_fd, dst_fd);
255         } else {
256                 ret = clone_file_range(fs_type, src_fd, dst_fd, src_off,
257                                        dst_off, len);
258         }
259         if (ret != 0) {
260                 printf("clone failed: %s\n", strerror(ret));
261                 goto err_dst_close;
262         }
263
264         ret = 0;
265 err_dst_close:
266         if (close(dst_fd)) {
267                 ret |= errno;
268                 printf("failed to close dst file: %s\n", strerror(errno));
269         }
270 err_src_close:
271         if (close(src_fd)) {
272                 ret |= errno;
273                 printf("failed to close src file: %s\n", strerror(errno));
274         }
275 err_dst_free:
276         free(dst_file);
277 err_src_free:
278         free(src_file);
279 err_out:
280         return ret;
281 }