diff --git a/src/lib.rs b/src/lib.rs index adb4c28..6f1990d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -52,6 +53,8 @@ use std::{ path::{Path, PathBuf}, }; +use loname::Name; + #[allow(non_camel_case_types)] #[allow(dead_code)] #[allow(non_snake_case)] @@ -59,6 +62,8 @@ 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"))] @@ -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, - 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) @@ -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 { + 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 @@ -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 { + 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 diff --git a/src/loname.rs b/src/loname.rs new file mode 100644 index 0000000..a2e2175 --- /dev/null +++ b/src/loname.rs @@ -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>(path: Y) -> Result { + 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::() + } +} + +#[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() + ) + } +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 84bd1cc..59f37f8 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -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::{ @@ -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()); +}