feature(loopdev): add native loop device implementation

The previous loop device implementation required bindgen for no reason,
making cross-compilation difficult.

Signed-off-by: Ariadne Conill <ariadne@ariadne.space>
This commit is contained in:
Ariadne Conill
2024-06-25 23:51:06 -07:00
parent 88a62441b1
commit 18965cb723
2 changed files with 369 additions and 0 deletions

15
crates/loopdev/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "krata-loopdev"
description = "Loop device handling library for krata"
license.workspace = true
version.workspace = true
homepage.workspace = true
repository.workspace = true
edition = "2021"
resolver = "2"
[lib]
name = "krataloopdev"
[dependencies]
libc.workspace = true

354
crates/loopdev/src/lib.rs Normal file
View File

@ -0,0 +1,354 @@
use libc::{c_int, ioctl};
use std::{
fs::{File, OpenOptions},
io,
os::fd::{AsRawFd, IntoRawFd, RawFd},
os::unix::fs::MetadataExt,
path::{Path, PathBuf},
};
#[cfg(all(not(target_os = "android"), not(target_env = "musl")))]
type IoctlRequest = libc::c_ulong;
#[cfg(any(target_os = "android", target_env = "musl"))]
type IoctlRequest = libc::c_int;
const LOOP_CONTROL: &str = "/dev/loop-control";
const LOOP_PREFIX: &str = "/dev/loop";
/// Loop control interface IOCTLs.
const LOOP_CTL_GET_FREE: IoctlRequest = 0x4C82;
/// Loop device flags.
const LO_FLAGS_READ_ONLY: u32 = 1;
const LO_FLAGS_AUTOCLEAR: u32 = 4;
const LO_FLAGS_PARTSCAN: u32 = 8;
const LO_FLAGS_DIRECT_IO: u32 = 16;
/// Loop device IOCTLs.
const LOOP_SET_FD: IoctlRequest = 0x4C00;
const LOOP_CLR_FD: IoctlRequest = 0x4C01;
const LOOP_SET_STATUS64: IoctlRequest = 0x4C04;
const LOOP_SET_CAPACITY: IoctlRequest = 0x4C07;
const LOOP_SET_DIRECT_IO: IoctlRequest = 0x4C08;
/// Interface which wraps a handle to the loop control device.
#[derive(Debug)]
pub struct LoopControl {
dev_file: File,
}
/// Translate ioctl results to errors if appropriate.
fn translate_error(ret: i32) -> io::Result<i32> {
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret)
}
}
impl LoopControl {
/// Open the loop control device.
///
/// # Errors
///
/// Any errors from physically opening the loop control device are
/// bubbled up.
pub fn open() -> io::Result<Self> {
Ok(Self {
dev_file: OpenOptions::new()
.read(true)
.write(true)
.open(LOOP_CONTROL)?,
})
}
/// Requests the next available loop device from the kernel and opens it.
///
/// # Examples
///
/// ```no_run
/// use krataloopdev::LoopControl;
/// let lc = LoopControl::open().unwrap();
/// let ld = lc.next_free().unwrap();
/// println!("{}", ld.path().unwrap().display());
/// ```
///
/// # Errors
///
/// Any errors from opening the loop device are bubbled up.
pub fn next_free(&self) -> io::Result<LoopDevice> {
let dev_num = translate_error(unsafe {
ioctl(
self.dev_file.as_raw_fd() as c_int,
LOOP_CTL_GET_FREE as IoctlRequest,
)
})?;
LoopDevice::open(format!("{}{}", LOOP_PREFIX, dev_num))
}
}
/// Interface to a loop device itself, e.g. `/dev/loop0`.
#[derive(Debug)]
pub struct LoopDevice {
device: File,
}
impl AsRawFd for LoopDevice {
fn as_raw_fd(&self) -> RawFd {
self.device.as_raw_fd()
}
}
impl IntoRawFd for LoopDevice {
fn into_raw_fd(self) -> RawFd {
self.device.into_raw_fd()
}
}
impl LoopDevice {
/// Opens a loop device.
///
/// # Errors
///
/// Any errors from opening the underlying physical loop device are bubbled up.
pub fn open<P: AsRef<Path>>(dev: P) -> io::Result<Self> {
Ok(Self {
device: OpenOptions::new().read(true).write(true).open(dev)?,
})
}
/// Attach a loop device to a file with the given options.
pub fn with(&self) -> AttachOptions<'_> {
AttachOptions {
device: self,
info: LoopInfo64::default(),
}
}
/// Enables or disables Direct I/O mode.
pub fn set_direct_io(&self, direct_io: bool) -> io::Result<()> {
translate_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_DIRECT_IO as IoctlRequest,
if direct_io { 1 } else { 0 },
)
})?;
Ok(())
}
/// Attach the loop device to a fully-mapped file.
pub fn attach_file<P: AsRef<Path>>(&self, backing_file: P) -> io::Result<()> {
let info = LoopInfo64 {
..Default::default()
};
Self::attach_with_loop_info(self, backing_file, info)
}
/// Attach the loop device to a file with `LoopInfo64`.
fn attach_with_loop_info(
&self,
backing_file: impl AsRef<Path>,
info: LoopInfo64,
) -> io::Result<()> {
let write_access = (info.lo_flags & LO_FLAGS_READ_ONLY) == 0;
let bf = OpenOptions::new()
.read(true)
.write(write_access)
.open(backing_file)?;
self.attach_fd_with_loop_info(bf, info)
}
/// Attach the loop device to a file descriptor with `LoopInfo64`.
fn attach_fd_with_loop_info(&self, bf: impl AsRawFd, info: LoopInfo64) -> io::Result<()> {
translate_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_FD as IoctlRequest,
bf.as_raw_fd() as c_int,
)
})?;
let result = unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_STATUS64 as IoctlRequest,
&info,
)
};
match translate_error(result) {
Err(err) => {
let _detach_err = self.detach();
Err(err)
}
Ok(_) => Ok(()),
}
}
/// Get the path for the loop device.
pub fn path(&self) -> Option<PathBuf> {
let mut p = PathBuf::from("/proc/self/fd");
p.push(self.device.as_raw_fd().to_string());
std::fs::read_link(&p).ok()
}
/// Detach a loop device.
pub fn detach(&self) -> io::Result<()> {
translate_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_CLR_FD as IoctlRequest,
0,
)
})?;
Ok(())
}
/// Update a loop device's capacity.
pub fn set_capacity(&self) -> io::Result<()> {
translate_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_CAPACITY as IoctlRequest,
0,
)
})?;
Ok(())
}
/// Return the major device node number.
pub fn major(&self) -> io::Result<u32> {
self.device
.metadata()
.map(|m| unsafe { libc::major(m.rdev()) })
.map(|m| m as u32)
}
/// Return the minor device node number.
pub fn minor(&self) -> io::Result<u32> {
self.device
.metadata()
.map(|m| unsafe { libc::minor(m.rdev()) })
.map(|m| m as u32)
}
}
#[allow(dead_code)]
#[derive(Clone)]
pub struct LoopInfo64 {
lo_device: u64,
lo_inode: u64,
lo_rdevice: u64,
lo_offset: u64,
lo_sizelimit: u64,
lo_number: u32,
lo_encrypt_type: u32,
lo_encrypt_key_size: u32,
lo_flags: u32,
lo_file_name: [u8; 64],
lo_crypt_name: [u8; 64],
lo_encrypt_key: [u8; 32],
lo_init: [u64; 2],
}
impl Default for LoopInfo64 {
fn default() -> Self {
Self {
lo_device: 0,
lo_inode: 0,
lo_rdevice: 0,
lo_offset: 0,
lo_sizelimit: 0,
lo_number: 0,
lo_encrypt_type: 0,
lo_encrypt_key_size: 0,
lo_flags: 0,
lo_file_name: [0; 64],
lo_crypt_name: [0; 64],
lo_encrypt_key: [0; 32],
lo_init: [0, 2],
}
}
}
#[must_use]
pub struct AttachOptions<'d> {
device: &'d LoopDevice,
info: LoopInfo64,
}
impl AttachOptions<'_> {
pub fn offset(mut self, offset: u64) -> Self {
self.info.lo_offset = offset;
self
}
pub fn size_limit(mut self, size_limit: u64) -> Self {
self.info.lo_sizelimit = size_limit;
self
}
pub fn read_only(mut self, read_only: bool) -> Self {
if read_only {
self.info.lo_flags |= LO_FLAGS_READ_ONLY;
} else {
self.info.lo_flags &= !LO_FLAGS_READ_ONLY;
}
self
}
pub fn autoclear(mut self, autoclear: bool) -> Self {
if autoclear {
self.info.lo_flags |= LO_FLAGS_AUTOCLEAR;
} else {
self.info.lo_flags &= !LO_FLAGS_AUTOCLEAR;
}
self
}
pub fn part_scan(mut self, part_scan: bool) -> Self {
if part_scan {
self.info.lo_flags |= LO_FLAGS_PARTSCAN;
} else {
self.info.lo_flags &= !LO_FLAGS_PARTSCAN;
}
self
}
pub fn set_direct_io(mut self, direct_io: bool) -> Self {
if direct_io {
self.info.lo_flags |= LO_FLAGS_DIRECT_IO;
} else {
self.info.lo_flags &= !LO_FLAGS_DIRECT_IO;
}
self
}
pub fn direct_io(&self) -> bool {
if (self.info.lo_flags & LO_FLAGS_DIRECT_IO) == LO_FLAGS_DIRECT_IO {
true
} else {
false
}
}
pub fn attach(&self, backing_file: impl AsRef<Path>) -> io::Result<()> {
self.device
.attach_with_loop_info(backing_file, self.info.clone())?;
if self.direct_io() {
self.device.set_direct_io(self.direct_io())?;
}
Ok(())
}
pub fn attach_fd(&self, backing_file_fd: impl AsRawFd) -> io::Result<()> {
self.device
.attach_fd_with_loop_info(backing_file_fd, self.info.clone())?;
if self.direct_io() {
self.device.set_direct_io(self.direct_io())?;
}
Ok(())
}
}