xref: /DADK/dadk/src/actions/rootfs/loopdev.rs (revision c10d7695af26de33f43f6cae8df30a886f572013)
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