use std::{error::Error, fmt, fs, io, path::PathBuf, process::Command, time::Duration}; pub struct LoopDevice { path: PathBuf, name: String, mountpoint: Option, } impl LoopDevice { pub fn new(path: PathBuf) -> Self { let status = Command::new("truncate") .arg("--size=512M") .arg(&path) .status() .unwrap(); assert!(status.success()); let output = Command::new("losetup") .arg("--show") .arg("--find") .arg(&path) .output() .unwrap(); let name = String::from_utf8(output.stdout).unwrap().trim().to_string(); Self { path, name, mountpoint: None, } } pub fn mount(&mut self, path: PathBuf) -> io::Result<()> { assert!(self.mountpoint.is_none()); fs::create_dir_all(&path)?; let status = Command::new("mount").arg(&self.name).arg(&path).status()?; assert!(status.success()); self.mountpoint.replace(path); Ok(()) } pub fn umount(&mut self) -> io::Result<()> { let mount_path = self.mountpoint.as_ref().unwrap(); let status = Command::new("umount").arg(mount_path).status()?; if !status.success() { return Err(io::ErrorKind::Other.into()); } assert!(status.success()); fs::remove_dir_all(mount_path)?; self.mountpoint.take(); Ok(()) } pub fn mountpoint(&self) -> Option<&PathBuf> { self.mountpoint.as_ref() } pub fn name(&self) -> &str { self.name.as_str() } } impl Drop for LoopDevice { fn drop(&mut self) { if self.mountpoint.is_some() { if self.umount().is_err() { std::thread::sleep(Duration::from_secs(1)); self.umount().unwrap(); } } let status = Command::new("losetup") .arg("--detach") .arg(&self.name) .status() .unwrap(); assert!(status.success()); fs::remove_file(&self.path).unwrap(); } } pub trait CommandExt { fn call(&mut self) -> Result>; } #[derive(Debug, Clone)] pub struct CommandCallError { code: Option, stderr: String, } impl fmt::Display for CommandCallError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "exitcode: {:?}, stderr: {}", self.code, self.stderr) } } impl Error for CommandCallError {} impl CommandExt for Command { fn call(&mut self) -> Result> { let output = self.output()?; if output.status.success() { Ok(String::from_utf8(output.stdout)?) } else { Err((CommandCallError { code: output.status.code(), stderr: String::from_utf8(output.stderr)?, }) .into()) } } } pub fn setup(device_path: PathBuf, mnt_dir: PathBuf) -> LoopDevice { let mut ret = LoopDevice::new(device_path); Command::new("mkfs.btrfs") .arg("-f") .arg(ret.name()) .call() .unwrap(); ret.mount(mnt_dir).unwrap(); ret }