1use std::ffi::CStr;
16use std::ops::RangeInclusive;
17
18#[cfg(fbcode_build)]
19pub use btrfs_sys::*;
20#[cfg(not(fbcode_build))]
21pub mod open_source;
22#[cfg(not(fbcode_build))]
23pub use open_source::btrfs_sys::*;
24
25#[cfg(test)]
26mod test;
27
28#[cfg(not(fbcode_build))]
29#[cfg(test)]
30mod sudotest;
31
32mod utils;
33use std::path::PathBuf;
34
35use thiserror::Error;
36
37pub use crate::btrfs_api::utils::*;
38
39#[derive(Error, Debug)]
40pub enum Error {
41 #[error("System Error: {0}")]
42 SysError(nix::errno::Errno),
43 #[error("{1:?}: {0:?}")]
44 IoError(PathBuf, #[source] std::io::Error),
45 #[error("Not btrfs filesystem: {0:?}")]
46 NotBtrfs(PathBuf),
47}
48
49pub type Result<T> = std::result::Result<T, Error>;
50
51mod ioctl {
54 use super::*;
55 nix::ioctl_readwrite!(search_v2, BTRFS_IOCTL_MAGIC, 17, btrfs_ioctl_search_args_v2);
56 nix::ioctl_readwrite!(
57 ino_lookup,
58 BTRFS_IOCTL_MAGIC,
59 18,
60 btrfs_ioctl_ino_lookup_args
61 );
62 nix::ioctl_readwrite!(ino_paths, BTRFS_IOCTL_MAGIC, 35, btrfs_ioctl_ino_path_args);
63 nix::ioctl_readwrite!(
64 logical_ino_v2,
65 BTRFS_IOCTL_MAGIC,
66 59,
67 btrfs_ioctl_logical_ino_args
68 );
69}
70
71#[repr(C)]
72#[derive(Debug, Clone, Copy)]
73pub struct LogicalInoItem {
76 pub inum: u64,
77 pub offset: u64,
78 pub root: u64,
79}
80
81pub fn logical_ino(
82 fd: i32,
83 logical: u64,
84 ignoring_offset: bool,
85 mut cb: impl FnMut(Result<&[LogicalInoItem]>),
86) {
87 let mut data = WithMemAfter::<btrfs_data_container, 4096>::new();
88
89 let mut args = btrfs_ioctl_logical_ino_args {
90 logical,
91 size: data.total_size() as u64,
92 reserved: Default::default(),
93 flags: if ignoring_offset {
94 BTRFS_LOGICAL_INO_ARGS_IGNORE_OFFSET as u64
95 } else {
96 0
97 },
98 inodes: data.as_mut_ptr() as u64,
99 };
100 unsafe {
101 match ioctl::logical_ino_v2(fd, &mut args) {
102 Ok(_) => {
103 let inodes = std::slice::from_raw_parts(
104 data.extra_ptr() as *const LogicalInoItem,
105 (data.elem_cnt / 3) as usize,
108 );
109 cb(Ok(inodes));
110 }
111 Err(err) => {
112 cb(Err(Error::SysError(err)));
113 }
114 }
115 }
116}
117
118pub fn ino_lookup(fd: i32, root: u64, inum: u64, mut cb: impl FnMut(Result<&CStr>)) {
119 let mut args = btrfs_ioctl_ino_lookup_args {
120 treeid: root,
121 objectid: inum,
122 name: [0; BTRFS_INO_LOOKUP_PATH_MAX as usize],
123 };
124
125 unsafe {
126 match ioctl::ino_lookup(fd, &mut args) {
127 Ok(_) => {
128 cb(Ok(CStr::from_ptr(args.name.as_ptr())));
129 }
130 Err(err) => {
131 cb(Err(Error::SysError(err)));
132 }
133 }
134 }
135}
136
137pub struct SearchKey {
138 pub objectid: u64,
139 pub typ: u8,
140 pub offset: u64,
141}
142
143impl SearchKey {
144 pub const MIN: Self = SearchKey::new(u64::MIN, u8::MIN, u64::MIN);
145 pub const MAX: Self = SearchKey::new(u64::MAX, u8::MAX, u64::MAX);
146
147 pub const ALL: RangeInclusive<Self> = Self::MIN..=Self::MAX;
148
149 pub const fn range_fixed_id_type(objectid: u64, typ: u8) -> RangeInclusive<Self> {
150 Self::new(objectid, typ, u64::MIN)..=Self::new(objectid, typ, u64::MAX)
151 }
152
153 pub const fn new(objectid: u64, typ: u8, offset: u64) -> Self {
154 Self {
155 objectid,
156 typ,
157 offset,
158 }
159 }
160
161 pub fn next(&self) -> Self {
162 let (offset, carry1) = self.offset.overflowing_add(1);
163 let (typ, carry2) = self.typ.overflowing_add(carry1 as u8);
164 let (objectid, _) = self.objectid.overflowing_add(carry2 as u64);
165 SearchKey {
166 objectid,
167 typ,
168 offset,
169 }
170 }
171
172 fn from(h: &btrfs_ioctl_search_header) -> Self {
173 SearchKey {
174 objectid: h.objectid,
175 typ: h.type_ as u8,
176 offset: h.offset,
177 }
178 }
179}
180
181pub fn tree_search_cb(
182 fd: i32,
183 tree_id: u64,
184 range: RangeInclusive<SearchKey>,
185 mut cb: impl FnMut(&btrfs_ioctl_search_header, &[u8]),
186) -> Result<()> {
187 const BUF_SIZE: usize = 16 * 1024;
188 let mut args = WithMemAfter::<btrfs_ioctl_search_args_v2, BUF_SIZE>::new();
189 args.key = btrfs_ioctl_search_key {
190 tree_id,
191 min_objectid: range.start().objectid,
192 max_objectid: range.end().objectid,
193 min_offset: range.start().offset,
194 max_offset: range.end().offset,
195 min_transid: u64::MIN,
196 max_transid: u64::MAX,
197 min_type: range.start().typ as u32,
198 max_type: range.end().typ as u32,
199 nr_items: u32::MAX,
200
201 unused: 0,
202 unused1: 0,
203 unused2: 0,
204 unused3: 0,
205 unused4: 0,
206 };
207 args.buf_size = args.extra_size() as u64;
208
209 loop {
210 args.key.nr_items = u32::MAX;
211 unsafe {
212 ioctl::search_v2(fd, args.as_mut_ptr()).map_err(Error::SysError)?;
213 }
214 if args.key.nr_items == 0 {
215 break;
216 }
217
218 let mut ptr = args.buf.as_ptr() as *const u8;
219 let mut last_search_header: *const btrfs_ioctl_search_header = std::ptr::null();
220 for _ in 0..args.key.nr_items {
221 let search_header =
222 unsafe { get_and_move_typed::<btrfs_ioctl_search_header>(&mut ptr) };
223
224 let data = unsafe {
225 std::slice::from_raw_parts(
226 get_and_move(&mut ptr, (*search_header).len as usize),
227 (*search_header).len as usize,
228 )
229 };
230 last_search_header = search_header;
231 unsafe {
232 cb(&*search_header, data);
233 }
234 }
235
236 let min_key = unsafe { SearchKey::from(&*last_search_header).next() };
237
238 args.key.min_objectid = min_key.objectid;
239 args.key.min_type = min_key.typ as u32;
240 args.key.min_offset = min_key.offset;
241 }
242
243 Ok(())
244}
245
246pub fn find_root_backref(fd: i32, root_id: u64) -> Result<Option<(String, u64)>> {
247 let mut res: Option<(String, u64)> = None;
249 tree_search_cb(
250 fd,
251 BTRFS_ROOT_TREE_OBJECTID as u64,
252 SearchKey::range_fixed_id_type(root_id, BTRFS_ROOT_BACKREF_KEY as u8),
253 |sh, data| {
254 if sh.type_ == BTRFS_ROOT_BACKREF_KEY {
255 let mut data_ptr = data.as_ptr();
256 let root_ref = unsafe { get_and_move_typed::<btrfs_root_ref>(&mut data_ptr) };
257 let name = unsafe {
258 std::str::from_utf8_unchecked(std::slice::from_raw_parts(
259 data_ptr,
260 (*root_ref).name_len as usize,
261 ))
262 };
263 res = Some((name.to_owned(), sh.offset));
264 };
265 },
266 )?;
267 Ok(res)
268}