]> git.sesse.net Git - bcachefs-tools-debian/commitdiff
feat(rust/wrappers): init `BcachefsHandle`
authorRyan Lahfa <bcachefs@lahfa.xyz>
Sat, 27 Jan 2024 03:23:20 +0000 (04:23 +0100)
committerRyan Lahfa <bcachefs@lahfa.xyz>
Sat, 27 Jan 2024 04:15:19 +0000 (05:15 +0100)
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>
src/bcachefs.rs
src/wrappers/handle.rs [new file with mode: 0644]
src/wrappers/mod.rs [new file with mode: 0644]

index 95f5e1f0552dfcc1d0477a239ac65455ac129c7b..62bfdbb7b851a37cb2c9239256e36d8d0614ed5d 100644 (file)
@@ -1,3 +1,4 @@
+mod wrappers;
 mod commands;
 mod key;
 
diff --git a/src/wrappers/handle.rs b/src/wrappers/handle.rs
new file mode 100644 (file)
index 0000000..9dd6618
--- /dev/null
@@ -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) };
+    }
+}
diff --git a/src/wrappers/mod.rs b/src/wrappers/mod.rs
new file mode 100644 (file)
index 0000000..b267960
--- /dev/null
@@ -0,0 +1 @@
+pub mod handle;