ladybird/Userland/Utilities/mkfs.fat.cpp

654 lines
25 KiB
C++

/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Array.h>
#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 auto s_disk_table_fat12 = to_array<DiskSizeToSectorsPerClusterMapping>({
{ 720, 2 }, // 360K floppies
{ 1440, 2 }, // 720K floppies
{ 2400, 1 }, // 1200K floppies
{ 2880, 1 }, // 1440K floppies
{ 5760, 2 }, // 2880K floppies
});
static constexpr auto s_disk_table_fat16 = to_array<DiskSizeToSectorsPerClusterMapping>({
{ 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 auto s_disk_table_fat32 = to_array<DiskSizeToSectorsPerClusterMapping>({
{ 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 (auto const& potential_entry : s_disk_table_fat12) {
if (boot_record.sector_count_16bit == potential_entry.disk_size) {
boot_record.sectors_per_cluster = potential_entry.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 (auto const& potential_entry : s_disk_table_fat16) {
if (get_fat16_sector_count() <= potential_entry.disk_size) {
boot_record.sectors_per_cluster = potential_entry.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 (auto const& potential_entry : s_disk_table_fat32) {
if (boot_record.sector_count_32bit <= potential_entry.disk_size) {
boot_record.sectors_per_cluster = potential_entry.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::DOS3BIOSParameterBlock const& boot_record, 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;
{
off_t const fat_sectors = boot_record_fat32.sectors_per_fat_32bit * boot_record.fat_count;
off_t const data_sector_count = boot_record.sector_count_32bit - boot_record.reserved_sector_count - fat_sectors;
fs_info.last_known_free_cluster_count = data_sector_count / boot_record.sectors_per_cluster - 1;
}
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, 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 {};
}
static ErrorOr<int> detect_fat_type_from_file_size(u64 file_size)
{
u32 sector_count = file_size / 512;
auto check_table_for_support = [sector_count](auto const& table, bool fat12 = false) {
for (auto const& potential_entry : table) {
if (fat12 && sector_count == potential_entry.disk_size)
return true;
else if (!fat12 && sector_count <= potential_entry.disk_size)
return potential_entry.sectors_per_cluster != 0;
}
return false;
};
if (check_table_for_support(s_disk_table_fat12, true))
return 12;
if (file_size < 512 * MiB && check_table_for_support(s_disk_table_fat16))
return 16;
if (check_table_for_support(s_disk_table_fat32))
return 32;
return Error::from_string_literal("Unable to autodetect a compatible FAT variant");
}
static bool is_valid_fat_type(int fat_type)
{
return fat_type == 12 || fat_type == 16 || fat_type == 32;
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
StringView file_path;
Optional<int> fat_type_or_empty;
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_or_empty, "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()));
int fat_type = 0;
if (fat_type_or_empty.has_value()) {
fat_type = fat_type_or_empty.release_value();
if (!is_valid_fat_type(fat_type))
return Error::from_string_literal("Invalid FAT type specified, valid types are 12, 16, and 32");
} else {
fat_type = TRY(detect_fat_type_from_file_size(file_size));
}
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:
VERIFY_NOT_REACHED();
}
sync();
return 0;
}