use std::fmt; use std::path::{Path,PathBuf}; use crate::Result; use super::mounts::Mounts; #[derive(Debug)] pub struct LoopDevice(PathBuf); impl LoopDevice { const LOSETUP: &'static str = "/usr/sbin/losetup"; const MOUNT: &'static str = "/usr/bin/mount"; fn new>(device: P) -> LoopDevice { let device = device.as_ref().to_path_buf(); LoopDevice(device) } pub fn create>(image: P, offset: Option, read_only: bool) -> Result { let image = image.as_ref(); let mut args = String::new(); if let Some(offset) = offset { args += &format!("--offset {} ", offset); } if read_only { args += "--read-only "; } args += &format!("-f --show {}", image.display()); let output = cmd_with_output!(Self::LOSETUP, args)?; Ok(LoopDevice::new(output)) } pub fn with_loop(image: P, offset: Option, read_only: bool, f: F) -> Result where P: AsRef, F: FnOnce(&LoopDevice) -> Result, { let loopdev = Self::create(image, offset, read_only)?; let result = f(&loopdev); let detach_result = loopdev.detach(); let r = result?; detach_result.map_err(context!("error detaching loop device"))?; Ok(r) } /// Search for an entry in /proc/mounts for a loop device which is mounted on the /// specified mountpoint. /// The relevant lines look like this: /// /// /dev/loop3 /run/citadel/realmfs/realmfs-name-rw.mountpoint ext4 rw,noatime,data=ordered 0 0 /// pub fn find_mounted_loop>(mount_target: P) -> Option { let mount_target = mount_target.as_ref(); Mounts::load().ok() .and_then(|mounts| mounts.mounts() .find(|m| m.target_path() == mount_target && m.source().starts_with("/dev/loop")) .map(|m| LoopDevice::new(m.source_path())) ) } pub fn find_devices_for>(image: P) -> Result> { let image = image.as_ref(); // Output from losetup -j looks like this: // /dev/loop1: [0036]:64845938 (/storage/resources/dev/citadel-extra-dev-001.img), offset 4096 let output:String = cmd_with_output!(Self::LOSETUP, "-j {}", image.display())?; Ok(output.lines() .flat_map(|line| line.splitn(2, ':').next()) .map(LoopDevice::new) .collect()) } pub fn detach(&self) -> Result<()> { cmd!(Self::LOSETUP, format!("-d {}", self.0.display())) } pub fn resize(&self) -> Result<()> { cmd!(Self::LOSETUP, format!("-c {}", self.0.display())) } pub fn device(&self) -> &Path { &self.0 } pub fn device_str(&self) -> &str { self.device().to_str().unwrap() } pub fn mount_ro>(&self, target: P) -> Result<()> { let target = target.as_ref(); cmd!(Self::MOUNT, "-oro,noatime {} {}", self, target.display()) } pub fn mount>(&self, target: P) -> Result<()> { let target = target.as_ref(); cmd!(Self::MOUNT, "-orw,noatime {} {}", self, target.display()) } pub fn mount_pair(&self, rw_target: P, ro_target: Q) -> Result<()> where P: AsRef, Q: AsRef { let rw = rw_target.as_ref(); let ro = ro_target.as_ref(); self.mount(rw)?; // From mount(8): // // mount --bind olddir newdir // mount -o remount,bind,ro olddir newdir cmd!(Self::MOUNT, "--bind {} {}", rw.display(), ro.display())?; cmd!(Self::MOUNT, "-o remount,bind,ro {} {}", rw.display(), ro.display()) } } impl fmt::Display for LoopDevice { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.device().display()) } }