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 }