Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@
//! ld.detach().unwrap();
//! ```
use crate::bindings::{
loop_info64, LOOP_CLR_FD, LOOP_CTL_ADD, LOOP_CTL_GET_FREE, LOOP_SET_CAPACITY, LOOP_SET_FD,
LOOP_SET_STATUS64, LO_FLAGS_AUTOCLEAR, LO_FLAGS_PARTSCAN, LO_FLAGS_READ_ONLY,
loop_info64, LOOP_CLR_FD, LOOP_CTL_ADD, LOOP_CTL_GET_FREE, LOOP_GET_STATUS64,
LOOP_SET_CAPACITY, LOOP_SET_FD, LOOP_SET_STATUS64, LO_FLAGS_AUTOCLEAR, LO_FLAGS_PARTSCAN,
LO_FLAGS_READ_ONLY,
};
#[cfg(feature = "direct_io")]
use bindings::LOOP_SET_DIRECT_IO;
Expand All @@ -52,13 +53,17 @@ use std::{
path::{Path, PathBuf},
};

use loname::Name;

#[allow(non_camel_case_types)]
#[allow(dead_code)]
#[allow(non_snake_case)]
mod bindings {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}

mod loname;

#[cfg(all(not(target_os = "android"), not(target_env = "musl")))]
type IoctlRequest = libc::c_ulong;
#[cfg(any(target_os = "android", target_env = "musl"))]
Expand Down Expand Up @@ -243,9 +248,15 @@ impl LoopDevice {
fn attach_with_loop_info(
&self, // TODO should be mut? - but changing it is a breaking change
backing_file: impl AsRef<Path>,
info: loop_info64,
mut info: loop_info64,
) -> io::Result<()> {
let write_access = (info.lo_flags & LO_FLAGS_READ_ONLY) == 0;

// store backing file name in the info
let name = loname::Name::from_path(&backing_file).unwrap_or_default();
info.lo_file_name = name.0;
info.lo_crypt_name = name.0;

let bf = OpenOptions::new()
.read(true)
.write(write_access)
Expand Down Expand Up @@ -312,6 +323,23 @@ impl LoopDevice {
std::fs::read_link(&p).ok()
}

/// Try to obtain the original file path used on mapping
/// The method ignores ioctl errors
///
/// # Return
/// The path expected to be stored in the loop_info64.
/// If it's not there, method returns None, otherwise - the string stored
pub fn original_path(&self) -> Option<PathBuf> {
self.info().ok().and_then(|info| {
let name = Name(info.lo_file_name).to_string();
if name.is_empty() {
None
} else {
Some(PathBuf::from(name))
}
})
}

/// Get the device major number
///
/// # Errors
Expand Down Expand Up @@ -390,6 +418,21 @@ impl LoopDevice {
Ok(())
}

/// Obtain loop_info64 struct for the loop device
/// # Return
/// Ok(loop_info64) - successfully obtained info
/// Err(std::io::Error) - error from ioctl
pub fn info(&self) -> Result<loop_info64, std::io::Error> {
let empty_info = Box::new(loop_info64::default());
let fd = self.device.as_raw_fd();

unsafe {
let ptr = Box::into_raw(empty_info);
let ret_code = libc::ioctl(fd.as_raw_fd(), LOOP_GET_STATUS64 as IoctlRequest, ptr);
ioctl_to_error(ret_code).map(|_| *Box::from_raw(ptr))
}
}

/// Enable or disable direct I/O for the backing file.
///
/// # Errors
Expand Down
88 changes: 88 additions & 0 deletions src/loname.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! Configuration structures for loop device

use std::path::Path;

use crate::bindings::LO_NAME_SIZE;

/// Loop device name
#[repr(C)]
#[derive(Debug)]
pub struct Name(pub [libc::__u8; LO_NAME_SIZE as usize]);

/// Allow to construct the config easily
impl Default for Name {
fn default() -> Self {
Self([0; LO_NAME_SIZE as usize])
}
}

/// Conversions simplifiers
impl Name {
pub fn from_path<Y: AsRef<Path>>(path: Y) -> Result<Self, String> {
let s = path.as_ref().as_os_str().as_encoded_bytes();
if s.len() > LO_NAME_SIZE as usize {
return Err(format!(
"too many bytes in the provided loop dev source file path: {}, max {LO_NAME_SIZE}",
s.len()
));
}
let mut data: [u8; 64] = [0; LO_NAME_SIZE as usize];
for (idx, byte) in s.iter().enumerate() {
data[idx] = *byte;
}
Ok(Self(data))
}
}

#[allow(clippy::to_string_trait_impl)]
impl ToString for Name {
fn to_string(&self) -> String {
self.0
.iter()
.filter_map(|ch| {
let ch: char = *ch as char;
if ch == '\0' {
None
} else {
Some(ch)
}
})
.collect::<String>()
}
}

#[cfg(test)]
mod test {
use std::path::PathBuf;

use super::Name;

#[test]
fn test_name_empty() {
let name = Name::default();
for num in name.0 {
assert_eq!(0, num);
}
}

#[test]
fn test_name_from_to() {
let path_string = "/a/b/some-file/cool name";
let path = PathBuf::from(&path_string);
let name = Name::from_path(path);

assert_eq!(path_string, name.unwrap().to_string());
}

#[test]
fn test_name_too_long() {
let path_string = "/too-long/too-long/too-long/too-long/too-long/too-long/too-long--";
let path = PathBuf::from(&path_string);
let name = Name::from_path(path);

assert_eq!(
"too many bytes in the provided loop dev source file path: 65, max 64",
name.unwrap_err()
)
}
}
51 changes: 50 additions & 1 deletion tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use loopdev::{LoopControl, LoopDevice};
use std::path::PathBuf;
use std::{fs::File, os::fd::AsFd, path::PathBuf};

mod util;
use crate::util::{
Expand Down Expand Up @@ -220,3 +220,52 @@ fn add_a_loop_device() {
assert!(lc.add(1).is_ok());
assert!(lc.add(1).is_err());
}

#[test]
fn test_device_name() {
let _lock = setup();

let lc = LoopControl::open().expect("should be able to open the LoopControl device");

let file = create_backing_file(128 * 1024 * 1024);
let file_path = file.to_path_buf();
let ld0 = lc
.next_free()
.expect("should not error finding the next free loopback device");

ld0.with()
.attach(&file)
.expect("should not error attaching the backing file to the loopdev");

file.close().expect("should delete the temp backing file");

assert_eq!(
file_path,
ld0.original_path()
.expect("expected a correct loop device name")
);

assert!(ld0.info().is_ok());
}

#[test]
fn test_device_name_absent() {
let _lock = setup();
let file_size = 128 * 1024 * 1024;

let lc = LoopControl::open().expect("should be able to open the LoopControl device");

let file =
File::open(create_backing_file(file_size)).expect("should be able to open our temp file");
let ld0 = lc
.next_free()
.expect("should not error finding the next free loopback device");

ld0.with()
.attach_fd(file.as_fd())
.expect("should not error attaching the backing file to the loopdev");

assert!(ld0.info().is_ok());

assert!(ld0.original_path().is_none());
}