17a97f354SLoGin use core::str; 2*c10d7695SLoGin use std::{path::PathBuf, process::Command, thread::sleep, time::Duration}; 3c6f35e8aSLoGin 4c6f35e8aSLoGin use anyhow::{anyhow, Result}; 57a97f354SLoGin use regex::Regex; 67a97f354SLoGin 77a97f354SLoGin use crate::utils::abs_path; 87a97f354SLoGin 97a97f354SLoGin const LOOP_DEVICE_LOSETUP_A_REGEX: &str = r"^/dev/loop(\d+)"; 10c6f35e8aSLoGin 11c6f35e8aSLoGin pub struct LoopDevice { 127a97f354SLoGin img_path: Option<PathBuf>, 13c6f35e8aSLoGin loop_device_path: Option<String>, 14c6f35e8aSLoGin } 15c6f35e8aSLoGin impl LoopDevice { 167a97f354SLoGin pub fn attached(&self) -> bool { 177a97f354SLoGin self.loop_device_path.is_some() 187a97f354SLoGin } 197a97f354SLoGin 207a97f354SLoGin pub fn dev_path(&self) -> Option<&String> { 217a97f354SLoGin self.loop_device_path.as_ref() 227a97f354SLoGin } 237a97f354SLoGin 24c6f35e8aSLoGin pub fn attach(&mut self) -> Result<()> { 257a97f354SLoGin if self.attached() { 26c6f35e8aSLoGin return Ok(()); 27c6f35e8aSLoGin } 287a97f354SLoGin if self.img_path.is_none() { 297a97f354SLoGin return Err(anyhow!("Image path not set")); 307a97f354SLoGin } 317a97f354SLoGin 32c6f35e8aSLoGin let output = Command::new("losetup") 33c6f35e8aSLoGin .arg("-f") 34c6f35e8aSLoGin .arg("--show") 35c6f35e8aSLoGin .arg("-P") 367a97f354SLoGin .arg(self.img_path.as_ref().unwrap()) 37c6f35e8aSLoGin .output()?; 38c6f35e8aSLoGin 39c6f35e8aSLoGin if output.status.success() { 40c6f35e8aSLoGin let loop_device = String::from_utf8(output.stdout)?.trim().to_string(); 41c6f35e8aSLoGin self.loop_device_path = Some(loop_device); 42*c10d7695SLoGin sleep(Duration::from_millis(100)); 43c6f35e8aSLoGin log::trace!( 44c6f35e8aSLoGin "Loop device attached: {}", 45c6f35e8aSLoGin self.loop_device_path.as_ref().unwrap() 46c6f35e8aSLoGin ); 47c6f35e8aSLoGin Ok(()) 48c6f35e8aSLoGin } else { 49c6f35e8aSLoGin Err(anyhow::anyhow!( 50c6f35e8aSLoGin "Failed to mount disk image: losetup command exited with status {}", 51c6f35e8aSLoGin output.status 52c6f35e8aSLoGin )) 53c6f35e8aSLoGin } 54c6f35e8aSLoGin } 557a97f354SLoGin 567a97f354SLoGin /// 尝试连接已经存在的loop device 577a97f354SLoGin pub fn attach_by_exists(&mut self) -> Result<()> { 587a97f354SLoGin if self.attached() { 597a97f354SLoGin return Ok(()); 607a97f354SLoGin } 617a97f354SLoGin if self.img_path.is_none() { 627a97f354SLoGin return Err(anyhow!("Image path not set")); 637a97f354SLoGin } 647a97f354SLoGin log::trace!( 657a97f354SLoGin "Try to attach loop device by exists: image path: {}", 667a97f354SLoGin self.img_path.as_ref().unwrap().display() 677a97f354SLoGin ); 687a97f354SLoGin // losetup -a 查看是否有已经attach了的,如果有,就附着上去 697a97f354SLoGin let cmd = Command::new("losetup") 707a97f354SLoGin .arg("-a") 717a97f354SLoGin .output() 727a97f354SLoGin .map_err(|e| anyhow!("Failed to run losetup -a: {}", e))?; 737a97f354SLoGin let output = String::from_utf8(cmd.stdout)?; 747a97f354SLoGin let s = __loop_device_path_by_disk_image_path( 757a97f354SLoGin self.img_path.as_ref().unwrap().to_str().unwrap(), 767a97f354SLoGin &output, 777a97f354SLoGin ) 787a97f354SLoGin .map_err(|e| anyhow!("Failed to find loop device: {}", e))?; 797a97f354SLoGin self.loop_device_path = Some(s); 807a97f354SLoGin Ok(()) 817a97f354SLoGin } 827a97f354SLoGin 83c6f35e8aSLoGin /// 获取指定分区的路径 84c6f35e8aSLoGin /// 85c6f35e8aSLoGin /// # 参数 86c6f35e8aSLoGin /// 87c6f35e8aSLoGin /// * `nth` - 分区的编号 88c6f35e8aSLoGin /// 89c6f35e8aSLoGin /// # 返回值 90c6f35e8aSLoGin /// 91c6f35e8aSLoGin /// 返回一个 `Result<String>`,包含分区路径的字符串。如果循环设备未附加,则返回错误。 92c6f35e8aSLoGin /// 93c6f35e8aSLoGin /// # 错误 94c6f35e8aSLoGin /// 95c6f35e8aSLoGin /// 如果循环设备未附加,则返回 `anyhow!("Loop device not attached")` 错误。 96c6f35e8aSLoGin pub fn partition_path(&self, nth: u8) -> Result<PathBuf> { 977a97f354SLoGin if !self.attached() { 98c6f35e8aSLoGin return Err(anyhow!("Loop device not attached")); 99c6f35e8aSLoGin } 100c6f35e8aSLoGin let s = format!("{}p{}", self.loop_device_path.as_ref().unwrap(), nth); 101c6f35e8aSLoGin let s = PathBuf::from(s); 102c6f35e8aSLoGin // 判断路径是否存在 103c6f35e8aSLoGin if !s.exists() { 104c6f35e8aSLoGin return Err(anyhow!("Partition not exist")); 105c6f35e8aSLoGin } 106c6f35e8aSLoGin Ok(s) 107c6f35e8aSLoGin } 108c6f35e8aSLoGin 109c6f35e8aSLoGin pub fn detach(&mut self) -> Result<()> { 110c6f35e8aSLoGin if self.loop_device_path.is_none() { 111c6f35e8aSLoGin return Ok(()); 112c6f35e8aSLoGin } 113c6f35e8aSLoGin let loop_device = self.loop_device_path.take().unwrap(); 114c6f35e8aSLoGin let output = Command::new("losetup") 115c6f35e8aSLoGin .arg("-d") 116c6f35e8aSLoGin .arg(loop_device) 117c6f35e8aSLoGin .output()?; 118c6f35e8aSLoGin 119c6f35e8aSLoGin if output.status.success() { 120c6f35e8aSLoGin self.loop_device_path = None; 121c6f35e8aSLoGin Ok(()) 122c6f35e8aSLoGin } else { 1237a97f354SLoGin Err(anyhow::anyhow!( 1247a97f354SLoGin "Failed to detach loop device: {}, {}", 1257a97f354SLoGin output.status, 1267a97f354SLoGin str::from_utf8(output.stderr.as_slice()).unwrap_or("<Unknown>") 1277a97f354SLoGin )) 128c6f35e8aSLoGin } 129c6f35e8aSLoGin } 130c6f35e8aSLoGin } 131c6f35e8aSLoGin 132c6f35e8aSLoGin impl Drop for LoopDevice { 133c6f35e8aSLoGin fn drop(&mut self) { 1347a97f354SLoGin if let Err(e) = self.detach() { 1357a97f354SLoGin log::warn!("Failed to detach loop device: {}", e); 1367a97f354SLoGin } 137c6f35e8aSLoGin } 138c6f35e8aSLoGin } 139c6f35e8aSLoGin 140c6f35e8aSLoGin pub struct LoopDeviceBuilder { 141c6f35e8aSLoGin img_path: Option<PathBuf>, 1427a97f354SLoGin loop_device_path: Option<String>, 143c6f35e8aSLoGin } 144c6f35e8aSLoGin 145c6f35e8aSLoGin impl LoopDeviceBuilder { 146c6f35e8aSLoGin pub fn new() -> Self { 1477a97f354SLoGin LoopDeviceBuilder { 1487a97f354SLoGin img_path: None, 1497a97f354SLoGin loop_device_path: None, 1507a97f354SLoGin } 151c6f35e8aSLoGin } 152c6f35e8aSLoGin 153c6f35e8aSLoGin pub fn img_path(mut self, img_path: PathBuf) -> Self { 1547a97f354SLoGin self.img_path = Some(abs_path(&img_path)); 155c6f35e8aSLoGin self 156c6f35e8aSLoGin } 157c6f35e8aSLoGin 158c6f35e8aSLoGin pub fn build(self) -> Result<LoopDevice> { 1597a97f354SLoGin let loop_dev = LoopDevice { 1607a97f354SLoGin img_path: self.img_path, 1617a97f354SLoGin loop_device_path: self.loop_device_path, 162c6f35e8aSLoGin }; 1637a97f354SLoGin 164c6f35e8aSLoGin Ok(loop_dev) 165c6f35e8aSLoGin } 166c6f35e8aSLoGin } 1677a97f354SLoGin 1687a97f354SLoGin fn __loop_device_path_by_disk_image_path( 1697a97f354SLoGin disk_img_path: &str, 1707a97f354SLoGin losetup_a_output: &str, 1717a97f354SLoGin ) -> Result<String> { 1727a97f354SLoGin let re = Regex::new(LOOP_DEVICE_LOSETUP_A_REGEX)?; 1737a97f354SLoGin for line in losetup_a_output.lines() { 1747a97f354SLoGin if !line.contains(disk_img_path) { 1757a97f354SLoGin continue; 1767a97f354SLoGin } 1777a97f354SLoGin let caps = re.captures(line); 1787a97f354SLoGin if caps.is_none() { 1797a97f354SLoGin continue; 1807a97f354SLoGin } 1817a97f354SLoGin let caps = caps.unwrap(); 1827a97f354SLoGin let loop_device = caps.get(1).unwrap().as_str(); 1837a97f354SLoGin let loop_device = format!("/dev/loop{}", loop_device); 1847a97f354SLoGin return Ok(loop_device); 1857a97f354SLoGin } 1867a97f354SLoGin Err(anyhow!("Loop device not found")) 1877a97f354SLoGin } 1887a97f354SLoGin 1897a97f354SLoGin #[cfg(test)] 1907a97f354SLoGin mod tests { 1917a97f354SLoGin use super::*; 1927a97f354SLoGin 1937a97f354SLoGin #[test] 1947a97f354SLoGin fn test_regex_find_loop_device() { 1957a97f354SLoGin const DEVICE_NAME_SHOULD_MATCH: [&str; 3] = 1967a97f354SLoGin ["/dev/loop11", "/dev/loop11p1", "/dev/loop11p1 "]; 1977a97f354SLoGin let device_name = "/dev/loop11"; 1987a97f354SLoGin let re = Regex::new(LOOP_DEVICE_LOSETUP_A_REGEX).unwrap(); 1997a97f354SLoGin for name in DEVICE_NAME_SHOULD_MATCH { 2007a97f354SLoGin assert!(re.find(name).is_some(), "{} should match", name); 2017a97f354SLoGin assert_eq!( 2027a97f354SLoGin re.find(name).unwrap().as_str(), 2037a97f354SLoGin device_name, 2047a97f354SLoGin "{} should match {}", 2057a97f354SLoGin name, 2067a97f354SLoGin device_name 2077a97f354SLoGin ); 2087a97f354SLoGin } 2097a97f354SLoGin } 2107a97f354SLoGin 2117a97f354SLoGin #[test] 2127a97f354SLoGin fn test_parse_losetup_a_output() { 2137a97f354SLoGin let losetup_a_output = r#"/dev/loop1: []: (/data/bin/disk-image-x86_64.img) 2147a97f354SLoGin /dev/loop29: []: (/var/lib/abc.img) 2157a97f354SLoGin /dev/loop13: []: (/var/lib/snapd/snaps/gtk-common-themes_1535.snap 2167a97f354SLoGin /dev/loop19: []: (/var/lib/snapd/snaps/gnome-42-2204_172.snap)"#; 2177a97f354SLoGin let disk_img_path = "/data/bin/disk-image-x86_64.img"; 2187a97f354SLoGin let loop_device_path = 2197a97f354SLoGin __loop_device_path_by_disk_image_path(disk_img_path, losetup_a_output).unwrap(); 2207a97f354SLoGin assert_eq!(loop_device_path, "/dev/loop1"); 2217a97f354SLoGin } 2227a97f354SLoGin 2237a97f354SLoGin #[test] 2247a97f354SLoGin fn test_parse_lsblk_output_not_match() { 2257a97f354SLoGin let losetup_a_output = r#"/dev/loop1: []: (/data/bin/disk-image-x86_64.img) 2267a97f354SLoGin /dev/loop29: []: (/var/lib/abc.img) 2277a97f354SLoGin /dev/loop13: []: (/var/lib/snapd/snaps/gtk-common-themes_1535.snap 2287a97f354SLoGin /dev/loop19: []: (/var/lib/snapd/snaps/gnome-42-2204_172.snap)"#; 2297a97f354SLoGin let disk_img_path = "/data/bin/disk-image-riscv64.img"; 2307a97f354SLoGin let loop_device_path = 2317a97f354SLoGin __loop_device_path_by_disk_image_path(disk_img_path, losetup_a_output); 2327a97f354SLoGin assert!( 2337a97f354SLoGin loop_device_path.is_err(), 2347a97f354SLoGin "should not match any loop device" 2357a97f354SLoGin ); 2367a97f354SLoGin } 2377a97f354SLoGin } 238