1 use core::str; 2 use std::{path::PathBuf, process::Command}; 3 4 use anyhow::{anyhow, Result}; 5 use regex::Regex; 6 7 use crate::utils::abs_path; 8 9 const LOOP_DEVICE_LOSETUP_A_REGEX: &str = r"^/dev/loop(\d+)"; 10 11 pub struct LoopDevice { 12 img_path: Option<PathBuf>, 13 loop_device_path: Option<String>, 14 } 15 impl LoopDevice { 16 pub fn attached(&self) -> bool { 17 self.loop_device_path.is_some() 18 } 19 20 pub fn dev_path(&self) -> Option<&String> { 21 self.loop_device_path.as_ref() 22 } 23 24 pub fn attach(&mut self) -> Result<()> { 25 if self.attached() { 26 return Ok(()); 27 } 28 if self.img_path.is_none() { 29 return Err(anyhow!("Image path not set")); 30 } 31 32 let output = Command::new("losetup") 33 .arg("-f") 34 .arg("--show") 35 .arg("-P") 36 .arg(self.img_path.as_ref().unwrap()) 37 .output()?; 38 39 if output.status.success() { 40 let loop_device = String::from_utf8(output.stdout)?.trim().to_string(); 41 self.loop_device_path = Some(loop_device); 42 log::trace!( 43 "Loop device attached: {}", 44 self.loop_device_path.as_ref().unwrap() 45 ); 46 Ok(()) 47 } else { 48 Err(anyhow::anyhow!( 49 "Failed to mount disk image: losetup command exited with status {}", 50 output.status 51 )) 52 } 53 } 54 55 /// 尝试连接已经存在的loop device 56 pub fn attach_by_exists(&mut self) -> Result<()> { 57 if self.attached() { 58 return Ok(()); 59 } 60 if self.img_path.is_none() { 61 return Err(anyhow!("Image path not set")); 62 } 63 log::trace!( 64 "Try to attach loop device by exists: image path: {}", 65 self.img_path.as_ref().unwrap().display() 66 ); 67 // losetup -a 查看是否有已经attach了的,如果有,就附着上去 68 let cmd = Command::new("losetup") 69 .arg("-a") 70 .output() 71 .map_err(|e| anyhow!("Failed to run losetup -a: {}", e))?; 72 let output = String::from_utf8(cmd.stdout)?; 73 let s = __loop_device_path_by_disk_image_path( 74 self.img_path.as_ref().unwrap().to_str().unwrap(), 75 &output, 76 ) 77 .map_err(|e| anyhow!("Failed to find loop device: {}", e))?; 78 self.loop_device_path = Some(s); 79 Ok(()) 80 } 81 82 /// 获取指定分区的路径 83 /// 84 /// # 参数 85 /// 86 /// * `nth` - 分区的编号 87 /// 88 /// # 返回值 89 /// 90 /// 返回一个 `Result<String>`,包含分区路径的字符串。如果循环设备未附加,则返回错误。 91 /// 92 /// # 错误 93 /// 94 /// 如果循环设备未附加,则返回 `anyhow!("Loop device not attached")` 错误。 95 pub fn partition_path(&self, nth: u8) -> Result<PathBuf> { 96 if !self.attached() { 97 return Err(anyhow!("Loop device not attached")); 98 } 99 let s = format!("{}p{}", self.loop_device_path.as_ref().unwrap(), nth); 100 let s = PathBuf::from(s); 101 // 判断路径是否存在 102 if !s.exists() { 103 return Err(anyhow!("Partition not exist")); 104 } 105 Ok(s) 106 } 107 108 pub fn detach(&mut self) -> Result<()> { 109 if self.loop_device_path.is_none() { 110 return Ok(()); 111 } 112 let loop_device = self.loop_device_path.take().unwrap(); 113 let output = Command::new("losetup") 114 .arg("-d") 115 .arg(loop_device) 116 .output()?; 117 118 if output.status.success() { 119 self.loop_device_path = None; 120 Ok(()) 121 } else { 122 Err(anyhow::anyhow!( 123 "Failed to detach loop device: {}, {}", 124 output.status, 125 str::from_utf8(output.stderr.as_slice()).unwrap_or("<Unknown>") 126 )) 127 } 128 } 129 } 130 131 impl Drop for LoopDevice { 132 fn drop(&mut self) { 133 if let Err(e) = self.detach() { 134 log::warn!("Failed to detach loop device: {}", e); 135 } 136 } 137 } 138 139 pub struct LoopDeviceBuilder { 140 img_path: Option<PathBuf>, 141 loop_device_path: Option<String>, 142 } 143 144 impl LoopDeviceBuilder { 145 pub fn new() -> Self { 146 LoopDeviceBuilder { 147 img_path: None, 148 loop_device_path: None, 149 } 150 } 151 152 pub fn img_path(mut self, img_path: PathBuf) -> Self { 153 self.img_path = Some(abs_path(&img_path)); 154 self 155 } 156 157 pub fn build(self) -> Result<LoopDevice> { 158 let loop_dev = LoopDevice { 159 img_path: self.img_path, 160 loop_device_path: self.loop_device_path, 161 }; 162 163 Ok(loop_dev) 164 } 165 } 166 167 fn __loop_device_path_by_disk_image_path( 168 disk_img_path: &str, 169 losetup_a_output: &str, 170 ) -> Result<String> { 171 let re = Regex::new(LOOP_DEVICE_LOSETUP_A_REGEX)?; 172 for line in losetup_a_output.lines() { 173 if !line.contains(disk_img_path) { 174 continue; 175 } 176 let caps = re.captures(line); 177 if caps.is_none() { 178 continue; 179 } 180 let caps = caps.unwrap(); 181 let loop_device = caps.get(1).unwrap().as_str(); 182 let loop_device = format!("/dev/loop{}", loop_device); 183 return Ok(loop_device); 184 } 185 Err(anyhow!("Loop device not found")) 186 } 187 188 #[cfg(test)] 189 mod tests { 190 use super::*; 191 192 #[test] 193 fn test_regex_find_loop_device() { 194 const DEVICE_NAME_SHOULD_MATCH: [&str; 3] = 195 ["/dev/loop11", "/dev/loop11p1", "/dev/loop11p1 "]; 196 let device_name = "/dev/loop11"; 197 let re = Regex::new(LOOP_DEVICE_LOSETUP_A_REGEX).unwrap(); 198 for name in DEVICE_NAME_SHOULD_MATCH { 199 assert!(re.find(name).is_some(), "{} should match", name); 200 assert_eq!( 201 re.find(name).unwrap().as_str(), 202 device_name, 203 "{} should match {}", 204 name, 205 device_name 206 ); 207 } 208 } 209 210 #[test] 211 fn test_parse_losetup_a_output() { 212 let losetup_a_output = r#"/dev/loop1: []: (/data/bin/disk-image-x86_64.img) 213 /dev/loop29: []: (/var/lib/abc.img) 214 /dev/loop13: []: (/var/lib/snapd/snaps/gtk-common-themes_1535.snap 215 /dev/loop19: []: (/var/lib/snapd/snaps/gnome-42-2204_172.snap)"#; 216 let disk_img_path = "/data/bin/disk-image-x86_64.img"; 217 let loop_device_path = 218 __loop_device_path_by_disk_image_path(disk_img_path, losetup_a_output).unwrap(); 219 assert_eq!(loop_device_path, "/dev/loop1"); 220 } 221 222 #[test] 223 fn test_parse_lsblk_output_not_match() { 224 let losetup_a_output = r#"/dev/loop1: []: (/data/bin/disk-image-x86_64.img) 225 /dev/loop29: []: (/var/lib/abc.img) 226 /dev/loop13: []: (/var/lib/snapd/snaps/gtk-common-themes_1535.snap 227 /dev/loop19: []: (/var/lib/snapd/snaps/gnome-42-2204_172.snap)"#; 228 let disk_img_path = "/data/bin/disk-image-riscv64.img"; 229 let loop_device_path = 230 __loop_device_path_by_disk_image_path(disk_img_path, losetup_a_output); 231 assert!( 232 loop_device_path.is_err(), 233 "should not match any loop device" 234 ); 235 } 236 } 237