Skip to main content

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 =
87        unsafe { &*(buf.as_ptr() as *const crate::raw::btrfs_data_container) };
88
89    let mut paths = Vec::new();
90
91    // Each element in val[] is an offset into the data buffer where a path string starts
92    for i in 0..container.elem_cnt as usize {
93        // Get the offset from val[i]
94        let val_offset = unsafe {
95            let val_ptr = container.val.as_ptr();
96            *val_ptr.add(i) as usize
97        };
98
99        // The path string starts at the base of val array plus the offset
100        let val_base = container.val.as_ptr() as usize;
101        let path_ptr = (val_base + val_offset) as *const i8;
102
103        // Convert C string to Rust String
104        let c_str = unsafe { std::ffi::CStr::from_ptr(path_ptr) };
105        if let Ok(path_str) = c_str.to_str() {
106            paths.push(path_str.to_string());
107        }
108    }
109
110    Ok(paths)
111}
112
113/// Result from logical-ino resolution: (inode, offset, root)
114#[derive(Debug, Clone)]
115pub struct LogicalInoResult {
116    pub inode: u64,
117    pub offset: u64,
118    pub root: u64,
119}
120
121/// Get inode, offset, and root information for a logical address.
122///
123/// Resolves a logical address on the filesystem to one or more (inode, offset, root) tuples.
124/// The offset is the position within the file, and the root is the subvolume ID.
125///
126/// # Arguments
127///
128/// * `fd` - File descriptor to a file or directory on the btrfs filesystem
129/// * `logical` - Logical address to resolve
130/// * `ignore_offset` - If true, ignores offsets when matching references
131/// * `bufsize` - Size of buffer to allocate (default 64KB, max 16MB)
132///
133/// # Returns
134///
135/// A vector of (inode, offset, root) tuples
136///
137/// # Errors
138///
139/// Returns an error if the ioctl fails
140pub fn logical_ino(
141    fd: BorrowedFd<'_>,
142    logical: u64,
143    ignore_offset: bool,
144    bufsize: Option<u64>,
145) -> nix::Result<Vec<LogicalInoResult>> {
146    const MAX_BUFSIZE: u64 = 16 * 1024 * 1024; // 16MB
147    const DEFAULT_BUFSIZE: u64 = 64 * 1024; // 64KB
148
149    let size = std::cmp::min(bufsize.unwrap_or(DEFAULT_BUFSIZE), MAX_BUFSIZE);
150    let mut buf = vec![0u8; size as usize];
151
152    // Set up flags for v2 ioctl
153    let mut flags = 0u64;
154    if ignore_offset {
155        flags |= crate::raw::BTRFS_LOGICAL_INO_ARGS_IGNORE_OFFSET as u64;
156    }
157
158    // Set up the ioctl arguments
159    let mut args = crate::raw::btrfs_ioctl_logical_ino_args {
160        logical,
161        size,
162        reserved: [0; 3],
163        flags,
164        inodes: buf.as_mut_ptr() as u64,
165    };
166
167    unsafe {
168        btrfs_ioc_logical_ino_v2(fd.as_raw_fd() as c_int, &mut args)?;
169    }
170
171    // Parse the results from the data container
172    let container =
173        unsafe { &*(buf.as_ptr() as *const crate::raw::btrfs_data_container) };
174
175    let mut results = Vec::new();
176
177    // Each result is 3 consecutive u64 values: inum, offset, root
178    for i in (0..container.elem_cnt as usize).step_by(3) {
179        if i + 2 < container.elem_cnt as usize {
180            let val_offset_inum = unsafe {
181                let val_ptr = container.val.as_ptr();
182                *val_ptr.add(i) as u64
183            };
184
185            let val_offset_offset = unsafe {
186                let val_ptr = container.val.as_ptr();
187                *val_ptr.add(i + 1) as u64
188            };
189
190            let val_offset_root = unsafe {
191                let val_ptr = container.val.as_ptr();
192                *val_ptr.add(i + 2) as u64
193            };
194
195            results.push(LogicalInoResult {
196                inode: val_offset_inum,
197                offset: val_offset_offset,
198                root: val_offset_root,
199            });
200        }
201    }
202
203    Ok(results)
204}
205
206/// Resolve a subvolume ID to its full path on the filesystem.
207///
208/// Recursively resolves the path to a subvolume by walking the root tree and using
209/// INO_LOOKUP to get directory names. The path is built from the subvolume's name
210/// and the names of all parent directories up to the mount point.
211///
212/// # Arguments
213///
214/// * `fd` - File descriptor to a file or directory on the btrfs filesystem
215/// * `subvol_id` - The subvolume ID to resolve
216///
217/// # Returns
218///
219/// The full path to the subvolume relative to the filesystem root, or an empty string
220/// for the filesystem root itself (FS_TREE_OBJECTID).
221///
222/// # Errors
223///
224/// Returns an error if:
225/// * The ioctl fails (fd is not on a btrfs filesystem)
226/// * The subvolume ID does not exist
227/// * The path buffer overflows
228///
229/// # Example
230///
231/// ```ignore
232/// let path = subvolid_resolve(fd, 5)?;
233/// println!("Subvolume 5 is at: {}", path);
234/// ```
235pub fn subvolid_resolve(
236    fd: BorrowedFd<'_>,
237    subvol_id: u64,
238) -> nix::Result<String> {
239    let mut path = String::new();
240    subvolid_resolve_sub(fd, &mut path, subvol_id)?;
241    Ok(path)
242}
243
244fn subvolid_resolve_sub(
245    fd: BorrowedFd<'_>,
246    path: &mut String,
247    subvol_id: u64,
248) -> nix::Result<()> {
249    use crate::raw::BTRFS_FS_TREE_OBJECTID;
250
251    // If this is the filesystem root, we're done (empty path means root)
252    if subvol_id == BTRFS_FS_TREE_OBJECTID as u64 {
253        return Ok(());
254    }
255
256    // Search the root tree for ROOT_BACKREF_KEY entries for this subvolume.
257    // ROOT_BACKREF_KEY (item type 156) has:
258    // - objectid: the subvolume ID
259    // - offset: the parent subvolume ID
260    // - data: btrfs_root_ref struct containing the subvolume name
261    let mut found = false;
262
263    tree_search(
264        fd,
265        SearchKey::for_objectid_range(
266            crate::raw::BTRFS_ROOT_TREE_OBJECTID as u64,
267            crate::raw::BTRFS_ROOT_BACKREF_KEY as u32,
268            subvol_id,
269            subvol_id,
270        ),
271        |hdr, data| {
272            found = true;
273
274            // The parent subvolume ID is stored in the offset field
275            let parent_subvol_id = hdr.offset;
276
277            // Recursively resolve the parent path first
278            subvolid_resolve_sub(fd, path, parent_subvol_id)?;
279
280            // data is a packed btrfs_root_ref followed by name bytes.
281            use std::mem::{offset_of, size_of};
282
283            let header_size = size_of::<btrfs_root_ref>();
284            if data.len() < header_size {
285                return Err(nix::errno::Errno::EOVERFLOW);
286            }
287
288            let dirid = u64::from_le_bytes(
289                data[offset_of!(btrfs_root_ref, dirid)..][..8]
290                    .try_into()
291                    .unwrap(),
292            );
293
294            let name_off = offset_of!(btrfs_root_ref, name_len);
295            let name_len =
296                u16::from_le_bytes([data[name_off], data[name_off + 1]])
297                    as usize;
298
299            if data.len() < header_size + name_len {
300                return Err(nix::errno::Errno::EOVERFLOW);
301            }
302
303            let name_bytes = &data[header_size..header_size + name_len];
304
305            // If dirid is not the first free objectid, we need to resolve the directory path too
306            if dirid != BTRFS_FIRST_FREE_OBJECTID as u64 {
307                // Look up the directory in the parent subvolume
308                let mut ino_lookup_args =
309                    crate::raw::btrfs_ioctl_ino_lookup_args {
310                        treeid: parent_subvol_id,
311                        objectid: dirid,
312                        ..unsafe { std::mem::zeroed() }
313                    };
314
315                unsafe {
316                    btrfs_ioc_ino_lookup(
317                        fd.as_raw_fd() as c_int,
318                        &mut ino_lookup_args,
319                    )?;
320                }
321
322                // Get the directory name (it's a null-terminated C string)
323                let dir_name = unsafe {
324                    std::ffi::CStr::from_ptr(ino_lookup_args.name.as_ptr())
325                }
326                .to_str()
327                .map_err(|_| nix::errno::Errno::EINVAL)?;
328
329                if !dir_name.is_empty() {
330                    if !path.is_empty() {
331                        path.push('/');
332                    }
333                    path.push_str(dir_name);
334                }
335            }
336
337            // Append the subvolume name
338            if !path.is_empty() {
339                path.push('/');
340            }
341
342            // Convert name bytes to string
343            let name_str = std::str::from_utf8(name_bytes)
344                .map_err(|_| nix::errno::Errno::EINVAL)?;
345            path.push_str(name_str);
346
347            Ok(())
348        },
349    )?;
350
351    if !found {
352        return Err(nix::errno::Errno::ENOENT);
353    }
354
355    Ok(())
356}