diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index d5b2e4e05a..25b5b7fb64 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -598,6 +598,7 @@ if (BUILD_LAGOM) lagom_utility(lzcat SOURCES ../../Userland/Utilities/lzcat.cpp LIBS LibCompress LibMain) lagom_utility(markdown-check SOURCES ../../Userland/Utilities/markdown-check.cpp LIBS LibFileSystem LibMarkdown LibMain LibManual) + lagom_utility(mkfs.fat SOURCES ../../Userland/Utilities/mkfs.fat.cpp LIBS LibFileSystem LibMain) if (NOT EMSCRIPTEN) lagom_utility(ntpquery SOURCES ../../Userland/Utilities/ntpquery.cpp LIBS LibMain) diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt index cab1d51709..9792ab1c3d 100644 --- a/Userland/Utilities/CMakeLists.txt +++ b/Userland/Utilities/CMakeLists.txt @@ -7,7 +7,7 @@ list(APPEND REQUIRED_TARGETS touch tr true umount uname uniq uptime w wc which whoami xargs yes ) list(APPEND RECOMMENDED_TARGETS - aconv adjtime aplay abench asctl bt checksum chres cksum copy fortune gzip init install keymap lsirq lsof lspci lzcat man mknod mktemp + aconv adjtime aplay abench asctl bt checksum chres cksum copy fortune gzip init install keymap lsirq lsof lspci lzcat man mkfs.fat mknod mktemp nc netstat notify ntpquery open passwd pixelflut pls printf pro shot strings tar tt unzip wallpaper xzcat zip ) @@ -120,6 +120,7 @@ target_link_libraries(man PRIVATE LibMarkdown LibManual) target_link_libraries(markdown-check PRIVATE LibFileSystem LibMarkdown LibManual) target_link_libraries(matroska PRIVATE LibVideo) target_link_libraries(md PRIVATE LibMarkdown) +target_link_libraries(mkfs.fat PRIVATE LibFileSystem) target_link_libraries(mktemp PRIVATE LibFileSystem) target_link_libraries(mv PRIVATE LibFileSystem) target_link_libraries(notify PRIVATE LibGfx LibGUI) diff --git a/Userland/Utilities/mkfs.fat.cpp b/Userland/Utilities/mkfs.fat.cpp new file mode 100644 index 0000000000..ef31c33bf0 --- /dev/null +++ b/Userland/Utilities/mkfs.fat.cpp @@ -0,0 +1,608 @@ +/* + * Copyright (c) 2024, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Public domain boot code adapted from: +// https://github.com/dosfstools/dosfstools/blob/289a48b9cb5b3c589391d28aa2515c325c932c7a/src/mkfs.fat.c#L205 + +// clang-format off +static u8 s_bootcode[74] = { + 0x0E, // push cs + 0x1F, // pop ds + 0xBE, 0x5B, 0x7C, // mov si, offset message_txt + // write_msg: + 0xAC, // lodsb + 0x22, 0xC0, // and al, al + 0x74, 0x0B, // jz key_press + 0x56, // push si + 0xB4, 0x0E, // mov ah, 0eh + 0xBB, 0x07, 0x00, // mov bx, 0007h + 0xCD, 0x10, // int 10h + 0x5E, // pop si + 0xEB, 0xF0, // jmp write_msg + // key_press: + 0x32, 0xE4, // xor ah, ah + 0xCD, 0x16, // int 16h + 0xCD, 0x19, // int 19h + 0xEB, 0xFE, // foo: jmp foo + // message_txt: + '\r', '\n', + 'N', 'o', 'n', '-', 's', 'y', 's', 't', + 'e', 'm', ' ', 'd', 'i', 's', 'k', + '\r', '\n', + 'P', 'r', 'e', 's', 's', ' ', 'a', 'n', + 'y', ' ', 'k', 'e', 'y', ' ', 't', 'o', + ' ', 'r', 'e', 'b', 'o', 'o', 't', + '\r', '\n', + 0 +}; +// clang-format on + +// FIXME: Modify the boot code to use relative offsets. +static constexpr size_t s_message_offset_offset = 3; + +struct DiskSizeToSectorsPerClusterMapping { + u32 disk_size; + u8 sectors_per_cluster; +}; + +// NOTE: Unlike when using the tables after this one, the values here should only +// be used if the given disk size is an exact match. +static constexpr DiskSizeToSectorsPerClusterMapping s_disk_table_fat12[] = { + { 720, 2 }, // 360K floppies + { 1440, 2 }, // 720K floppies + { 2400, 1 }, // 1200K floppies + { 2880, 1 }, // 1440K floppies + { 5760, 2 }, // 2880K floppies +}; + +static constexpr DiskSizeToSectorsPerClusterMapping s_disk_table_fat16[] = { + { 8400, 0 }, // disks up to 4.1 MiB, the 0 value for trips an error + { 32680, 2 }, // disks up to 16 MiB, 1k cluster + { 262144, 4 }, // disks up to 128 MiB, 2k cluster + { 524288, 8 }, // disks up to 256 MiB, 4k cluster + { 1048576, 16 }, // disks up to 512 MiB, 8k cluster + { 2097152, 32 }, // disks up to 1 GiB, 16k cluster + { 4194304, 64 }, // disks up to 2 GiB, 32k cluster + { 0xFFFFFFFF, 0 }, // any disk greater than 2GiB, the 0 value trips an error +}; + +static constexpr DiskSizeToSectorsPerClusterMapping s_disk_table_fat32[] = { + { 66600, 0 }, // disks up to 32.5 MiB, the 0 value trips an error + { 532480, 1 }, // disks up to 260 MiB, .5k cluster + { 16777216, 8 }, // disks up to 8 GiB, 4k cluster + { 33554432, 16 }, // disks up to 16 GiB, 8k cluster + { 67108864, 32 }, // disks up to 32 GiB, 16k cluster + { 0xFFFFFFFF, 64 }, // disks greater than 32GiB, 32k cluster +}; + +static constexpr u8 s_boot_signature[2] = { 0x55, 0xAA }; +static constexpr u8 s_empty_12_bit_fat[4] = { 0xF0, 0xFF, 0xFF, 0x00 }; +static constexpr u8 s_empty_16_bit_fat[4] = { 0xF8, 0xFF, 0xFF, 0xFF }; +static constexpr u8 s_empty_32_bit_fat[12] = { 0xF8, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F }; + +static constexpr u8 s_zero_buffer[4096] { 0 }; + +enum class FATType { + FAT12, + FAT16, + FAT32, +}; + +template +static void write_little_endian(T value, u8* output, size_t& offset) +{ + value = AK::convert_between_host_and_little_endian(value); + __builtin_memcpy(output + offset, reinterpret_cast(&value), sizeof(T)); + offset += sizeof(T); +} + +static void write_to_buffer(u8* output, ReadonlyBytes source, size_t& offset) +{ + __builtin_memcpy(output + offset, source.data(), source.size()); + offset += source.size(); +} + +static Array serialize_dos_3_bios_parameter_block(Kernel::DOS3BIOSParameterBlock const& boot_record) +{ + Array output; + size_t offset = 0; + + write_to_buffer(output.data(), { &boot_record.boot_jump, sizeof(boot_record.boot_jump) }, offset); + write_to_buffer(output.data(), { &boot_record.oem_identifier, sizeof(boot_record.oem_identifier) }, offset); + write_little_endian(boot_record.bytes_per_sector, output.data(), offset); + write_little_endian(boot_record.sectors_per_cluster, output.data(), offset); + write_little_endian(boot_record.reserved_sector_count, output.data(), offset); + write_little_endian(boot_record.fat_count, output.data(), offset); + write_little_endian(boot_record.root_directory_entry_count, output.data(), offset); + write_little_endian(boot_record.sector_count_16bit, output.data(), offset); + write_little_endian(boot_record.media_descriptor_type, output.data(), offset); + write_little_endian(boot_record.sectors_per_fat_16bit, output.data(), offset); + write_little_endian(boot_record.sectors_per_track, output.data(), offset); + write_little_endian(boot_record.head_count, output.data(), offset); + write_little_endian(boot_record.hidden_sector_count, output.data(), offset); + write_little_endian(boot_record.sector_count_32bit, output.data(), offset); + + return output; +} + +static Array serialize_dos_4_bios_parameter_block(Kernel::DOS4BIOSParameterBlock const& boot_record) +{ + Array output; + size_t offset = 0; + + write_little_endian(boot_record.drive_number, output.data(), offset); + write_little_endian(boot_record.flags, output.data(), offset); + write_little_endian(boot_record.signature, output.data(), offset); + write_little_endian(boot_record.volume_id, output.data(), offset); + write_to_buffer(output.data(), { &boot_record.volume_label_string, sizeof(boot_record.volume_label_string) }, offset); + write_to_buffer(output.data(), { &boot_record.file_system_type, sizeof(boot_record.file_system_type) }, offset); + + return output; +} + +static Array serialize_dos_7_bios_parameter_block(Kernel::DOS7BIOSParameterBlock const& boot_record) +{ + Array output; + size_t offset = 0; + + write_little_endian(boot_record.sectors_per_fat_32bit, output.data(), offset); + write_little_endian(boot_record.flags, output.data(), offset); + write_little_endian(boot_record.fat_version, output.data(), offset); + write_little_endian(boot_record.root_directory_cluster, output.data(), offset); + write_little_endian(boot_record.fs_info_sector, output.data(), offset); + write_little_endian(boot_record.backup_boot_sector, output.data(), offset); + write_to_buffer(output.data(), { &boot_record.unused3, sizeof(boot_record.unused3) }, offset); + write_little_endian(boot_record.drive_number, output.data(), offset); + write_little_endian(boot_record.unused4, output.data(), offset); + write_little_endian(boot_record.signature, output.data(), offset); + write_little_endian(boot_record.volume_id, output.data(), offset); + write_to_buffer(output.data(), { &boot_record.volume_label_string, sizeof(boot_record.volume_label_string) }, offset); + write_to_buffer(output.data(), { &boot_record.file_system_type, sizeof(boot_record.file_system_type) }, offset); + + return output; +} + +static Array serialize_fat32_fs_info(Kernel::FAT32FSInfo const& fs_info) +{ + Array output; + size_t offset = 0; + + write_little_endian(fs_info.lead_signature, output.data(), offset); + write_to_buffer(output.data(), { &fs_info.unused1, sizeof(fs_info.unused1) }, offset); + write_little_endian(fs_info.struct_signature, output.data(), offset); + write_little_endian(fs_info.last_known_free_cluster_count, output.data(), offset); + write_little_endian(fs_info.next_free_cluster_hint, output.data(), offset); + write_to_buffer(output.data(), { &fs_info.unused2, sizeof(fs_info.unused2) }, offset); + write_little_endian(fs_info.trailing_signature, output.data(), offset); + + return output; +} + +static ErrorOr wipe_file(Core::File& file, u64 file_size) +{ + VERIFY(static_cast(file_size) >= 360 * KiB); + + TRY(file.seek(0, SeekMode::SetPosition)); + + // Wipe the entire partition or the first 34 MiB, whichever is smaller. + for (size_t i = 0; i < 34 * MiB; i += sizeof(s_zero_buffer)) { + size_t to_write = min(file_size - i, sizeof(s_zero_buffer)); + TRY(file.write_until_depleted({ &s_zero_buffer, to_write })); + if (file_size - i <= sizeof(s_zero_buffer)) + break; + } + + TRY(file.seek(0, SeekMode::SetPosition)); + + return {}; +} + +// This algorithm only works for 512 byte sectors, which are the only ones we support anyway. +// This may also produce slightly inefficient results, using up to 2 extra sectors for FAT16 and up to 8 for FAT32. +static u32 get_sectors_per_fat(Kernel::DOS3BIOSParameterBlock const& boot_record, FATType fat_type) +{ + if (fat_type == FATType::FAT12) { + switch (boot_record.sector_count_16bit / 2) { + case 360: + return 3; + case 720: + return 5; + case 1200: + return 7; + case 1440: + case 2880: + return 9; + default: + VERIFY_NOT_REACHED(); + } + } + + u32 sector_count = boot_record.sector_count_32bit; + + if (fat_type == FATType::FAT16 && boot_record.sector_count_16bit != 0) + sector_count = boot_record.sector_count_16bit; + + VERIFY(sector_count != 0); + + u32 root_directory_sectors = ((boot_record.root_directory_entry_count * 32) + (boot_record.bytes_per_sector - 1)) / boot_record.bytes_per_sector; + u32 sectors_per_container = sector_count - (boot_record.reserved_sector_count + root_directory_sectors); + u32 container_count = (256 * boot_record.sectors_per_cluster) + boot_record.fat_count; + + if (fat_type == FATType::FAT32) + container_count /= 2; + + return (sectors_per_container + (container_count - 1)) / container_count; +} + +static ErrorOr generate_dos_3_bios_parameter_block(u64 file_size, FATType fat_type) +{ + Kernel::DOS3BIOSParameterBlock boot_record {}; + boot_record.boot_jump[0] = 0xEB; // jmp + switch (fat_type) { + case FATType::FAT12: + case FATType::FAT16: + boot_record.boot_jump[1] = sizeof(Kernel::DOS3BIOSParameterBlock) + sizeof(Kernel::DOS4BIOSParameterBlock) - 2; + break; + case FATType::FAT32: + boot_record.boot_jump[1] = sizeof(Kernel::DOS3BIOSParameterBlock) + sizeof(Kernel::DOS7BIOSParameterBlock) - 2; + break; + } + boot_record.boot_jump[2] = 0x90; // nop + + __builtin_memcpy(&boot_record.oem_identifier[0], "MSWIN4.1", 8); + boot_record.bytes_per_sector = 512; + boot_record.sectors_per_cluster = 0; + // These are set early since we'll need one of them to look up the amount of sectors per cluster. + switch (fat_type) { + case FATType::FAT12: + case FATType::FAT16: + if (file_size / boot_record.bytes_per_sector <= NumericLimits::max()) { + boot_record.sector_count_16bit = file_size / boot_record.bytes_per_sector; + boot_record.sector_count_32bit = 0; + break; + } + [[fallthrough]]; + case FATType::FAT32: + boot_record.sector_count_16bit = 0; + boot_record.sector_count_32bit = file_size / boot_record.bytes_per_sector; + break; + } + + // FAT12 and FAT16 may place the total sector count in either sector_count_16bit or sector_count_32bit, + // depending on where it fits. Currently we only support FAT12 on floppy disks, + // where the sector count will always fit in sector_count_16bit, so this should only be used when dealing with FAT16. + auto get_fat16_sector_count = [&]() -> u32 { + VERIFY(fat_type == FATType::FAT16); + if (boot_record.sector_count_16bit != 0) { + return boot_record.sector_count_16bit; + } + + return boot_record.sector_count_32bit; + }; + + switch (fat_type) { + case FATType::FAT12: + for (size_t i = 0; i < sizeof(s_disk_table_fat12) / sizeof(s_disk_table_fat12[0]); ++i) { + if (boot_record.sector_count_16bit == s_disk_table_fat12[i].disk_size) { + boot_record.sectors_per_cluster = s_disk_table_fat12[i].sectors_per_cluster; + break; + } + } + + if (boot_record.sectors_per_cluster == 0) + return Error::from_string_literal("Unsupported partition size for FAT12 (supported sizes are 360K, 720K, 1200K, 1440K and 2880K)"); + + break; + case FATType::FAT16: + for (size_t i = 0; i < sizeof(s_disk_table_fat16) / sizeof(s_disk_table_fat16[0]); ++i) { + if (get_fat16_sector_count() <= s_disk_table_fat16[i].disk_size) { + boot_record.sectors_per_cluster = s_disk_table_fat16[i].sectors_per_cluster; + break; + } + } + + if (boot_record.sectors_per_cluster == 0) { + if (get_fat16_sector_count() <= s_disk_table_fat16[0].disk_size) { + return Error::from_string_literal("Partition too small for FAT16"); + } + + return Error::from_string_literal("Partition too large for FAT16"); + } + break; + case FATType::FAT32: + for (size_t i = 0; i < sizeof(s_disk_table_fat32) / sizeof(s_disk_table_fat32[0]); ++i) { + if (boot_record.sector_count_32bit <= s_disk_table_fat32[i].disk_size) { + boot_record.sectors_per_cluster = s_disk_table_fat32[i].sectors_per_cluster; + break; + } + } + + if (boot_record.sectors_per_cluster == 0) + return Error::from_string_literal("Partition too small for FAT32"); + + break; + } + + VERIFY(boot_record.bytes_per_sector * boot_record.sectors_per_cluster <= 32 * KiB); + + boot_record.fat_count = 2; + + switch (fat_type) { + case FATType::FAT12: + boot_record.reserved_sector_count = 1; + switch (file_size / 1024) { + case 360: + case 720: + boot_record.root_directory_entry_count = 112; + break; + case 1200: + case 1440: + case 2880: + boot_record.root_directory_entry_count = 224; + break; + default: + VERIFY_NOT_REACHED(); + } + break; + case FATType::FAT16: + boot_record.reserved_sector_count = 1; + boot_record.root_directory_entry_count = 512; + break; + case FATType::FAT32: + boot_record.reserved_sector_count = 32; + boot_record.root_directory_entry_count = 0; + break; + } + + switch (fat_type) { + case FATType::FAT12: + boot_record.head_count = 2; + switch (file_size / 1024) { + case 360: + boot_record.media_descriptor_type = 0xFD; + boot_record.sectors_per_track = 9; + break; + case 720: + boot_record.media_descriptor_type = 0xF9; + boot_record.sectors_per_track = 9; + break; + case 1200: + boot_record.media_descriptor_type = 0xF9; + boot_record.sectors_per_track = 15; + break; + case 1440: + boot_record.media_descriptor_type = 0xF0; + boot_record.sectors_per_track = 18; + break; + case 2880: + boot_record.media_descriptor_type = 0xF0; + boot_record.sectors_per_track = 36; + break; + default: + VERIFY_NOT_REACHED(); + } + break; + case FATType::FAT16: + case FATType::FAT32: + // FIXME: Fill in real values for these when dealing with hardware where disk geometry is relevant. + boot_record.media_descriptor_type = 0xF8; // This is a fixed disk, i.e. a partition on a hard drive. + boot_record.sectors_per_track = 63; + boot_record.head_count = 255; + } + + boot_record.hidden_sector_count = 0; + + // Fill in sectors_per_fat_16bit in last to make sure we've set everything that get_sectors_per_fat needs. + switch (fat_type) { + case FATType::FAT12: + case FATType::FAT16: + boot_record.sectors_per_fat_16bit = get_sectors_per_fat(boot_record, fat_type); + break; + case FATType::FAT32: + boot_record.sectors_per_fat_16bit = 0; + break; + } + + return boot_record; +} + +static Kernel::DOS4BIOSParameterBlock generate_dos_4_bios_parameter_block(FATType fat_type, u32 volume_id) +{ + Kernel::DOS4BIOSParameterBlock boot_record_16_bit {}; + if (fat_type == FATType::FAT12) + boot_record_16_bit.drive_number = 0x00; // Signify that this is a floppy disk. + else + boot_record_16_bit.drive_number = 0x80; // Signify that this is a hard disk. + boot_record_16_bit.flags = 0; + boot_record_16_bit.signature = 0x29; + boot_record_16_bit.volume_id = volume_id; + __builtin_memcpy(&boot_record_16_bit.volume_label_string[0], "NO NAME ", 11); // Must be padded with spaces. + switch (fat_type) { + case FATType::FAT12: + __builtin_memcpy(&boot_record_16_bit.file_system_type[0], "FAT12 ", 8); + break; + case FATType::FAT16: + __builtin_memcpy(&boot_record_16_bit.file_system_type[0], "FAT16 ", 8); + break; + default: + VERIFY_NOT_REACHED(); + } + + return boot_record_16_bit; +} + +static Kernel::DOS7BIOSParameterBlock generate_dos_7_bios_parameter_block(Kernel::DOS3BIOSParameterBlock const& boot_record, u32 volume_id) +{ + Kernel::DOS7BIOSParameterBlock boot_record_fat32 {}; + boot_record_fat32.sectors_per_fat_32bit = get_sectors_per_fat(boot_record, FATType::FAT32); + boot_record_fat32.flags = 0; + boot_record_fat32.fat_version = 0; + boot_record_fat32.root_directory_cluster = 2; + boot_record_fat32.fs_info_sector = 1; + boot_record_fat32.backup_boot_sector = 6; + + __builtin_memset(&boot_record_fat32.unused3[0], 0, 12); // Reserved field. + + boot_record_fat32.drive_number = 0x80; // Signify that this is a hard disk. + boot_record_fat32.unused4 = 0; // Windows NT flags. + boot_record_fat32.signature = 0x29; + + boot_record_fat32.volume_id = volume_id; + + __builtin_memcpy(&boot_record_fat32.volume_label_string[0], "NO NAME ", 11); // Must be padded with spaces. + __builtin_memcpy(&boot_record_fat32.file_system_type[0], "FAT32 ", 8); + + return boot_record_fat32; +} + +static Kernel::FAT32FSInfo generate_fat32_fs_info(Kernel::DOS7BIOSParameterBlock const& boot_record_fat32) +{ + Kernel::FAT32FSInfo fs_info {}; + fs_info.lead_signature = 0x41615252; + __builtin_memset(&fs_info.unused1[0], 0, 480); + fs_info.struct_signature = 0x61417272; + fs_info.last_known_free_cluster_count = 0xFFFFFFFF; // Signify that the amount of free clusters is unknown. + fs_info.next_free_cluster_hint = boot_record_fat32.root_directory_cluster + 1; + __builtin_memset(&fs_info.unused2[0], 0, 12); + fs_info.trailing_signature = 0xAA550000; + + return fs_info; +} + +// Note that all I/O is aligned to 512 byte sectors for compatibility with "raw" BSD character-special +// devices. (e.g. /dev/rdisk* on macOS) +static ErrorOr format_fat_16_bit(Core::File& file, FATType fat_type, u64 file_size, u32 volume_id) +{ + VERIFY(fat_type == FATType::FAT12 || fat_type == FATType::FAT16); + auto boot_record = TRY(generate_dos_3_bios_parameter_block(file_size, fat_type)); + auto boot_record_16_bit = generate_dos_4_bios_parameter_block(fat_type, volume_id); + + TRY(wipe_file(file, file_size)); + + Vector mbr; + mbr.append(serialize_dos_3_bios_parameter_block(boot_record).data(), sizeof(boot_record)); + mbr.append(serialize_dos_4_bios_parameter_block(boot_record_16_bit).data(), sizeof(boot_record_16_bit)); + mbr.append(&s_bootcode[0], sizeof(s_bootcode)); + mbr.resize(512); + mbr[510] = s_boot_signature[0]; + mbr[511] = s_boot_signature[1]; + + TRY(file.write_until_depleted({ mbr.data(), mbr.size() })); + + Vector FAT_sector; + if (fat_type == FATType::FAT12) + FAT_sector.append(&s_empty_12_bit_fat[0], sizeof(s_empty_12_bit_fat)); + else + FAT_sector.append(&s_empty_16_bit_fat[0], sizeof(s_empty_16_bit_fat)); + + FAT_sector.resize(512); + + for (size_t i = 0; i < boot_record.fat_count; ++i) { + TRY(file.write_until_depleted({ FAT_sector.data(), FAT_sector.size() })); + if (i + 1 != boot_record.fat_count) + TRY(file.seek(boot_record.bytes_per_sector * (boot_record.sectors_per_fat_16bit - 1), SeekMode::FromCurrentPosition)); + } + + return {}; +} + +static ErrorOr format_fat32(Core::File& file, u64 file_size, u32 volume_id) +{ + auto boot_record = TRY(generate_dos_3_bios_parameter_block(file_size, FATType::FAT32)); + auto boot_record_fat32 = generate_dos_7_bios_parameter_block(boot_record, volume_id); + auto fs_info = generate_fat32_fs_info(boot_record_fat32); + s_bootcode[s_message_offset_offset] = 0x77; + + TRY(wipe_file(file, file_size)); + + Vector mbr; + mbr.append(serialize_dos_3_bios_parameter_block(boot_record).data(), sizeof(boot_record)); + mbr.append(serialize_dos_7_bios_parameter_block(boot_record_fat32).data(), sizeof(boot_record_fat32)); + mbr.append(&s_bootcode[0], sizeof(s_bootcode)); + mbr.resize(512); + mbr[510] = s_boot_signature[0]; + mbr[511] = s_boot_signature[1]; + + Vector serialized_fs_info; + serialized_fs_info.append(serialize_fat32_fs_info(fs_info).data(), sizeof(fs_info)); + VERIFY(serialized_fs_info.size() == 512); + + // Write the boot record and the FSInfo block at the start of the file, and also back them up at sectors 6 and 7 respectively. + for (size_t i = 0; i < 2; ++i) { + TRY(file.write_until_depleted({ mbr.data(), mbr.size() })); + TRY(file.write_until_depleted({ serialized_fs_info.data(), serialized_fs_info.size() })); + + if (i == 0) + TRY(file.seek(boot_record.bytes_per_sector * 4, SeekMode::FromCurrentPosition)); + } + + Vector FAT_sector; + FAT_sector.append(&s_empty_32_bit_fat[0], sizeof(s_empty_32_bit_fat)); + FAT_sector.resize(512); + + TRY(file.seek(boot_record.bytes_per_sector * boot_record.reserved_sector_count, SeekMode::SetPosition)); + for (size_t i = 0; i < boot_record.fat_count; ++i) { + TRY(file.write_until_depleted({ FAT_sector.data(), FAT_sector.size() })); + if (i + 1 != boot_record.fat_count) + TRY(file.seek(boot_record.bytes_per_sector * (boot_record_fat32.sectors_per_fat_32bit - 1), SeekMode::FromCurrentPosition)); + } + + return {}; +} + +ErrorOr serenity_main(Main::Arguments arguments) +{ + StringView file_path; + int fat_type = 0; + + struct timeval time; + gettimeofday(&time, NULL); + u32 volume_id = static_cast(time.tv_sec) | static_cast(time.tv_usec); + + Core::ArgsParser args_parser; + args_parser.add_option(fat_type, "FAT type to use, valid types are 12, 16, and 32", "FAT-type", 'F', "FAT type"); + args_parser.add_positional_argument(file_path, "File to format", "file", Core::ArgsParser::Required::Yes); + args_parser.parse(arguments); + + auto file = TRY(Core::File::open(file_path, Core::File::OpenMode::ReadWrite | Core::File::OpenMode::DontCreate)); + + u64 file_size = 0; + + if (FileSystem::is_device(file->fd())) + file_size = TRY(FileSystem::block_device_size_from_ioctl(file->fd())); + else + file_size = TRY(FileSystem::size_from_fstat(file->fd())); + + switch (fat_type) { + case 12: + TRY(format_fat_16_bit(*file, FATType::FAT12, file_size, volume_id)); + break; + case 16: + TRY(format_fat_16_bit(*file, FATType::FAT16, file_size, volume_id)); + break; + case 32: + TRY(format_fat32(*file, file_size, volume_id)); + break; + default: + return Error::from_string_literal("Invalid or no FAT type specified, valid types are 12, 16, and 32"); + } + + sync(); + + return 0; +}