mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 07:27:45 +00:00
Kernel/FileSystem/FATFS: Support for FAT12/16 DOS BIOS Parameter Blocks
This commit is contained in:
parent
02edd240ae
commit
67f567348f
4 changed files with 332 additions and 44 deletions
|
@ -9,27 +9,60 @@
|
||||||
#include <AK/DOSPackedTime.h>
|
#include <AK/DOSPackedTime.h>
|
||||||
#include <AK/EnumBits.h>
|
#include <AK/EnumBits.h>
|
||||||
#include <AK/Types.h>
|
#include <AK/Types.h>
|
||||||
|
#include <Kernel/Library/KBuffer.h>
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
struct [[gnu::packed]] FAT32BootRecord {
|
// This structure represents the DOS 3.31 BIOS Partition Block.
|
||||||
|
// While DOS 3.31 predates FAT verions 12/16/32 (the versions supported by this driver),
|
||||||
|
// the fields in this block are common with the DOS 4 and DOS 7 BIOS Parameter blocks.
|
||||||
|
// This structure will be followed by an "Extended BIOS Partition Block" (EBPB).
|
||||||
|
//
|
||||||
|
// The DOS 4 EBPB is *typically* used by FAT 12/16 file systems, while the DOS 7 EBPB
|
||||||
|
// is *typically* used by FAT 32. _However_, any combination is possible, as the FAT
|
||||||
|
// version is only determined by the number of clusters.
|
||||||
|
//
|
||||||
|
// Note that the DOS 4 and DOS 7 EBPB extensions are incompatible with each other
|
||||||
|
// (contain fields in different orders and of different lenghts) and do not contain
|
||||||
|
// an explicit indication to differentiate them.
|
||||||
|
// This driver uses heuristics to identify the EBPB version (based on the signature bytes
|
||||||
|
// and sector counts).
|
||||||
|
// FIXME: Consider also using the MBR parition type field in the future.
|
||||||
|
struct [[gnu::packed]] DOS3BIOSParameterBlock {
|
||||||
u8 boot_jump[3];
|
u8 boot_jump[3];
|
||||||
char oem_identifier[8];
|
char oem_identifier[8];
|
||||||
u16 bytes_per_sector;
|
u16 bytes_per_sector; // Offset 0x0B -- beginning of DOS 3.31 BPB.
|
||||||
u8 sectors_per_cluster;
|
u8 sectors_per_cluster;
|
||||||
u16 reserved_sector_count;
|
u16 reserved_sector_count;
|
||||||
u8 fat_count;
|
u8 fat_count;
|
||||||
u16 root_directory_entry_count;
|
u16 root_directory_entry_count;
|
||||||
u16 unused1;
|
u16 sector_count_16bit;
|
||||||
u8 media_descriptor_type;
|
u8 media_descriptor_type;
|
||||||
u16 unused2;
|
u16 sectors_per_fat_16bit;
|
||||||
u16 sectors_per_track;
|
u16 sectors_per_track;
|
||||||
u16 head_count;
|
u16 head_count;
|
||||||
u32 hidden_sector_count;
|
u32 hidden_sector_count;
|
||||||
u32 sector_count;
|
u32 sector_count_32bit; // 0x020 -- end of DOS 3.31 BPB.
|
||||||
u32 sectors_per_fat;
|
};
|
||||||
|
// 11 is the boot jump/OEM identifier prefix prior to the official BPB.
|
||||||
|
static_assert(sizeof(DOS3BIOSParameterBlock) == 11 + 25);
|
||||||
|
|
||||||
|
struct [[gnu::packed]] DOS4BIOSParameterBlock {
|
||||||
|
// Begins at sector offset 0x024.
|
||||||
|
u8 drive_number; // 0x024
|
||||||
|
u8 flags;
|
||||||
|
u8 signature;
|
||||||
|
u32 volume_id;
|
||||||
|
char volume_label_string[11];
|
||||||
|
char file_system_type[8];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(DOS4BIOSParameterBlock) == 26);
|
||||||
|
|
||||||
|
struct [[gnu::packed]] DOS7BIOSParameterBlock {
|
||||||
|
// Begins at sector offset 0x024.
|
||||||
|
u32 sectors_per_fat_32bit; // 0x024
|
||||||
u16 flags;
|
u16 flags;
|
||||||
u16 fat_version;
|
u16 fat_version; // Expected value 0x2b2a.
|
||||||
u32 root_directory_cluster;
|
u32 root_directory_cluster;
|
||||||
u16 fs_info_sector;
|
u16 fs_info_sector;
|
||||||
u16 backup_boot_sector;
|
u16 backup_boot_sector;
|
||||||
|
@ -39,9 +72,22 @@ struct [[gnu::packed]] FAT32BootRecord {
|
||||||
u8 signature;
|
u8 signature;
|
||||||
u32 volume_id;
|
u32 volume_id;
|
||||||
char volume_label_string[11];
|
char volume_label_string[11];
|
||||||
char system_identifier_string[8];
|
char file_system_type[8];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(DOS7BIOSParameterBlock) == 54);
|
||||||
|
|
||||||
|
enum DOSBIOSParameterBlockVersion {
|
||||||
|
DOS_BPB_UNKNOWN,
|
||||||
|
DOS_BPB_3, // Version 3.4.
|
||||||
|
DOS_BPB_4, // Version 4.0
|
||||||
|
DOS_BPB_7 // Version 7.0
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class FATVersion {
|
||||||
|
FAT12,
|
||||||
|
FAT16,
|
||||||
|
FAT32,
|
||||||
};
|
};
|
||||||
static_assert(sizeof(FAT32BootRecord) == 90);
|
|
||||||
|
|
||||||
enum class FATAttributes : u8 {
|
enum class FATAttributes : u8 {
|
||||||
ReadOnly = 0x01,
|
ReadOnly = 0x01,
|
||||||
|
|
|
@ -10,6 +10,85 @@
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
|
DOSBIOSParameterBlockVersion DOSBIOSParameterBlock::bpb_version() const
|
||||||
|
{
|
||||||
|
bool dos3_valid = m_dos4_block->signature == 0x28;
|
||||||
|
bool dos4_valid = m_dos4_block->signature == 0x29;
|
||||||
|
bool dos7_valid = m_dos7_block->signature == 0x28 || m_dos7_block->signature == 0x29;
|
||||||
|
// A DOS 7 EBPB should _never_ contain the values 0x28 or 0x29 at
|
||||||
|
// the offset associated with `m_dos4_block->signature`
|
||||||
|
// (aka `m_dos7_block->sectors_per_fat_32bit`) due to the maximum number of
|
||||||
|
// clusters ensuring the number of sectors per fat will not exceed 0x200000.
|
||||||
|
// As a result, it should be safe to determine BPB version through the
|
||||||
|
// signature fields by checking the DOS 4 signature offset prior to the DOS 7 one.
|
||||||
|
//
|
||||||
|
// With a DOS 3 or DOS 4 EBPB, the DOS 7 signature offset references uninitialized
|
||||||
|
// space. While unlikely to be set to a valid signature value, it is not implausible.
|
||||||
|
// We warn the user here, but because it does not represent an invalid FS configuration,
|
||||||
|
// do not error.
|
||||||
|
if ((dos3_valid || dos4_valid) && dos7_valid)
|
||||||
|
dbgln("FATFS: DOS 4 and DOS 7 EBPB signatures detected, EBPB/FAT version detection may be incorrect.");
|
||||||
|
|
||||||
|
if (dos3_valid)
|
||||||
|
return DOS_BPB_3;
|
||||||
|
else if (dos4_valid)
|
||||||
|
return DOS_BPB_4;
|
||||||
|
else if (dos7_valid)
|
||||||
|
return DOS_BPB_7;
|
||||||
|
else
|
||||||
|
return DOS_BPB_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOS3BIOSParameterBlock const* DOSBIOSParameterBlock::common_bpb() const
|
||||||
|
{
|
||||||
|
return m_common_block;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOS4BIOSParameterBlock const* DOSBIOSParameterBlock::dos4_bpb() const
|
||||||
|
{
|
||||||
|
// Only return parameter block if signature indicates this portion of the block is filled out.
|
||||||
|
if (m_dos4_block->signature == 0x28 || m_dos4_block->signature == 0x29)
|
||||||
|
return m_dos4_block;
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOS7BIOSParameterBlock const* DOSBIOSParameterBlock::dos7_bpb() const
|
||||||
|
{
|
||||||
|
// Only return parameter block if signature indicates this portion of the block is filled out.
|
||||||
|
if (m_dos7_block->signature == 0x28 || m_dos7_block->signature == 0x29)
|
||||||
|
return m_dos7_block;
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 DOSBIOSParameterBlock::sectors_per_fat() const
|
||||||
|
{
|
||||||
|
return common_bpb()->sectors_per_fat_16bit != 0 ? common_bpb()->sectors_per_fat_16bit : m_dos7_block->sectors_per_fat_32bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 DOSBIOSParameterBlock::sector_count() const
|
||||||
|
{
|
||||||
|
if (common_bpb()->sector_count_16bit != 0) {
|
||||||
|
// The `16bit` field is only used on partitions smaller than 32 MB,
|
||||||
|
// and never for FAT32.
|
||||||
|
// It is set to `0` when the 32 bit field contains the sector count.
|
||||||
|
return common_bpb()->sector_count_16bit;
|
||||||
|
} else {
|
||||||
|
return common_bpb()->sector_count_32bit;
|
||||||
|
// FIXME: If this is 0 for a FAT32 EBPB with a signature of 0x29,
|
||||||
|
// read 0x052, which is a 64-bit wide sector count.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 DOSBIOSParameterBlock::signature() const
|
||||||
|
{
|
||||||
|
if (bpb_version() == DOS_BPB_3 || bpb_version() == DOS_BPB_4)
|
||||||
|
return m_dos4_block->signature;
|
||||||
|
else
|
||||||
|
return m_dos7_block->signature;
|
||||||
|
}
|
||||||
|
|
||||||
ErrorOr<NonnullRefPtr<FileSystem>> FATFS::try_create(OpenFileDescription& file_description, ReadonlyBytes)
|
ErrorOr<NonnullRefPtr<FileSystem>> FATFS::try_create(OpenFileDescription& file_description, ReadonlyBytes)
|
||||||
{
|
{
|
||||||
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) FATFS(file_description)));
|
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) FATFS(file_description)));
|
||||||
|
@ -34,46 +113,126 @@ ErrorOr<void> FATFS::initialize_while_locked()
|
||||||
m_boot_record = TRY(KBuffer::try_create_with_size("FATFS: Boot Record"sv, m_device_block_size));
|
m_boot_record = TRY(KBuffer::try_create_with_size("FATFS: Boot Record"sv, m_device_block_size));
|
||||||
auto boot_record_buffer = UserOrKernelBuffer::for_kernel_buffer(m_boot_record->data());
|
auto boot_record_buffer = UserOrKernelBuffer::for_kernel_buffer(m_boot_record->data());
|
||||||
TRY(raw_read(0, boot_record_buffer));
|
TRY(raw_read(0, boot_record_buffer));
|
||||||
|
m_parameter_block = TRY(adopt_nonnull_own_or_enomem(new (nothrow) DOSBIOSParameterBlock(m_boot_record)));
|
||||||
|
|
||||||
|
// Alias for extended BPB.
|
||||||
|
DOSBIOSParameterBlock& ebpb = *m_parameter_block;
|
||||||
|
// Alias for block of common parameters in BPB.
|
||||||
|
DOS3BIOSParameterBlock const* block = ebpb.common_bpb();
|
||||||
|
|
||||||
if constexpr (FAT_DEBUG) {
|
if constexpr (FAT_DEBUG) {
|
||||||
dbgln("FATFS: oem_identifier: {}", boot_record()->oem_identifier);
|
dbgln("FATFS: oem_identifier: {}", block->oem_identifier);
|
||||||
dbgln("FATFS: bytes_per_sector: {}", boot_record()->bytes_per_sector);
|
dbgln("FATFS: bytes_per_sector: {}", block->bytes_per_sector);
|
||||||
dbgln("FATFS: sectors_per_cluster: {}", boot_record()->sectors_per_cluster);
|
dbgln("FATFS: sectors_per_cluster: {}", block->sectors_per_cluster);
|
||||||
dbgln("FATFS: reserved_sector_count: {}", boot_record()->reserved_sector_count);
|
dbgln("FATFS: reserved_sector_count: {}", block->reserved_sector_count);
|
||||||
dbgln("FATFS: fat_count: {}", boot_record()->fat_count);
|
dbgln("FATFS: fat_count: {}", block->fat_count);
|
||||||
dbgln("FATFS: root_directory_entry_count: {}", boot_record()->root_directory_entry_count);
|
dbgln("FATFS: root_directory_entry_count: {}", block->root_directory_entry_count);
|
||||||
dbgln("FATFS: media_descriptor_type: {}", boot_record()->media_descriptor_type);
|
dbgln("FATFS: media_descriptor_type: {}", block->media_descriptor_type);
|
||||||
dbgln("FATFS: sectors_per_track: {}", boot_record()->sectors_per_track);
|
dbgln("FATFS: sectors_per_track: {}", block->sectors_per_track);
|
||||||
dbgln("FATFS: head_count: {}", boot_record()->head_count);
|
dbgln("FATFS: head_count: {}", block->head_count);
|
||||||
dbgln("FATFS: hidden_sector_count: {}", boot_record()->hidden_sector_count);
|
dbgln("FATFS: hidden_sector_count: {}", block->hidden_sector_count);
|
||||||
dbgln("FATFS: sector_count: {}", boot_record()->sector_count);
|
dbgln("FATFS: sector_count: {}", ebpb.sector_count());
|
||||||
dbgln("FATFS: sectors_per_fat: {}", boot_record()->sectors_per_fat);
|
dbgln("FATFS: sectors_per_fat: {}", ebpb.sectors_per_fat());
|
||||||
dbgln("FATFS: flags: {}", boot_record()->flags);
|
|
||||||
dbgln("FATFS: fat_version: {}", boot_record()->fat_version);
|
auto ebpb_version = ebpb.bpb_version();
|
||||||
dbgln("FATFS: root_directory_cluster: {}", boot_record()->root_directory_cluster);
|
if (ebpb_version == DOSBIOSParameterBlockVersion::DOS_BPB_7) {
|
||||||
dbgln("FATFS: fs_info_sector: {}", boot_record()->fs_info_sector);
|
DOS7BIOSParameterBlock const* dos7_boot_record = ebpb.dos7_bpb();
|
||||||
dbgln("FATFS: backup_boot_sector: {}", boot_record()->backup_boot_sector);
|
dbgln("FATFS: EBPB: DOS 7");
|
||||||
dbgln("FATFS: drive_number: {}", boot_record()->drive_number);
|
dbgln("FATFS: flags: {}", dos7_boot_record->flags);
|
||||||
dbgln("FATFS: volume_id: {}", boot_record()->volume_id);
|
dbgln("FATFS: fat_version: {}", dos7_boot_record->fat_version);
|
||||||
|
dbgln("FATFS: root_directory_cluster: {}", dos7_boot_record->root_directory_cluster);
|
||||||
|
dbgln("FATFS: fs_info_sector: {}", dos7_boot_record->fs_info_sector);
|
||||||
|
dbgln("FATFS: backup_boot_sector: {}", dos7_boot_record->backup_boot_sector);
|
||||||
|
dbgln("FATFS: drive_number: {}", dos7_boot_record->drive_number);
|
||||||
|
dbgln("FATFS: volume_id: {}", dos7_boot_record->volume_id);
|
||||||
|
} else if (ebpb_version == DOSBIOSParameterBlockVersion::DOS_BPB_3 || ebpb_version == DOSBIOSParameterBlockVersion::DOS_BPB_4) {
|
||||||
|
DOS4BIOSParameterBlock const* dos4_boot_record = ebpb.dos4_bpb();
|
||||||
|
if (ebpb_version == DOSBIOSParameterBlockVersion::DOS_BPB_3) {
|
||||||
|
dbgln("FATFS: EBPB: DOS 3.4");
|
||||||
|
} else if (ebpb_version == DOSBIOSParameterBlockVersion::DOS_BPB_4) {
|
||||||
|
dbgln("FATFS: EBPB: DOS 4");
|
||||||
|
}
|
||||||
|
dbgln("FATFS: drive_number: {}", dos4_boot_record->drive_number);
|
||||||
|
dbgln("FATFS: flags: {}", dos4_boot_record->flags);
|
||||||
|
dbgln("FATFS: volume_id: {}", dos4_boot_record->volume_id);
|
||||||
|
|
||||||
|
// volume_label_string and file_system_type are only valid when
|
||||||
|
// ebpb_version == DOSBIOSParameterBlockVersion::DOS4.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boot_record()->signature != signature_1 && boot_record()->signature != signature_2) {
|
if (ebpb.signature() != signature_1 && ebpb.signature() != signature_2) {
|
||||||
dbgln("FATFS: Invalid signature");
|
dbgln("FATFS: Invalid signature");
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_device_block_size = boot_record()->bytes_per_sector;
|
DOS3BIOSParameterBlock const* ebpb_block = ebpb.common_bpb();
|
||||||
|
// The number of data area sectors is what DOS/Windows used to determine
|
||||||
|
// if a partition was a FAT12, FAT16, or FAT32 file system.
|
||||||
|
// From "FAT Type Determination" section of Microsoft FAT Specification
|
||||||
|
// (fatgen103.doc):
|
||||||
|
// The FAT type—one of FAT12, FAT16, or FAT32—is determined by the count
|
||||||
|
// of clusters on the volume and nothing else.
|
||||||
|
//
|
||||||
|
// The following calculations are based on the equations provided in this
|
||||||
|
// section.
|
||||||
|
|
||||||
|
// "RootDirSectors" from MS FAT Specification. This is calculated as:
|
||||||
|
// Number of bytes occupied by root directory area (0 on FAT32)
|
||||||
|
// +
|
||||||
|
// Bytes to fill final sector (ie, round up)
|
||||||
|
// Converted into sector count (by dividing by bytes per sector).
|
||||||
|
u32 root_directory_sectors = ((ebpb_block->root_directory_entry_count * sizeof(FATEntry)) + (ebpb_block->bytes_per_sector - 1)) / ebpb_block->bytes_per_sector;
|
||||||
|
|
||||||
|
// "DataSec" from MS FAT Specification.
|
||||||
|
u32 data_area_sectors = ebpb.sector_count() - ((ebpb_block->reserved_sector_count) + (ebpb_block->fat_count * ebpb.sectors_per_fat()) + root_directory_sectors);
|
||||||
|
|
||||||
|
// CountofClusters from MS FAT Specification.
|
||||||
|
u32 data_area_clusters = data_area_sectors / ebpb_block->sectors_per_cluster;
|
||||||
|
|
||||||
|
// Cluster thresholds and operators as defined in MS FAT Specification.
|
||||||
|
if (data_area_clusters < 4085) {
|
||||||
|
dbgln("FATFS: Detected FAT12 with {} data area clusters", data_area_clusters);
|
||||||
|
m_fat_version = FATVersion::FAT12;
|
||||||
|
} else if (data_area_clusters < 65525) {
|
||||||
|
dbgln("FATFS: Detected FAT16 with {} data area clusters", data_area_clusters);
|
||||||
|
m_fat_version = FATVersion::FAT16;
|
||||||
|
} else {
|
||||||
|
dbgln("FATFS: Assuming FAT32 with {} data area clusters", data_area_clusters);
|
||||||
|
m_fat_version = FATVersion::FAT32;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_device_block_size = ebpb_block->bytes_per_sector;
|
||||||
set_logical_block_size(m_device_block_size);
|
set_logical_block_size(m_device_block_size);
|
||||||
|
|
||||||
u32 root_directory_sectors = ((boot_record()->root_directory_entry_count * sizeof(FATEntry)) + (m_device_block_size - 1)) / m_device_block_size;
|
m_first_data_sector = block->reserved_sector_count + (block->fat_count * ebpb.sectors_per_fat()) + root_directory_sectors;
|
||||||
m_first_data_sector = boot_record()->reserved_sector_count + (boot_record()->fat_count * boot_record()->sectors_per_fat) + root_directory_sectors;
|
|
||||||
|
|
||||||
TRY(BlockBasedFileSystem::initialize_while_locked());
|
TRY(BlockBasedFileSystem::initialize_while_locked());
|
||||||
|
|
||||||
FATEntry root_entry {};
|
FATEntry root_entry {};
|
||||||
|
|
||||||
root_entry.first_cluster_low = boot_record()->root_directory_cluster & 0xFFFF;
|
if (m_fat_version == FATVersion::FAT32) {
|
||||||
root_entry.first_cluster_high = boot_record()->root_directory_cluster >> 16;
|
// FAT32 stores the root directory within the FAT (at the clusters specified
|
||||||
|
// in the boot record), as opposed to the root directory area
|
||||||
|
// (as done by FAT 12/16).
|
||||||
|
|
||||||
|
DOS7BIOSParameterBlock const* boot_record = ebpb.dos7_bpb();
|
||||||
|
// Ensure we have a DOS7 BPB (so that we can find the root directory cluster).
|
||||||
|
if (boot_record == nullptr) {
|
||||||
|
dbgln("FATFS: Non-DOS7 BPB for FAT32 FS.");
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
root_entry.first_cluster_low = boot_record->root_directory_cluster & 0xFFFF;
|
||||||
|
root_entry.first_cluster_high = boot_record->root_directory_cluster >> 16;
|
||||||
|
} else {
|
||||||
|
// FAT12/FAT16.
|
||||||
|
// Use cluster = 0 as a signal to `first_block_of_cluster()` to look in the
|
||||||
|
// root directory area for the root entry.
|
||||||
|
// Clusters 0 and 1 hold special values, and will never be used to store file
|
||||||
|
// data.
|
||||||
|
root_entry.first_cluster_low = 0;
|
||||||
|
root_entry.first_cluster_high = 0;
|
||||||
|
}
|
||||||
|
|
||||||
root_entry.attributes = FATAttributes::Directory;
|
root_entry.attributes = FATAttributes::Directory;
|
||||||
m_root_inode = TRY(FATInode::create(*this, root_entry));
|
m_root_inode = TRY(FATInode::create(*this, root_entry));
|
||||||
|
@ -86,9 +245,29 @@ Inode& FATFS::root_inode()
|
||||||
return *m_root_inode;
|
return *m_root_inode;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockBasedFileSystem::BlockIndex FATFS::first_block_of_cluster(u32 cluster) const
|
FatBlockSpan FATFS::first_block_of_cluster(u32 cluster) const
|
||||||
{
|
{
|
||||||
return ((cluster - first_data_cluster) * boot_record()->sectors_per_cluster) + m_first_data_sector;
|
// For FAT12/16, we use a value of cluster 0 to indicate this is a cluster for the root directory.
|
||||||
|
// Cluster 0 and cluster 1 hold special values (cluster 0 holds the FAT ID, and cluster 1
|
||||||
|
// the "end of chain marker"), neither of which will be present in the table or associated
|
||||||
|
// with any file.
|
||||||
|
// "Entries with the Volume Label flag, subdirectory ".." pointing to the FAT12 and FAT16 root, and empty files with size 0 should have first cluster 0."
|
||||||
|
// --Wikipedia
|
||||||
|
//
|
||||||
|
DOSBIOSParameterBlock ebpb(m_boot_record);
|
||||||
|
DOS3BIOSParameterBlock const* ebpb_block = ebpb.common_bpb();
|
||||||
|
if (m_fat_version != FATVersion::FAT32 && cluster == 0) {
|
||||||
|
// Root directory area follows the FATs after the reserved sectors.
|
||||||
|
return FatBlockSpan {
|
||||||
|
ebpb_block->reserved_sector_count + (ebpb_block->fat_count * ebpb.sectors_per_fat()),
|
||||||
|
(ebpb_block->root_directory_entry_count * sizeof(FATEntry)) / ebpb_block->bytes_per_sector
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return FatBlockSpan {
|
||||||
|
((cluster - first_data_cluster) * ebpb_block->sectors_per_cluster) + m_first_data_sector,
|
||||||
|
ebpb_block->sectors_per_cluster
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u8 FATFS::internal_file_type_to_directory_entry_type(DirectoryEntryView const& entry) const
|
u8 FATFS::internal_file_type_to_directory_entry_type(DirectoryEntryView const& entry) const
|
||||||
|
|
|
@ -17,6 +17,44 @@
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
|
|
||||||
|
class DOSBIOSParameterBlock final {
|
||||||
|
private:
|
||||||
|
KBuffer const* const m_boot_record;
|
||||||
|
const struct DOS4BIOSParameterBlock* m_dos4_block;
|
||||||
|
const struct DOS7BIOSParameterBlock* m_dos7_block;
|
||||||
|
const struct DOS3BIOSParameterBlock* m_common_block;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DOSBIOSParameterBlock(KBuffer const* const boot_record)
|
||||||
|
: m_boot_record(boot_record)
|
||||||
|
, m_dos4_block(reinterpret_cast<const struct DOS4BIOSParameterBlock*>(boot_record->bytes().offset_pointer(0x024)))
|
||||||
|
, m_dos7_block(reinterpret_cast<const struct DOS7BIOSParameterBlock*>(boot_record->bytes().offset_pointer(0x024)))
|
||||||
|
, m_common_block(reinterpret_cast<const struct DOS3BIOSParameterBlock*>(boot_record->bytes().data()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DOSBIOSParameterBlockVersion bpb_version() const;
|
||||||
|
|
||||||
|
DOS3BIOSParameterBlock const* common_bpb() const;
|
||||||
|
|
||||||
|
DOS4BIOSParameterBlock const* dos4_bpb() const;
|
||||||
|
|
||||||
|
DOS7BIOSParameterBlock const* dos7_bpb() const;
|
||||||
|
|
||||||
|
u16 sectors_per_fat() const;
|
||||||
|
|
||||||
|
u32 sector_count() const;
|
||||||
|
|
||||||
|
u8 signature() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Represents a block of contiguous sectors to read. This typically represents a
|
||||||
|
// cluster, but is also used to define areas of the root directory region.
|
||||||
|
struct FatBlockSpan {
|
||||||
|
BlockBasedFileSystem::BlockIndex start_block;
|
||||||
|
size_t number_of_sectors;
|
||||||
|
};
|
||||||
|
|
||||||
class FATFS final : public BlockBasedFileSystem {
|
class FATFS final : public BlockBasedFileSystem {
|
||||||
friend FATInode;
|
friend FATInode;
|
||||||
|
|
||||||
|
@ -42,13 +80,13 @@ private:
|
||||||
|
|
||||||
static constexpr u32 first_data_cluster = 2;
|
static constexpr u32 first_data_cluster = 2;
|
||||||
|
|
||||||
FAT32BootRecord const* boot_record() const { return reinterpret_cast<FAT32BootRecord const*>(m_boot_record->data()); }
|
FatBlockSpan first_block_of_cluster(u32 cluster) const;
|
||||||
|
|
||||||
BlockBasedFileSystem::BlockIndex first_block_of_cluster(u32 cluster) const;
|
OwnPtr<KBuffer> m_boot_record;
|
||||||
|
OwnPtr<DOSBIOSParameterBlock> m_parameter_block;
|
||||||
OwnPtr<KBuffer> m_boot_record {};
|
|
||||||
RefPtr<FATInode> m_root_inode;
|
RefPtr<FATInode> m_root_inode;
|
||||||
u32 m_first_data_sector { 0 };
|
u32 m_first_data_sector { 0 };
|
||||||
|
FATVersion m_fat_version;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,12 +58,28 @@ ErrorOr<Vector<BlockBasedFileSystem::BlockIndex>> FATInode::compute_block_list()
|
||||||
while (cluster < no_more_clusters) {
|
while (cluster < no_more_clusters) {
|
||||||
dbgln_if(FAT_DEBUG, "FATFS: Appending cluster {} to inode {}'s cluster chain", cluster, index());
|
dbgln_if(FAT_DEBUG, "FATFS: Appending cluster {} to inode {}'s cluster chain", cluster, index());
|
||||||
|
|
||||||
BlockBasedFileSystem::BlockIndex first_block = fs().first_block_of_cluster(cluster);
|
auto first_block_and_length = fs().first_block_of_cluster(cluster);
|
||||||
for (u8 i = 0; i < fs().boot_record()->sectors_per_cluster; i++)
|
for (u8 i = 0; i < first_block_and_length.number_of_sectors; i++)
|
||||||
block_list.append(BlockBasedFileSystem::BlockIndex { first_block.value() + i });
|
block_list.append(BlockBasedFileSystem::BlockIndex { first_block_and_length.start_block.value() + i });
|
||||||
|
|
||||||
|
// Clusters 0 and 1 are reserved in the FAT, and their entries in the FAT will
|
||||||
|
// not point to another valid cluster in the chain (Cluster 0 typically holds
|
||||||
|
// the "FAT ID" field with some flags, Cluster 1 should be the end of chain
|
||||||
|
// marker).
|
||||||
|
// Internally, we use `cluster == 0` to represent the root directory Inode,
|
||||||
|
// which is a signal to read the root directory region blocks on FAT12/16
|
||||||
|
// file systems. (`fs().first_block_of_cluster` will return the appropriate
|
||||||
|
// block/sectors to read given cluster == 0).
|
||||||
|
// Therefore, we read one set of sectors for these invalud cluster numbers,
|
||||||
|
// and then terminate the loop becuase the FAT entry at `cluster` for these
|
||||||
|
// values does not represent the next step in the chain (because there is
|
||||||
|
// nothing else to read).
|
||||||
|
if (cluster <= 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
u32 fat_offset = cluster * sizeof(u32);
|
u32 fat_offset = cluster * sizeof(u32);
|
||||||
u32 fat_sector_index = fs().boot_record()->reserved_sector_count + (fat_offset / fs().m_device_block_size);
|
u32 fat_sector_index = fs().m_parameter_block->common_bpb()->reserved_sector_count + (fat_offset / fs().m_device_block_size);
|
||||||
u32 entry_offset = fat_offset % fs().m_device_block_size;
|
u32 entry_offset = fat_offset % fs().m_device_block_size;
|
||||||
|
|
||||||
TRY(fs().raw_read(fat_sector_index, fat_sector_buffer));
|
TRY(fs().raw_read(fat_sector_index, fat_sector_buffer));
|
||||||
|
@ -126,6 +142,15 @@ ErrorOr<RefPtr<FATInode>> FATInode::traverse(Function<ErrorOr<bool>(RefPtr<FATIn
|
||||||
} else if (entry->attributes == FATAttributes::LongFileName) {
|
} else if (entry->attributes == FATAttributes::LongFileName) {
|
||||||
dbgln_if(FAT_DEBUG, "FATFS: Found LFN entry");
|
dbgln_if(FAT_DEBUG, "FATFS: Found LFN entry");
|
||||||
TRY(lfn_entries.try_append(*reinterpret_cast<FATLongFileNameEntry*>(entry)));
|
TRY(lfn_entries.try_append(*reinterpret_cast<FATLongFileNameEntry*>(entry)));
|
||||||
|
} else if ((entry->first_cluster_high << 16 | entry->first_cluster_low) <= 1 && entry->file_size > 0) {
|
||||||
|
// Because clusters 0 and 1 are reserved, only empty files (size == 0 files)
|
||||||
|
// should specify these clusters.
|
||||||
|
// This driver uses a cluster number == 0 to represent the root directory inode
|
||||||
|
// on FAT12/16 file systems (a signal to look in the root directory region),
|
||||||
|
// so we ensure that no entries read off the file system have a cluster number
|
||||||
|
// that would also point to this region.
|
||||||
|
dbgln_if(FAT_DEBUG, "FATFS: Invalid cluster for entry");
|
||||||
|
return EINVAL;
|
||||||
} else {
|
} else {
|
||||||
dbgln_if(FAT_DEBUG, "FATFS: Found 8.3 entry");
|
dbgln_if(FAT_DEBUG, "FATFS: Found 8.3 entry");
|
||||||
lfn_entries.reverse();
|
lfn_entries.reverse();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue