/* * Copyright (c) 2024, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include #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 auto s_disk_table_fat12 = to_array({ { 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({ { 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({ { 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 (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 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, 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 {}; } static ErrorOr 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 serenity_main(Main::Arguments arguments) { StringView file_path; Optional fat_type_or_empty; 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_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; }