loopdev.rs 8.6 KB

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