libbtrfs/
subvolume.rs

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
28/** Subvolume iterator */
29pub use crate::subvolume_iterator::iter::SubvolumeIterator;
30
31/*
32 * NOTE: for +nightly doc (docs.rs) SubvolumeIterator was not found in scope for doc comments
33 *       -- may work in the future 
34 */
35/* Ordering flag for SubvolumeIterator */
36pub 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/**
47 * Information about a fs tree root.
48 *
49 * The [`SubvolInfo::get`] methood returns a `SubvolInfo` struct for the given path
50 */
51#[derive(Debug)]
52pub struct SubvolInfo {
53    /** Id of this subvolume */
54    pub treeid: u64,
55    /** Name of this subvolume */
56    pub name: String,
57    /** Id of the subvolume which contains this subvolume */
58    pub parent_id: u64,
59    /** Inode number of the directory which contains this subvolume */
60    pub dirid: u64,
61    /** Latest transaction id of this subvolume */
62    pub generation: u64,
63    /** Flags for this subvolume */
64    pub flags: u64,
65    /** UUID of this subvolume (not filesystem) */
66    pub uuid: Uuid,
67    /** 
68     * UUID of the subvolume of which this subvolume is a snapshot.
69     * None for a non-snapshot subvolume
70     */
71    pub parent_uuid: Option<Uuid>,
72    /**
73     * UUID of the subvolume from which this subvolume was received. 
74     * None for non-received subvolume
75     */
76    pub received_uuid: Option<Uuid>,
77    /** transaction ID indicating when change happened */
78    pub ctransid: u64,
79    /** transaction ID indicating when create happened */
80    pub otransid: u64,
81    /** transaction ID indicating when send happened */
82    pub stransid: u64,
83    /** transaction ID indicating when receive happened */
84    pub rtransid: u64,
85    /** Time corresponding to ctransid */
86    pub ctime: Timespec,
87    /** Time corresponding to otransid */
88    pub otime: Timespec,
89    /** Time corresponding to stransid */
90    pub stime: Timespec,
91    /** Time corresponding to rtransid */
92    pub rtime: Timespec,
93}
94
95impl SubvolInfo {
96    /**
97     * Returns a SubvolInfo struct for @path
98     *
99     * `@path`: path to a btrfs subvolume. Does not need to be a subvolume root.
100     *
101     * Returns [`SubvolInfo`] in a Ok if successful or an [`io::Error`] in the case of an error
102     */
103    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
161/**
162 * Adds a new btrfs subvolume
163 *
164 * The first argument is the name for the new subvol and can be a path
165 *
166 * Returns unit type Ok if successful or ['io::Error'] in the case of an error
167 * */
168pub 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
186/**
187 * Adds a new btrfs subvolume
188 *
189 * `@destination`: Path where the new subvolume will go <BR>
190 * `@name`: Name of new subvolume
191 *
192 * Returns unit type Ok if successful or [`io::Error`] in the case of an error
193 */
194pub 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
209/**
210 * Remove a btrfs subvolume
211 *
212 * The first argument is the path the to subvolume
213 *
214 * This functions removes btrfs subvolumes as well as snapshots
215 *
216 * This operation requires sudo privileges unless the filesystem is mounted with
217 * 'user_subvol_rm_allowed'
218 *
219 * For more information on btrfs mount options see <https://btrfs.readthedocs.io/en/latest/ch-mount-options.html>
220 * */
221pub 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
238/**
239 * Remove a btrfs subvolume
240 *
241 * Removes btrfs subvolumes as well as snapshots
242 *
243 * `@destination`: Directory that the subvolume is in <BR>
244 * `@name`: Name of the subvolume to be deleted
245 *
246 * Returns unit type Ok if successful or [`io::Error`] in the case of an error
247 *
248 * This operation requires sudo privileges unless the filesystem is mounted with
249 * 'user_subvol_rm_allowed'
250 *
251 * For more information on btrfs mount options see <https://btrfs.readthedocs.io/en/latest/ch-mount-options.html>
252 */
253pub 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
268/**
269 * Remove a btrfs subvolume by its subvolume id
270 *
271 * `@subvolid`: Subvolume id (treeid) of the subvolume to be removed
272 *
273 * Returns unit type Ok if successful or [`io::Error`] in the case of an error
274 *
275 * This function must be called from the same filesystem as the subvolume to be removed
276 */
277pub 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
288/**
289 * Remove a btrfs subvolume by its subvolume id
290 *
291 * `@filesystem`: Path to the filesystem that contains the subvolume to be removed <BR>
292 * `@subvolid`: Subvolume id (treeid) of the subvolume to be removed
293 *
294 * Returns unit type Ok if successful or [`io::Error`] in the case of an error
295 */
296pub 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
309/**
310 * Add a btrfs snapshot 
311 *
312 * First arguement is the subvolume to snapshot. This can be any file or directory within the
313 * subvolume
314 *
315 * The second argument is where the snapshot should go. this can be a path
316 * 
317 * The Third argument is a boolian which indicating if the snapshot should be readonly or not
318 *
319 * Note: sudo privileges are required if the file or or directory to snapshot (snapvol) is owned
320 * by root. Otherwise sudo privileges are not required
321 * */
322pub 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
345/**
346 * Add a btrfs snapshot 
347 *
348 * `@snapvol`: Path to the subvolume in which to snapshot. Does not need to be a subvolume root <BR>
349 * `@destination`: Path where the new snapshot will go <BR>
350 * `@name`: Name of the new snapshot
351 * 
352 * Returns unit type Ok if successful or [`io::Error`] in the case of an error
353 *
354 * Note: sudo privileges are required if the file or or directory to snapshot (snapvol) is owned
355 * by root. Otherwise sudo privileges are not required
356 */
357pub 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
379/**
380 * Get the subvolume id of a given path
381 *
382 * `@path`: Path to the subvolume. Does not need to be a subvolume root.
383 *
384 * Returns the subvolume id in a Ok if successful or [`io::Error`] in the case of an error
385 */
386pub 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
398/**
399 * Get the full subvolume path to the filesystem root
400 *
401 * `@path`: Path to the subvolume. Does not need to be a subvolume root.
402 *
403 * Returns the full subvolume path as a String in a Ok or [`io::Error`] in the case of an error
404 *
405 * Note: Sudo privileges are required 
406 */
407pub 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        // must be before setting the id
440        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
464/**
465 * Get the default subvolume for the filesystem
466 *
467 * `@path`: Path to subvolume
468 *
469 * Returns the subvolume id in a Ok or [`io::Error`] in the case of an error
470 */
471pub 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
527/**
528 * Set the default subvolume for the filesystem by id
529 *
530 * `@filesystem`: Any valid path in the filesystem in which to set the default subvolume <BR>
531 * `@id`: Subvolume id (treeid) of the subvolume to be the new default. 0 or 5 will set the default
532 * subvolume to the filesystem root (5)
533 *
534 * Returns unit type Ok if successful or [`io::Error`] in the case of an error
535 */
536pub 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
544/**
545 * Set the default subvolume for the filesystem by path
546 *
547 * `@subvolid`: Path to subvolume. Must be a subvolume root
548 *
549 * Returns unit type Ok if successful or [`io::Error`] in the case of an error
550 */
551pub 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
561/**
562 * Gets the readonly flag for a subvolume
563 *
564 * `@subv`: Path to subvolume to get the readonly flag. Must be a subvolume root.
565 *
566 * Returns true or false in a Ok if successful or [`io::Error`] in the case of an error
567 */
568pub 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
580/**
581 * Sets the readonly flag for a subvolume
582 *
583 * `@subv`: Path to subvolume to set the the readonly flag of. Must be a subvolume root.
584 *
585 * Returns unit type Ok if successful or [`io::Error`] in the case of an error
586 */
587pub 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}