below_btrfs/btrfs_api/
mod.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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
51// Magic numbers for ioctl system calls can be found here:
52// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/btrfs.h
53mod 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)]
73// This struct is derived from here:
74// https://elixir.bootlin.com/linux/latest/source/fs/btrfs/ioctl.c#L4195
75pub 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                    // Magic number 3 comes from size_of(LogicalInoItem) / size_of(u64)
106                    // (the elements of btrfs_data_container val are u64).
107                    (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    // This is parent name and parent subvolume id.
248    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}