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 struct SameExtentResult
71 {
72 	ulong totalBytesDeduped;
73 }
74 
75 SameExtentResult sameExtent(in Extent[] extents, ulong length)
76 {
77 	assert(extents.length >= 2, "Need at least 2 extents to deduplicate");
78 
79 	auto buf = new ubyte[
80 		      btrfs_ioctl_same_args.sizeof +
81 		      btrfs_ioctl_same_extent_info.sizeof * extents.length];
82 	auto same = cast(btrfs_ioctl_same_args*) buf.ptr;
83 
84 	same.length = length;
85 	same.logical_offset = extents[0].offset;
86 	same.dest_count = (extents.length - 1).to!ushort;
87 
88 	foreach (i, ref extent; extents[1..$])
89 	{
90 		same.info.ptr[i].fd = extent.file.fileno;
91 		same.info.ptr[i].logical_offset = extent.offset;
92 		same.info.ptr[i].status = -1;
93 	}
94 
95 	int ret = ioctl(extents[0].file.fileno, BTRFS_IOC_FILE_EXTENT_SAME, same);
96 	errnoEnforce(ret >= 0, "ioctl(BTRFS_IOC_FILE_EXTENT_SAME)");
97 
98 	SameExtentResult result;
99 
100 	foreach (i, ref extent; extents[1..$])
101 	{
102 		auto status = same.info.ptr[i].status;
103 		if (status)
104 		{
105 			enforce(status != BTRFS_SAME_DATA_DIFFERS,
106 				"Extent #%d differs".format(i+1));
107 			errno = -status;
108 			errnoEnforce(false,
109 				"Deduplicating extent #%d returned status %d".format(i+1, status));
110 		}
111 		result.totalBytesDeduped += same.info.ptr[i].bytes_deduped;
112 	}
113 
114 	return result;
115 }
116 
117 unittest
118 {
119 	if (!checkBtrfs())
120 		return;
121 	import std.range, std.random, std.algorithm, std.file;
122 	enum blockSize = 16*1024; // TODO: detect
123 	auto data = blockSize.iota.map!(n => uniform!ubyte).array();
124 	std.file.write("test1.bin", data);
125 	scope(exit) remove("test1.bin");
126 	std.file.write("test2.bin", data);
127 	scope(exit) remove("test2.bin");
128 
129 	sameExtent([
130 		Extent(File("test1.bin", "r+b"), 0),
131 		Extent(File("test2.bin", "r+b"), 0),
132 	], blockSize);
133 
134 	data[0]++;
135 	std.file.write("test2.bin", data);
136 	assertThrown!Exception(sameExtent([
137 		Extent(File("test1.bin", "r+b"), 0),
138 		Extent(File("test2.bin", "r+b"), 0),
139 	], blockSize));
140 }