From e66ebfa82317e4076830f13444a64d6182293155 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 2 Feb 2026 12:36:32 -0800 Subject: [PATCH 1/5] Add a failing test for FAT32 images fsck.vfat doesn't error when in `-n` mode, so we pass `-y` to have it fix anything it finds, and if it fixes something it will exit with status code 1. --- tests/fsck.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/fsck.rs b/tests/fsck.rs index c68022f..472111a 100644 --- a/tests/fsck.rs +++ b/tests/fsck.rs @@ -46,3 +46,57 @@ fn test_fsck_1mb() { .expect("wait on fsck"); assert!(fsck_status.success(), "fsck was not successful ({fsck_status:?})"); } + +#[test] +fn test_fsck_33mb_fat32() { + let _ = env_logger::builder().is_test(true).try_init(); + + let disk_img = "/tmp/test-fat32.img"; + + let mut image = std::fs::OpenOptions::new() + .write(true) + .read(true) + .create(true) + .open(disk_img) + .expect("open temporary image file"); + let len = 33 * MB; + image.set_len(len as u64).expect("set_len on temp file"); + + fatfs::format_volume( + &mut fatfs::StdIoWrapper::from(image.try_clone().expect("clone tempfile")), + fatfs::FormatVolumeOptions::new().fat_type(fatfs::FatType::Fat32), + ) + .expect("format volume"); + + let fsck_status = std::process::Command::new("fsck.vfat") + .args(&["-y", disk_img]) + .spawn() + .expect("spawn fsck") + .wait() + .expect("wait on fsck"); + assert!(fsck_status.success(), "fsck was not successful ({fsck_status:?})"); + + let fs = fatfs::FileSystem::new(image, fatfs::FsOptions::new()).expect("open fs"); + fs.root_dir().create_dir("dir1").expect("create dir1"); + fs.root_dir() + .create_file("root file.bin") + .expect("create root file") + .write_all(&[0xab; (16 * KB) as usize]) + .expect("root file write"); + let dir2 = fs.root_dir().create_dir("dir2").expect("create dir2"); + dir2.create_dir("subdir").expect("subdir"); + dir2.create_file("file1") + .expect("file1") + .write_all(b"testing 1 2 1 2") + .expect("file 1 write"); + core::mem::drop(dir2); + core::mem::drop(fs); + + let fsck_status = std::process::Command::new("fsck.vfat") + .args(&["-y", disk_img]) + .spawn() + .expect("spawn fsck") + .wait() + .expect("wait on fsck"); + assert!(fsck_status.success(), "fsck was not successful ({fsck_status:?})"); +} From a4a68345c633fccacd3330a805994b3c1fdd9831 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 2 Feb 2026 12:42:25 -0800 Subject: [PATCH 2/5] fixup: actually test that fsck.vfat doesn't repair anything in test_fsck_1mb --- tests/fsck.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/fsck.rs b/tests/fsck.rs index 472111a..d2bb84b 100644 --- a/tests/fsck.rs +++ b/tests/fsck.rs @@ -22,6 +22,14 @@ fn test_fsck_1mb() { ) .expect("format volume"); + let fsck_status = std::process::Command::new("fsck.vfat") + .args(&["-y", "/tmp/test.img"]) + .spawn() + .expect("spawn fsck") + .wait() + .expect("wait on fsck"); + assert!(fsck_status.success(), "fsck was not successful ({fsck_status:?})"); + let fs = fatfs::FileSystem::new(image, fatfs::FsOptions::new()).expect("open fs"); fs.root_dir().create_dir("dir1").expect("create dir1"); fs.root_dir() @@ -39,7 +47,7 @@ fn test_fsck_1mb() { core::mem::drop(fs); let fsck_status = std::process::Command::new("fsck.vfat") - .args(&["-n", "/tmp/test.img"]) + .args(&["-y", "/tmp/test.img"]) .spawn() .expect("spawn fsck") .wait() From 82374f9d8b208815894aa8e48c01d0429670f9d1 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Sun, 8 Feb 2026 11:46:29 -0800 Subject: [PATCH 3/5] Use a common function for fscking Fat images --- Cargo.toml | 1 + tests/fsck.rs | 93 +++++++++++++-------------------------------------- 2 files changed, 25 insertions(+), 69 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 238efb5..7923552 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,3 +47,4 @@ chrono = { version = "0.4", default-features = false, features = ["clock"], opti [dev-dependencies] env_logger = "0.9" fscommon = "0.1" +tempfile = "3.24.0" diff --git a/tests/fsck.rs b/tests/fsck.rs index d2bb84b..1b98161 100644 --- a/tests/fsck.rs +++ b/tests/fsck.rs @@ -1,33 +1,30 @@ #![cfg(target_os = "linux")] -use fatfs::Write; + +use fatfs::{FatType, Write}; const KB: u32 = 1024; const MB: u32 = KB * 1024; -#[test] -fn test_fsck_1mb() { - let _ = env_logger::builder().is_test(true).try_init(); +fn create_and_fsck_image(fat_type: FatType, size: u32) { + let mut disk_img = + tempfile::NamedTempFile::with_suffix(format!("-{fat_type:?}-{size}.img")).expect("create named tempfile"); + let disk_img_path = disk_img.path().display().to_string(); - let mut image = std::fs::OpenOptions::new() - .write(true) - .read(true) - .create(true) - .open("/tmp/test.img") - .expect("open temporary image file"); - image.set_len(MB as u64).expect("set_len on temp file"); + let image = disk_img.as_file_mut(); + image.set_len(size as u64).expect("set_len on temp file"); fatfs::format_volume( &mut fatfs::StdIoWrapper::from(image.try_clone().expect("clone tempfile")), - fatfs::FormatVolumeOptions::new().total_sectors(MB / 512), + fatfs::FormatVolumeOptions::new() + .fat_type(fat_type) + .total_sectors(size / 512), ) .expect("format volume"); let fsck_status = std::process::Command::new("fsck.vfat") - .args(&["-y", "/tmp/test.img"]) - .spawn() - .expect("spawn fsck") - .wait() - .expect("wait on fsck"); + .args(&["-y", &disk_img_path]) + .status() + .expect("get fsck.vfat status"); assert!(fsck_status.success(), "fsck was not successful ({fsck_status:?})"); let fs = fatfs::FileSystem::new(image, fatfs::FsOptions::new()).expect("open fs"); @@ -47,64 +44,22 @@ fn test_fsck_1mb() { core::mem::drop(fs); let fsck_status = std::process::Command::new("fsck.vfat") - .args(&["-y", "/tmp/test.img"]) - .spawn() - .expect("spawn fsck") - .wait() - .expect("wait on fsck"); + .args(&["-y", &disk_img_path]) + .status() + .expect("get fsck.vfat status"); assert!(fsck_status.success(), "fsck was not successful ({fsck_status:?})"); } #[test] -fn test_fsck_33mb_fat32() { +fn test_fsck_1mb_fat12() { let _ = env_logger::builder().is_test(true).try_init(); - let disk_img = "/tmp/test-fat32.img"; - - let mut image = std::fs::OpenOptions::new() - .write(true) - .read(true) - .create(true) - .open(disk_img) - .expect("open temporary image file"); - let len = 33 * MB; - image.set_len(len as u64).expect("set_len on temp file"); - - fatfs::format_volume( - &mut fatfs::StdIoWrapper::from(image.try_clone().expect("clone tempfile")), - fatfs::FormatVolumeOptions::new().fat_type(fatfs::FatType::Fat32), - ) - .expect("format volume"); - - let fsck_status = std::process::Command::new("fsck.vfat") - .args(&["-y", disk_img]) - .spawn() - .expect("spawn fsck") - .wait() - .expect("wait on fsck"); - assert!(fsck_status.success(), "fsck was not successful ({fsck_status:?})"); + create_and_fsck_image(FatType::Fat12, MB); +} - let fs = fatfs::FileSystem::new(image, fatfs::FsOptions::new()).expect("open fs"); - fs.root_dir().create_dir("dir1").expect("create dir1"); - fs.root_dir() - .create_file("root file.bin") - .expect("create root file") - .write_all(&[0xab; (16 * KB) as usize]) - .expect("root file write"); - let dir2 = fs.root_dir().create_dir("dir2").expect("create dir2"); - dir2.create_dir("subdir").expect("subdir"); - dir2.create_file("file1") - .expect("file1") - .write_all(b"testing 1 2 1 2") - .expect("file 1 write"); - core::mem::drop(dir2); - core::mem::drop(fs); +#[test] +fn test_fsck_33mb_fat32() { + let _ = env_logger::builder().is_test(true).try_init(); - let fsck_status = std::process::Command::new("fsck.vfat") - .args(&["-y", disk_img]) - .spawn() - .expect("spawn fsck") - .wait() - .expect("wait on fsck"); - assert!(fsck_status.success(), "fsck was not successful ({fsck_status:?})"); + create_and_fsck_image(FatType::Fat32, 33 * MB); } From 0fb7dd1bab78b0fcca42fbd6537fc5c47cf6de22 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 2 Feb 2026 12:43:04 -0800 Subject: [PATCH 4/5] Update FAT32 FSInfo after root dir allocation Overwrite the FSInfo sector once the root directory cluster is allocated so free-cluster counts match the FAT and fresh images pass fsck cleanly. Prior to this change, FAT32 disks created by this library would fail `fsck.vfat` like so: fsck.fat 4.2 (2021-01-31) Free cluster summary uninitialized (should be 66535) Auto-setting. *** Filesystem was changed *** Writing changes. /tmp/test-fat32.img: 0 files, 1/66536 clusters Now, they pass: fsck.fat 4.2 (2021-01-31) /tmp/test-fat32.img: 0 files, 1/66536 clusters --- src/fs.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/fs.rs b/src/fs.rs index e08b7fa..4cc7dc7 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1173,16 +1173,6 @@ pub fn format_volume(storage: &mut S, options: FormatVolumeOpt let bpb = &boot.bpb; if bpb.is_fat32() { - // FSInfo sector - let fs_info_sector = FsInfoSector { - free_cluster_count: None, - next_free_cluster: None, - dirty: false, - }; - storage.seek(SeekFrom::Start(bpb.bytes_from_sectors(bpb.fs_info_sector())))?; - fs_info_sector.serialize(storage)?; - write_zeros_until_end_of_sector(storage, bytes_per_sector)?; - // backup boot sector storage.seek(SeekFrom::Start(bpb.bytes_from_sectors(bpb.backup_boot_sector())))?; boot.serialize(storage)?; @@ -1220,6 +1210,16 @@ pub fn format_volume(storage: &mut S, options: FormatVolumeOpt let fat32_root_dir_pos = bpb.bytes_from_sectors(fat32_root_dir_first_sector); storage.seek(SeekFrom::Start(fat32_root_dir_pos))?; write_zeros(storage, u64::from(bpb.cluster_size()))?; + + let free_cluster_count = bpb.total_clusters() - 1; + let fs_info_sector = FsInfoSector { + free_cluster_count: Some(free_cluster_count), + next_free_cluster: Some(root_dir_first_cluster + 1), + dirty: false, + }; + storage.seek(SeekFrom::Start(bpb.bytes_from_sectors(bpb.fs_info_sector())))?; + fs_info_sector.serialize(storage)?; + write_zeros_until_end_of_sector(storage, bytes_per_sector)?; } // Create volume label directory entry if volume label is specified in options From 6afbbb1a374987d4e5862df7c592f2253cfa980b Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Tue, 17 Feb 2026 10:30:06 -0800 Subject: [PATCH 5/5] Add test for FAT16 as well --- tests/fsck.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/fsck.rs b/tests/fsck.rs index 1b98161..3f7e917 100644 --- a/tests/fsck.rs +++ b/tests/fsck.rs @@ -57,6 +57,13 @@ fn test_fsck_1mb_fat12() { create_and_fsck_image(FatType::Fat12, MB); } +#[test] +fn test_fsck_33mb_fat16() { + let _ = env_logger::builder().is_test(true).try_init(); + + create_and_fsck_image(FatType::Fat16, 33 * MB); +} + #[test] fn test_fsck_33mb_fat32() { let _ = env_logger::builder().is_test(true).try_init();