btrfs: add small program for clone testing
[xfstests-dev.git] / src / cloner.c
1 /*
2  *  Tiny program to perform file (range) clones using raw Btrfs ioctls.
3  *  It should only be needed until btrfs-progs has an xfs_io equivalent.
4  *
5  *  Copyright (C) 2014 SUSE Linux Products GmbH. All Rights Reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20  */
21
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <sys/ioctl.h>
25 #include <stdint.h>
26 #include <stdbool.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <errno.h>
33
34 struct btrfs_ioctl_clone_range_args {
35         int64_t src_fd;
36         uint64_t src_offset;
37         uint64_t src_length;
38         uint64_t dest_offset;
39 };
40
41 #define BTRFS_IOCTL_MAGIC 0x94
42 #define BTRFS_IOC_CLONE       _IOW(BTRFS_IOCTL_MAGIC, 9, int)
43 #define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \
44                                    struct btrfs_ioctl_clone_range_args)
45
46 static void
47 usage(char *name, const char *msg)
48 {
49         printf("Fatal: %s\n"
50                "Usage:\n"
51                "%s [options] <src_file> <dest_file>\n"
52                "\tA full file clone (reflink) is performed by default, "
53                "unless any of the following are specified:\n"
54                "\t-s <offset>:  source file offset (default = 0)\n"
55                "\t-d <offset>:  destination file offset (default = 0)\n"
56                "\t-l <length>:  length of clone (default = 0)\n",
57                msg, name);
58         _exit(1);
59 }
60
61 static int
62 clone_file(int src_fd, int dst_fd)
63 {
64         int ret = ioctl(dst_fd, BTRFS_IOC_CLONE, src_fd);
65         if (ret != 0)
66                 ret = errno;
67         return ret;
68 }
69
70 static int
71 clone_file_range(int src_fd, int dst_fd, uint64_t src_off, uint64_t dst_off,
72                  uint64_t len)
73 {
74         struct btrfs_ioctl_clone_range_args cr_args;
75         int ret;
76
77         memset(&cr_args, 0, sizeof(cr_args));
78         cr_args.src_fd = src_fd;
79         cr_args.src_offset = src_off;
80         cr_args.src_length = len;
81         cr_args.dest_offset = dst_off;
82         ret = ioctl(dst_fd, BTRFS_IOC_CLONE_RANGE, &cr_args);
83         if (ret != 0)
84                 ret = errno;
85         return ret;
86 }
87
88 int
89 main(int argc, char **argv)
90 {
91         bool full_file = true;
92         uint64_t src_off = 0;
93         uint64_t dst_off = 0;
94         uint64_t len = 0;
95         char *src_file;
96         int src_fd;
97         char *dst_file;
98         int dst_fd;
99         int ret;
100         int opt;
101
102         while ((opt = getopt(argc, argv, "s:d:l:")) != -1) {
103                 char *sval_end;
104                 switch (opt) {
105                 case 's':
106                         errno = 0;
107                         src_off = strtoull(optarg, &sval_end, 10);
108                         if ((errno) || (*sval_end != '\0'))
109                                 usage(argv[0], "invalid source offset");
110                         full_file = false;
111                         break;
112                 case 'd':
113                         errno = 0;
114                         dst_off = strtoull(optarg, &sval_end, 10);
115                         if ((errno) || (*sval_end != '\0'))
116                                 usage(argv[0], "invalid destination offset");
117                         full_file = false;
118                         break;
119                 case 'l':
120                         errno = 0;
121                         len = strtoull(optarg, &sval_end, 10);
122                         if ((errno) || (*sval_end != '\0'))
123                                 usage(argv[0], "invalid length");
124                         full_file = false;
125                         break;
126                 default:
127                         usage(argv[0], "invalid argument");
128                 }
129         }
130
131         /* should be exactly two args left */
132         if (optind != argc - 2)
133                 usage(argv[0], "src_file and dst_file arguments are madatory");
134
135         src_file = (char *)strdup(argv[optind++]);
136         if (src_file == NULL) {
137                 ret = ENOMEM;
138                 printf("no memory\n");
139                 goto err_out;
140         }
141         dst_file = (char *)strdup(argv[optind++]);
142         if (dst_file == NULL) {
143                 ret = ENOMEM;
144                 printf("no memory\n");
145                 goto err_src_free;
146         }
147
148         src_fd = open(src_file, O_RDONLY);
149         if (src_fd == -1) {
150                 ret = errno;
151                 printf("failed to open %s: %s\n", src_file, strerror(errno));
152                 goto err_dst_free;
153         }
154         dst_fd = open(dst_file, O_CREAT | O_WRONLY, 0644);
155         if (dst_fd == -1) {
156                 ret = errno;
157                 printf("failed to open %s: %s\n", dst_file, strerror(errno));
158                 goto err_src_close;
159         }
160
161         if (full_file) {
162                 ret = clone_file(src_fd, dst_fd);
163         } else {
164                 ret = clone_file_range(src_fd, dst_fd, src_off, dst_off, len);
165         }
166         if (ret != 0) {
167                 printf("clone failed: %s\n", strerror(ret));
168                 goto err_dst_close;
169         }
170
171         ret = 0;
172 err_dst_close:
173         if (close(dst_fd)) {
174                 ret |= errno;
175                 printf("failed to close dst file: %s\n", strerror(errno));
176         }
177 err_src_close:
178         if (close(src_fd)) {
179                 ret |= errno;
180                 printf("failed to close src file: %s\n", strerror(errno));
181         }
182 err_dst_free:
183         free(dst_file);
184 err_src_free:
185         free(src_file);
186 err_out:
187         return ret;
188 }