#![allow(unsafe_code)]
use std::{
fs::OpenOptions,
io::{Error, Result},
os::fd::{AsFd, AsRawFd, OwnedFd},
};
use rustix::ioctl::{Opcode, Setter, ioctl};
pub mod flags {
pub const LO_FLAGS_READ_ONLY: u32 = 1;
pub const LO_FLAGS_AUTOCLEAR: u32 = 4;
pub const LO_FLAGS_PARTSCAN: u32 = 8;
pub const LO_FLAGS_DIRECT_IO: u32 = 16;
}
const LO_NAME_SIZE: usize = 64;
const LO_KEY_SIZE: usize = 32;
#[repr(C)]
#[derive(Default)]
struct LoopConfig {
fd: u32,
block_size: u32,
info: LoopInfo64,
reserved: [u64; 8],
}
#[repr(C)]
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; LO_NAME_SIZE / 2], [u8; LO_NAME_SIZE / 2]),
lo_crypt_name: ([u8; LO_NAME_SIZE / 2], [u8; LO_NAME_SIZE / 2]),
lo_encrypt_key: [u8; LO_KEY_SIZE],
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; LO_NAME_SIZE / 2], [0; LO_NAME_SIZE / 2]),
lo_crypt_name: ([0; LO_NAME_SIZE / 2], [0; LO_NAME_SIZE / 2]),
lo_encrypt_key: [0; LO_KEY_SIZE],
lo_init: [0; 2],
}
}
}
struct LoopCtlGetFree;
unsafe impl rustix::ioctl::Ioctl for LoopCtlGetFree {
type Output = std::ffi::c_int;
const IS_MUTATING: bool = false;
fn opcode(&self) -> rustix::ioctl::Opcode {
LOOP_CTL_GET_FREE
}
fn as_ptr(&mut self) -> *mut std::ffi::c_void {
std::ptr::null_mut()
}
unsafe fn output_from_ptr(
out: rustix::ioctl::IoctlOutput,
_ptr: *mut std::ffi::c_void,
) -> rustix::io::Result<std::ffi::c_int> {
Ok(out)
}
}
const LOOP_CTL_GET_FREE: Opcode = 0x4C82;
const LOOP_CONFIGURE: Opcode = 0x4C0A;
pub fn loopify(fd: impl AsFd) -> Result<OwnedFd> {
loopify_with_flags(
fd,
flags::LO_FLAGS_READ_ONLY | flags::LO_FLAGS_AUTOCLEAR | flags::LO_FLAGS_DIRECT_IO,
)
}
pub fn loopify_with_flags(fd: impl AsFd, lo_flags: u32) -> Result<OwnedFd> {
let control = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/loop-control")?;
let free: i32 = unsafe { ioctl(&control, LoopCtlGetFree) }.map_err(Error::other)?;
if free < 0 {
return Err(Error::other("no free loop device"));
}
let loop_path = format!("/dev/loop{free}");
let loop_dev = OpenOptions::new().read(true).write(true).open(&loop_path)?;
let config = LoopConfig {
fd: fd.as_fd().as_raw_fd() as u32,
block_size: 4096,
info: LoopInfo64 {
lo_flags,
..Default::default()
},
reserved: [0; 8],
};
unsafe {
ioctl(
&loop_dev,
Setter::<{ LOOP_CONFIGURE }, LoopConfig>::new(config),
)
.map_err(Error::other)?;
}
Ok(loop_dev.into())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_loopify_not_root() {
let mut tf = NamedTempFile::new().unwrap();
tf.write_all(&[0u8; 4096]).unwrap();
tf.flush().unwrap();
let file = std::fs::File::open(tf.path()).unwrap();
let result = loopify(&file);
if !rustix::process::getuid().is_root() {
assert!(result.is_err());
}
}
}