use libc::fallocate; use serde::{Deserialize, Deserializer}; use std::{ io, os::unix::io::AsRawFd, process::Command, sync::{Arc, Mutex, MutexGuard}, }; use tempfile::{NamedTempFile, TempPath}; // All tests use the same loopback device interface and so can tread on each others toes leading to // racy tests. So we need to lock all tests to ensure only one runs at a time. lazy_static! { static ref LOCK: Arc> = Arc::new(Mutex::new(())); } pub fn create_backing_file(size: i64) -> TempPath { let file = NamedTempFile::new().expect("should be able to create a temp file"); if unsafe { fallocate(file.as_raw_fd(), 0, 0, size) } < 0 { panic!( "should be able to allocate the tenp file: {}", io::Error::last_os_error() ); } file.into_temp_path() } pub fn setup() -> MutexGuard<'static, ()> { let lock = LOCK.lock().unwrap(); detach_all(); lock } pub fn attach_file(loop_dev: &str, backing_file: &str, offset: u64, sizelimit: u64) { if !Command::new("losetup") .args(&[ loop_dev, backing_file, "--offset", &offset.to_string(), "--sizelimit", &sizelimit.to_string(), ]) .status() .expect("failed to attach backing file to loop device") .success() { panic!("failed to cleanup existing loop devices") } } pub fn detach_all() { std::thread::sleep(std::time::Duration::from_millis(10)); if !Command::new("losetup") .args(&["-D"]) .status() .expect("failed to cleanup existing loop devices") .success() { panic!("failed to cleanup existing loop devices") } std::thread::sleep(std::time::Duration::from_millis(10)); } pub fn list_device(dev_file: Option<&str>) -> Vec { let mut output = Command::new("losetup"); output.args(&["-J", "-l"]); if let Some(dev_file) = dev_file { output.arg(dev_file); } let output = output .output() .expect("failed to cleanup existing loop devices"); if output.stdout.is_empty() { Vec::new() } else { serde_json::from_slice::(&output.stdout) .unwrap() .loopdevices } } #[derive(Deserialize, Debug)] pub struct LoopDeviceOutput { pub name: String, #[serde(rename = "sizelimit")] #[serde(deserialize_with = "deserialize_optional_number_from_string")] pub size_limit: Option, #[serde(deserialize_with = "deserialize_optional_number_from_string")] pub offset: Option, #[serde(rename = "back-file")] //#[serde(deserialize_with = "deserialize_nullable_string")] pub back_file: Option, } #[derive(Deserialize, Debug)] pub struct ListOutput { pub loopdevices: Vec, } pub fn deserialize_optional_number_from_string<'de, D>( deserializer: D, ) -> Result, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] #[serde(untagged)] enum StringOrInt { String(Option), Number(Option), } match StringOrInt::deserialize(deserializer)? { StringOrInt::String(None) | StringOrInt::Number(None) => Ok(None), StringOrInt::String(Some(s)) => Ok(Some(s.parse().map_err(serde::de::Error::custom)?)), StringOrInt::Number(Some(i)) => Ok(Some(i)), } }