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 }