disk_img.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. use std::{fs::File, io::Write, mem::ManuallyDrop, path::PathBuf, process::Command};
  2. use crate::context::DADKExecContext;
  3. use anyhow::{anyhow, Result};
  4. use dadk_config::rootfs::{fstype::FsType, partition::PartitionType};
  5. use super::loopdev::LoopDeviceBuilder;
  6. pub(super) fn create(ctx: &DADKExecContext) -> Result<()> {
  7. let disk_image_path = ctx.disk_image_path();
  8. if disk_image_path.exists() {
  9. return Err(anyhow!(
  10. "Disk image already exists: {}",
  11. disk_image_path.display()
  12. ));
  13. }
  14. disk_path_safety_check(&disk_image_path)?;
  15. // 获取镜像大小
  16. let image_size = ctx.disk_image_size();
  17. create_raw_img(&disk_image_path, image_size).expect("Failed to create raw disk image");
  18. // 判断是否需要分区?
  19. let r = if ctx.rootfs().partition.image_should_be_partitioned() {
  20. create_partitioned_image(ctx, &disk_image_path)
  21. } else {
  22. create_unpartitioned_image(ctx, &disk_image_path)
  23. };
  24. if r.is_err() {
  25. std::fs::remove_file(&disk_image_path).expect("Failed to remove disk image");
  26. }
  27. r
  28. }
  29. pub fn mount(ctx: &DADKExecContext) -> Result<()> {
  30. let disk_image_path = ctx.disk_image_path();
  31. if !disk_image_path.exists() {
  32. return Err(anyhow!(
  33. "Disk image does not exist: {}",
  34. disk_image_path.display()
  35. ));
  36. }
  37. let disk_mount_path = ctx.disk_mount_path();
  38. // 尝试创建挂载点
  39. std::fs::create_dir_all(&disk_mount_path)
  40. .map_err(|e| anyhow!("Failed to create disk mount path: {}", e))?;
  41. let partitioned = ctx.rootfs().partition.image_should_be_partitioned();
  42. log::trace!("Disk image is partitioned: {}", partitioned);
  43. if partitioned {
  44. mount_partitioned_image(ctx, &disk_image_path, &disk_mount_path)?
  45. } else {
  46. mount_unpartitioned_image(ctx, &disk_image_path, &disk_mount_path)?
  47. }
  48. log::info!("Disk image mounted at {}", disk_mount_path.display());
  49. Ok(())
  50. }
  51. fn mount_partitioned_image(
  52. ctx: &DADKExecContext,
  53. disk_image_path: &PathBuf,
  54. disk_mount_path: &PathBuf,
  55. ) -> Result<()> {
  56. let mut loop_device = ManuallyDrop::new(
  57. LoopDeviceBuilder::new()
  58. .img_path(disk_image_path.clone())
  59. .build()
  60. .map_err(|e| anyhow!("Failed to create loop device: {}", e))?,
  61. );
  62. loop_device
  63. .attach()
  64. .map_err(|e| anyhow!("Failed to attach loop device: {}", e))?;
  65. let dev_path = loop_device.partition_path(1)?;
  66. mount_unpartitioned_image(ctx, &dev_path, disk_mount_path)?;
  67. Ok(())
  68. }
  69. fn mount_unpartitioned_image(
  70. _ctx: &DADKExecContext,
  71. disk_image_path: &PathBuf,
  72. disk_mount_path: &PathBuf,
  73. ) -> Result<()> {
  74. let cmd = Command::new("mount")
  75. .arg(disk_image_path)
  76. .arg(disk_mount_path)
  77. .output()
  78. .map_err(|e| anyhow!("Failed to mount disk image: {}", e))?;
  79. if !cmd.status.success() {
  80. return Err(anyhow!(
  81. "Failed to mount disk image: {}",
  82. String::from_utf8_lossy(&cmd.stderr)
  83. ));
  84. }
  85. Ok(())
  86. }
  87. pub fn umount(ctx: &DADKExecContext) -> Result<()> {
  88. let disk_img_path = ctx.disk_image_path();
  89. let disk_mount_path = ctx.disk_mount_path();
  90. let mut loop_device = LoopDeviceBuilder::new().img_path(disk_img_path).build();
  91. let should_detach_loop_device: bool;
  92. if let Ok(loop_device) = loop_device.as_mut() {
  93. if let Err(e) = loop_device.attach_by_exists() {
  94. log::trace!("umount: Failed to attach loop device: {}", e);
  95. }
  96. should_detach_loop_device = loop_device.attached();
  97. } else {
  98. should_detach_loop_device = false;
  99. }
  100. if disk_mount_path.exists() {
  101. let cmd = Command::new("umount")
  102. .arg(disk_mount_path)
  103. .output()
  104. .map_err(|e| anyhow!("Failed to umount disk image: {}", e));
  105. match cmd {
  106. Ok(cmd) => {
  107. if !cmd.status.success() {
  108. let e = anyhow!(
  109. "Failed to umount disk image: {}",
  110. String::from_utf8_lossy(&cmd.stderr)
  111. );
  112. if should_detach_loop_device {
  113. log::error!("{}", e);
  114. } else {
  115. return Err(e);
  116. }
  117. }
  118. }
  119. Err(e) => {
  120. if should_detach_loop_device {
  121. log::error!("{}", e);
  122. } else {
  123. return Err(e);
  124. }
  125. }
  126. }
  127. }
  128. if let Ok(mut loop_device) = loop_device {
  129. let loop_dev_path = loop_device.dev_path().cloned();
  130. if let Err(e) = loop_device.detach().map_err(|e| anyhow!("{}", e)) {
  131. if ctx.rootfs().partition.image_should_be_partitioned() {
  132. return Err(e);
  133. }
  134. }
  135. log::info!("Loop device detached: {:?}", loop_dev_path);
  136. }
  137. Ok(())
  138. }
  139. /// Ensures the provided disk image path is not a device node.
  140. fn disk_path_safety_check(disk_image_path: &PathBuf) -> Result<()> {
  141. const DONT_ALLOWED_PREFIX: [&str; 5] =
  142. ["/dev/sd", "/dev/hd", "/dev/vd", "/dev/nvme", "/dev/mmcblk"];
  143. let path = disk_image_path.to_str().ok_or(anyhow!(
  144. "disk path safety check failed: disk path is not valid utf-8"
  145. ))?;
  146. DONT_ALLOWED_PREFIX.iter().for_each(|prefix| {
  147. if path.starts_with(prefix) {
  148. panic!("disk path safety check failed: disk path is not allowed to be a device node(except loop dev)");
  149. }
  150. });
  151. Ok(())
  152. }
  153. fn create_partitioned_image(ctx: &DADKExecContext, disk_image_path: &PathBuf) -> Result<()> {
  154. let part_type = ctx.rootfs().partition.partition_type;
  155. DiskPartitioner::create_partitioned_image(disk_image_path, part_type)?;
  156. // 挂载loop设备
  157. let mut loop_device = LoopDeviceBuilder::new()
  158. .img_path(disk_image_path.clone())
  159. .build()
  160. .map_err(|e| anyhow!("Failed to create loop device: {}", e))?;
  161. loop_device
  162. .attach()
  163. .map_err(|e| anyhow!("Failed to attach loop device: {}", e))?;
  164. let partition_path = loop_device.partition_path(1)?;
  165. let fs_type = ctx.rootfs().metadata.fs_type;
  166. DiskFormatter::format_disk(&partition_path, &fs_type)?;
  167. loop_device.detach()?;
  168. Ok(())
  169. }
  170. fn create_unpartitioned_image(ctx: &DADKExecContext, disk_image_path: &PathBuf) -> Result<()> {
  171. // 直接对整块磁盘镜像进行格式化
  172. let fs_type = ctx.rootfs().metadata.fs_type;
  173. DiskFormatter::format_disk(disk_image_path, &fs_type)
  174. }
  175. /// 创建全0的raw镜像
  176. fn create_raw_img(disk_image_path: &PathBuf, image_size: usize) -> Result<()> {
  177. log::trace!("Creating raw disk image: {}", disk_image_path.display());
  178. // 创建父目录
  179. if let Some(parent) = disk_image_path.parent() {
  180. log::trace!("Creating parent directory: {}", parent.display());
  181. std::fs::create_dir_all(parent)?;
  182. }
  183. // 打开或创建文件
  184. let mut file = File::create(disk_image_path)?;
  185. // 将文件大小设置为指定大小
  186. file.set_len(image_size.try_into().unwrap())?;
  187. // 写入全0数据
  188. let zero_buffer = vec![0u8; 4096]; // 4KB buffer for writing zeros
  189. let mut remaining_size = image_size;
  190. while remaining_size > 0 {
  191. let write_size = std::cmp::min(remaining_size, zero_buffer.len());
  192. file.write_all(&zero_buffer[..write_size as usize])?;
  193. remaining_size -= write_size;
  194. }
  195. Ok(())
  196. }
  197. struct DiskPartitioner;
  198. impl DiskPartitioner {
  199. fn create_partitioned_image(disk_image_path: &PathBuf, part_type: PartitionType) -> Result<()> {
  200. match part_type {
  201. PartitionType::None => {
  202. // This case should not be reached as we are in the partitioned image creation function
  203. return Err(anyhow::anyhow!("Invalid partition type: None"));
  204. }
  205. PartitionType::Mbr => {
  206. // Create MBR partitioned disk image
  207. Self::create_mbr_partitioned_image(disk_image_path)?;
  208. }
  209. PartitionType::Gpt => {
  210. // Create GPT partitioned disk image
  211. Self::create_gpt_partitioned_image(disk_image_path)?;
  212. }
  213. }
  214. Ok(())
  215. }
  216. fn create_mbr_partitioned_image(disk_image_path: &PathBuf) -> Result<()> {
  217. let disk_image_path_str = disk_image_path.to_str().expect("Invalid path");
  218. // 检查 fdisk 是否存在
  219. let output = Command::new("fdisk")
  220. .arg("--help")
  221. .stdin(std::process::Stdio::piped())
  222. .stdout(std::process::Stdio::piped())
  223. .spawn()?
  224. .wait_with_output()?;
  225. if !output.status.success() {
  226. return Err(anyhow::anyhow!("Command fdisk not found"));
  227. }
  228. // 向 fdisk 发送命令
  229. let fdisk_commands = "o\nn\n\n\n\n\na\nw\n";
  230. let mut fdisk_child = Command::new("fdisk")
  231. .arg(disk_image_path_str)
  232. .stdin(std::process::Stdio::piped())
  233. .stdout(std::process::Stdio::piped())
  234. .spawn()?;
  235. let fdisk_stdin = fdisk_child.stdin.as_mut().expect("Failed to open stdin");
  236. fdisk_stdin.write_all(fdisk_commands.as_bytes())?;
  237. fdisk_stdin.flush()?;
  238. fdisk_child
  239. .wait()
  240. .unwrap_or_else(|e| panic!("Failed to run fdisk: {}", e));
  241. Ok(())
  242. }
  243. fn create_gpt_partitioned_image(_disk_image_path: &PathBuf) -> Result<()> {
  244. // Implement the logic to create a GPT partitioned disk image
  245. // This is a placeholder for the actual implementation
  246. unimplemented!("Not implemented: create_gpt_partitioned_image");
  247. }
  248. }
  249. struct DiskFormatter;
  250. impl DiskFormatter {
  251. fn format_disk(disk_image_path: &PathBuf, fs_type: &FsType) -> Result<()> {
  252. match fs_type {
  253. FsType::Fat32 => Self::format_fat32(disk_image_path),
  254. }
  255. }
  256. fn format_fat32(disk_image_path: &PathBuf) -> Result<()> {
  257. // Use the `mkfs.fat` command to format the disk image as FAT32
  258. let status = Command::new("mkfs.fat")
  259. .arg("-F32")
  260. .arg(disk_image_path.to_str().unwrap())
  261. .status()?;
  262. if status.success() {
  263. Ok(())
  264. } else {
  265. Err(anyhow::anyhow!("Failed to format disk image as FAT32"))
  266. }
  267. }
  268. }
  269. #[cfg(test)]
  270. mod tests {
  271. use super::*;
  272. use std::fs;
  273. use std::io::Read;
  274. use tempfile::NamedTempFile;
  275. #[test]
  276. fn test_create_raw_img_functional() -> Result<()> {
  277. // 创建一个临时文件路径
  278. let temp_file = NamedTempFile::new()?;
  279. let disk_image_path = temp_file.path().to_path_buf();
  280. let disk_image_size = 1024 * 1024usize;
  281. // 调用函数
  282. create_raw_img(&disk_image_path, disk_image_size)?;
  283. // 验证文件大小
  284. let metadata = fs::metadata(&disk_image_path)?;
  285. assert_eq!(metadata.len(), disk_image_size as u64);
  286. // 验证文件内容是否全为0
  287. let mut file = File::open(&disk_image_path)?;
  288. let mut buffer = vec![0u8; 4096];
  289. let mut all_zeros = true;
  290. while file.read(&mut buffer)? > 0 {
  291. for byte in &buffer {
  292. if *byte != 0 {
  293. all_zeros = false;
  294. break;
  295. }
  296. }
  297. }
  298. assert!(all_zeros, "File content is not all zeros");
  299. Ok(())
  300. }
  301. #[test]
  302. fn test_format_fat32() {
  303. // Create a temporary file to use as the disk image
  304. let temp_file = NamedTempFile::new().expect("Failed to create temp file");
  305. let disk_image_path = temp_file.path().to_path_buf();
  306. // 16MB
  307. let image_size = 16 * 1024 * 1024usize;
  308. create_raw_img(&disk_image_path, image_size).expect("Failed to create raw disk image");
  309. // Call the function to format the disk image
  310. DiskFormatter::format_disk(&disk_image_path, &FsType::Fat32)
  311. .expect("Failed to format disk image as FAT32");
  312. // Optionally, you can check if the disk image was actually formatted as FAT32
  313. // by running a command to inspect the filesystem type
  314. let output = Command::new("file")
  315. .arg("-sL")
  316. .arg(&disk_image_path)
  317. .output()
  318. .expect("Failed to execute 'file' command");
  319. let output_str = String::from_utf8_lossy(&output.stdout);
  320. assert!(
  321. output_str.contains("FAT (32 bit)"),
  322. "Disk image is not formatted as FAT32"
  323. );
  324. }
  325. #[test]
  326. fn test_create_mbr_partitioned_image() -> Result<()> {
  327. // Create a temporary file to use as the disk image
  328. let temp_file = NamedTempFile::new()?;
  329. let disk_image_path = temp_file.path().to_path_buf();
  330. eprintln!("Disk image path: {:?}", disk_image_path);
  331. // Create a raw disk image
  332. let disk_image_size = 16 * 1024 * 1024usize; // 16MB
  333. create_raw_img(&disk_image_path, disk_image_size)?;
  334. // Call the function to create the MBR partitioned image
  335. DiskPartitioner::create_mbr_partitioned_image(&disk_image_path)?;
  336. // Verify the disk image has been correctly partitioned
  337. let output = Command::new("fdisk")
  338. .env("LANG", "C") // Set LANG to C to force English output
  339. .env("LC_ALL", "C") // Set LC_ALL to C to force English output
  340. .arg("-l")
  341. .arg(&disk_image_path)
  342. .output()
  343. .expect("Failed to execute 'fdisk -l' command");
  344. let output_str = String::from_utf8_lossy(&output.stdout);
  345. assert!(
  346. output_str.contains("Disklabel type: dos"),
  347. "Disk image does not have an MBR partition table"
  348. );
  349. assert!(
  350. output_str.contains("Start"),
  351. "Disk image does not have a partition"
  352. );
  353. Ok(())
  354. }
  355. }