1use crate::raw::{
9 BTRFS_FS_INFO_FLAG_GENERATION, BTRFS_LABEL_SIZE, btrfs_ioc_fs_info,
10 btrfs_ioc_get_fslabel, btrfs_ioc_resize, btrfs_ioc_set_fslabel,
11 btrfs_ioc_start_sync, btrfs_ioc_sync, btrfs_ioc_wait_sync,
12 btrfs_ioctl_fs_info_args, btrfs_ioctl_vol_args,
13};
14use nix::libc::c_char;
15use std::{
16 ffi::{CStr, CString},
17 mem,
18 os::{fd::AsRawFd, unix::io::BorrowedFd},
19};
20use uuid::Uuid;
21
22#[derive(Debug, Clone)]
25pub struct FilesystemInfo {
26 pub uuid: Uuid,
28 pub num_devices: u64,
30 pub max_id: u64,
32 pub nodesize: u32,
34 pub sectorsize: u32,
36 pub generation: u64,
38}
39
40pub fn filesystem_info(fd: BorrowedFd) -> nix::Result<FilesystemInfo> {
46 let mut raw: btrfs_ioctl_fs_info_args = unsafe { mem::zeroed() };
47 raw.flags = u64::from(BTRFS_FS_INFO_FLAG_GENERATION);
48 unsafe { btrfs_ioc_fs_info(fd.as_raw_fd(), &raw mut raw) }?;
49
50 Ok(FilesystemInfo {
51 uuid: Uuid::from_bytes(raw.fsid),
52 num_devices: raw.num_devices,
53 max_id: raw.max_id,
54 nodesize: raw.nodesize,
55 sectorsize: raw.sectorsize,
56 generation: raw.generation,
57 })
58}
59
60pub fn sync(fd: BorrowedFd) -> nix::Result<()> {
67 unsafe { btrfs_ioc_sync(fd.as_raw_fd()) }?;
68 Ok(())
69}
70
71pub fn start_sync(fd: BorrowedFd) -> nix::Result<u64> {
80 let mut transid: u64 = 0;
81 unsafe { btrfs_ioc_start_sync(fd.as_raw_fd(), &raw mut transid) }?;
82 Ok(transid)
83}
84
85pub fn wait_sync(fd: BorrowedFd, transid: u64) -> nix::Result<()> {
94 unsafe { btrfs_ioc_wait_sync(fd.as_raw_fd(), &raw const transid) }?;
95 Ok(())
96}
97
98pub fn label_get(fd: BorrowedFd) -> nix::Result<CString> {
106 let mut buf = [0i8; BTRFS_LABEL_SIZE as usize];
107 unsafe { btrfs_ioc_get_fslabel(fd.as_raw_fd(), &raw mut buf) }?;
108 let cstr = unsafe { CStr::from_ptr(buf.as_ptr()) };
109 Ok(cstr.to_owned())
112}
113
114#[allow(clippy::cast_possible_wrap)] pub fn label_set(fd: BorrowedFd, label: &CStr) -> nix::Result<()> {
128 let bytes = label.to_bytes();
129 if bytes.len() >= BTRFS_LABEL_SIZE as usize {
130 return Err(nix::errno::Errno::EINVAL);
131 }
132 let mut buf = [0i8; BTRFS_LABEL_SIZE as usize];
133 for (i, &b) in bytes.iter().enumerate() {
134 buf[i] = b as c_char;
135 }
136 unsafe { btrfs_ioc_set_fslabel(fd.as_raw_fd(), &raw const buf) }?;
137 Ok(())
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub enum ResizeAmount {
143 Cancel,
145 Max,
147 Set(u64),
149 Add(u64),
151 Sub(u64),
153}
154
155impl std::fmt::Display for ResizeAmount {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 match self {
158 Self::Cancel => f.write_str("cancel"),
159 Self::Max => f.write_str("max"),
160 Self::Set(n) => write!(f, "{n}"),
161 Self::Add(n) => write!(f, "+{n}"),
162 Self::Sub(n) => write!(f, "-{n}"),
163 }
164 }
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub struct ResizeArgs {
173 pub devid: Option<u64>,
176 pub amount: ResizeAmount,
178}
179
180impl ResizeArgs {
181 #[must_use]
184 pub fn new(amount: ResizeAmount) -> Self {
185 Self {
186 devid: None,
187 amount,
188 }
189 }
190
191 #[must_use]
193 pub fn with_devid(mut self, devid: u64) -> Self {
194 self.devid = Some(devid);
195 self
196 }
197
198 fn format_name(&self) -> String {
201 let amount = self.amount.to_string();
202 match self.devid {
203 Some(devid) => format!("{devid}:{amount}"),
204 None => amount,
205 }
206 }
207}
208
209#[allow(clippy::cast_possible_wrap)] pub fn resize(fd: BorrowedFd, args: ResizeArgs) -> nix::Result<()> {
219 let name = args.format_name();
220 let name_bytes = name.as_bytes();
221
222 if name_bytes.len() >= 4088 {
227 return Err(nix::errno::Errno::EINVAL);
228 }
229
230 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
231 for (i, &b) in name_bytes.iter().enumerate() {
232 raw.name[i] = b as c_char;
233 }
234
235 unsafe { btrfs_ioc_resize(fd.as_raw_fd(), &raw const raw) }?;
236 Ok(())
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
246 fn resize_amount_cancel() {
247 assert_eq!(ResizeAmount::Cancel.to_string(), "cancel");
248 }
249
250 #[test]
251 fn resize_amount_max() {
252 assert_eq!(ResizeAmount::Max.to_string(), "max");
253 }
254
255 #[test]
256 fn resize_amount_set() {
257 assert_eq!(ResizeAmount::Set(1073741824).to_string(), "1073741824");
258 }
259
260 #[test]
261 fn resize_amount_add() {
262 assert_eq!(ResizeAmount::Add(512000000).to_string(), "+512000000");
263 }
264
265 #[test]
266 fn resize_amount_sub() {
267 assert_eq!(ResizeAmount::Sub(256000000).to_string(), "-256000000");
268 }
269
270 #[test]
273 fn resize_args_no_devid() {
274 let args = ResizeArgs::new(ResizeAmount::Max);
275 assert!(args.devid.is_none());
276 assert_eq!(args.format_name(), "max");
277 }
278
279 #[test]
280 fn resize_args_with_devid() {
281 let args = ResizeArgs::new(ResizeAmount::Add(1024)).with_devid(2);
282 assert_eq!(args.devid, Some(2));
283 assert_eq!(args.format_name(), "2:+1024");
284 }
285
286 #[test]
287 fn resize_args_set_with_devid() {
288 let args = ResizeArgs::new(ResizeAmount::Set(999)).with_devid(1);
289 assert_eq!(args.format_name(), "1:999");
290 }
291}
292
293pub fn is_mounted(device: &std::path::Path) -> std::io::Result<bool> {
303 let canonical = std::fs::canonicalize(device)?;
304 let contents = std::fs::read_to_string("/proc/mounts")?;
305 Ok(contents.lines().any(|line| {
306 line.split_whitespace()
307 .next()
308 .and_then(|src| std::fs::canonicalize(src).ok())
309 .is_some_and(|src_canon| src_canon == canonical)
310 }))
311}