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
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 }