btrfs_uapi/inode.rs
1//! # Inode and path resolution: mapping between inodes, logical addresses, and paths
2//!
3//! Covers looking up the subvolume root ID that contains a given file, resolving
4//! an inode number to its filesystem paths, mapping a logical byte address back
5//! to the inodes that reference it, and resolving a subvolume ID to its path
6//! within the filesystem.
7
8use crate::{
9 raw::{
10 BTRFS_FIRST_FREE_OBJECTID, btrfs_ioc_ino_lookup, btrfs_ioc_ino_paths,
11 btrfs_ioc_logical_ino_v2, btrfs_root_ref,
12 },
13 tree_search::{SearchKey, tree_search},
14};
15use nix::libc::c_int;
16use std::os::fd::{AsRawFd, BorrowedFd};
17
18/// Look up the tree ID (root ID) of the subvolume containing the given file or directory.
19///
20/// For a file or directory, returns the containing tree root ID.
21/// For a subvolume, returns its own tree ID.
22///
23/// # Arguments
24///
25/// * `fd` - File descriptor to a file or directory on the btrfs filesystem
26///
27/// # Returns
28///
29/// The tree ID (root ID) of the containing subvolume
30///
31/// # Errors
32///
33/// Returns an error if the ioctl fails (e.g., file descriptor is not on a btrfs filesystem)
34pub fn lookup_path_rootid(fd: BorrowedFd<'_>) -> nix::Result<u64> {
35 let mut args = crate::raw::btrfs_ioctl_ino_lookup_args {
36 treeid: 0,
37 objectid: BTRFS_FIRST_FREE_OBJECTID as u64,
38 ..unsafe { std::mem::zeroed() }
39 };
40
41 unsafe {
42 btrfs_ioc_ino_lookup(fd.as_raw_fd() as c_int, &mut args)?;
43 }
44
45 Ok(args.treeid)
46}
47
48/// Get file system paths for the given inode.
49///
50/// Returns a vector of path strings relative to the filesystem root that correspond
51/// to the given inode number.
52///
53/// # Arguments
54///
55/// * `fd` - File descriptor to a file or directory on the btrfs filesystem
56/// * `inum` - Inode number to look up
57///
58/// # Returns
59///
60/// A vector of path strings for the given inode
61///
62/// # Errors
63///
64/// Returns an error if the ioctl fails
65pub fn ino_paths(fd: BorrowedFd<'_>, inum: u64) -> nix::Result<Vec<String>> {
66 const PATH_MAX: usize = 4096;
67
68 // First, allocate a buffer for the response
69 // The buffer needs to be large enough to hold btrfs_data_container plus the path data
70 let mut buf = vec![0u8; PATH_MAX];
71
72 // Set up the ioctl arguments
73 let mut args = crate::raw::btrfs_ioctl_ino_path_args {
74 inum,
75 size: PATH_MAX as u64,
76 reserved: [0; 4],
77 fspath: buf.as_mut_ptr() as u64,
78 };
79
80 unsafe {
81 btrfs_ioc_ino_paths(fd.as_raw_fd() as c_int, &mut args)?;
82 }
83
84 // Parse the results from the data container
85 // The buffer is laid out as: btrfs_data_container header, followed by val[] array
86 let container = unsafe { &*(buf.as_ptr() as *const crate::raw::btrfs_data_container) };
87
88 let mut paths = Vec::new();
89
90 // Each element in val[] is an offset into the data buffer where a path string starts
91 for i in 0..container.elem_cnt as usize {
92 // Get the offset from val[i]
93 let val_offset = unsafe {
94 let val_ptr = container.val.as_ptr();
95 *val_ptr.add(i) as usize
96 };
97
98 // The path string starts at the base of val array plus the offset
99 let val_base = container.val.as_ptr() as usize;
100 let path_ptr = (val_base + val_offset) as *const i8;
101
102 // Convert C string to Rust String
103 let c_str = unsafe { std::ffi::CStr::from_ptr(path_ptr) };
104 if let Ok(path_str) = c_str.to_str() {
105 paths.push(path_str.to_string());
106 }
107 }
108
109 Ok(paths)
110}
111
112/// Result from logical-ino resolution: (inode, offset, root)
113#[derive(Debug, Clone)]
114pub struct LogicalInoResult {
115 pub inode: u64,
116 pub offset: u64,
117 pub root: u64,
118}
119
120/// Get inode, offset, and root information for a logical address.
121///
122/// Resolves a logical address on the filesystem to one or more (inode, offset, root) tuples.
123/// The offset is the position within the file, and the root is the subvolume ID.
124///
125/// # Arguments
126///
127/// * `fd` - File descriptor to a file or directory on the btrfs filesystem
128/// * `logical` - Logical address to resolve
129/// * `ignore_offset` - If true, ignores offsets when matching references
130/// * `bufsize` - Size of buffer to allocate (default 64KB, max 16MB)
131///
132/// # Returns
133///
134/// A vector of (inode, offset, root) tuples
135///
136/// # Errors
137///
138/// Returns an error if the ioctl fails
139pub fn logical_ino(
140 fd: BorrowedFd<'_>,
141 logical: u64,
142 ignore_offset: bool,
143 bufsize: Option<u64>,
144) -> nix::Result<Vec<LogicalInoResult>> {
145 const MAX_BUFSIZE: u64 = 16 * 1024 * 1024; // 16MB
146 const DEFAULT_BUFSIZE: u64 = 64 * 1024; // 64KB
147
148 let size = std::cmp::min(bufsize.unwrap_or(DEFAULT_BUFSIZE), MAX_BUFSIZE);
149 let mut buf = vec![0u8; size as usize];
150
151 // Set up flags for v2 ioctl
152 let mut flags = 0u64;
153 if ignore_offset {
154 flags |= crate::raw::BTRFS_LOGICAL_INO_ARGS_IGNORE_OFFSET as u64;
155 }
156
157 // Set up the ioctl arguments
158 let mut args = crate::raw::btrfs_ioctl_logical_ino_args {
159 logical,
160 size,
161 reserved: [0; 3],
162 flags,
163 inodes: buf.as_mut_ptr() as u64,
164 };
165
166 unsafe {
167 btrfs_ioc_logical_ino_v2(fd.as_raw_fd() as c_int, &mut args)?;
168 }
169
170 // Parse the results from the data container
171 let container = unsafe { &*(buf.as_ptr() as *const crate::raw::btrfs_data_container) };
172
173 let mut results = Vec::new();
174
175 // Each result is 3 consecutive u64 values: inum, offset, root
176 for i in (0..container.elem_cnt as usize).step_by(3) {
177 if i + 2 < container.elem_cnt as usize {
178 let val_offset_inum = unsafe {
179 let val_ptr = container.val.as_ptr();
180 *val_ptr.add(i) as u64
181 };
182
183 let val_offset_offset = unsafe {
184 let val_ptr = container.val.as_ptr();
185 *val_ptr.add(i + 1) as u64
186 };
187
188 let val_offset_root = unsafe {
189 let val_ptr = container.val.as_ptr();
190 *val_ptr.add(i + 2) as u64
191 };
192
193 results.push(LogicalInoResult {
194 inode: val_offset_inum,
195 offset: val_offset_offset,
196 root: val_offset_root,
197 });
198 }
199 }
200
201 Ok(results)
202}
203
204/// Resolve a subvolume ID to its full path on the filesystem.
205///
206/// Recursively resolves the path to a subvolume by walking the root tree and using
207/// INO_LOOKUP to get directory names. The path is built from the subvolume's name
208/// and the names of all parent directories up to the mount point.
209///
210/// # Arguments
211///
212/// * `fd` - File descriptor to a file or directory on the btrfs filesystem
213/// * `subvol_id` - The subvolume ID to resolve
214///
215/// # Returns
216///
217/// The full path to the subvolume relative to the filesystem root, or an empty string
218/// for the filesystem root itself (FS_TREE_OBJECTID).
219///
220/// # Errors
221///
222/// Returns an error if:
223/// * The ioctl fails (fd is not on a btrfs filesystem)
224/// * The subvolume ID does not exist
225/// * The path buffer overflows
226///
227/// # Example
228///
229/// ```ignore
230/// let path = subvolid_resolve(fd, 5)?;
231/// println!("Subvolume 5 is at: {}", path);
232/// ```
233pub fn subvolid_resolve(fd: BorrowedFd<'_>, subvol_id: u64) -> nix::Result<String> {
234 let mut path = String::new();
235 subvolid_resolve_sub(fd, &mut path, subvol_id)?;
236 Ok(path)
237}
238
239fn subvolid_resolve_sub(fd: BorrowedFd<'_>, path: &mut String, subvol_id: u64) -> nix::Result<()> {
240 use crate::raw::BTRFS_FS_TREE_OBJECTID;
241
242 // If this is the filesystem root, we're done (empty path means root)
243 if subvol_id == BTRFS_FS_TREE_OBJECTID as u64 {
244 return Ok(());
245 }
246
247 // Search the root tree for ROOT_BACKREF_KEY entries for this subvolume.
248 // ROOT_BACKREF_KEY (item type 156) has:
249 // - objectid: the subvolume ID
250 // - offset: the parent subvolume ID
251 // - data: btrfs_root_ref struct containing the subvolume name
252 let mut found = false;
253
254 tree_search(
255 fd,
256 SearchKey::for_objectid_range(
257 crate::raw::BTRFS_ROOT_TREE_OBJECTID as u64,
258 crate::raw::BTRFS_ROOT_BACKREF_KEY as u32,
259 subvol_id,
260 subvol_id,
261 ),
262 |hdr, data| {
263 found = true;
264
265 // The parent subvolume ID is stored in the offset field
266 let parent_subvol_id = hdr.offset;
267
268 // Recursively resolve the parent path first
269 subvolid_resolve_sub(fd, path, parent_subvol_id)?;
270
271 // data is a packed btrfs_root_ref followed by name bytes.
272 use std::mem::{offset_of, size_of};
273
274 let header_size = size_of::<btrfs_root_ref>();
275 if data.len() < header_size {
276 return Err(nix::errno::Errno::EOVERFLOW);
277 }
278
279 let dirid = u64::from_le_bytes(
280 data[offset_of!(btrfs_root_ref, dirid)..][..8]
281 .try_into()
282 .unwrap(),
283 );
284
285 let name_off = offset_of!(btrfs_root_ref, name_len);
286 let name_len = u16::from_le_bytes([data[name_off], data[name_off + 1]]) as usize;
287
288 if data.len() < header_size + name_len {
289 return Err(nix::errno::Errno::EOVERFLOW);
290 }
291
292 let name_bytes = &data[header_size..header_size + name_len];
293
294 // If dirid is not the first free objectid, we need to resolve the directory path too
295 if dirid != BTRFS_FIRST_FREE_OBJECTID as u64 {
296 // Look up the directory in the parent subvolume
297 let mut ino_lookup_args = crate::raw::btrfs_ioctl_ino_lookup_args {
298 treeid: parent_subvol_id,
299 objectid: dirid,
300 ..unsafe { std::mem::zeroed() }
301 };
302
303 unsafe {
304 btrfs_ioc_ino_lookup(fd.as_raw_fd() as c_int, &mut ino_lookup_args)?;
305 }
306
307 // Get the directory name (it's a null-terminated C string)
308 let dir_name = unsafe { std::ffi::CStr::from_ptr(ino_lookup_args.name.as_ptr()) }
309 .to_str()
310 .map_err(|_| nix::errno::Errno::EINVAL)?;
311
312 if !dir_name.is_empty() {
313 if !path.is_empty() {
314 path.push('/');
315 }
316 path.push_str(dir_name);
317 }
318 }
319
320 // Append the subvolume name
321 if !path.is_empty() {
322 path.push('/');
323 }
324
325 // Convert name bytes to string
326 let name_str =
327 std::str::from_utf8(name_bytes).map_err(|_| nix::errno::Errno::EINVAL)?;
328 path.push_str(name_str);
329
330 Ok(())
331 },
332 )?;
333
334 if !found {
335 return Err(nix::errno::Errno::ENOENT);
336 }
337
338 Ok(())
339}