Răsfoiți Sursa

feat(rootfs, config): add support for old builders

Samuka007 2 săptămâni în urmă
părinte
comite
ab20161745

+ 9 - 0
dadk-config/src/manifest.rs

@@ -58,6 +58,11 @@ fn check_used_default() -> bool {
 pub struct Metadata {
     /// Target processor architecture
     pub arch: TargetArch,
+
+    /// DADK builder version for isolating unstable features
+    #[serde(default = "default_dadk_builder_version", rename = "builder-version")]
+    pub builder_version: String,
+
     /// Rootfs configuration file path
     #[serde(default = "default_rootfs_config_path", rename = "rootfs-config")]
     pub rootfs_config: PathBuf,
@@ -88,6 +93,10 @@ pub struct Metadata {
     pub user_config_dir: PathBuf,
 }
 
+fn default_dadk_builder_version() -> String {
+    "v1".to_string()
+}
+
 /// Returns the default path for the rootfs configuration file.
 fn default_rootfs_config_path() -> PathBuf {
     set_used_default();

+ 10 - 1
dadk/src/actions/mod.rs

@@ -10,7 +10,16 @@ pub fn run(ctx: DADKExecContext) {
             unimplemented!("kernel command has not implemented for run yet.")
         }
         crate::console::Action::Rootfs(rootfs_command) => {
-            rootfs::run(&ctx, rootfs_command).expect("Run rootfs action error.")
+            match ctx.manifest().metadata.builder_version.as_str() {
+                "v2" => {
+                    // v2版本的rootfs命令
+                    rootfs::v2::run(&ctx, rootfs_command).expect("Run rootfs action error.")
+                }
+                "v1" | _ => {
+                    // v1版本的rootfs命令
+                    rootfs::v1::run(&ctx, rootfs_command).expect("Run rootfs action error.")
+                }
+            }
         }
         crate::console::Action::User(user_command) => {
             user::run(&ctx, user_command).expect("Run user action error.")

+ 2 - 19
dadk/src/actions/rootfs/mod.rs

@@ -1,19 +1,2 @@
-use crate::{console::rootfs::RootFSCommand, context::DADKExecContext};
-use anyhow::Result;
-
-mod disk_img;
-mod loopdev;
-mod sysroot;
-
-pub(super) fn run(ctx: &DADKExecContext, rootfs_cmd: &RootFSCommand) -> Result<()> {
-    match rootfs_cmd {
-        RootFSCommand::Create(param) => disk_img::create(ctx, param.skip_if_exists),
-        RootFSCommand::Delete => disk_img::delete(ctx, false),
-        RootFSCommand::DeleteSysroot => sysroot::delete(ctx),
-        RootFSCommand::Mount => disk_img::mount(ctx),
-        RootFSCommand::Umount => disk_img::umount(ctx),
-        RootFSCommand::CheckDiskImageExists => disk_img::check_disk_image_exists(ctx),
-        RootFSCommand::ShowMountPoint => disk_img::show_mount_point(ctx),
-        RootFSCommand::ShowLoopDevice => disk_img::show_loop_device(ctx),
-    }
-}
+pub mod v1;
+pub mod v2;

+ 4 - 1
dadk/src/actions/rootfs/disk_img.rs → dadk/src/actions/rootfs/v1/disk_img.rs

@@ -261,7 +261,10 @@ pub fn show_mount_point(ctx: &DADKExecContext) -> Result<()> {
 
 pub fn show_loop_device(ctx: &DADKExecContext) -> Result<()> {
     let disk_image_path = ctx.disk_image_path();
-    let mut loop_device = LoopDeviceBuilder::new().detach_on_drop(false).img_path(disk_image_path).build()?;
+    let mut loop_device = LoopDeviceBuilder::new()
+        .detach_on_drop(false)
+        .img_path(disk_image_path)
+        .build()?;
     if let Err(e) = loop_device.attach_by_exists() {
         log::error!("Failed to attach loop device: {}", e);
     } else {

+ 19 - 11
dadk/src/actions/rootfs/loopdev.rs → dadk/src/actions/rootfs/v1/loopdev.rs

@@ -115,7 +115,11 @@ impl LoopDevice {
             if new_path.exists() {
                 return Ok(new_path);
             }
-            log::error!("Both {} and {} not exist!", direct_path.display(), new_path.display());
+            log::error!(
+                "Both {} and {} not exist!",
+                direct_path.display(),
+                new_path.display()
+            );
             return Err(anyhow!("Unable to find partition path {}", nth));
         }
         Ok(direct_path)
@@ -139,13 +143,14 @@ impl LoopDevice {
             self.mapper = false;
         }
 
-        let output = Command::new("losetup")
-            .arg("-d")
-            .arg(&loop_device)
-            .output();
+        let output = Command::new("losetup").arg("-d").arg(&loop_device).output();
 
         if !output.is_ok() {
-            log::error!("losetup failed to detach loop device [{}]: {}", &loop_device, output.unwrap_err());
+            log::error!(
+                "losetup failed to detach loop device [{}]: {}",
+                &loop_device,
+                output.unwrap_err()
+            );
             return;
         }
 
@@ -159,7 +164,6 @@ impl LoopDevice {
                 str::from_utf8(output.stderr.as_slice()).unwrap_or("<Unknown>")
             );
         }
-
     }
 
     #[allow(dead_code)]
@@ -182,9 +186,9 @@ impl Drop for LoopDevice {
 }
 
 mod mapper {
-    use std::process::Command;
-    use anyhow::Result;
     use anyhow::anyhow;
+    use anyhow::Result;
+    use std::process::Command;
 
     pub(super) fn create_mapper(dev_path: &str) -> Result<()> {
         let output = Command::new("kpartx")
@@ -218,7 +222,11 @@ mod mapper {
                 );
             }
         } else {
-            log::error!("Failed to detach mapper device [{}]: {}", dev_path, output.unwrap_err());
+            log::error!(
+                "Failed to detach mapper device [{}]: {}",
+                dev_path,
+                output.unwrap_err()
+            );
         }
     }
 }
@@ -254,7 +262,7 @@ impl LoopDeviceBuilder {
             img_path: self.img_path,
             loop_device_path: self.loop_device_path,
             detach_on_drop: self.detach_on_drop,
-            mapper: false
+            mapper: false,
         };
 
         Ok(loop_dev)

+ 19 - 0
dadk/src/actions/rootfs/v1/mod.rs

@@ -0,0 +1,19 @@
+use crate::{console::rootfs::RootFSCommand, context::DADKExecContext};
+use anyhow::Result;
+
+mod disk_img;
+mod loopdev;
+mod sysroot;
+
+pub fn run(ctx: &DADKExecContext, rootfs_cmd: &RootFSCommand) -> Result<()> {
+    match rootfs_cmd {
+        RootFSCommand::Create(param) => disk_img::create(ctx, param.skip_if_exists),
+        RootFSCommand::Delete => disk_img::delete(ctx, false),
+        RootFSCommand::DeleteSysroot => sysroot::delete(ctx),
+        RootFSCommand::Mount => disk_img::mount(ctx),
+        RootFSCommand::Umount => disk_img::umount(ctx),
+        RootFSCommand::CheckDiskImageExists => disk_img::check_disk_image_exists(ctx),
+        RootFSCommand::ShowMountPoint => disk_img::show_mount_point(ctx),
+        RootFSCommand::ShowLoopDevice => disk_img::show_loop_device(ctx),
+    }
+}

+ 0 - 0
dadk/src/actions/rootfs/sysroot.rs → dadk/src/actions/rootfs/v1/sysroot.rs


+ 464 - 0
dadk/src/actions/rootfs/v2/disk_img.rs

@@ -0,0 +1,464 @@
+use std::{fs::File, io::Write, mem::ManuallyDrop, path::PathBuf, process::Command};
+
+use crate::context::DADKExecContext;
+use anyhow::{anyhow, Result};
+use dadk_config::rootfs::{fstype::FsType, partition::PartitionType};
+
+use super::loopdev::LoopDeviceBuilder;
+pub(super) fn create(ctx: &DADKExecContext, skip_if_exists: bool) -> Result<()> {
+    let disk_image_path = ctx.disk_image_path();
+    if disk_image_path.exists() {
+        if skip_if_exists {
+            return Ok(());
+        }
+        return Err(anyhow!(
+            "Disk image already exists: {}",
+            disk_image_path.display()
+        ));
+    }
+
+    disk_path_safety_check(&disk_image_path)?;
+
+    // 获取镜像大小
+    let image_size = ctx.disk_image_size();
+    create_raw_img(&disk_image_path, image_size).expect("Failed to create raw disk image");
+
+    // 判断是否需要分区?
+
+    let r = if ctx.rootfs().partition.image_should_be_partitioned() {
+        create_partitioned_image(ctx, &disk_image_path)
+    } else {
+        create_unpartitioned_image(ctx, &disk_image_path)
+    };
+
+    if r.is_err() {
+        std::fs::remove_file(&disk_image_path).expect("Failed to remove disk image");
+    }
+    r
+}
+
+pub(super) fn delete(ctx: &DADKExecContext, skip_if_not_exists: bool) -> Result<()> {
+    let disk_image_path = ctx.disk_image_path();
+    if !disk_image_path.exists() {
+        if skip_if_not_exists {
+            return Ok(());
+        }
+        return Err(anyhow!(
+            "Disk image does not exist: {}",
+            disk_image_path.display()
+        ));
+    }
+    disk_path_safety_check(&disk_image_path)?;
+
+    std::fs::remove_file(&disk_image_path)
+        .map_err(|e| anyhow!("Failed to remove disk image: {}", e))?;
+    Ok(())
+}
+
+pub fn mount(ctx: &DADKExecContext) -> Result<()> {
+    let disk_image_path = ctx.disk_image_path();
+    if !disk_image_path.exists() {
+        return Err(anyhow!(
+            "Disk image does not exist: {}",
+            disk_image_path.display()
+        ));
+    }
+    let disk_mount_path = ctx.disk_mount_path();
+
+    // 尝试创建挂载点
+    std::fs::create_dir_all(&disk_mount_path)
+        .map_err(|e| anyhow!("Failed to create disk mount path: {}", e))?;
+
+    let partitioned = ctx.rootfs().partition.image_should_be_partitioned();
+    log::trace!("Disk image is partitioned: {}", partitioned);
+    if partitioned {
+        mount_partitioned_image(ctx, &disk_image_path, &disk_mount_path)?
+    } else {
+        mount_unpartitioned_image(ctx, &disk_image_path, &disk_mount_path)?
+    }
+    log::info!("Disk image mounted at {}", disk_mount_path.display());
+    Ok(())
+}
+
+fn mount_partitioned_image(
+    ctx: &DADKExecContext,
+    disk_image_path: &PathBuf,
+    disk_mount_path: &PathBuf,
+) -> Result<()> {
+    let mut loop_device = ManuallyDrop::new(
+        LoopDeviceBuilder::new()
+            .img_path(disk_image_path.clone())
+            .detach_on_drop(false)
+            .build()
+            .map_err(|e| anyhow!("Failed to create loop device: {}", e))?,
+    );
+
+    loop_device
+        .attach()
+        .map_err(|e| anyhow!("mount: Failed to attach loop device: {}", e))?;
+
+    let dev_path = loop_device.partition_path(1)?;
+    mount_unpartitioned_image(ctx, &dev_path, disk_mount_path)?;
+
+    Ok(())
+}
+
+fn mount_unpartitioned_image(
+    _ctx: &DADKExecContext,
+    disk_image_path: &PathBuf,
+    disk_mount_path: &PathBuf,
+) -> Result<()> {
+    let cmd = Command::new("mount")
+        .arg(disk_image_path)
+        .arg(disk_mount_path)
+        .output()
+        .map_err(|e| anyhow!("Failed to mount disk image: {}", e))?;
+    if !cmd.status.success() {
+        return Err(anyhow!(
+            "Failed to mount disk image: {}",
+            String::from_utf8_lossy(&cmd.stderr)
+        ));
+    }
+    Ok(())
+}
+
+pub fn umount(ctx: &DADKExecContext) -> Result<()> {
+    let disk_img_path = ctx.disk_image_path();
+    let disk_mount_path = ctx.disk_mount_path();
+    let mut loop_device = LoopDeviceBuilder::new().img_path(disk_img_path).build();
+
+    let should_detach_loop_device: bool;
+    if let Ok(loop_device) = loop_device.as_mut() {
+        if let Err(e) = loop_device.attach_by_exists() {
+            log::trace!("umount: Failed to attach loop device: {}", e);
+        }
+
+        should_detach_loop_device = loop_device.attached();
+    } else {
+        should_detach_loop_device = false;
+    }
+
+    if disk_mount_path.exists() {
+        let cmd = Command::new("umount")
+            .arg(disk_mount_path)
+            .output()
+            .map_err(|e| anyhow!("Failed to umount disk image: {}", e));
+        match cmd {
+            Ok(cmd) => {
+                if !cmd.status.success() {
+                    let e = anyhow!(
+                        "Failed to umount disk image: {}",
+                        String::from_utf8_lossy(&cmd.stderr)
+                    );
+                    if should_detach_loop_device {
+                        log::error!("{}", e);
+                    } else {
+                        return Err(e);
+                    }
+                }
+            }
+            Err(e) => {
+                if should_detach_loop_device {
+                    log::error!("{}", e);
+                } else {
+                    return Err(e);
+                }
+            }
+        }
+    }
+
+    if let Ok(loop_device) = loop_device {
+        let loop_dev_path = loop_device.dev_path().cloned();
+
+        log::info!("Loop device going to detached: {:?}", loop_dev_path);
+    }
+
+    Ok(())
+}
+
+/// Ensures the provided disk image path is not a device node.
+fn disk_path_safety_check(disk_image_path: &PathBuf) -> Result<()> {
+    const DONT_ALLOWED_PREFIX: [&str; 5] =
+        ["/dev/sd", "/dev/hd", "/dev/vd", "/dev/nvme", "/dev/mmcblk"];
+    let path = disk_image_path.to_str().ok_or(anyhow!(
+        "disk path safety check failed: disk path is not valid utf-8"
+    ))?;
+
+    DONT_ALLOWED_PREFIX.iter().for_each(|prefix| {
+        if path.starts_with(prefix) {
+            panic!("disk path safety check failed: disk path is not allowed to be a device node(except loop dev)");
+        }
+    });
+    Ok(())
+}
+
+fn create_partitioned_image(ctx: &DADKExecContext, disk_image_path: &PathBuf) -> Result<()> {
+    let part_type = ctx.rootfs().partition.partition_type;
+    DiskPartitioner::create_partitioned_image(disk_image_path, part_type)?;
+    // 挂载loop设备
+    let mut loop_device = LoopDeviceBuilder::new()
+        .img_path(disk_image_path.clone())
+        .build()
+        .map_err(|e| anyhow!("Failed to create loop device: {}", e))?;
+    loop_device
+        .attach()
+        .map_err(|e| anyhow!("creat: Failed to attach loop device: {}", e))?;
+
+    let partition_path = loop_device.partition_path(1)?;
+    let fs_type = ctx.rootfs().metadata.fs_type;
+    DiskFormatter::format_disk(&partition_path, &fs_type)?;
+    Ok(())
+}
+
+fn create_unpartitioned_image(ctx: &DADKExecContext, disk_image_path: &PathBuf) -> Result<()> {
+    // 直接对整块磁盘镜像进行格式化
+    let fs_type = ctx.rootfs().metadata.fs_type;
+    DiskFormatter::format_disk(disk_image_path, &fs_type)
+}
+
+/// 创建全0的raw镜像
+fn create_raw_img(disk_image_path: &PathBuf, image_size: usize) -> Result<()> {
+    log::trace!("Creating raw disk image: {}", disk_image_path.display());
+    // 创建父目录
+    if let Some(parent) = disk_image_path.parent() {
+        log::trace!("Creating parent directory: {}", parent.display());
+        std::fs::create_dir_all(parent)?;
+    }
+    // 打开或创建文件
+    let mut file = File::create(disk_image_path)?;
+
+    // 将文件大小设置为指定大小
+    file.set_len(image_size.try_into().unwrap())?;
+
+    // 写入全0数据
+    let zero_buffer = vec![0u8; 4096]; // 4KB buffer for writing zeros
+    let mut remaining_size = image_size;
+
+    while remaining_size > 0 {
+        let write_size = std::cmp::min(remaining_size, zero_buffer.len());
+        file.write_all(&zero_buffer[..write_size as usize])?;
+        remaining_size -= write_size;
+    }
+
+    Ok(())
+}
+
+pub fn check_disk_image_exists(ctx: &DADKExecContext) -> Result<()> {
+    let disk_image_path = ctx.disk_image_path();
+    if disk_image_path.exists() {
+        println!("1");
+    } else {
+        println!("0");
+    }
+    Ok(())
+}
+
+pub fn show_mount_point(ctx: &DADKExecContext) -> Result<()> {
+    let disk_mount_path = ctx.disk_mount_path();
+    println!("{}", disk_mount_path.display());
+    Ok(())
+}
+
+pub fn show_loop_device(ctx: &DADKExecContext) -> Result<()> {
+    let disk_image_path = ctx.disk_image_path();
+    let mut loop_device = LoopDeviceBuilder::new()
+        .detach_on_drop(false)
+        .img_path(disk_image_path)
+        .build()?;
+    if let Err(e) = loop_device.attach_by_exists() {
+        log::error!("Failed to attach loop device: {}", e);
+    } else {
+        println!("{}", loop_device.dev_path().unwrap());
+    }
+    Ok(())
+}
+
+struct DiskPartitioner;
+
+impl DiskPartitioner {
+    fn create_partitioned_image(disk_image_path: &PathBuf, part_type: PartitionType) -> Result<()> {
+        match part_type {
+            PartitionType::None => {
+                // This case should not be reached as we are in the partitioned image creation function
+                return Err(anyhow::anyhow!("Invalid partition type: None"));
+            }
+            PartitionType::Mbr => {
+                // Create MBR partitioned disk image
+                Self::create_mbr_partitioned_image(disk_image_path)?;
+            }
+            PartitionType::Gpt => {
+                // Create GPT partitioned disk image
+                Self::create_gpt_partitioned_image(disk_image_path)?;
+            }
+        }
+        Ok(())
+    }
+
+    fn create_mbr_partitioned_image(disk_image_path: &PathBuf) -> Result<()> {
+        let disk_image_path_str = disk_image_path.to_str().expect("Invalid path");
+
+        // 检查 fdisk 是否存在
+        let output = Command::new("fdisk")
+            .arg("--help")
+            .stdin(std::process::Stdio::piped())
+            .stdout(std::process::Stdio::piped())
+            .spawn()?
+            .wait_with_output()?;
+
+        if !output.status.success() {
+            return Err(anyhow::anyhow!("Command fdisk not found"));
+        }
+
+        // 向 fdisk 发送命令
+        let fdisk_commands = "o\nn\n\n\n\n\na\nw\n";
+        let mut fdisk_child = Command::new("fdisk")
+            .arg(disk_image_path_str)
+            .stdin(std::process::Stdio::piped())
+            .stdout(std::process::Stdio::piped())
+            .spawn()?;
+
+        let fdisk_stdin = fdisk_child.stdin.as_mut().expect("Failed to open stdin");
+        fdisk_stdin.write_all(fdisk_commands.as_bytes())?;
+        fdisk_stdin.flush()?;
+        fdisk_child
+            .wait()
+            .unwrap_or_else(|e| panic!("Failed to run fdisk: {}", e));
+        Ok(())
+    }
+
+    fn create_gpt_partitioned_image(_disk_image_path: &PathBuf) -> Result<()> {
+        // Implement the logic to create a GPT partitioned disk image
+        // This is a placeholder for the actual implementation
+        unimplemented!("Not implemented: create_gpt_partitioned_image");
+    }
+}
+
+struct DiskFormatter;
+
+impl DiskFormatter {
+    fn format_disk(disk_image_path: &PathBuf, fs_type: &FsType) -> Result<()> {
+        match fs_type {
+            FsType::Fat32 => Self::format_fat32(disk_image_path),
+        }
+    }
+
+    fn format_fat32(disk_image_path: &PathBuf) -> Result<()> {
+        // Use the `mkfs.fat` command to format the disk image as FAT32
+        let status = Command::new("mkfs.fat")
+            .arg("-F32")
+            .arg(disk_image_path.to_str().unwrap())
+            .status()?;
+
+        if status.success() {
+            Ok(())
+        } else {
+            Err(anyhow::anyhow!("Failed to format disk image as FAT32"))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::fs;
+    use std::io::Read;
+    use tempfile::NamedTempFile;
+
+    #[test]
+    fn test_create_raw_img_functional() -> Result<()> {
+        // 创建一个临时文件路径
+        let temp_file = NamedTempFile::new()?;
+        let disk_image_path = temp_file.path().to_path_buf();
+        let disk_image_size = 1024 * 1024usize;
+
+        // 调用函数
+        create_raw_img(&disk_image_path, disk_image_size)?;
+
+        // 验证文件大小
+        let metadata = fs::metadata(&disk_image_path)?;
+        assert_eq!(metadata.len(), disk_image_size as u64);
+
+        // 验证文件内容是否全为0
+        let mut file = File::open(&disk_image_path)?;
+        let mut buffer = vec![0u8; 4096];
+        let mut all_zeros = true;
+
+        while file.read(&mut buffer)? > 0 {
+            for byte in &buffer {
+                if *byte != 0 {
+                    all_zeros = false;
+                    break;
+                }
+            }
+        }
+
+        assert!(all_zeros, "File content is not all zeros");
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_format_fat32() {
+        // Create a temporary file to use as the disk image
+        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
+        let disk_image_path = temp_file.path().to_path_buf();
+
+        // 16MB
+        let image_size = 16 * 1024 * 1024usize;
+        create_raw_img(&disk_image_path, image_size).expect("Failed to create raw disk image");
+
+        // Call the function to format the disk image
+        DiskFormatter::format_disk(&disk_image_path, &FsType::Fat32)
+            .expect("Failed to format disk image as FAT32");
+
+        // Optionally, you can check if the disk image was actually formatted as FAT32
+        // by running a command to inspect the filesystem type
+        let output = Command::new("file")
+            .arg("-sL")
+            .arg(&disk_image_path)
+            .output()
+            .expect("Failed to execute 'file' command");
+
+        let output_str = String::from_utf8_lossy(&output.stdout);
+        assert!(
+            output_str.contains("FAT (32 bit)"),
+            "Disk image is not formatted as FAT32"
+        );
+    }
+
+    #[test]
+    fn test_create_mbr_partitioned_image() -> Result<()> {
+        // Create a temporary file to use as the disk image
+        let temp_file = NamedTempFile::new()?;
+        let disk_image_path = temp_file.path().to_path_buf();
+
+        eprintln!("Disk image path: {:?}", disk_image_path);
+        // Create a raw disk image
+        let disk_image_size = 16 * 1024 * 1024usize; // 16MB
+        create_raw_img(&disk_image_path, disk_image_size)?;
+
+        // Call the function to create the MBR partitioned image
+        DiskPartitioner::create_mbr_partitioned_image(&disk_image_path)?;
+
+        // Verify the disk image has been correctly partitioned
+        let output = Command::new("fdisk")
+            .env("LANG", "C") // Set LANG to C to force English output
+            .env("LC_ALL", "C") // Set LC_ALL to C to force English output
+            .arg("-l")
+            .arg(&disk_image_path)
+            .output()
+            .expect("Failed to execute 'fdisk -l' command");
+
+        let output_str = String::from_utf8_lossy(&output.stdout);
+        assert!(
+            output_str.contains("Disklabel type: dos"),
+            "Disk image does not have an MBR partition table"
+        );
+        assert!(
+            output_str.contains("Start"),
+            "Disk image does not have a partition"
+        );
+
+        Ok(())
+    }
+}

+ 341 - 0
dadk/src/actions/rootfs/v2/loopdev.rs

@@ -0,0 +1,341 @@
+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
+    detach_on_drop: bool,
+    /// mapper created
+    mapper: 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(&mut self, nth: u8) -> Result<PathBuf> {
+        if !self.attached() {
+            return Err(anyhow!("Loop device not attached"));
+        }
+        let dev_path = self.loop_device_path.as_ref().unwrap();
+        let direct_path = PathBuf::from(format!("{}p{}", dev_path, nth));
+
+        // 判断路径是否存在
+        if !direct_path.exists() {
+            mapper::create_mapper(self.loop_device_path.as_ref().unwrap())?;
+            self.mapper = true;
+            let device_name = direct_path.file_name().unwrap();
+            let parent_path = direct_path.parent().unwrap();
+            let new_path = parent_path.join("mapper").join(device_name);
+            if new_path.exists() {
+                return Ok(new_path);
+            }
+            log::error!(
+                "Both {} and {} not exist!",
+                direct_path.display(),
+                new_path.display()
+            );
+            return Err(anyhow!("Unable to find partition path {}", nth));
+        }
+        Ok(direct_path)
+    }
+
+    pub fn detach(&mut self) {
+        if self.loop_device_path.is_none() {
+            return;
+        }
+        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()
+        );
+
+        if self.mapper {
+            mapper::detach_mapper(&loop_device);
+            log::trace!("Detach mapper device: {}", &loop_device);
+            self.mapper = false;
+        }
+
+        let output = Command::new("losetup").arg("-d").arg(&loop_device).output();
+
+        if !output.is_ok() {
+            log::error!(
+                "losetup failed to detach loop device [{}]: {}",
+                &loop_device,
+                output.unwrap_err()
+            );
+            return;
+        }
+
+        let output = output.unwrap();
+
+        if !output.status.success() {
+            log::error!(
+                "losetup failed to detach loop device [{}]: {}, {}",
+                loop_device,
+                output.status,
+                str::from_utf8(output.stderr.as_slice()).unwrap_or("<Unknown>")
+            );
+        }
+    }
+
+    #[allow(dead_code)]
+    pub fn detach_on_drop(&self) -> bool {
+        self.detach_on_drop
+    }
+
+    #[allow(dead_code)]
+    pub fn set_try_detach_when_drop(&mut self, try_detach_when_drop: bool) {
+        self.detach_on_drop = try_detach_when_drop;
+    }
+}
+
+impl Drop for LoopDevice {
+    fn drop(&mut self) {
+        if self.detach_on_drop {
+            self.detach();
+        }
+    }
+}
+
+mod mapper {
+    use anyhow::anyhow;
+    use anyhow::Result;
+    use std::process::Command;
+
+    pub(super) fn create_mapper(dev_path: &str) -> Result<()> {
+        let output = Command::new("kpartx")
+            .arg("-a")
+            .arg("-v")
+            .arg(dev_path)
+            .output()
+            .map_err(|e| anyhow!("Failed to run kpartx: {}", e))?;
+        if output.status.success() {
+            let output_str = String::from_utf8(output.stdout)?;
+            log::trace!("kpartx output: {}", output_str);
+            return Ok(());
+        }
+        Err(anyhow!("Failed to create mapper"))
+    }
+
+    pub(super) fn detach_mapper(dev_path: &str) {
+        let output = Command::new("kpartx")
+            .arg("-d")
+            .arg("-v")
+            .arg(dev_path)
+            .output();
+        if output.is_ok() {
+            let output = output.unwrap();
+            if !output.status.success() {
+                log::error!(
+                    "kpartx failed to detach mapper device [{}]: {}, {}",
+                    dev_path,
+                    output.status,
+                    String::from_utf8(output.stderr).unwrap_or("<Unknown>".to_string())
+                );
+            }
+        } else {
+            log::error!(
+                "Failed to detach mapper device [{}]: {}",
+                dev_path,
+                output.unwrap_err()
+            );
+        }
+    }
+}
+
+pub struct LoopDeviceBuilder {
+    img_path: Option<PathBuf>,
+    loop_device_path: Option<String>,
+    detach_on_drop: bool,
+}
+
+impl LoopDeviceBuilder {
+    pub fn new() -> Self {
+        LoopDeviceBuilder {
+            img_path: None,
+            loop_device_path: None,
+            detach_on_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 detach_on_drop(mut self, detach_on_drop: bool) -> Self {
+        self.detach_on_drop = detach_on_drop;
+        self
+    }
+
+    pub fn build(self) -> Result<LoopDevice> {
+        let loop_dev = LoopDevice {
+            img_path: self.img_path,
+            loop_device_path: self.loop_device_path,
+            detach_on_drop: self.detach_on_drop,
+            mapper: false,
+        };
+
+        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/x86_64/disk.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/x86_64/disk.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/x86_64/disk.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/riscv64/disk.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"
+        );
+    }
+}

+ 19 - 0
dadk/src/actions/rootfs/v2/mod.rs

@@ -0,0 +1,19 @@
+use crate::{console::rootfs::RootFSCommand, context::DADKExecContext};
+use anyhow::Result;
+
+mod disk_img;
+mod loopdev;
+mod sysroot;
+
+pub fn run(ctx: &DADKExecContext, rootfs_cmd: &RootFSCommand) -> Result<()> {
+    match rootfs_cmd {
+        RootFSCommand::Create(param) => disk_img::create(ctx, param.skip_if_exists),
+        RootFSCommand::Delete => disk_img::delete(ctx, false),
+        RootFSCommand::DeleteSysroot => sysroot::delete(ctx),
+        RootFSCommand::Mount => disk_img::mount(ctx),
+        RootFSCommand::Umount => disk_img::umount(ctx),
+        RootFSCommand::CheckDiskImageExists => disk_img::check_disk_image_exists(ctx),
+        RootFSCommand::ShowMountPoint => disk_img::show_mount_point(ctx),
+        RootFSCommand::ShowLoopDevice => disk_img::show_loop_device(ctx),
+    }
+}

+ 26 - 0
dadk/src/actions/rootfs/v2/sysroot.rs

@@ -0,0 +1,26 @@
+use anyhow::{anyhow, Result};
+
+use crate::context::DADKExecContext;
+
+pub(super) fn delete(ctx: &DADKExecContext) -> Result<()> {
+    let sysroot_dir = ctx.sysroot_dir()?;
+    // 检查 sysroot_dir 是否存在
+    if !sysroot_dir.exists() {
+        return Err(anyhow!("Sysroot directory does not exist"));
+    }
+
+    // 检查 sysroot_dir 是否是一个目录
+    if !sysroot_dir.is_dir() {
+        return Err(anyhow!("Sysroot path is not a directory"));
+    }
+
+    // 检查 sysroot_dir 是否是当前工作目录的子目录
+    if !sysroot_dir.starts_with(&ctx.workdir()) {
+        return Err(anyhow!(
+            "Sysroot directory must be a subdirectory of the current working directory"
+        ));
+    }
+
+    std::fs::remove_dir_all(sysroot_dir)?;
+    Ok(())
+}

+ 5 - 0
tests/data/dadk-manifest.toml

@@ -5,6 +5,11 @@
 # Target architecture. Options: x86_64, riscv64
 arch = "x86_64"
 
+# Builer version, affect the behavior of image generation
+# Options: v1, v2
+# Default: v1, to be compatible with the old version
+builder-version = "v2"
+
 # Hypervisor config path
 hypervisor-config = "config/hypervisor.toml"