#![allow(deref_nullptr)]
#![allow(unknown_lints)]
extern crate libc;
#[cfg(feature = "direct_io")]
use bindings::LOOP_SET_DIRECT_IO;
use bindings::{
loop_info64, LOOP_CLR_FD, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY, LOOP_SET_FD, LOOP_SET_STATUS64,
LO_FLAGS_AUTOCLEAR, LO_FLAGS_READ_ONLY,
};
use libc::{c_int, ioctl};
use std::{
default::Default,
fs::{File, OpenOptions},
io,
os::unix::prelude::*,
path::{Path, PathBuf},
};
#[allow(non_camel_case_types)]
#[allow(dead_code)]
#[allow(non_snake_case)]
mod bindings {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
#[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";
#[cfg(not(target_os = "android"))]
const LOOP_PREFIX: &str = "/dev/loop";
#[cfg(target_os = "android")]
const LOOP_PREFIX: &str = "/dev/block/loop";
#[derive(Debug)]
pub struct LoopControl {
dev_file: File,
}
impl LoopControl {
pub fn open() -> io::Result<Self> {
Ok(Self {
dev_file: OpenOptions::new()
.read(true)
.write(true)
.open(LOOP_CONTROL)?,
})
}
pub fn next_free(&self) -> io::Result<LoopDevice> {
let dev_num = ioctl_to_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))
}
}
impl AsRawFd for LoopControl {
fn as_raw_fd(&self) -> RawFd {
self.dev_file.as_raw_fd()
}
}
impl IntoRawFd for LoopControl {
fn into_raw_fd(self) -> RawFd {
self.dev_file.into_raw_fd()
}
}
#[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 {
pub fn open<P: AsRef<Path>>(dev: P) -> io::Result<Self> {
Ok(Self {
device: OpenOptions::new().read(true).write(true).open(dev)?,
})
}
pub fn with(&self) -> AttachOptions<'_> {
AttachOptions {
device: self,
info: Default::default(),
#[cfg(feature = "direct_io")]
direct_io: false,
}
}
pub fn attach_file<P: AsRef<Path>>(&self, backing_file: P) -> io::Result<()> {
let info = loop_info64 {
..Default::default()
};
Self::attach_with_loop_info(self, backing_file, info)
}
fn attach_with_loop_info(
&self, backing_file: impl AsRef<Path>,
info: loop_info64,
) -> 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)
}
fn attach_fd_with_loop_info(&self, bf: impl AsRawFd, info: loop_info64) -> io::Result<()> {
ioctl_to_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 ioctl_to_error(result) {
Err(err) => {
let _ = self.detach();
Err(err)
}
Ok(_) => Ok(()),
}
}
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()
}
pub fn major(&self) -> io::Result<u32> {
self.device
.metadata()
.map(|m| unsafe { libc::major(m.rdev()) as u32 })
}
pub fn minor(&self) -> io::Result<u32> {
self.device
.metadata()
.map(|m| unsafe { libc::minor(m.rdev()) as u32 })
}
pub fn detach(&self) -> io::Result<()> {
ioctl_to_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_CLR_FD as IoctlRequest,
0,
)
})?;
Ok(())
}
pub fn set_capacity(&self) -> io::Result<()> {
ioctl_to_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_CAPACITY as IoctlRequest,
0,
)
})?;
Ok(())
}
#[cfg(feature = "direct_io")]
pub fn set_direct_io(&self, direct_io: bool) -> io::Result<()> {
ioctl_to_error(unsafe {
ioctl(
self.device.as_raw_fd() as c_int,
LOOP_SET_DIRECT_IO as IoctlRequest,
if direct_io { 1 } else { 0 },
)
})?;
Ok(())
}
}
pub struct AttachOptions<'d> {
device: &'d LoopDevice,
info: loop_info64,
#[cfg(feature = "direct_io")]
direct_io: bool,
}
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, read_only: bool) -> Self {
if read_only {
self.info.lo_flags |= LO_FLAGS_AUTOCLEAR;
} else {
self.info.lo_flags &= !LO_FLAGS_AUTOCLEAR;
}
self
}
#[cfg(feature = "direct_io")]
pub fn set_direct_io(mut self, direct_io: bool) -> Self {
self.direct_io = direct_io;
self
}
pub fn part_scan(mut self, enable: bool) -> Self {
if enable {
self.info.lo_flags |= 1 << 4;
} else {
self.info.lo_flags &= u32::max_value() - (1 << 4);
}
self
}
pub fn attach(self, backing_file: impl AsRef<Path>) -> io::Result<()> {
self.device.attach_with_loop_info(backing_file, self.info)?;
#[cfg(feature = "direct_io")]
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)?;
#[cfg(feature = "direct_io")]
if self.direct_io {
self.device.set_direct_io(self.direct_io)?;
}
Ok(())
}
}
fn ioctl_to_error(ret: i32) -> io::Result<i32> {
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret)
}
}