1 module btrfs; 2 3 import core.sys.posix.sys.ioctl; 4 5 import std.algorithm.comparison; 6 import std.exception; 7 import std.math; 8 import std.string; 9 10 import ae.utils.bitmanip; 11 import ae.utils.math : eq; 12 13 import btrfs.c.ioctl; 14 import btrfs.c.kerncompat; 15 import btrfs.c.kernel_shared.ctree; 16 import btrfs.c.kernel_lib.sizes; 17 18 enum __u64[2] treeSearchAllObjectIDs = [0, -1]; 19 enum __u64[2] treeSearchAllOffsets = [0, -1]; 20 enum __u64[2] treeSearchAllTransIDs = [0, -1]; 21 22 /// Raw tree search 23 void treeSearch( 24 /// Handle to the filesystem 25 int fd, 26 /// Tree to search in 27 __u64 treeID, 28 /// Min and max (inclusive) object IDs to search 29 __u64[2] objectIDs, 30 /// Min and max (inclusive) types to search 31 __u8[2] types, 32 /// Min and max (inclusive) offsets to search 33 __u64[2] offsets, 34 /// Min and max (inclusive) transaction IDs to search 35 __u64[2] transIDs, 36 /// Callback receiving the search results 37 scope void delegate( 38 /// Search result header, containing the object and transaction ID, offset and type 39 const ref btrfs_ioctl_search_header header, 40 /// Raw data - must be cast to the correct type 41 const void[] data, 42 ) callback, 43 ) 44 { 45 // Lexicographic ordering for tree search 46 static union Key 47 { 48 struct Fields 49 { 50 align(1): 51 BigEndian!__u64 objectID; 52 BigEndian!__u8 type; 53 BigEndian!__u64 offset; 54 } 55 static assert(Fields.sizeof == 0x11); 56 57 Fields fields; 58 ubyte[Fields.sizeof] bytes; 59 60 this(__u64 objectID, __u8 type, __u64 offset) 61 { 62 fields = Fields( 63 BigEndian!__u64(objectID), 64 BigEndian!__u8 (type), 65 BigEndian!__u64(offset), 66 ); 67 } 68 69 void opUnary(string op : "++")() 70 { 71 foreach_reverse (ref b; bytes) 72 if (++b != 0) 73 return; // No overflow (otherwise, continue to carry the 1) 74 assert(false, "Search key overflow"); 75 } 76 77 bool opBinary(string op)(const ref Key o) const 78 if (is(typeof(mixin(`0` ~ op ~ `0`)) == bool)) 79 { 80 return mixin(`bytes` ~ op ~ `o.bytes`); 81 } 82 83 int opCmp(const ref Key o) const 84 { 85 return cmp(bytes[], o.bytes[]); 86 } 87 } 88 89 Key min = Key(objectIDs[0], types[0], offsets[0]); 90 Key max = Key(objectIDs[1], types[1], offsets[1]); 91 92 btrfs_ioctl_search_args args; 93 btrfs_ioctl_search_key* sk = &args.key; 94 95 sk.tree_id = treeID; 96 sk.max_objectid = max.fields.objectID; 97 sk.max_type = max.fields.type; 98 sk.max_offset = max.fields.offset; 99 sk.min_transid = transIDs[0]; 100 sk.max_transid = transIDs[1]; 101 sk.nr_items = 4096; 102 103 do 104 { 105 sk.min_objectid = min.fields.objectID; 106 sk.min_type = min.fields.type ; 107 sk.min_offset = min.fields.offset ; 108 ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args).eq(0).errnoEnforce("tree search"); 109 110 if (sk.nr_items == 0) 111 break; 112 113 ulong off = 0; 114 btrfs_ioctl_search_header* sh; 115 foreach (i; 0 .. sk.nr_items) 116 { 117 sh = cast(btrfs_ioctl_search_header *)(args.buf.ptr + off); 118 off += (*sh).sizeof; 119 auto data = (args.buf.ptr + off)[0 .. sh.len]; 120 off += sh.len; 121 122 if ( 123 objectIDs[0] <= sh.objectid && sh.objectid <= objectIDs[1] && 124 types [0] <= sh.type && sh.type <= types [1] && 125 offsets [0] <= sh.offset && sh.offset <= offsets [1] && 126 transIDs [0] <= sh.transid && sh.transid <= transIDs [1] 127 ) 128 callback(*sh, data); 129 } 130 131 assert(sh.type < 256); 132 min.fields.objectID = sh.objectid; 133 min.fields.type = cast(__u8)sh.type; 134 min.fields.offset = sh.offset; 135 min++; 136 } 137 while (min <= max); 138 } 139 140 /// Typed tree search 141 void treeSearch( 142 /// BTRFS type identifier to search for 143 __u8 btrfsType, 144 /// Structure type describing the data 145 Type, 146 )( 147 /// Handle to the filesystem 148 int fd, 149 /// Tree to search in 150 __u64 treeID, 151 /// Min and max (inclusive) object IDs to search 152 __u64[2] objectIDs, 153 /// Min and max (inclusive) offsets to search 154 __u64[2] offsets, 155 /// Min and max (inclusive) transaction IDs to search 156 __u64[2] transIDs, 157 /// Callback receiving the search results 158 scope void delegate( 159 /// Search result header, containing the object and transaction ID, and offset 160 const ref btrfs_ioctl_search_header header, 161 /// Search result data 162 /// For variable-length structures, additional data can be 163 /// accessed by reading past the end of this variable. 164 /// `header.len` indicates the real total size of the data. 165 const ref Type data, 166 ) callback, 167 ) 168 { 169 __u8[2] types = [btrfsType, btrfsType]; 170 treeSearch( 171 fd, treeID, objectIDs, types, offsets, transIDs, 172 (const ref btrfs_ioctl_search_header header, const void[] data) 173 { 174 callback(header, *cast(Type*)data.ptr); 175 } 176 ); 177 } 178 179 /// Enumerate all chunks in the filesystem. 180 void enumerateChunks( 181 /// Handle to the filesystem 182 int fd, 183 /// Result callback 184 scope void delegate( 185 /// Chunk logical address 186 u64 offset, 187 /// Chunk info 188 /// Stripes can be indexed according to num_stripes 189 const ref btrfs_chunk chunk, 190 ) callback, 191 ) 192 { 193 treeSearch!( 194 BTRFS_CHUNK_ITEM_KEY, 195 btrfs_chunk, 196 )( 197 fd, 198 BTRFS_CHUNK_TREE_OBJECTID, 199 treeSearchAllObjectIDs, 200 treeSearchAllOffsets, 201 treeSearchAllTransIDs, 202 (const ref btrfs_ioctl_search_header header, const ref btrfs_chunk chunk) 203 { 204 callback(header.offset, chunk); 205 } 206 ); 207 } 208 209 private ubyte[SZ_64K] logicalInoBuf; 210 211 /// Get inode at this logical offset 212 void logicalIno( 213 /// File descriptor to the root (subvolume) containing the inode 214 int fd, 215 /// Logical offset to resolve 216 u64 logical, 217 /// Result callback 218 scope void delegate( 219 /// The inode 220 u64 inode, 221 /// The offset within the inode file 222 /// Will be zero if `ignoreOffset` is true 223 u64 offset, 224 /// The filesystem root ID containing the inode 225 u64 root, 226 ) callback, 227 /// Ignore the offset when querying extent ownership 228 /// If this particular offset is not in use by any file but the extent is, 229 /// this allows querying which file is pinning the offset. 230 bool ignoreOffset = false, 231 /// Query buffer size 232 ubyte[] buf = logicalInoBuf[], 233 ) 234 { 235 u64 flags = 0; 236 if (ignoreOffset) 237 flags |= BTRFS_LOGICAL_INO_ARGS_IGNORE_OFFSET; 238 239 assert(buf.length > btrfs_data_container.sizeof); 240 auto inodes = cast(btrfs_data_container*)buf.ptr; 241 242 ulong request = BTRFS_IOC_LOGICAL_INO; 243 if (buf.length > SZ_64K || flags != 0) 244 request = BTRFS_IOC_LOGICAL_INO_V2; 245 246 btrfs_ioctl_logical_ino_args loi; 247 loi.logical = logical; 248 loi.size = buf.length; 249 loi.flags = flags; 250 loi.inodes = cast(__u64)inodes; 251 252 ioctl(fd, request, &loi).eq(0).errnoEnforce("logical ino"); 253 for (auto i = 0; i < inodes.elem_cnt; i += 3) 254 { 255 u64 inum = inodes.val.ptr[i]; 256 u64 offset = inodes.val.ptr[i+1]; 257 u64 root = inodes.val.ptr[i+2]; 258 259 callback(inum, offset, root); 260 } 261 } 262 263 /// Obtain all paths for an inode. 264 void inoPaths( 265 /// Handle to the filesystem 266 int fd, 267 /// The inode 268 u64 inode, 269 /// Callback for receiving file names 270 scope void delegate(char[] fn) callback, 271 ) 272 { 273 union Buf 274 { 275 btrfs_data_container container; 276 ubyte[0x10000] buf; 277 } 278 Buf fspath; 279 280 btrfs_ioctl_ino_path_args ipa; 281 ipa.inum = inode; 282 ipa.size = fspath.sizeof; 283 ipa.fspath = ptr_to_u64(&fspath); 284 285 ioctl(fd, BTRFS_IOC_INO_PATHS, &ipa).eq(0).errnoEnforce("ino paths"); 286 287 foreach (i; 0 .. fspath.container.elem_cnt) 288 { 289 auto ptr = fspath.buf.ptr; 290 ptr += fspath.container.val.offsetof; 291 ptr += fspath.container.val.ptr[i]; 292 auto str = cast(char *)ptr; 293 callback(fromStringz(str)); 294 } 295 } 296 297 /// Obtains the relative path of a given filesystem object for the given filesystem root. 298 void inoLookup( 299 /// Handle to the filesystem 300 int fd, 301 /// Tree ID containing the filesystem object 302 u64 treeID, 303 /// Filesystem object 304 u64 objectID, 305 /// Callback receiving the relative path (it ends with /) 306 scope void delegate(char[] fn) callback, 307 ) 308 { 309 btrfs_ioctl_ino_lookup_args args; 310 args.treeid = treeID; 311 args.objectid = objectID; 312 313 ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args).eq(0).errnoEnforce("ino lookup"); 314 args.name[$-1] = 0; 315 callback(fromStringz(args.name.ptr)); 316 } 317 318 /// Find a root's parent root, and where it is within it 319 void findRootBackRef( 320 /// Handle to the filesystem 321 int fd, 322 /// The child root ID whose parent to find 323 __u64 rootID, 324 /// Result callback 325 scope void delegate( 326 /// The parent root ID 327 __u64 parentRootID, 328 /// The directory ID (within the parent root) containing the child 329 __u64 dirID, 330 /// The sequence of the child entry within the directory 331 __u64 sequence, 332 /// The base file name of the child within the directory 333 char[] name, 334 ) callback, 335 ) 336 { 337 treeSearch!( 338 BTRFS_ROOT_BACKREF_KEY, 339 btrfs_root_ref, 340 )( 341 fd, 342 BTRFS_ROOT_TREE_OBJECTID, 343 [rootID, rootID], 344 treeSearchAllOffsets, 345 treeSearchAllTransIDs, 346 (const ref btrfs_ioctl_search_header header, const ref btrfs_root_ref data) 347 { 348 auto parentRoot = header.offset; 349 auto name = (cast(char*)(&data + 1))[0 .. data.name_len]; 350 callback(parentRoot, data.dirid, data.sequence, name); 351 } 352 ); 353 }