summaryrefslogtreecommitdiff
path: root/src/cloner.c
blob: ffad82f000b8dac9ef2fa04570aae896c841655a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
// SPDX-License-Identifier: GPL-2.0+
/*
 *  Tiny program to perform file (range) clones using raw Btrfs and CIFS ioctls.
 *  Copyright (C) 2014 SUSE Linux Products GmbH. All Rights Reserved.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/vfs.h>
#include <stdint.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <linux/magic.h>
#ifdef HAVE_BTRFS_IOCTL_H
#include <btrfs/ioctl.h>
#else

struct btrfs_ioctl_clone_range_args {
	int64_t src_fd;
	uint64_t src_offset;
	uint64_t src_length;
	uint64_t dest_offset;
};

#define BTRFS_IOCTL_MAGIC 0x94
#define BTRFS_IOC_CLONE       _IOW(BTRFS_IOCTL_MAGIC, 9, int)
#define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \
				   struct btrfs_ioctl_clone_range_args)
#endif

#ifdef HAVE_CIFS_IOCTL_H
#include <cifs/ioctl.h>
#else

#define CIFS_IOCTL_MAGIC 0xCF
#define CIFS_IOC_COPYCHUNK_FILE _IOW(CIFS_IOCTL_MAGIC, 3, int)

#endif

#ifndef BTRFS_SUPER_MAGIC
#define BTRFS_SUPER_MAGIC    0x9123683E
#endif
#ifndef CIFS_MAGIC_NUMBER
#define CIFS_MAGIC_NUMBER    0xFE534D42
#endif

static void
usage(char *name, const char *msg)
{
	printf("Fatal: %s\n"
	       "Usage:\n"
	       "%s [options] <src_file> <dest_file>\n"
	       "\tA full file clone is performed by default, "
	       "unless any of the following are specified (Btrfs only):\n"
	       "\t-s <offset>:	source file offset (default = 0)\n"
	       "\t-d <offset>:	destination file offset (default = 0)\n"
	       "\t-l <length>:	length of clone (default = 0)\n\n"
	       "\tBoth Btrfs and CIFS are supported. On Btrfs, a COW clone "
	       "is attempted. On CIFS, a server-side copy is requested.\n",
	       msg, name);
	_exit(1);
}

static int
clone_file_btrfs(int src_fd, int dst_fd)
{
	int ret = ioctl(dst_fd, BTRFS_IOC_CLONE, src_fd);
	if (ret != 0)
		ret = errno;
	return ret;
}

static int
clone_file_cifs(int src_fd, int dst_fd)
{
	int ret = ioctl(dst_fd, CIFS_IOC_COPYCHUNK_FILE, src_fd);
	if (ret != 0)
		ret = errno;
	return ret;
}

static int
clone_file(unsigned int fs_type, int src_fd, int dst_fd)
{
	switch (fs_type) {
	case BTRFS_SUPER_MAGIC:
		return clone_file_btrfs(src_fd, dst_fd);
		break;
	case CIFS_MAGIC_NUMBER:
		return clone_file_cifs(src_fd, dst_fd);
		break;
	default:
		return ENOTSUP;
		break;
	}
}

static int
clone_file_range_btrfs(int src_fd, int dst_fd, uint64_t src_off,
		       uint64_t dst_off, uint64_t len)
{
	struct btrfs_ioctl_clone_range_args cr_args;
	int ret;

	memset(&cr_args, 0, sizeof(cr_args));
	cr_args.src_fd = src_fd;
	cr_args.src_offset = src_off;
	cr_args.src_length = len;
	cr_args.dest_offset = dst_off;
	ret = ioctl(dst_fd, BTRFS_IOC_CLONE_RANGE, &cr_args);
	if (ret != 0)
		ret = errno;
	return ret;
}

static int
clone_file_range(unsigned int fs_type, int src_fd, int dst_fd, uint64_t src_off,
		 uint64_t dst_off, uint64_t len)
{
	switch (fs_type) {
	case BTRFS_SUPER_MAGIC:
		return clone_file_range_btrfs(src_fd, dst_fd, src_off, dst_off,
					      len);
		break;
	case CIFS_MAGIC_NUMBER:	/* only supports full file server-side copies */
	default:
		return ENOTSUP;
		break;
	}
}

