use crate::fs_primitive_error::PrimitiveError;
use std::ffi::OsStr;
use std::fs::File;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::AsRawFd;
use std::path::Path;
const BTRFS_IOCTL_MAGIC: u32 = 0x94;
const IOC_SIZE_SHIFT: u32 = 16;
const IOC_TYPE_SHIFT: u32 = 8;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct IocLayout {
pub write: u32,
pub read: u32,
pub direction_shift: u32,
}
pub const GENERIC_IOC_LAYOUT: IocLayout = IocLayout { write: 1, read: 2, direction_shift: 30 };
pub const WIDE_DIRECTION_IOC_LAYOUT: IocLayout = IocLayout { write: 4, read: 2, direction_shift: 29 };
pub const PARISC_IOC_LAYOUT: IocLayout = IocLayout { write: 2, read: 1, direction_shift: 30 };
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64", target_arch = "mips", target_arch = "mips32r6", target_arch = "mips64", target_arch = "mips64r6", target_arch = "sparc", target_arch = "sparc64"))]
pub const HOST_IOC_LAYOUT: IocLayout = WIDE_DIRECTION_IOC_LAYOUT;
#[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64", target_arch = "mips", target_arch = "mips32r6", target_arch = "mips64", target_arch = "mips64r6", target_arch = "sparc", target_arch = "sparc64")))]
pub const HOST_IOC_LAYOUT: IocLayout = GENERIC_IOC_LAYOUT;
pub const VOLUME_ARGUMENT_BYTES: usize = 4096;
const V2_NAME_CAPACITY: usize = 4040;
const V1_NAME_CAPACITY: usize = 4088;
const NAME_MAX: usize = 255;
pub const fn ioc_write_with(layout: IocLayout, number: u32, size: usize) -> u32 {
return (layout.write << layout.direction_shift) | ((size as u32) << IOC_SIZE_SHIFT) | (BTRFS_IOCTL_MAGIC << IOC_TYPE_SHIFT) | number;
}
pub const fn ioc_read_write_with(layout: IocLayout, number: u32, size: usize) -> u32 {
return ((layout.write | layout.read) << layout.direction_shift) | ((size as u32) << IOC_SIZE_SHIFT) | (BTRFS_IOCTL_MAGIC << IOC_TYPE_SHIFT) | number;
}
pub const fn ioc_write(number: u32, size: usize) -> libc::Ioctl {
return ioc_write_with(HOST_IOC_LAYOUT, number, size) as libc::Ioctl;
}
pub const BTRFS_IOC_SUBVOL_CREATE: libc::Ioctl = ioc_write(14, VOLUME_ARGUMENT_BYTES);
pub const BTRFS_IOC_SNAP_DESTROY: libc::Ioctl = ioc_write(15, VOLUME_ARGUMENT_BYTES);
pub const BTRFS_IOC_SNAP_CREATE_V2: libc::Ioctl = ioc_write(23, VOLUME_ARGUMENT_BYTES);
pub const BTRFS_IOC_SNAP_DESTROY_V2: libc::Ioctl = ioc_write(63, VOLUME_ARGUMENT_BYTES);
pub const BTRFS_IOC_INO_LOOKUP: libc::Ioctl = ioc_read_write_with(HOST_IOC_LAYOUT, 18, VOLUME_ARGUMENT_BYTES) as libc::Ioctl;
const BTRFS_SUBVOL_SPEC_BY_ID: u64 = 1 << 4;
const BTRFS_FIRST_FREE_OBJECTID: u64 = 256;
#[repr(C)]
pub struct VolArgs {
pub fd: i64,
pub name: [u8; V1_NAME_CAPACITY],
}
#[repr(C)]
pub struct VolArgsV2 {
pub fd: i64,
pub transid: u64,
pub flags: u64,
pub union_bytes: [u8; 32],
pub name: [u8; V2_NAME_CAPACITY],
}
fn fill_name<const N: usize>(buffer: &mut [u8; N], name: &OsStr, operation: &'static str, parent: &Path) -> Result<(), PrimitiveError> {
let bytes: &[u8] = name.as_bytes();
let refused: bool = bytes.is_empty()
|| bytes.len() > NAME_MAX
|| bytes.len() >= N
|| bytes.contains(&b'/')
|| bytes.contains(&0)
|| bytes == b"."
|| bytes == b"..";
if refused {
return Err(PrimitiveError { operation, path: parent.join(name), errno: libc::EINVAL });
}
buffer[..bytes.len()].copy_from_slice(bytes);
return Ok(());
}
fn open_directory(directory: &Path, operation: &'static str) -> Result<File, PrimitiveError> {
return File::open(directory)
.map_err(|error| { return PrimitiveError { operation, path: directory.to_path_buf(), errno: error.raw_os_error().unwrap_or(0) }; });
}
pub fn subvolume_create(parent: &Path, name: &OsStr) -> Result<(), PrimitiveError> {
let operation: &'static str = "BTRFS_IOC_SUBVOL_CREATE";
let directory: File = open_directory(parent, operation)?;
let mut arguments: VolArgs = unsafe { std::mem::zeroed() };
fill_name(&mut arguments.name, name, operation, parent)?;
let result: libc::c_int = unsafe { libc::ioctl(directory.as_raw_fd(), BTRFS_IOC_SUBVOL_CREATE, &arguments) };
if result != 0 {
return Err(PrimitiveError::last_os_error(operation, parent.join(name).as_path()));
}
return Ok(());
}
pub fn snapshot_create(source: &Path, parent: &Path, name: &OsStr) -> Result<(), PrimitiveError> {
let operation: &'static str = "BTRFS_IOC_SNAP_CREATE_V2";
let directory: File = open_directory(parent, operation)?;
let origin: File = open_directory(source, operation)?;
let mut arguments: VolArgsV2 = unsafe { std::mem::zeroed() };
arguments.fd = i64::from(origin.as_raw_fd());
fill_name(&mut arguments.name, name, operation, parent)?;
let result: libc::c_int = unsafe { libc::ioctl(directory.as_raw_fd(), BTRFS_IOC_SNAP_CREATE_V2, &arguments) };
if result != 0 {
return Err(PrimitiveError::last_os_error(operation, parent.join(name).as_path()));
}
return Ok(());
}
pub fn snapshot_destroy(parent: &Path, name: &OsStr) -> Result<(), PrimitiveError> {
let operation: &'static str = "BTRFS_IOC_SNAP_DESTROY";
let directory: File = open_directory(parent, operation)?;
let mut arguments: VolArgs = unsafe { std::mem::zeroed() };
fill_name(&mut arguments.name, name, operation, parent)?;
let result: libc::c_int = unsafe { libc::ioctl(directory.as_raw_fd(), BTRFS_IOC_SNAP_DESTROY, &arguments) };
if result != 0 {
return Err(PrimitiveError::last_os_error(operation, parent.join(name).as_path()));
}
return Ok(());
}
pub fn subvolume_id(held: &File, path: &Path) -> Result<u64, PrimitiveError> {
let mut arguments: [u64; VOLUME_ARGUMENT_BYTES / 8] = [0; VOLUME_ARGUMENT_BYTES / 8];
arguments[1] = BTRFS_FIRST_FREE_OBJECTID;
let result: libc::c_int = unsafe { libc::ioctl(held.as_raw_fd(), BTRFS_IOC_INO_LOOKUP, arguments.as_mut_ptr()) };
if result != 0 {
return Err(PrimitiveError::last_os_error("BTRFS_IOC_INO_LOOKUP", path));
}
return Ok(arguments[0]);
}
pub fn snapshot_destroy_by_id(directory: &File, subvolume: u64, path: &Path) -> Result<(), PrimitiveError> {
let operation: &'static str = "BTRFS_IOC_SNAP_DESTROY_V2";
let mut arguments: VolArgsV2 = unsafe { std::mem::zeroed() };
arguments.flags = BTRFS_SUBVOL_SPEC_BY_ID;
arguments.name[..8].copy_from_slice(&subvolume.to_ne_bytes());
let result: libc::c_int = unsafe { libc::ioctl(directory.as_raw_fd(), BTRFS_IOC_SNAP_DESTROY_V2, &arguments) };
if result != 0 {
return Err(PrimitiveError::last_os_error(operation, path));
}
return Ok(());
}
pub fn snapshot_destroy_in(parent: &File, name: &OsStr, parent_path: &Path) -> Result<(), PrimitiveError> {
let operation: &'static str = "BTRFS_IOC_SNAP_DESTROY";
let mut arguments: VolArgs = unsafe { std::mem::zeroed() };
fill_name(&mut arguments.name, name, operation, parent_path)?;
let result: libc::c_int = unsafe { libc::ioctl(parent.as_raw_fd(), BTRFS_IOC_SNAP_DESTROY, &arguments) };
if result != 0 {
return Err(PrimitiveError::last_os_error(operation, parent_path.join(name).as_path()));
}
return Ok(());
}
pub fn split_parent(path: &Path, operation: &'static str) -> Result<(std::path::PathBuf, std::ffi::OsString), PrimitiveError> {
let refused = || { return PrimitiveError { operation, path: path.to_path_buf(), errno: libc::EINVAL }; };
let name: &OsStr = path.file_name().ok_or_else(refused)?;
let parent: &Path = path.parent().ok_or_else(refused)?;
let parent_path: std::path::PathBuf = if parent.as_os_str().is_empty() { std::path::PathBuf::from(".") } else { parent.to_path_buf() };
return Ok((parent_path, name.to_os_string()));
}
pub fn subvolume_create_at(path: &Path) -> Result<(), PrimitiveError> {
let (parent, name) = split_parent(path, "BTRFS_IOC_SUBVOL_CREATE")?;
return subvolume_create(parent.as_path(), name.as_os_str());
}
pub fn subvolume_destroy_at(path: &Path) -> Result<(), PrimitiveError> {
let (parent, name) = split_parent(path, "BTRFS_IOC_SNAP_DESTROY")?;
return snapshot_destroy(parent.as_path(), name.as_os_str());
}
pub fn snapshot_create_at(source: &Path, destination: &Path) -> Result<(), PrimitiveError> {
let (parent, name) = split_parent(destination, "BTRFS_IOC_SNAP_CREATE_V2")?;
return snapshot_create(source, parent.as_path(), name.as_os_str());
}
#[cfg(test)]
#[path = "fs_btrfs_ioctl_tests.rs"]
mod tests;