17a97f354SLoGin use core::str;
2c10d7695SLoGin 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>,
14*ce26d675SLoGin /// 尝试在drop时自动detach
15*ce26d675SLoGin try_detach_when_drop: bool,
16c6f35e8aSLoGin }
17c6f35e8aSLoGin impl LoopDevice {
attached(&self) -> bool187a97f354SLoGin pub fn attached(&self) -> bool {
197a97f354SLoGin self.loop_device_path.is_some()
207a97f354SLoGin }
217a97f354SLoGin
dev_path(&self) -> Option<&String>227a97f354SLoGin pub fn dev_path(&self) -> Option<&String> {
237a97f354SLoGin self.loop_device_path.as_ref()
247a97f354SLoGin }
257a97f354SLoGin
attach(&mut self) -> Result<()>26c6f35e8aSLoGin pub fn attach(&mut self) -> Result<()> {
277a97f354SLoGin if self.attached() {
28c6f35e8aSLoGin return Ok(());
29c6f35e8aSLoGin }
307a97f354SLoGin if self.img_path.is_none() {
317a97f354SLoGin return Err(anyhow!("Image path not set"));
327a97f354SLoGin }
337a97f354SLoGin
34c6f35e8aSLoGin let output = Command::new("losetup")
35c6f35e8aSLoGin .arg("-f")
36c6f35e8aSLoGin .arg("--show")
37c6f35e8aSLoGin .arg("-P")
387a97f354SLoGin .arg(self.img_path.as_ref().unwrap())
39c6f35e8aSLoGin .output()?;
40c6f35e8aSLoGin
41c6f35e8aSLoGin if output.status.success() {
42c6f35e8aSLoGin let loop_device = String::from_utf8(output.stdout)?.trim().to_string();
43c6f35e8aSLoGin self.loop_device_path = Some(loop_device);
44c10d7695SLoGin sleep(Duration::from_millis(100));
45c6f35e8aSLoGin log::trace!(
46c6f35e8aSLoGin "Loop device attached: {}",
47c6f35e8aSLoGin self.loop_device_path.as_ref().unwrap()
48c6f35e8aSLoGin );
49c6f35e8aSLoGin Ok(())
50c6f35e8aSLoGin } else {
51c6f35e8aSLoGin Err(anyhow::anyhow!(
52c6f35e8aSLoGin "Failed to mount disk image: losetup command exited with status {}",
53c6f35e8aSLoGin output.status
54c6f35e8aSLoGin ))
55c6f35e8aSLoGin }
56c6f35e8aSLoGin }
577a97f354SLoGin
587a97f354SLoGin /// 尝试连接已经存在的loop device
attach_by_exists(&mut self) -> Result<()>597a97f354SLoGin pub fn attach_by_exists(&mut self) -> Result<()> {
607a97f354SLoGin if self.attached() {
617a97f354SLoGin return Ok(());
627a97f354SLoGin }
637a97f354SLoGin if self.img_path.is_none() {
647a97f354SLoGin return Err(anyhow!("Image path not set"));
657a97f354SLoGin }
667a97f354SLoGin log::trace!(
677a97f354SLoGin "Try to attach loop device by exists: image path: {}",
687a97f354SLoGin self.img_path.as_ref().unwrap().display()
697a97f354SLoGin );
707a97f354SLoGin // losetup -a 查看是否有已经attach了的,如果有,就附着上去
717a97f354SLoGin let cmd = Command::new("losetup")
727a97f354SLoGin .arg("-a")
737a97f354SLoGin .output()
747a97f354SLoGin .map_err(|e| anyhow!("Failed to run losetup -a: {}", e))?;
757a97f354SLoGin let output = String::from_utf8(cmd.stdout)?;
767a97f354SLoGin let s = __loop_device_path_by_disk_image_path(
777a97f354SLoGin self.img_path.as_ref().unwrap().to_str().unwrap(),
787a97f354SLoGin &output,
797a97f354SLoGin )
807a97f354SLoGin .map_err(|e| anyhow!("Failed to find loop device: {}", e))?;
817a97f354SLoGin self.loop_device_path = Some(s);
827a97f354SLoGin Ok(())
837a97f354SLoGin }
847a97f354SLoGin
85c6f35e8aSLoGin /// 获取指定分区的路径
86c6f35e8aSLoGin ///
87c6f35e8aSLoGin /// # 参数
88c6f35e8aSLoGin ///
89c6f35e8aSLoGin /// * `nth` - 分区的编号
90c6f35e8aSLoGin ///
91c6f35e8aSLoGin /// # 返回值
92c6f35e8aSLoGin ///
93c6f35e8aSLoGin /// 返回一个 `Result<String>`,包含分区路径的字符串。如果循环设备未附加,则返回错误。
94c6f35e8aSLoGin ///
95c6f35e8aSLoGin /// # 错误
96c6f35e8aSLoGin ///
97c6f35e8aSLoGin /// 如果循环设备未附加,则返回 `anyhow!("Loop device not attached")` 错误。
partition_path(&self, nth: u8) -> Result<PathBuf>98c6f35e8aSLoGin pub fn partition_path(&self, nth: u8) -> Result<PathBuf> {
997a97f354SLoGin if !self.attached() {
100c6f35e8aSLoGin return Err(anyhow!("Loop device not attached"));
101c6f35e8aSLoGin }
102c6f35e8aSLoGin let s = format!("{}p{}", self.loop_device_path.as_ref().unwrap(), nth);
103c6f35e8aSLoGin let s = PathBuf::from(s);
104c6f35e8aSLoGin // 判断路径是否存在
105c6f35e8aSLoGin if !s.exists() {
106c6f35e8aSLoGin return Err(anyhow!("Partition not exist"));
107c6f35e8aSLoGin }
108c6f35e8aSLoGin Ok(s)
109c6f35e8aSLoGin }
110c6f35e8aSLoGin
detach(&mut self) -> Result<()>111c6f35e8aSLoGin pub fn detach(&mut self) -> Result<()> {
112c6f35e8aSLoGin if self.loop_device_path.is_none() {
113c6f35e8aSLoGin return Ok(());
114c6f35e8aSLoGin }
115c6f35e8aSLoGin let loop_device = self.loop_device_path.take().unwrap();
116*ce26d675SLoGin let p = PathBuf::from(&loop_device);
117*ce26d675SLoGin log::trace!(
118*ce26d675SLoGin "Detach loop device: {}, exists: {}",
119*ce26d675SLoGin p.display(),
120*ce26d675SLoGin p.exists()
121*ce26d675SLoGin );
122c6f35e8aSLoGin let output = Command::new("losetup")
123c6f35e8aSLoGin .arg("-d")
124c6f35e8aSLoGin .arg(loop_device)
125c6f35e8aSLoGin .output()?;
126c6f35e8aSLoGin
127c6f35e8aSLoGin if output.status.success() {
128c6f35e8aSLoGin self.loop_device_path = None;
129c6f35e8aSLoGin Ok(())
130c6f35e8aSLoGin } else {
1317a97f354SLoGin Err(anyhow::anyhow!(
1327a97f354SLoGin "Failed to detach loop device: {}, {}",
1337a97f354SLoGin output.status,
1347a97f354SLoGin str::from_utf8(output.stderr.as_slice()).unwrap_or("<Unknown>")
1357a97f354SLoGin ))
136c6f35e8aSLoGin }
137c6f35e8aSLoGin }
138*ce26d675SLoGin
try_detach_when_drop(&self) -> bool139*ce26d675SLoGin pub fn try_detach_when_drop(&self) -> bool {
140*ce26d675SLoGin self.try_detach_when_drop
141*ce26d675SLoGin }
142*ce26d675SLoGin
143*ce26d675SLoGin #[allow(dead_code)]
set_try_detach_when_drop(&mut self, try_detach_when_drop: bool)144*ce26d675SLoGin pub fn set_try_detach_when_drop(&mut self, try_detach_when_drop: bool) {
145*ce26d675SLoGin self.try_detach_when_drop = try_detach_when_drop;
146*ce26d675SLoGin }
147c6f35e8aSLoGin }
148c6f35e8aSLoGin
149c6f35e8aSLoGin impl Drop for LoopDevice {
drop(&mut self)150c6f35e8aSLoGin fn drop(&mut self) {
151*ce26d675SLoGin if self.try_detach_when_drop() {
1527a97f354SLoGin if let Err(e) = self.detach() {
1537a97f354SLoGin log::warn!("Failed to detach loop device: {}", e);
1547a97f354SLoGin }
155c6f35e8aSLoGin }
156c6f35e8aSLoGin }
157*ce26d675SLoGin }
158c6f35e8aSLoGin
159c6f35e8aSLoGin pub struct LoopDeviceBuilder {
160c6f35e8aSLoGin img_path: Option<PathBuf>,
1617a97f354SLoGin loop_device_path: Option<String>,
162*ce26d675SLoGin try_detach_when_drop: bool,
163c6f35e8aSLoGin }
164c6f35e8aSLoGin
165c6f35e8aSLoGin impl LoopDeviceBuilder {
new() -> Self166c6f35e8aSLoGin pub fn new() -> Self {
1677a97f354SLoGin LoopDeviceBuilder {
1687a97f354SLoGin img_path: None,
1697a97f354SLoGin loop_device_path: None,
170*ce26d675SLoGin try_detach_when_drop: true,
1717a97f354SLoGin }
172c6f35e8aSLoGin }
173c6f35e8aSLoGin
img_path(mut self, img_path: PathBuf) -> Self174c6f35e8aSLoGin pub fn img_path(mut self, img_path: PathBuf) -> Self {
1757a97f354SLoGin self.img_path = Some(abs_path(&img_path));
176c6f35e8aSLoGin self
177c6f35e8aSLoGin }
178c6f35e8aSLoGin
179*ce26d675SLoGin #[allow(dead_code)]
try_detach_when_drop(mut self, try_detach_when_drop: bool) -> Self180*ce26d675SLoGin pub fn try_detach_when_drop(mut self, try_detach_when_drop: bool) -> Self {
181*ce26d675SLoGin self.try_detach_when_drop = try_detach_when_drop;
182*ce26d675SLoGin self
183*ce26d675SLoGin }
184*ce26d675SLoGin
build(self) -> Result<LoopDevice>185c6f35e8aSLoGin pub fn build(self) -> Result<LoopDevice> {
1867a97f354SLoGin let loop_dev = LoopDevice {
1877a97f354SLoGin img_path: self.img_path,
1887a97f354SLoGin loop_device_path: self.loop_device_path,
189*ce26d675SLoGin try_detach_when_drop: self.try_detach_when_drop,
190c6f35e8aSLoGin };
1917a97f354SLoGin
192c6f35e8aSLoGin Ok(loop_dev)
193c6f35e8aSLoGin }
194c6f35e8aSLoGin }
1957a97f354SLoGin
__loop_device_path_by_disk_image_path( disk_img_path: &str, losetup_a_output: &str, ) -> Result<String>1967a97f354SLoGin fn __loop_device_path_by_disk_image_path(
1977a97f354SLoGin disk_img_path: &str,
1987a97f354SLoGin losetup_a_output: &str,
1997a97f354SLoGin ) -> Result<String> {
2007a97f354SLoGin let re = Regex::new(LOOP_DEVICE_LOSETUP_A_REGEX)?;
2017a97f354SLoGin for line in losetup_a_output.lines() {
2027a97f354SLoGin if !line.contains(disk_img_path) {
2037a97f354SLoGin continue;
2047a97f354SLoGin }
2057a97f354SLoGin let caps = re.captures(line);
2067a97f354SLoGin if caps.is_none() {
2077a97f354SLoGin continue;
2087a97f354SLoGin }
2097a97f354SLoGin let caps = caps.unwrap();
2107a97f354SLoGin let loop_device = caps.get(1).unwrap().as_str();
2117a97f354SLoGin let loop_device = format!("/dev/loop{}", loop_device);
2127a97f354SLoGin return Ok(loop_device);
2137a97f354SLoGin }
2147a97f354SLoGin Err(anyhow!("Loop device not found"))
2157a97f354SLoGin }
2167a97f354SLoGin
2177a97f354SLoGin #[cfg(test)]
2187a97f354SLoGin mod tests {
2197a97f354SLoGin use super::*;
2207a97f354SLoGin
2217a97f354SLoGin #[test]
test_regex_find_loop_device()2227a97f354SLoGin fn test_regex_find_loop_device() {
2237a97f354SLoGin const DEVICE_NAME_SHOULD_MATCH: [&str; 3] =
2247a97f354SLoGin ["/dev/loop11", "/dev/loop11p1", "/dev/loop11p1 "];
2257a97f354SLoGin let device_name = "/dev/loop11";
2267a97f354SLoGin let re = Regex::new(LOOP_DEVICE_LOSETUP_A_REGEX).unwrap();
2277a97f354SLoGin for name in DEVICE_NAME_SHOULD_MATCH {
2287a97f354SLoGin assert!(re.find(name).is_some(), "{} should match", name);
2297a97f354SLoGin assert_eq!(
2307a97f354SLoGin re.find(name).unwrap().as_str(),
2317a97f354SLoGin device_name,
2327a97f354SLoGin "{} should match {}",
2337a97f354SLoGin name,
2347a97f354SLoGin device_name
2357a97f354SLoGin );
2367a97f354SLoGin }
2377a97f354SLoGin }
2387a97f354SLoGin
2397a97f354SLoGin #[test]
test_parse_losetup_a_output()2407a97f354SLoGin fn test_parse_losetup_a_output() {
2417a97f354SLoGin let losetup_a_output = r#"/dev/loop1: []: (/data/bin/disk-image-x86_64.img)
2427a97f354SLoGin /dev/loop29: []: (/var/lib/abc.img)
2437a97f354SLoGin /dev/loop13: []: (/var/lib/snapd/snaps/gtk-common-themes_1535.snap
2447a97f354SLoGin /dev/loop19: []: (/var/lib/snapd/snaps/gnome-42-2204_172.snap)"#;
2457a97f354SLoGin let disk_img_path = "/data/bin/disk-image-x86_64.img";
2467a97f354SLoGin let loop_device_path =
2477a97f354SLoGin __loop_device_path_by_disk_image_path(disk_img_path, losetup_a_output).unwrap();
2487a97f354SLoGin assert_eq!(loop_device_path, "/dev/loop1");
2497a97f354SLoGin }
2507a97f354SLoGin
2517a97f354SLoGin #[test]
test_parse_lsblk_output_not_match()2527a97f354SLoGin fn test_parse_lsblk_output_not_match() {
2537a97f354SLoGin let losetup_a_output = r#"/dev/loop1: []: (/data/bin/disk-image-x86_64.img)
2547a97f354SLoGin /dev/loop29: []: (/var/lib/abc.img)
2557a97f354SLoGin /dev/loop13: []: (/var/lib/snapd/snaps/gtk-common-themes_1535.snap
2567a97f354SLoGin /dev/loop19: []: (/var/lib/snapd/snaps/gnome-42-2204_172.snap)"#;
2577a97f354SLoGin let disk_img_path = "/data/bin/disk-image-riscv64.img";
2587a97f354SLoGin let loop_device_path =
2597a97f354SLoGin __loop_device_path_by_disk_image_path(disk_img_path, losetup_a_output);
2607a97f354SLoGin assert!(
2617a97f354SLoGin loop_device_path.is_err(),
2627a97f354SLoGin "should not match any loop device"
2637a97f354SLoGin );
2647a97f354SLoGin }
2657a97f354SLoGin }
266