123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- use core::str;
- use std::{path::PathBuf, process::Command, thread::sleep, time::Duration};
- use anyhow::{anyhow, Result};
- use regex::Regex;
- use crate::utils::abs_path;
- const LOOP_DEVICE_LOSETUP_A_REGEX: &str = r"^/dev/loop(\d+)";
- pub struct LoopDevice {
- img_path: Option<PathBuf>,
- loop_device_path: Option<String>,
- /// 尝试在drop时自动detach
- try_detach_when_drop: bool,
- }
- impl LoopDevice {
- pub fn attached(&self) -> bool {
- self.loop_device_path.is_some()
- }
- pub fn dev_path(&self) -> Option<&String> {
- self.loop_device_path.as_ref()
- }
- pub fn attach(&mut self) -> Result<()> {
- if self.attached() {
- return Ok(());
- }
- if self.img_path.is_none() {
- return Err(anyhow!("Image path not set"));
- }
- let output = Command::new("losetup")
- .arg("-f")
- .arg("--show")
- .arg("-P")
- .arg(self.img_path.as_ref().unwrap())
- .output()?;
- if output.status.success() {
- let loop_device = String::from_utf8(output.stdout)?.trim().to_string();
- self.loop_device_path = Some(loop_device);
- sleep(Duration::from_millis(100));
- log::trace!(
- "Loop device attached: {}",
- self.loop_device_path.as_ref().unwrap()
- );
- Ok(())
- } else {
- Err(anyhow::anyhow!(
- "Failed to mount disk image: losetup command exited with status {}",
- output.status
- ))
- }
- }
- /// 尝试连接已经存在的loop device
- pub fn attach_by_exists(&mut self) -> Result<()> {
- if self.attached() {
- return Ok(());
- }
- if self.img_path.is_none() {
- return Err(anyhow!("Image path not set"));
- }
- log::trace!(
- "Try to attach loop device by exists: image path: {}",
- self.img_path.as_ref().unwrap().display()
- );
- // losetup -a 查看是否有已经attach了的,如果有,就附着上去
- let cmd = Command::new("losetup")
- .arg("-a")
- .output()
- .map_err(|e| anyhow!("Failed to run losetup -a: {}", e))?;
- let output = String::from_utf8(cmd.stdout)?;
- let s = __loop_device_path_by_disk_image_path(
- self.img_path.as_ref().unwrap().to_str().unwrap(),
- &output,
- )
- .map_err(|e| anyhow!("Failed to find loop device: {}", e))?;
- self.loop_device_path = Some(s);
- Ok(())
- }
- /// 获取指定分区的路径
- ///
- /// # 参数
- ///
- /// * `nth` - 分区的编号
- ///
- /// # 返回值
- ///
- /// 返回一个 `Result<String>`,包含分区路径的字符串。如果循环设备未附加,则返回错误。
- ///
- /// # 错误
- ///
- /// 如果循环设备未附加,则返回 `anyhow!("Loop device not attached")` 错误。
- pub fn partition_path(&self, nth: u8) -> Result<PathBuf> {
- if !self.attached() {
- return Err(anyhow!("Loop device not attached"));
- }
- let s = format!("{}p{}", self.loop_device_path.as_ref().unwrap(), nth);
- let s = PathBuf::from(s);
- // 判断路径是否存在
- if !s.exists() {
- return Err(anyhow!("Partition not exist"));
- }
- Ok(s)
- }
- pub fn detach(&mut self) -> Result<()> {
- if self.loop_device_path.is_none() {
- return Ok(());
- }
- let loop_device = self.loop_device_path.take().unwrap();
- let p = PathBuf::from(&loop_device);
- log::trace!(
- "Detach loop device: {}, exists: {}",
- p.display(),
- p.exists()
- );
- let output = Command::new("losetup")
- .arg("-d")
- .arg(loop_device)
- .output()?;
- if output.status.success() {
- self.loop_device_path = None;
- Ok(())
- } else {
- Err(anyhow::anyhow!(
- "Failed to detach loop device: {}, {}",
- output.status,
- str::from_utf8(output.stderr.as_slice()).unwrap_or("<Unknown>")
- ))
- }
- }
- pub fn try_detach_when_drop(&self) -> bool {
- self.try_detach_when_drop
- }
- #[allow(dead_code)]
- pub fn set_try_detach_when_drop(&mut self, try_detach_when_drop: bool) {
- self.try_detach_when_drop = try_detach_when_drop;
- }
- }
- impl Drop for LoopDevice {
- fn drop(&mut self) {
- if self.try_detach_when_drop() {
- if let Err(e) = self.detach() {
- log::warn!("Failed to detach loop device: {}", e);
- }
- }
- }
- }
- pub struct LoopDeviceBuilder {
- img_path: Option<PathBuf>,
- loop_device_path: Option<String>,
- try_detach_when_drop: bool,
- }
- impl LoopDeviceBuilder {
- pub fn new() -> Self {
- LoopDeviceBuilder {
- img_path: None,
- loop_device_path: None,
- try_detach_when_drop: true,
- }
- }
- pub fn img_path(mut self, img_path: PathBuf) -> Self {
- self.img_path = Some(abs_path(&img_path));
- self
- }
- #[allow(dead_code)]
- pub fn try_detach_when_drop(mut self, try_detach_when_drop: bool) -> Self {
- self.try_detach_when_drop = try_detach_when_drop;
- self
- }
- pub fn build(self) -> Result<LoopDevice> {
- let loop_dev = LoopDevice {
- img_path: self.img_path,
- loop_device_path: self.loop_device_path,
- try_detach_when_drop: self.try_detach_when_drop,
- };
- Ok(loop_dev)
- }
- }
- fn __loop_device_path_by_disk_image_path(
- disk_img_path: &str,
- losetup_a_output: &str,
- ) -> Result<String> {
- let re = Regex::new(LOOP_DEVICE_LOSETUP_A_REGEX)?;
- for line in losetup_a_output.lines() {
- if !line.contains(disk_img_path) {
- continue;
- }
- let caps = re.captures(line);
- if caps.is_none() {
- continue;
- }
- let caps = caps.unwrap();
- let loop_device = caps.get(1).unwrap().as_str();
- let loop_device = format!("/dev/loop{}", loop_device);
- return Ok(loop_device);
- }
- Err(anyhow!("Loop device not found"))
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- #[test]
- fn test_regex_find_loop_device() {
- const DEVICE_NAME_SHOULD_MATCH: [&str; 3] =
- ["/dev/loop11", "/dev/loop11p1", "/dev/loop11p1 "];
- let device_name = "/dev/loop11";
- let re = Regex::new(LOOP_DEVICE_LOSETUP_A_REGEX).unwrap();
- for name in DEVICE_NAME_SHOULD_MATCH {
- assert!(re.find(name).is_some(), "{} should match", name);
- assert_eq!(
- re.find(name).unwrap().as_str(),
- device_name,
- "{} should match {}",
- name,
- device_name
- );
- }
- }
- #[test]
- fn test_parse_losetup_a_output() {
- let losetup_a_output = r#"/dev/loop1: []: (/data/bin/disk-image-x86_64.img)
- /dev/loop29: []: (/var/lib/abc.img)
- /dev/loop13: []: (/var/lib/snapd/snaps/gtk-common-themes_1535.snap
- /dev/loop19: []: (/var/lib/snapd/snaps/gnome-42-2204_172.snap)"#;
- let disk_img_path = "/data/bin/disk-image-x86_64.img";
- let loop_device_path =
- __loop_device_path_by_disk_image_path(disk_img_path, losetup_a_output).unwrap();
- assert_eq!(loop_device_path, "/dev/loop1");
- }
- #[test]
- fn test_parse_lsblk_output_not_match() {
- let losetup_a_output = r#"/dev/loop1: []: (/data/bin/disk-image-x86_64.img)
- /dev/loop29: []: (/var/lib/abc.img)
- /dev/loop13: []: (/var/lib/snapd/snaps/gtk-common-themes_1535.snap
- /dev/loop19: []: (/var/lib/snapd/snaps/gnome-42-2204_172.snap)"#;
- let disk_img_path = "/data/bin/disk-image-riscv64.img";
- let loop_device_path =
- __loop_device_path_by_disk_image_path(disk_img_path, losetup_a_output);
- assert!(
- loop_device_path.is_err(),
- "should not match any loop device"
- );
- }
- }
|