loopdev.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. use core::str;
  2. use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
  3. use regex::Regex;
  4. use crate::utils::abs_path;
  5. const LOOP_DEVICE_LOSETUP_A_REGEX: &str = r"^/dev/loop(\d+)";
  6. #[derive(Debug)]
  7. pub enum LoopError {
  8. InvalidUtf8,
  9. ImageNotFound,
  10. LoopDeviceNotFound,
  11. NoMapperAvailable,
  12. NoPartitionAvailable,
  13. Losetup(String),
  14. Kpartx(String),
  15. #[allow(dead_code)]
  16. Other(anyhow::Error),
  17. }
  18. impl From<std::string::FromUtf8Error> for LoopError {
  19. fn from(_: std::string::FromUtf8Error) -> Self {
  20. LoopError::InvalidUtf8
  21. }
  22. }
  23. impl std::fmt::Display for LoopError {
  24. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  25. match self {
  26. LoopError::InvalidUtf8 => write!(f, "Invalid UTF-8"),
  27. LoopError::ImageNotFound => write!(f, "Image not found"),
  28. LoopError::LoopDeviceNotFound => write!(f, "Loop device not found"),
  29. LoopError::NoMapperAvailable => write!(f, "No mapper available"),
  30. LoopError::NoPartitionAvailable => write!(f, "No partition available"),
  31. LoopError::Losetup(err) => write!(f, "Losetup error: {}", err),
  32. LoopError::Kpartx(err) => write!(f, "Kpartx error: {}", err),
  33. LoopError::Other(err) => write!(f, "Other error: {}", err),
  34. }
  35. }
  36. }
  37. impl std::error::Error for LoopError {}
  38. pub struct LoopDeviceBuilder {
  39. img_path: Option<PathBuf>,
  40. // loop_device_path: Option<String>,
  41. detach_on_drop: bool,
  42. }
  43. impl LoopDeviceBuilder {
  44. pub fn new() -> Self {
  45. LoopDeviceBuilder {
  46. img_path: None,
  47. // loop_device_path: None,
  48. detach_on_drop: true,
  49. }
  50. }
  51. pub fn img_path(mut self, img_path: PathBuf) -> Self {
  52. self.img_path = Some(abs_path(&img_path));
  53. self
  54. }
  55. #[allow(dead_code)]
  56. pub fn detach_on_drop(mut self, detach_on_drop: bool) -> Self {
  57. self.detach_on_drop = detach_on_drop;
  58. self
  59. }
  60. pub fn build(self) -> Result<LoopDevice, LoopError> {
  61. if self.img_path.is_none() {
  62. return Err(LoopError::ImageNotFound);
  63. }
  64. let img_path = self.img_path.unwrap();
  65. log::trace!(
  66. "Try to attach loop device by exists: image path: {}",
  67. img_path.display()
  68. );
  69. let loop_device = LoopDevice::new(img_path, self.detach_on_drop)?;
  70. return Ok(loop_device);
  71. }
  72. }
  73. fn attach_loop_by_image(img_path: &str) -> Result<String, LoopError> {
  74. LosetupCmd::new()
  75. .arg("-f")
  76. .arg("--show")
  77. .arg("-P")
  78. .arg(img_path)
  79. .output()
  80. .map(|output_path| output_path.trim().to_string())
  81. }
  82. fn attach_exists_loop_by_image(img_path: &str) -> Result<String, LoopError> {
  83. // losetup -a 查看是否有已经attach了的,如果有,就附着上去
  84. let output = LosetupCmd::new().arg("-a").output()?;
  85. __loop_device_path_by_disk_image_path(img_path, &output)
  86. }
  87. fn __loop_device_path_by_disk_image_path(
  88. disk_img_path: &str,
  89. losetup_a_output: &str,
  90. ) -> Result<String, LoopError> {
  91. let re = Regex::new(LOOP_DEVICE_LOSETUP_A_REGEX).unwrap();
  92. for line in losetup_a_output.lines() {
  93. if !line.contains(disk_img_path) {
  94. continue;
  95. }
  96. let caps = re.captures(line);
  97. if caps.is_none() {
  98. continue;
  99. }
  100. let caps = caps.unwrap();
  101. let loop_device = caps.get(1).unwrap().as_str().trim();
  102. let loop_device = format!("/dev/loop{}", loop_device);
  103. return Ok(loop_device);
  104. }
  105. Err(LoopError::LoopDeviceNotFound)
  106. }
  107. pub struct LoopDevice {
  108. path: PathBuf,
  109. detach_on_drop: bool,
  110. mapper: Mapper,
  111. }
  112. impl LoopDevice {
  113. fn new(img_path: PathBuf, detach_on_drop: bool) -> Result<Self, LoopError> {
  114. if !img_path.exists() {
  115. return Err(LoopError::ImageNotFound);
  116. }
  117. let str_img_path = img_path.to_str().ok_or(LoopError::InvalidUtf8)?;
  118. let may_attach = attach_exists_loop_by_image(str_img_path);
  119. let loop_device_path = match may_attach {
  120. Ok(loop_device_path) => {
  121. log::trace!("Loop device already attached: {}", loop_device_path);
  122. loop_device_path
  123. }
  124. Err(LoopError::LoopDeviceNotFound) => {
  125. log::trace!("No loop device found, try to attach");
  126. attach_loop_by_image(str_img_path)?
  127. }
  128. Err(err) => {
  129. log::error!("Failed to attach loop device: {}", err);
  130. return Err(err);
  131. }
  132. };
  133. let path = PathBuf::from(loop_device_path);
  134. sleep(Duration::from_millis(100));
  135. if !path.exists() {
  136. return Err(LoopError::LoopDeviceNotFound);
  137. }
  138. let mapper = Mapper::new(path.clone(), detach_on_drop)?;
  139. Ok(Self {
  140. path,
  141. detach_on_drop,
  142. mapper,
  143. })
  144. }
  145. pub fn dev_path(&self) -> String {
  146. self.path.to_string_lossy().to_string()
  147. }
  148. pub fn partition_path(&self, nth: u8) -> Result<PathBuf, LoopError> {
  149. self.mapper.partition_path(nth)
  150. }
  151. // #[allow(dead_code)]
  152. // pub fn detach_on_drop(&self) -> bool {
  153. // self.detach_on_drop
  154. // }
  155. // #[allow(dead_code)]
  156. // pub fn set_detach_on_drop(&mut self, detach_on_drop: bool) {
  157. // self.detach_on_drop = detach_on_drop;
  158. // }
  159. }
  160. impl Drop for LoopDevice {
  161. fn drop(&mut self) {
  162. if !self.detach_on_drop {
  163. return;
  164. }
  165. log::trace!(
  166. "Detach loop device: {}, exists: {}",
  167. &self.path.display(),
  168. self.path.exists()
  169. );
  170. if self.path.exists() {
  171. let path = self.path.to_string_lossy();
  172. if let Err(err) = LosetupCmd::new().arg("-d").arg(&path).output() {
  173. log::error!("Failed to detach loop device: {}", err);
  174. }
  175. }
  176. }
  177. }
  178. struct LosetupCmd {
  179. inner: Command,
  180. }
  181. impl LosetupCmd {
  182. fn new() -> Self {
  183. LosetupCmd {
  184. inner: Command::new("losetup"),
  185. }
  186. }
  187. fn arg(&mut self, arg: &str) -> &mut Self {
  188. self.inner.arg(arg);
  189. self
  190. }
  191. fn output(&mut self) -> Result<String, LoopError> {
  192. let output = self
  193. .inner
  194. .output()
  195. .map_err(|e| LoopError::Losetup(e.to_string()))?;
  196. if output.status.success() {
  197. let stdout = String::from_utf8(output.stdout)?;
  198. Ok(stdout)
  199. } else {
  200. Err(LoopError::Losetup(format!(
  201. "losetup failed: {}",
  202. String::from_utf8_lossy(&output.stderr)
  203. )))
  204. }
  205. }
  206. }
  207. struct Mapper {
  208. dev_path: PathBuf,
  209. detach_on_drop: bool,
  210. use_kpartx: bool,
  211. partitions: Vec<String>,
  212. }
  213. impl Mapper {
  214. fn new(path: PathBuf, detach_on_drop: bool) -> Result<Self, LoopError> {
  215. // check if raw part mapper is available, if {device_dir}/loopXpX
  216. let mut parts = Vec::new();
  217. let partition_name_prefix = format!("{}p", path.file_name().unwrap().to_str().unwrap());
  218. let device_dir = path.parent().unwrap();
  219. for entry in device_dir.read_dir().unwrap() {
  220. if let Ok(entry) = entry {
  221. let entry = entry.file_name().into_string().unwrap();
  222. if entry.starts_with(&partition_name_prefix) {
  223. parts.push(entry);
  224. }
  225. }
  226. }
  227. if !parts.is_empty() {
  228. log::trace!("Found raw part mapper: {:?}", parts);
  229. return Ok(Self {
  230. dev_path: path.to_path_buf(),
  231. detach_on_drop,
  232. partitions: parts,
  233. use_kpartx: false,
  234. });
  235. }
  236. // check if mapper is created, found if {device_dir}/mapper/loopX
  237. let mapper_path = device_dir.join("mapper");
  238. for entry in mapper_path.read_dir().unwrap() {
  239. if let Ok(entry) = entry {
  240. let entry = entry.file_name().into_string().unwrap();
  241. if entry.starts_with(&partition_name_prefix) {
  242. parts.push(entry);
  243. }
  244. }
  245. }
  246. if !parts.is_empty() {
  247. log::trace!("Found kpartx mapper exist: {:?}", parts);
  248. return Ok(Self {
  249. dev_path: path,
  250. detach_on_drop,
  251. partitions: parts,
  252. use_kpartx: true,
  253. });
  254. }
  255. KpartxCmd::new()
  256. .arg("-a")
  257. .arg(path.to_str().unwrap())
  258. .output()?;
  259. for entry in mapper_path.read_dir().unwrap() {
  260. if let Ok(entry) = entry {
  261. let entry = entry.file_name().into_string().unwrap();
  262. if entry.starts_with(&partition_name_prefix) {
  263. parts.push(entry);
  264. }
  265. }
  266. }
  267. if !parts.is_empty() {
  268. log::trace!("New kpartx with parts: {:?}", parts);
  269. return Ok(Self {
  270. dev_path: path,
  271. detach_on_drop,
  272. partitions: parts,
  273. use_kpartx: true,
  274. });
  275. }
  276. Err(LoopError::NoMapperAvailable)
  277. }
  278. fn partition_path(&self, nth: u8) -> Result<PathBuf, LoopError> {
  279. if self.partitions.is_empty() {
  280. // unlikely, already checked in new()
  281. log::warn!("No partition available, but the mapper device exists!");
  282. return Err(LoopError::NoPartitionAvailable);
  283. }
  284. let map_root = if !self.use_kpartx {
  285. self.dev_path
  286. .parent()
  287. .unwrap()
  288. .to_string_lossy()
  289. .into_owned()
  290. } else {
  291. // kpartx mapper device
  292. self.dev_path
  293. .with_file_name("mapper")
  294. .to_string_lossy()
  295. .into_owned()
  296. };
  297. let partition = PathBuf::from(format!(
  298. "{}/{}",
  299. map_root,
  300. self.partitions
  301. .get((nth - 1) as usize)
  302. .ok_or(LoopError::NoPartitionAvailable)?,
  303. ));
  304. if !partition.exists() {
  305. log::warn!("Partition exists, but the specified partition does not exist!");
  306. log::warn!("Available partitions: {:?}", self.partitions);
  307. log::warn!("Try to find partition: {}", partition.display());
  308. return Err(LoopError::NoPartitionAvailable);
  309. }
  310. Ok(partition)
  311. }
  312. }
  313. impl Drop for Mapper {
  314. fn drop(&mut self) {
  315. if !self.detach_on_drop {
  316. return;
  317. }
  318. if self.dev_path.exists() {
  319. let path = self.dev_path.to_string_lossy();
  320. if self.use_kpartx {
  321. if let Err(err) = KpartxCmd::new().arg("-d").arg(&path).output() {
  322. log::error!("Failed to detach mapper device: {}", err);
  323. }
  324. }
  325. }
  326. }
  327. }
  328. struct KpartxCmd {
  329. inner: Command,
  330. }
  331. impl KpartxCmd {
  332. fn new() -> Self {
  333. KpartxCmd {
  334. inner: Command::new("kpartx"),
  335. }
  336. }
  337. fn arg(&mut self, arg: &str) -> &mut Self {
  338. self.inner.arg(arg);
  339. self
  340. }
  341. fn output(&mut self) -> Result<String, LoopError> {
  342. let output = self
  343. .inner
  344. .output()
  345. .map_err(|e| LoopError::Kpartx(e.to_string()))?;
  346. if output.status.success() {
  347. let stdout = String::from_utf8(output.stdout)?;
  348. Ok(stdout)
  349. } else {
  350. Err(LoopError::Kpartx(format!(
  351. "kpartx failed execute: {:?}, Result: {}",
  352. self.inner.get_args(),
  353. String::from_utf8_lossy(&output.stderr)
  354. )))
  355. }
  356. }
  357. }
  358. #[cfg(test)]
  359. mod tests {
  360. use super::*;
  361. #[test]
  362. fn test_regex_find_loop_device() {
  363. const DEVICE_NAME_SHOULD_MATCH: [&str; 3] =
  364. ["/dev/loop11", "/dev/loop11p1", "/dev/loop11p1 "];
  365. let device_name = "/dev/loop11";
  366. let re = Regex::new(LOOP_DEVICE_LOSETUP_A_REGEX).unwrap();
  367. for name in DEVICE_NAME_SHOULD_MATCH {
  368. assert!(re.find(name).is_some(), "{} should match", name);
  369. assert_eq!(
  370. re.find(name).unwrap().as_str(),
  371. device_name,
  372. "{} should match {}",
  373. name,
  374. device_name
  375. );
  376. }
  377. }
  378. #[test]
  379. fn test_parse_losetup_a_output() {
  380. let losetup_a_output = r#"/dev/loop1: []: (/data/bin/x86_64/disk.img)
  381. /dev/loop29: []: (/var/lib/abc.img)
  382. /dev/loop13: []: (/var/lib/snapd/snaps/gtk-common-themes_1535.snap
  383. /dev/loop19: []: (/var/lib/snapd/snaps/gnome-42-2204_172.snap)"#;
  384. let disk_img_path = "/data/bin/x86_64/disk.img";
  385. let loop_device_path =
  386. __loop_device_path_by_disk_image_path(disk_img_path, losetup_a_output).unwrap();
  387. assert_eq!(loop_device_path, "/dev/loop1");
  388. }
  389. #[test]
  390. fn test_parse_lsblk_output_not_match() {
  391. let losetup_a_output = r#"/dev/loop1: []: (/data/bin/x86_64/disk.img)
  392. /dev/loop29: []: (/var/lib/abc.img)
  393. /dev/loop13: []: (/var/lib/snapd/snaps/gtk-common-themes_1535.snap
  394. /dev/loop19: []: (/var/lib/snapd/snaps/gnome-42-2204_172.snap)"#;
  395. let disk_img_path = "/data/bin/riscv64/disk.img";
  396. let loop_device_path =
  397. __loop_device_path_by_disk_image_path(disk_img_path, losetup_a_output);
  398. assert!(
  399. loop_device_path.is_err(),
  400. "should not match any loop device"
  401. );
  402. }
  403. }