feat(rust/wrappers): init BcachefsHandle

We propose a simple low-level wrapper which can perform various subvolume-related operations
as an example for the API surface.

This will be used in an upcoming commit to migrate the subvolume CLI fully to Rust.

The API design is the following:

- `BcachefsHandle` is meant as a low level handle to carry around whenever you need a filesystem handle
  to send ioctl to.
- it possess type-safe operations

Type safe operations are handled by having type safe wrappers for ioctl commands
*and* their payloads.

We assume that all ioctl payloads only use *one* argument, this can easily be changed if needed.

When the handle goes out of scope, we automatically close it à la C++ RAII.

Signed-off-by: Ryan Lahfa <bcachefs@lahfa.xyz>
This commit is contained in:
Ryan Lahfa 2024-01-27 04:23:20 +01:00
parent 930646e8dd
commit 9282cb953c
3 changed files with 105 additions and 0 deletions

View File

@ -1,3 +1,4 @@
mod wrappers;
mod commands;
mod key;

103
src/wrappers/handle.rs Normal file
View File

@ -0,0 +1,103 @@
use std::{path::Path, os::unix::ffi::OsStrExt, ffi::CString};
use bch_bindgen::c::{bchfs_handle, BCH_IOCTL_SUBVOLUME_CREATE, BCH_IOCTL_SUBVOLUME_DESTROY, bch_ioctl_subvolume, bcache_fs_open, BCH_SUBVOL_SNAPSHOT_CREATE, bcache_fs_close};
use errno::Errno;
/// A handle to a bcachefs filesystem
/// This can be used to send [`libc::ioctl`] to the underlying filesystem.
pub(crate) struct BcachefsHandle {
inner: bchfs_handle
}
impl BcachefsHandle {
/// Opens a bcachefs filesystem and returns its handle
/// TODO(raitobezarius): how can this not be faillible?
pub(crate) unsafe fn open<P: AsRef<Path>>(path: P) -> Self {
let path = CString::new(path.as_ref().as_os_str().as_bytes()).expect("Failed to cast path into a C-style string");
Self {
inner: bcache_fs_open(path.as_ptr())
}
}
}
/// I/O control commands that can be sent to a bcachefs filesystem
/// Those are non-exhaustive
#[repr(u64)]
#[non_exhaustive]
pub enum BcachefsIoctl {
SubvolumeCreate = BCH_IOCTL_SUBVOLUME_CREATE,
SubvolumeDestroy = BCH_IOCTL_SUBVOLUME_DESTROY,
}
/// I/O control commands payloads
#[non_exhaustive]
pub enum BcachefsIoctlPayload {
Subvolume(bch_ioctl_subvolume),
}
impl From<&BcachefsIoctlPayload> for *const libc::c_void {
fn from(value: &BcachefsIoctlPayload) -> Self {
match value {
BcachefsIoctlPayload::Subvolume(p) => p as *const _ as *const libc::c_void
}
}
}
impl BcachefsHandle {
/// Type-safe [`libc::ioctl`] for bcachefs filesystems
pub fn ioctl(&self, request: BcachefsIoctl, payload: &BcachefsIoctlPayload) -> Result<(), Errno> {
let payload_ptr: *const libc::c_void = payload.into();
let ret = unsafe { libc::ioctl(self.inner.ioctl_fd, request as u64, payload_ptr) };
if ret == -1 {
Err(errno::errno())
} else {
Ok(())
}
}
/// Create a subvolume for this bcachefs filesystem
/// at the given path
pub fn create_subvolume<P: AsRef<Path>>(&self, dst: P) -> Result<(), Errno> {
let dst = CString::new(dst.as_ref().as_os_str().as_bytes()).expect("Failed to cast destination path for subvolume in a C-style string");
self.ioctl(BcachefsIoctl::SubvolumeCreate, &BcachefsIoctlPayload::Subvolume(bch_ioctl_subvolume {
dirfd: libc::AT_FDCWD,
mode: 0o777,
dst_ptr: dst.as_ptr() as u64,
..Default::default()
}))
}
/// Delete the subvolume at the given path
/// for this bcachefs filesystem
pub fn delete_subvolume<P: AsRef<Path>>(&self, dst: P) -> Result<(), Errno> {
let dst = CString::new(dst.as_ref().as_os_str().as_bytes()).expect("Failed to cast destination path for subvolume in a C-style string");
self.ioctl(BcachefsIoctl::SubvolumeDestroy, &BcachefsIoctlPayload::Subvolume(bch_ioctl_subvolume {
dirfd: libc::AT_FDCWD,
mode: 0o777,
dst_ptr: dst.as_ptr() as u64,
..Default::default()
}))
}
/// Snapshot a subvolume for this bcachefs filesystem
/// at the given path
pub fn snapshot_subvolume<P: AsRef<Path>>(&self, extra_flags: u32, src: P, dst: P) -> Result<(), Errno> {
let src = CString::new(src.as_ref().as_os_str().as_bytes()).expect("Failed to cast source path for subvolume in a C-style string");
let dst = CString::new(dst.as_ref().as_os_str().as_bytes()).expect("Failed to cast destination path for subvolume in a C-style string");
self.ioctl(BcachefsIoctl::SubvolumeCreate, &BcachefsIoctlPayload::Subvolume(bch_ioctl_subvolume {
flags: BCH_SUBVOL_SNAPSHOT_CREATE | extra_flags,
dirfd: libc::AT_FDCWD,
mode: 0o777,
src_ptr: src.as_ptr() as u64,
dst_ptr: dst.as_ptr() as u64,
..Default::default()
}))
}
}
impl Drop for BcachefsHandle {
fn drop(&mut self) {
unsafe { bcache_fs_close(self.inner) };
}
}

1
src/wrappers/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod handle;