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