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> {
42 let mut raw: btrfs_ioctl_fs_info_args = unsafe { mem::zeroed() };
43 raw.flags = u64::from(BTRFS_FS_INFO_FLAG_GENERATION);
44 unsafe { btrfs_ioc_fs_info(fd.as_raw_fd(), &raw 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(), &raw 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(), &raw const transid) }?;
79 Ok(())
80}
81
82pub fn label_get(fd: BorrowedFd) -> nix::Result<CString> {
86 let mut buf = [0i8; BTRFS_LABEL_SIZE as usize];
87 unsafe { btrfs_ioc_get_fslabel(fd.as_raw_fd(), &raw mut buf) }?;
88 let cstr = unsafe { CStr::from_ptr(buf.as_ptr()) };
89 Ok(cstr.to_owned())
92}
93
94pub fn label_set(fd: BorrowedFd, label: &CStr) -> nix::Result<()> {
103 let bytes = label.to_bytes();
104 if bytes.len() >= BTRFS_LABEL_SIZE as usize {
105 return Err(nix::errno::Errno::EINVAL);
106 }
107 let mut buf = [0i8; BTRFS_LABEL_SIZE as usize];
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(), &raw const 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 std::fmt::Display for ResizeAmount {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 match self {
133 Self::Cancel => f.write_str("cancel"),
134 Self::Max => f.write_str("max"),
135 Self::Set(n) => write!(f, "{n}"),
136 Self::Add(n) => write!(f, "+{n}"),
137 Self::Sub(n) => write!(f, "-{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 #[must_use]
154 pub fn new(amount: ResizeAmount) -> Self {
155 Self {
156 devid: None,
157 amount,
158 }
159 }
160
161 #[must_use]
162 pub fn with_devid(mut self, devid: u64) -> Self {
163 self.devid = Some(devid);
164 self
165 }
166
167 fn format_name(&self) -> String {
170 let amount = self.amount.to_string();
171 match self.devid {
172 Some(devid) => format!("{devid}:{amount}"),
173 None => amount,
174 }
175 }
176}
177
178pub fn resize(fd: BorrowedFd, args: ResizeArgs) -> nix::Result<()> {
183 let name = args.format_name();
184 let name_bytes = name.as_bytes();
185
186 if name_bytes.len() >= 4088 {
191 return Err(nix::errno::Errno::EINVAL);
192 }
193
194 let mut raw: btrfs_ioctl_vol_args = unsafe { mem::zeroed() };
195 for (i, &b) in name_bytes.iter().enumerate() {
196 raw.name[i] = b as c_char;
197 }
198
199 unsafe { btrfs_ioc_resize(fd.as_raw_fd(), &raw const raw) }?;
200 Ok(())
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
210 fn resize_amount_cancel() {
211 assert_eq!(ResizeAmount::Cancel.to_string(), "cancel");
212 }
213
214 #[test]
215 fn resize_amount_max() {
216 assert_eq!(ResizeAmount::Max.to_string(), "max");
217 }
218
219 #[test]
220 fn resize_amount_set() {
221 assert_eq!(ResizeAmount::Set(1073741824).to_string(), "1073741824");
222 }
223
224 #[test]
225 fn resize_amount_add() {
226 assert_eq!(ResizeAmount::Add(512000000).to_string(), "+512000000");
227 }
228
229 #[test]
230 fn resize_amount_sub() {
231 assert_eq!(ResizeAmount::Sub(256000000).to_string(), "-256000000");
232 }
233
234 #[test]
237 fn resize_args_no_devid() {
238 let args = ResizeArgs::new(ResizeAmount::Max);
239 assert!(args.devid.is_none());
240 assert_eq!(args.format_name(), "max");
241 }
242
243 #[test]
244 fn resize_args_with_devid() {
245 let args = ResizeArgs::new(ResizeAmount::Add(1024)).with_devid(2);
246 assert_eq!(args.devid, Some(2));
247 assert_eq!(args.format_name(), "2:+1024");
248 }
249
250 #[test]
251 fn resize_args_set_with_devid() {
252 let args = ResizeArgs::new(ResizeAmount::Set(999)).with_devid(1);
253 assert_eq!(args.format_name(), "1:999");
254 }
255}