1use std::{io, io::{Error, ErrorKind}, mem::size_of, ops::{BitAndAssign, BitOrAssign}};
2
3use crate::{
4 btrfs::{
5 btrfs_ioctl_vol_args_v2, btrfs_ioctl_get_subvol_info_args, btrfs_ioctl_ino_lookup_args,
6 btrfs_ioctl_search_args, btrfs_ioctl_search_key,
7 btrfs_ioctl_search_header, vol_args_U2 as args_union2,
8
9 BTRFS_IOC_TREE_SEARCH, BTRFS_IOC_INO_LOOKUP, BTRFS_IOC_GET_SUBVOL_INFO,
10 BTRFS_IOC_SUBVOL_CREATE_V2, BTRFS_IOC_SNAP_DESTROY_V2, BTRFS_IOC_SNAP_CREATE_V2,
11 BTRFS_IOC_SUBVOL_GETFLAGS, BTRFS_SUBVOL_SPEC_BY_ID, BTRFS_SUBVOL_RDONLY, BTRFS_IOC_SUBVOL_SETFLAGS,
12 BTRFS_IOC_DEFAULT_SUBVOL, BTRFS_SEARCH_ARGS_BUFSIZE,
13 },
14 btrfs_tree::{
15 btrfs_root_ref, btrfs_dir_item,
16
17 BTRFS_DIR_ITEM_KEY, BTRFS_ROOT_BACKREF_KEY, BTRFS_FS_TREE_OBJECTID,
18 BTRFS_FIRST_FREE_OBJECTID, BTRFS_ROOT_TREE_OBJECTID, BTRFS_ROOT_TREE_DIR_OBJECTID
19 },
20 util::{
21 ioctl, open_fd, i8slice_to_string, ptr_to_string, subvol_name_from_str, valid_subvol_name,
22 valid_filesystem, valid_subvol, valid_destination, subvol_exists,
23 },
24};
25
26use uuid::Uuid;
27
28pub use crate::subvolume_iterator::iter::SubvolumeIterator;
30
31pub use crate::subvolume_iterator::iter::{ITERATOR_PRE_ORDER, ITERATOR_POST_ORDER};
37
38
39
40#[derive(Debug)]
41pub struct Timespec {
42 pub sec: u64,
43 pub nsec: u32,
44}
45
46#[derive(Debug)]
52pub struct SubvolInfo {
53 pub treeid: u64,
55 pub name: String,
57 pub parent_id: u64,
59 pub dirid: u64,
61 pub generation: u64,
63 pub flags: u64,
65 pub uuid: Uuid,
67 pub parent_uuid: Option<Uuid>,
72 pub received_uuid: Option<Uuid>,
77 pub ctransid: u64,
79 pub otransid: u64,
81 pub stransid: u64,
83 pub rtransid: u64,
85 pub ctime: Timespec,
87 pub otime: Timespec,
89 pub stime: Timespec,
91 pub rtime: Timespec,
93}
94
95impl SubvolInfo {
96 pub fn get(path: &str) -> io::Result<Self> {
104 let fd = open_fd(path)?;
105
106 let args = btrfs_ioctl_get_subvol_info_args::default();
107
108 ioctl(fd, BTRFS_IOC_GET_SUBVOL_INFO, &args)?;
109
110 let name = i8slice_to_string(args.name.as_slice());
111
112 let uuid = Uuid::from_bytes(args.uuid);
113
114 let parent_uuid = if Uuid::from_bytes(args.parent_uuid).is_nil() {
115 None
116 } else {
117 Some(Uuid::from_bytes(args.parent_uuid))
118 };
119
120 let received_uuid = if Uuid::from_bytes(args.received_uuid).is_nil() {
121 None
122 } else {
123 Some(Uuid::from_bytes(args.received_uuid))
124 };
125
126 Ok(Self {
127 treeid: args.treeid,
128 name,
129 parent_id: args.parent_id,
130 generation: args.generation,
131 dirid: args.dirid,
132 flags: args.flags,
133 uuid,
134 parent_uuid,
135 received_uuid,
136 ctransid: args.ctransid,
137 otransid: args.otransid,
138 stransid: args.stransid,
139 rtransid: args.rtransid,
140 ctime: Timespec {
141 sec: args.ctime.sec,
142 nsec: args.ctime.nsec
143 },
144 otime: Timespec {
145 sec: args.otime.sec,
146 nsec: args.otime.nsec,
147 },
148 stime: Timespec {
149 sec: args.stime.sec,
150 nsec: args.stime.nsec,
151 },
152 rtime: Timespec {
153 sec: args.rtime.sec,
154 nsec: args.rtime.nsec,
155 }
156 })
157 }
158}
159
160
161pub fn new(pathname: &str) -> io::Result<()> {
169 let basename = crate::util::basename(pathname);
170
171 let dirname = crate::util::dirname(pathname);
172
173 valid_subvol_name(basename)?;
174
175 let fd = open_fd(dirname)?;
176
177 let args = btrfs_ioctl_vol_args_v2 {
178 union2: args_union2 { name: subvol_name_from_str(basename) },
179 ..Default::default()
180 };
181
182 ioctl(fd, BTRFS_IOC_SUBVOL_CREATE_V2, &args)
183}
184
185
186pub fn new_at(destination: &str, name: &str) -> io::Result<()> {
195 valid_subvol_name(name)?;
196
197 valid_destination(destination)?;
198
199 let fd = open_fd(destination)?;
200
201 let args = btrfs_ioctl_vol_args_v2 {
202 union2: args_union2 { name: subvol_name_from_str(name) },
203 ..Default::default()
204 };
205
206 ioctl(fd, BTRFS_IOC_SUBVOL_CREATE_V2, &args)
207}
208
209pub fn remove(pathname: &str) -> io::Result<()> {
222 let dirname = crate::util::dirname(pathname);
223
224 let basename = crate::util::basename(pathname);
225
226 subvol_exists(dirname, basename)?;
227
228 let fd = open_fd(dirname)?;
229
230 let args = btrfs_ioctl_vol_args_v2 {
231 union2: args_union2 { name: subvol_name_from_str(basename) },
232 ..Default::default()
233 };
234
235 ioctl(fd, BTRFS_IOC_SNAP_DESTROY_V2, &args)
236}
237
238pub fn remove_at(destination: &str, name: &str) -> io::Result<()> {
254 valid_destination(destination)?;
255
256 subvol_exists(destination, name)?;
257
258 let fd = open_fd(destination)?;
259
260 let args = btrfs_ioctl_vol_args_v2 {
261 union2: args_union2 { name: subvol_name_from_str(name) },
262 ..Default::default()
263 };
264
265 ioctl(fd, BTRFS_IOC_SNAP_DESTROY_V2, &args)
266}
267
268pub fn remove_by_id(subvolid: u64) -> io::Result<()> {
278 let fd = open_fd(".")?;
279
280 let args = btrfs_ioctl_vol_args_v2 {
281 union2: args_union2 { subvolid }, flags: BTRFS_SUBVOL_SPEC_BY_ID,
282 ..Default::default()
283 };
284
285 ioctl(fd, BTRFS_IOC_SNAP_DESTROY_V2, &args)
286}
287
288pub fn remove_by_id_at(filesystem: &str, subvolid: u64) -> io::Result<()> {
297 valid_filesystem(filesystem)?;
298
299 let fd = open_fd(filesystem)?;
300
301 let args = btrfs_ioctl_vol_args_v2 {
302 union2: args_union2 { subvolid }, flags: BTRFS_SUBVOL_SPEC_BY_ID,
303 ..Default::default()
304 };
305
306 ioctl(fd, BTRFS_IOC_SNAP_DESTROY_V2, &args)
307}
308
309pub fn snapshot(snapvol: &str, pathname: &str, readonly: bool) -> io::Result<()> {
323 let basename = crate::util::basename(pathname);
324
325 let dirname = crate::util::dirname(pathname);
326
327 valid_subvol_name(basename)?;
328
329 let fd_dst = open_fd(dirname)?;
330
331 let fd_snapvol = open_fd(snapvol)?;
332
333 let mut args = btrfs_ioctl_vol_args_v2 {
334 union2: args_union2 { name: subvol_name_from_str(basename) },
335 fd: fd_snapvol as i64, ..Default::default()
336 };
337
338 if readonly {
339 args.flags = BTRFS_SUBVOL_RDONLY;
340 }
341
342 ioctl(fd_dst, BTRFS_IOC_SNAP_CREATE_V2, &args)
343}
344
345pub fn snapshot_at(snapvol: &str, destination: &str, name: &str, readonly: bool) -> io::Result<()> {
358 valid_subvol_name(name)?;
359
360 valid_destination(destination)?;
361
362 let fd_dst = open_fd(destination)?;
363
364 let fd_snapvol = open_fd(snapvol)?;
365
366 let mut args = btrfs_ioctl_vol_args_v2 {
367 union2: args_union2 { name: subvol_name_from_str(name) },
368 fd: fd_snapvol as i64,..Default::default()
369 };
370
371 if readonly {
372 args.flags = BTRFS_SUBVOL_RDONLY;
373 }
374
375 ioctl(fd_dst, BTRFS_IOC_SNAP_CREATE_V2, &args)
376}
377
378
379pub fn get_subvol_id(path: &str) -> io::Result<u64> {
387 let fd = open_fd(path)?;
388
389 let args = btrfs_ioctl_ino_lookup_args {
390 objectid: BTRFS_FIRST_FREE_OBJECTID, ..Default::default()
391 };
392
393 ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args)?;
394
395 Ok(args.treeid)
396}
397
398pub fn get_subvol_path(path: &str) -> io::Result<String> {
408 let fd = open_fd(path)?;
409 let mut id = get_subvol_id(path)?;
410 let mut full_path = String::new();
411
412 while id != BTRFS_FS_TREE_OBJECTID {
413 let search = btrfs_ioctl_search_args {
414 key: btrfs_ioctl_search_key {
415 tree_id: BTRFS_ROOT_TREE_OBJECTID,
416 min_objectid: id,
417 max_objectid: id,
418 min_offset: 0,
419 max_offset: u64::MAX,
420 min_transid: 0,
421 max_transid: u64::MAX,
422 min_type: BTRFS_ROOT_BACKREF_KEY as u32,
423 max_type: BTRFS_ROOT_BACKREF_KEY as u32,
424 nr_items: 1,
425 ..Default::default()
426 },
427 ..Default::default()
428 };
429
430 ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search)?;
431
432 if search.key.nr_items == 0 {
433 return Err(Error::new(ErrorKind::NotFound, "Subvolume not found"))
434 }
435 let header = search.buf.as_ptr() as *const btrfs_ioctl_search_header;
436 let root_ref = unsafe { header.offset(1) } as *const btrfs_root_ref;
437 let name = unsafe { root_ref.offset(1) } as *const libc::c_char;
438
439 let subv_name = if full_path.is_empty() {
441 ptr_to_string(name)
442 } else {
443 ptr_to_string(name) + "/"
444 };
445
446 id = unsafe { (*header).offset };
447
448 let lookup = btrfs_ioctl_ino_lookup_args {
449 treeid: id, objectid: unsafe { (*root_ref).dirid }, ..Default::default()
450 };
451
452 ioctl(fd, BTRFS_IOC_INO_LOOKUP, &lookup)?;
453
454 let lookup_name = i8slice_to_string(lookup.name.as_slice());
455 let suffix = full_path.split_off(0);
456
457 full_path.push_str(&(lookup_name + &subv_name + &suffix));
458 }
459
460 Ok(full_path)
461}
462
463
464pub fn get_default(path: &str) -> io::Result<u64> {
472 valid_filesystem(path)?;
473
474 let fd = open_fd(path)?;
475
476 let mut search = btrfs_ioctl_search_args {
477 key: btrfs_ioctl_search_key {
478 tree_id: BTRFS_ROOT_TREE_OBJECTID,
479 min_objectid: BTRFS_ROOT_TREE_DIR_OBJECTID,
480 max_objectid: BTRFS_ROOT_TREE_DIR_OBJECTID,
481 min_offset: 0,
482 max_offset: u64::MAX,
483 min_transid: 0,
484 max_transid: u64::MAX,
485 min_type: BTRFS_DIR_ITEM_KEY as u32,
486 max_type: BTRFS_DIR_ITEM_KEY as u32,
487 nr_items: 0,
488 ..Default::default()
489 },
490 ..Default::default()
491 };
492
493 let mut item_pos: u32 = 0;
494 let mut buf_off: usize = 0;
495
496 loop {
497 if item_pos >= search.key.nr_items {
498 search.key.nr_items = BTRFS_SEARCH_ARGS_BUFSIZE as u32;
499 item_pos = 0;
500 buf_off = 0;
501
502 ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search)?;
503
504 if search.key.nr_items == 0 {
505 return Err(Error::new(ErrorKind::NotFound, "Subvolume not found"))
506 }
507 }
508 let header = unsafe {
509 search.buf.as_ptr().add(buf_off) as *const btrfs_ioctl_search_header
510 };
511
512 if unsafe { (*header).typ } == BTRFS_DIR_ITEM_KEY as u32 {
513 let dir = unsafe { header.offset(1) } as *const btrfs_dir_item;
514 let name = unsafe { dir.offset(1) } as *const libc::c_char;
515 let name = ptr_to_string(name);
516
517 if name == "default" {
518 return Ok(unsafe { (*dir).location.objectid })
519 }
520 }
521 item_pos +=1;
522 buf_off +=size_of::<btrfs_ioctl_search_header>() + unsafe { (*header).len } as usize;
523 search.key.min_offset = unsafe { (*header).offset + 1 };
524 }
525}
526
527pub fn set_default_id(filesystem: &str, id: u64) -> io::Result<()> {
537 valid_filesystem(filesystem)?;
538
539 let fd = open_fd(filesystem)?;
540
541 ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &id)
542}
543
544pub fn set_default_subvol(subvol: &str) -> io::Result<()> {
552 valid_subvol(subvol)?;
553
554 let id = get_subvol_id(subvol)?;
555
556 let fd = open_fd(subvol)?;
557
558 ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &id)
559}
560
561pub fn get_readonly(subv: &str) -> io::Result<bool> {
569 valid_subvol(subv)?;
570
571 let fd = open_fd(subv)?;
572
573 let args: u64 = 0;
574
575 ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &args)?;
576
577 Ok(args == BTRFS_SUBVOL_RDONLY)
578}
579
580pub fn set_readonly(subv: &str, readonly: bool) -> io::Result<()> {
588 valid_subvol(subv)?;
589
590 let fd = open_fd(subv)?;
591
592 let mut flag: u64 = 0;
593
594 if readonly {
595 flag.bitor_assign(BTRFS_SUBVOL_RDONLY)
596 } else {
597 flag.bitand_assign(BTRFS_SUBVOL_RDONLY.reverse_bits())
598 }
599
600 ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flag)
601}