static int
cloner_check_fs_support(int src_fd, int dest_fd, unsigned int *fs_type)
{
	int ret;
	struct statfs sfs;

	ret = fstatfs(src_fd, &sfs);
	if (ret != 0) {
		printf("failed to stat source FS\n");
		return errno;
	}

	if ((sfs.f_type != BTRFS_SUPER_MAGIC)
	 && (sfs.f_type != CIFS_MAGIC_NUMBER)) {
		printf("unsupported source FS 0x%x\n",
		       (unsigned int)sfs.f_type);
		return ENOTSUP;
	}

	*fs_type = (unsigned int)sfs.f_type;

	ret = fstatfs(dest_fd, &sfs);
	if (ret != 0) {
		printf("failed to stat destination FS\n");
		return errno;
	}

	if (sfs.f_type != *fs_type) {
		printf("dest FS type 0x%x does not match source 0x%x\n",
		       (unsigned int)sfs.f_type, *fs_type);
		return ENOTSUP;
	}

	return 0;
}

int
main(int argc, char **argv)
{
	bool full_file = true;
	uint64_t src_off = 0;
	uint64_t dst_off = 0;
	uint64_t len = 0;
	char *src_file;
	int src_fd;
	char *dst_file;
	int dst_fd;
	int ret;
	int opt;
	unsigned int fs_type = 0;

	while ((opt = getopt(argc, argv, "s:d:l:")) != -1) {
		char *sval_end;
		switch (opt) {
		case 's':
			errno = 0;
			src_off = strtoull(optarg, &sval_end, 10);
			if ((errno) || (*sval_end != '\0'))
				usage(argv[0], "invalid source offset");
			full_file = false;
			break;
		case 'd':
			errno = 0;
			dst_off = strtoull(optarg, &sval_end, 10);
			if ((errno) || (*sval_end != '\0'))
				usage(argv[0], "invalid destination offset");
			full_file = false;
			break;
		case 'l':
			errno = 0;
			len = strtoull(optarg, &sval_end, 10);
			if ((errno) || (*sval_end != '\0'))
				usage(argv[0], "invalid length");
			full_file = false;
			break;
		default:
			usage(argv[0], "invalid argument");
		}
	}

	/* should be exactly two args left */
	if (optind != argc - 2)
		usage(argv[0], "src_file and dst_file arguments are madatory");

	src_file = (char *)strdup(argv[optind++]);
	if (src_file == NULL) {
		ret = ENOMEM;
		printf("no memory\n");
		goto err_out;
	}
	dst_file = (char *)strdup(argv[optind++]);
	if (dst_file == NULL) {
		ret = ENOMEM;
		printf("no memory\n");
		goto err_src_free;
	}

	src_fd = open(src_file, O_RDONLY);
	if (src_fd == -1) {
		ret = errno;
		printf("failed to open %s: %s\n", src_file, strerror(errno));
		goto err_dst_free;
	}
	dst_fd = open(dst_file, O_CREAT | O_WRONLY, 0644);
	if (dst_fd == -1) {
		ret = errno;
		printf("failed to open %s: %s\n", dst_file, strerror(errno));
		goto err_src_close;
	}

	ret = cloner_check_fs_support(src_fd, dst_fd, &fs_type);
	if (ret != 0) {
		goto err_dst_close;
	}

	if (full_file) {
		ret = clone_file(fs_type, src_fd, dst_fd);
	} else {
		ret = clone_file_range(fs_type, src_fd, dst_fd, src_off,
				       dst_off, len);
	}
	if (ret != 0) {
		printf("clone failed: %s\n", strerror(ret));
		goto err_dst_close;
	}

	ret = 0;
err_dst_close:
	if (close(dst_fd)) {
		ret |= errno;
		printf("failed to close dst file: %s\n", strerror(errno));
	}
err_src_close:
	if (close(src_fd)) {
		ret |= errno;
		printf("failed to close src file: %s\n", strerror(errno));
	}
err_dst_free:
	free(dst_file);
err_src_free:
	free(src_file);
err_out:
	return ret;
}