1 /**
2  * BTRFS_IOC_FILE_EXTENT_SAME.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.sys.btrfs.extent_same;
15 
16 version(linux):
17 
18 import core.stdc.errno;
19 import core.sys.posix.sys.ioctl;
20 
21 import std.conv : to;
22 import std.exception;
23 import std.stdio : File;
24 import std.string : format;
25 
26 import ae.sys.btrfs.common;
27 
28 private:
29 
30 enum BTRFS_IOC_FILE_EXTENT_SAME = _IOWR!btrfs_ioctl_same_args(BTRFS_IOCTL_MAGIC, 54);
31 
32 enum BTRFS_SAME_DATA_DIFFERS = 1;
33 
34 /* For extent-same ioctl */
35 struct btrfs_ioctl_same_extent_info
36 {
37 	long fd;						/* in - destination file */
38 	ulong logical_offset;			/* in - start of extent in destination */
39 	ulong bytes_deduped;			/* out - total # of bytes we
40 									 * were able to dedupe from
41 									 * this file */
42 	/* status of this dedupe operation:
43 	 * 0 if dedup succeeds
44 	 * < 0 for error
45 	 * == BTRFS_SAME_DATA_DIFFERS if data differs
46 	 */
47 	int status;						/* out - see above description */
48 	uint reserved;
49 }
50 
51 struct btrfs_ioctl_same_args
52 {
53 	ulong logical_offset;		/* in - start of extent in source */
54 	ulong length;				/* in - length of extent */
55 	ushort dest_count;			/* in - total elements in info array */
56 								/* out - number of files that got deduped */
57 	ushort reserved1;
58 	uint reserved2;
59 	btrfs_ioctl_same_extent_info[0] info;
60 }
61 
62 public:
63 
64 struct Extent
65 {
66 	File file;
67 	ulong offset;
68 }
69 
70 void sameExtent(in Extent[] extents, ulong length)
71 {
72 	assert(extents.length >= 2, "Need at least 2 extents to deduplicate");
73 
74 	auto buf = new ubyte[
75 		      btrfs_ioctl_same_args.sizeof +
76 		      btrfs_ioctl_same_extent_info.sizeof * extents.length];
77 	auto same = cast(btrfs_ioctl_same_args*) buf.ptr;
78 
79 	same.length = length;
80 	same.logical_offset = extents[0].offset;
81 	same.dest_count = (extents.length - 1).to!ushort;
82 
83 	foreach (i, ref extent; extents[1..$])
84 	{
85 		same.info.ptr[i].fd = extent.file.fileno;
86 		same.info.ptr[i].logical_offset = extent.offset;
87 		same.info.ptr[i].status = -1;
88 	}
89 
90 	int ret = ioctl(extents[0].file.fileno, BTRFS_IOC_FILE_EXTENT_SAME, same);
91 	errnoEnforce(ret >= 0, "ioctl(BTRFS_IOC_FILE_EXTENT_SAME)");
92 
93 	foreach (i, ref extent; extents[1..$])
94 	{
95 		auto status = same.info.ptr[i].status;
96 		if (status)
97 		{
98 			enforce(status != BTRFS_SAME_DATA_DIFFERS,
99 				"Extent #%d differs".format(i+1));
100 			errno = -status;
101 			errnoEnforce(false,
102 				"Deduplicating extent #%d returned status %d".format(i+1, status));
103 		}
104 	}
105 }
106 
107 unittest
108 {
109 	if (!checkBtrfs())
110 		return;
111 	import std.range, std.random, std.algorithm, std.file;
112 	enum blockSize = 16*1024; // TODO: detect
113 	auto data = blockSize.iota.map!(n => uniform!ubyte).array();
114 	std.file.write("test1.bin", data);
115 	scope(exit) remove("test1.bin");
116 	std.file.write("test2.bin", data);
117 	scope(exit) remove("test2.bin");
118 
119 	sameExtent([
120 		Extent(File("test1.bin", "r+b"), 0),
121 		Extent(File("test2.bin", "r+b"), 0),
122 	], blockSize);
123 
124 	data[0]++;
125 	std.file.write("test2.bin", data);
126 	assertThrown!Exception(sameExtent([
127 		Extent(File("test1.bin", "r+b"), 0),
128 		Extent(File("test2.bin", "r+b"), 0),
129 	], blockSize));
130 }