1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
use libc::fallocate;
use serde::{Deserialize, Deserializer};
use std::{
convert::TryInto,
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<Mutex<()>> = 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.try_into().unwrap()) } < 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("/sbin/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("/sbin/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<LoopDeviceOutput> {
let mut output = Command::new("/sbin/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::<ListOutput>(&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<u64>,
#[serde(deserialize_with = "deserialize_optional_number_from_string")]
pub offset: Option<u64>,
#[serde(rename = "back-file")]
//#[serde(deserialize_with = "deserialize_nullable_string")]
pub back_file: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct ListOutput {
pub loopdevices: Vec<LoopDeviceOutput>,
}
pub fn deserialize_optional_number_from_string<'de, D>(
deserializer: D,
) -> Result<Option<u64>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrInt {
String(Option<String>),
Number(Option<u64>),
}
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)),
}
}
|