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