1use crate::raw::{
9 BTRFS_FS_INFO_FLAG_GENERATION, btrfs_ioc_fs_info, btrfs_ioc_get_fslabel,
10 btrfs_ioc_resize, btrfs_ioc_set_fslabel, btrfs_ioc_start_sync,
11 btrfs_ioc_sync, btrfs_ioc_wait_sync, btrfs_ioctl_fs_info_args,
12 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> {
42 let mut raw: btrfs_ioctl_fs_info_args = unsafe { mem::zeroed() };
43 raw.flags = BTRFS_FS_INFO_FLAG_GENERATION as u64;
44 unsafe { btrfs_ioc_fs_info(fd.as_raw_fd(), &mut raw) }?;
45
46 Ok(FilesystemInfo {
47 uuid: Uuid::from_bytes(raw.fsid),
48 num_devices: raw.num_devices,
49 max_id: raw.max_id,
50 nodesize: raw.nodesize,
51 sectorsize: raw.sectorsize,
52 generation: raw.generation,
53 })
54}
55
56pub fn sync(fd: BorrowedFd) -> nix::Result<()> {
59 unsafe { btrfs_ioc_sync(fd.as_raw_fd()) }?;
60 Ok(())
61}
62
63pub fn start_sync(fd: BorrowedFd) -> nix::Result<u64> {
68 let mut transid: u64 = 0;
69 unsafe { btrfs_ioc_start_sync(fd.as_raw_fd(), &mut transid) }?;
70 Ok(transid)
71}
72
73pub fn wait_sync(fd: BorrowedFd, transid: u64) -> nix::Result<()> {
78 unsafe { btrfs_ioc_wait_sync(fd.as_raw_fd(), &transid) }?;
79 Ok(())
80}
81
82const BTRFS_LABEL_SIZE: usize = crate::raw::BTRFS_LABEL_SIZE as usize;
84
85pub fn label_get(fd: BorrowedFd) -> nix::Result<CString> {
89 let mut buf = [0i8; BTRFS_LABEL_SIZE];
90 unsafe { btrfs_ioc_get_fslabel(fd.as_raw_fd(), &mut buf) }?;
91 let cstr = unsafe { CStr::from_ptr(buf.as_ptr()) };
92 Ok(cstr.to_owned())
95}
96
97pub fn label_set(fd: BorrowedFd, label: &CStr) -> nix::Result<()> {
103 let bytes = label.to_bytes();
104 if bytes.len() >= BTRFS_LABEL_SIZE {
105 return Err(nix::errno::Errno::EINVAL);
106 }
107 let mut buf = [0i8; BTRFS_LABEL_SIZE];
108 for (i, &b) in bytes.iter().enumerate() {
109 buf[i] = b as c_char;
110 }
111 unsafe { btrfs_ioc_set_fslabel(fd.as_raw_fd(), &buf) }?;
112 Ok(())
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum ResizeAmount {
118 Cancel,
120 Max,
122 Set(u64),
124 Add(u64),
126 Sub(u64),
128}
129
130impl ResizeAmount {
131 fn to_string(&self) -> String {
132 match self {
133 Self::Cancel => "cancel".to_owned(),
134 Self::Max => "max".to_owned(),
135 Self::Set(n) => n.to_string(),
136 Self::Add(n) => format!("+{n}"),
137 Self::Sub(n) => format!("-{n}"),
138 }
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub struct ResizeArgs {
148 pub devid: Option<u64>,
149 pub amount: ResizeAmount,
150}
151
152impl ResizeArgs {
153 pub fn new(amount: ResizeAmount) -> Self {
154 Self {
155 devid: None,
156 amount,
157 }
158 }
159
160 pub fn with_devid(mut self, devid: u64) -> Self {
161 self.devid = Some(devid);
162 self
163 }
164
165 fn format_name(&self) -> String {
168 let amount = self.amount.to_string();
169 match self.devid {
170 Some(devid) => format!("{devid}:{amount}"),
171 None => amount,
172 }
173 }
174}
175
176pub fn resize(fd: BorrowedFd, args: ResizeArgs) -> nix::Result<()> {
181 let name = args.format_name();
182 let name_bytes = name.as_bytes();
183
184 if name_bytes.len() >= 4088 {
189 return Err(nix::errno::Errno::EINVAL);
190 }
191
192 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
193 for (i, &b) in name_bytes.iter().enumerate() {
194 raw.name[i] = b as c_char;
195 }
196
197 unsafe { btrfs_ioc_resize(fd.as_raw_fd(), &mut raw) }?;
198 Ok(())
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
208 fn resize_amount_cancel() {
209 assert_eq!(ResizeAmount::Cancel.to_string(), "cancel");
210 }
211
212 #[test]
213 fn resize_amount_max() {
214 assert_eq!(ResizeAmount::Max.to_string(), "max");
215 }
216
217 #[test]
218 fn resize_amount_set() {
219 assert_eq!(ResizeAmount::Set(1073741824).to_string(), "1073741824");
220 }
221
222 #[test]
223 fn resize_amount_add() {
224 assert_eq!(ResizeAmount::Add(512000000).to_string(), "+512000000");
225 }
226
227 #[test]
228 fn resize_amount_sub() {
229 assert_eq!(ResizeAmount::Sub(256000000).to_string(), "-256000000");
230 }
231
232 #[test]
235 fn resize_args_no_devid() {
236 let args = ResizeArgs::new(ResizeAmount::Max);
237 assert!(args.devid.is_none());
238 assert_eq!(args.format_name(), "max");
239 }
240
241 #[test]
242 fn resize_args_with_devid() {
243 let args = ResizeArgs::new(ResizeAmount::Add(1024)).with_devid(2);
244 assert_eq!(args.devid, Some(2));
245 assert_eq!(args.format_name(), "2:+1024");
246 }
247
248 #[test]
249 fn resize_args_set_with_devid() {
250 let args = ResizeArgs::new(ResizeAmount::Set(999)).with_devid(1);
251 assert_eq!(args.format_name(), "1:999");
252 }
253}