use crate::{
bindings::{
btrfs_dir_item, btrfs_ioctl_get_subvol_info_args, btrfs_ioctl_timespec,
btrfs_ioctl_vol_args_v2, btrfs_root_ref, vol_args_v2_volume, BTRFS_DIR_ITEM_KEY,
BTRFS_FIRST_FREE_OBJECTID, BTRFS_FS_TREE_OBJECTID, BTRFS_IOC_DEFAULT_SUBVOL,
BTRFS_IOC_GET_SUBVOL_INFO, BTRFS_IOC_SNAP_CREATE_V2, BTRFS_IOC_SNAP_DESTROY_V2,
BTRFS_IOC_SUBVOL_CREATE_V2, BTRFS_IOC_SUBVOL_GETFLAGS, BTRFS_IOC_SUBVOL_SETFLAGS,
BTRFS_ROOT_BACKREF_KEY, BTRFS_ROOT_TREE_DIR_OBJECTID, BTRFS_ROOT_TREE_OBJECTID,
BTRFS_SUBVOL_RDONLY, BTRFS_SUBVOL_SPEC_BY_ID,
},
lookup,
tree_search::fd::TreeSearch,
util::{btrfs_ioctl, subvolume_parent_and_name, vol_args_v2_name_from_str_checked},
};
use std::{
ffi::CStr,
fs::File,
io,
os::unix::{fs::OpenOptionsExt, io::AsRawFd},
path::Path,
};
use uuid::Uuid;
mod subvol_entry;
pub use subvol_entry::{walk, walk_user, Iter, IterUser, SubvolEntry};
mod subvol_info;
pub use subvol_info::{get_info, get_info_by_id, SubvolInfo};
pub type Timespec = btrfs_ioctl_timespec;
pub fn is_subvol<P: AsRef<Path>>(subvol: P) -> io::Result<bool> {
#[cfg(VERSION_3_12)]
{
let fd = File::options()
.read(true)
.custom_flags(libc::O_PATH)
.open(subvol)?;
fd::is_subvol(fd.as_raw_fd())
}
#[cfg(not(VERSION_3_12))]
{
use std::os::unix::fs::MetadataExt;
Ok(crate::fs::is_btrfs(subvol.as_ref())?
&& subvol.as_ref().metadata()?.ino() == BTRFS_FIRST_FREE_OBJECTID)
}
}
pub fn create<P: AsRef<Path>>(pathname: P) -> io::Result<()> {
let (parent, name) = subvolume_parent_and_name(pathname.as_ref())?;
let parent_fd = File::options()
.read(true)
.custom_flags(libc::O_DIRECTORY)
.open(parent)?;
fd::create(parent_fd.as_raw_fd(), name)
}
pub fn destroy<P: AsRef<Path>>(subvol: P) -> io::Result<()> {
let (parent, name) = subvolume_parent_and_name(subvol.as_ref())?;
let parent_fd = File::options()
.read(true)
.custom_flags(libc::O_DIRECTORY)
.open(parent)?;
fd::destroy(parent_fd.as_raw_fd(), name)
}
pub fn destroy_by_id<P: AsRef<Path>>(subvolid: u64, fs: P) -> io::Result<()> {
let fd = File::open(fs.as_ref())?;
fd::destroy_by_id(subvolid, fd.as_raw_fd())
}
pub mod snap {
use super::*;
pub fn create<P: AsRef<Path>>(snapvol: P, pathname: P, readonly: bool) -> io::Result<()> {
let (parent, name) = subvolume_parent_and_name(pathname.as_ref())?;
let snap_fd = File::open(snapvol.as_ref())?;
let parent_fd = File::options()
.read(true)
.custom_flags(libc::O_DIRECTORY)
.open(parent)?;
fd::create(snap_fd.as_raw_fd(), parent_fd.as_raw_fd(), name, readonly)
}
pub mod fd {
use super::*;
use std::os::unix::io::RawFd;
pub fn create(snapfd: RawFd, dirfd: RawFd, name: &str, readonly: bool) -> io::Result<()> {
let mut vol_args = btrfs_ioctl_vol_args_v2 {
fd: snapfd as i64,
inner2: vol_args_v2_volume {
name: vol_args_v2_name_from_str_checked(name)?,
},
..Default::default()
};
if readonly {
vol_args.flags |= BTRFS_SUBVOL_RDONLY
}
btrfs_ioctl(dirfd, BTRFS_IOC_SNAP_CREATE_V2, &mut vol_args)
}
}
}
pub fn get_path<P: AsRef<Path>>(treeid: u64, fs: P) -> io::Result<String> {
let fd = File::open(fs.as_ref())?;
fd::get_path(treeid, fd.as_raw_fd())
}
pub fn get_default<P: AsRef<Path>>(fs: P) -> io::Result<u64> {
let fd = File::open(fs.as_ref())?;
fd::get_default(fd.as_raw_fd())
}
pub fn set_default<P: AsRef<Path>>(id: u64, fs: P) -> io::Result<()> {
let fd = File::open(fs.as_ref())?;
fd::set_default(id, fd.as_raw_fd())
}
pub fn is_readonly<P: AsRef<Path>>(subvol: P) -> io::Result<bool> {
let fd = File::open(subvol.as_ref())?;
fd::is_readonly(fd.as_raw_fd())
}
pub fn get_flags<P: AsRef<Path>>(subvol: P) -> io::Result<u64> {
let fd = File::open(subvol.as_ref())?;
fd::get_flags(fd.as_raw_fd())
}
pub fn set_readonly<P: AsRef<Path>>(subvol: P, readonly: bool) -> io::Result<()> {
let fd = File::open(&subvol)?;
fd::set_readonly(fd.as_raw_fd(), readonly)
}
pub mod fd {
use super::*;
use std::{mem::MaybeUninit as Uninit, os::unix::io::RawFd};
pub use subvol_entry::fd::{walk, walk_user};
pub use subvol_info::fd::{get_info, get_info_by_id};
pub fn is_subvol(fd: RawFd) -> io::Result<bool> {
let mut sb = Uninit::<libc::stat>::uninit();
unsafe {
syscall!(fstat(fd, sb.as_mut_ptr()))?;
Ok(crate::fs::fd::is_btrfs(fd)?
&& (sb.assume_init().st_ino == BTRFS_FIRST_FREE_OBJECTID))
}
}
pub fn create(dirfd: RawFd, name: &str) -> io::Result<()> {
let mut vol_args = btrfs_ioctl_vol_args_v2 {
inner2: vol_args_v2_volume {
name: vol_args_v2_name_from_str_checked(name)?,
},
..Default::default()
};
btrfs_ioctl(dirfd, BTRFS_IOC_SUBVOL_CREATE_V2, &mut vol_args)
}
pub fn destroy(dirfd: RawFd, name: &str) -> io::Result<()> {
let mut vol_args = btrfs_ioctl_vol_args_v2 {
inner2: vol_args_v2_volume {
name: vol_args_v2_name_from_str_checked(name)?,
},
..Default::default()
};
btrfs_ioctl(dirfd, BTRFS_IOC_SNAP_DESTROY_V2, &mut vol_args)
}
pub fn destroy_by_id(subvolid: u64, fd: RawFd) -> io::Result<()> {
let mut vol_args = btrfs_ioctl_vol_args_v2 {
flags: BTRFS_SUBVOL_SPEC_BY_ID,
inner2: vol_args_v2_volume { subvolid },
..Default::default()
};
btrfs_ioctl(fd, BTRFS_IOC_SNAP_DESTROY_V2, &mut vol_args)
}
pub fn get_path(treeid: u64, fd: RawFd) -> io::Result<String> {
let mut pos = 1024;
let mut buf = vec![0u8; pos];
let mut ts = TreeSearch::new(fd, |key| {
key.tree_id = BTRFS_ROOT_TREE_OBJECTID;
key.min_objectid = treeid;
key.max_objectid = treeid;
key.min_type = BTRFS_ROOT_BACKREF_KEY;
key.max_type = BTRFS_ROOT_BACKREF_KEY;
key.nr_items = 1;
});
while let Some(mut item) = ts.search()? {
let item = item.next().unwrap();
let treeid = item.offset();
let rref = item.get::<&btrfs_root_ref>();
let name = item.name_as_bytes(rref);
let lookup = lookup::fd::path_as_bytes(fd, u64::from_le(rref.dirid), treeid)?;
let total_len = lookup.len() + name.len();
if total_len + (treeid != BTRFS_FS_TREE_OBJECTID) as usize > pos {
let old_len = buf.len();
buf.resize_with(old_len << 1, Default::default);
buf.copy_within(..old_len, old_len);
pos += old_len;
}
pos -= total_len;
unsafe {
lookup
.as_ptr()
.copy_to_nonoverlapping(buf.as_mut_ptr().add(pos), lookup.len());
name.as_ptr()
.copy_to_nonoverlapping(buf.as_mut_ptr().add(pos + lookup.len()), name.len());
}
if treeid != BTRFS_FS_TREE_OBJECTID {
ts.min_objectid(treeid);
pos -= 1;
buf[pos] = b'/';
} else {
return String::from_utf8(buf[pos..].to_vec())
.or(Err(io::Error::from(io::ErrorKind::InvalidData)));
}
}
error!(NotFound)
}
pub fn get_default(fd: RawFd) -> io::Result<u64> {
let mut search = TreeSearch::new(fd, |key| {
key.tree_id = BTRFS_ROOT_TREE_OBJECTID;
key.min_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
key.max_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
key.min_type = BTRFS_DIR_ITEM_KEY;
key.max_type = BTRFS_DIR_ITEM_KEY;
});
while let Some(items) = search.search_with(|key, item| {
key.min_offset = item.offset() + 1;
})? {
for item in items {
debug_assert!(item.key() == BTRFS_DIR_ITEM_KEY);
let dir = item.get::<&btrfs_dir_item>();
let name = item.name_as_bytes(dir);
if name == b"default" {
return Ok(u64::from_le(dir.location.objectid));
}
}
}
error!(NotFound)
}
pub fn set_default(mut id: u64, fd: RawFd) -> io::Result<()> {
btrfs_ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &mut id)
}
pub fn get_flags(fd: RawFd) -> io::Result<u64> {
let mut flags: u64 = 0;
btrfs_ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &mut flags)?;
Ok(flags)
}
pub fn is_readonly(fd: RawFd) -> io::Result<bool> {
let flags = get_flags(fd)?;
Ok(flags & BTRFS_SUBVOL_RDONLY != 0)
}
pub fn set_readonly(fd: RawFd, readonly: bool) -> io::Result<()> {
let mut flags = get_flags(fd)?;
if BTRFS_SUBVOL_RDONLY & flags != BTRFS_SUBVOL_RDONLY * readonly as u64 {
flags ^= BTRFS_SUBVOL_RDONLY;
btrfs_ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &mut flags)?;
}
Ok(())
}
}