Utilities: Add mkfs.fat

This adds a basic `mkfs.fat` utility, which can format FAT12, FAT16
and FAT32 partitions.

This does have a few limitations, namely in that FAT12 formatting is
limited to a set known floppy disk sizes, and we can only generate
512-byte sectors.
This commit is contained in:
implicitfield 2024-01-14 13:39:30 +04:00 committed by Andrew Kaster
parent a69e113d49
commit 84252db976
Notes: sideshowbarker 2024-07-17 06:28:38 +09:00
3 changed files with 611 additions and 1 deletions

View file

@ -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)

View file

@ -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)

View file

@ -0,0 +1,608 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Endian.h>
#include <AK/Format.h>
#include <AK/Types.h>
#include <Kernel/API/FileSystem/FATStructures.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibCore/System.h>
#include <LibFileSystem/FileSystem.h>
#include <LibMain/Main.h>
#include <sys/time.h>
#include <unistd.h>
// 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<typename T>
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<u8 const*>(&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<u8, sizeof(Kernel::DOS3BIOSParameterBlock)> serialize_dos_3_bios_parameter_block(Kernel::DOS3BIOSParameterBlock const& boot_record)
{
Array<u8, sizeof(Kernel::DOS3BIOSParameterBlock)> 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<u8, sizeof(Kernel::DOS4BIOSParameterBlock)> serialize_dos_4_bios_parameter_block(Kernel::DOS4BIOSParameterBlock const& boot_record)
{
Array<u8, sizeof(Kernel::DOS4BIOSParameterBlock)> 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<u8, sizeof(Kernel::DOS7BIOSParameterBlock)> serialize_dos_7_bios_parameter_block(Kernel::DOS7BIOSParameterBlock const& boot_record)
{
Array<u8, sizeof(Kernel::DOS7BIOSParameterBlock)> 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<u8, sizeof(Kernel::FAT32FSInfo)> serialize_fat32_fs_info(Kernel::FAT32FSInfo const& fs_info)
{
Array<u8, sizeof(Kernel::FAT32FSInfo)> 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<void> wipe_file(Core::File& file, u64 file_size)
{
VERIFY(static_cast<u64>(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<Kernel::DOS3BIOSParameterBlock> 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<u16>::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<void> 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<u8> 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<u8> 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<void> 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<u8> 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<u8> 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<u8> 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<int> serenity_main(Main::Arguments arguments)
{
StringView file_path;
int fat_type = 0;
struct timeval time;
gettimeofday(&time, NULL);
u32 volume_id = static_cast<u32>(time.tv_sec) | static_cast<u32>(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;
}