filesystem/fat: WIP

This commit is contained in:
Edgaru089 2021-11-04 18:05:19 +08:00
parent 7158c3535e
commit 964893b14a
5 changed files with 405 additions and 3 deletions

View File

@ -0,0 +1,84 @@
#include "fat.hpp"
#include <cstring>
namespace helos {
namespace filesystem {
FAT::~FAT() {
if (sector)
delete[](char *) sector;
if (bpb)
delete bpb;
// either ebr or ebr32
if (ebr) {
if (type == FAT32)
delete ebr32;
else
delete ebr;
}
}
Filesystem *FAT::AllocateBlock(BlockDevice *block, FAT::Config *config) {
FAT *fat = new FAT;
fat->sector = new char[512];
// read the first sector
uint64_t read = block->ReadBlock(0, fat->sector, 1);
if (read == 0) {
delete fat;
return nullptr;
}
// allocate the BPB
fat->bpb = new BIOSParamBlock;
memcpy(fat->bpb, fat->sector, sizeof(BIOSParamBlock));
fat->numSectors = ((fat->bpb->numSectors_short != 0) ? fat->bpb->numSectors_short : fat->bpb->numSectors_large);
// tell the filesystem FAT type
if (fat->bpb->numBytesPerSector == 0) { // exFAT
fat->type = exFAT;
delete fat;
return nullptr; // TODO exFAT is not supported
} else if (fat->bpb->numSectorsPerFAT_12_16 == 0) { // FAT32
fat->type = FAT32;
fat->ebr32 = new ExtBootRecord_32;
memcpy(fat->ebr32, (char *)fat->sector + sizeof(BIOSParamBlock), sizeof(ExtBootRecord_32));
} else { // FAT12 or 16
fat->ebr = new ExtBootRecord_12_16;
memcpy(fat->ebr, (char *)fat->sector + sizeof(BIOSParamBlock), sizeof(ExtBootRecord_12_16));
// Let's tell FAT12 from FAT16
// see fat12_fat16.txt
// "For disks containing more than 4085 clusters (note that
// 4085 is the correct number), a 16-bit FAT entry is used."
//
// Let's first try the name
if (strncmp(fat->ebr->type, "FAT12", 5) == 0)
fat->type = FAT12;
else if (strncmp(fat->ebr->type, "FAT16", 5) == 0)
fat->type = FAT16;
// Try the number of clusters
else {
int clusters = fat->numSectors / fat->bpb->numSectorsPerCluster;
if (fat->numSectors % fat->bpb->numSectorsPerCluster != 0)
clusters++; // round up
if (clusters > 4085)
fat->type = FAT16;
else
fat->type = FAT12;
}
}
// read the FAT
return fat;
}
} // namespace filesystem
} // namespace helos

View File

@ -0,0 +1,183 @@
#pragma once
#include "../../../cppruntime/runtime.hpp"
#include "../filesystem.hpp"
#include "../../../main.h"
namespace helos {
namespace filesystem {
// FAT is a FAT12/16/32 driver.
class FAT: public Filesystem {
private:
// Type of the FAT filesystem (FAT12, FAT16 or FAT32)
enum FATType {
FAT12,
FAT16,
FAT32,
exFAT,
};
// The 1st sector of the FAT filesystem, BIOS Parameter Block and Extended Boot Record
// Same for FAT12/16 and FAT32.
struct BIOSParamBlock {
char jumpcode[3]; // EB XX 90, a jmp:short XX; nop command
char oemid[8]; // OEM identifier, can be any string
uint16_t numBytesPerSector; // number of bytes per sector (512)
uint8_t numSectorsPerCluster; // number of sectors per cluster (8, 512*8=4096)
uint16_t numReservedSector; // number of reserved sectors, including the BPB
uint8_t numFATs; // number of FATs, often 2
uint16_t numDirEntry; // number of directory entries
uint16_t numSectors_short; // total sectors in the volume, 0 if the value is too large to fit and in sectorCount_large;
uint8_t mediaDescriptorType; // Media Descriptor Type
uint16_t numSectorsPerFAT_12_16; // number of sectors per FAT, set only for FAT12/16
uint16_t numSectorsPerTrack; // number of sectors per track
uint16_t numSides; // number of heads or sides of the media
uint32_t offsetLBA; // number of hidden sectors (i.e., the LBA of the beginning of the partition.)
uint32_t numSectors_large; // large sector count, set if sectorCount_short is 0
} PACKED;
static_assert(sizeof(BIOSParamBlock) == 36, "BIOS Parameter Block struct not packed");
// Extended Boot Record for FAT12/16, comes right after BPB
struct ExtBootRecord_12_16 {
uint8_t driveNumber; // drive number, i.e., 0x00 for floppy and 0x80 for harddisk. Useless.
uint8_t ntFlags; // flags in Windows NT, reserved and zero.
uint8_t signature; // signature, must be 0x28 or 0x29.
uint32_t volumeID; // volume ID (partition UUID)
char label[11]; // label, padded with spaces
char type[8]; // system identifier string, containing the FAT filesystem type. Not to be trusted.
} PACKED;
static_assert(sizeof(ExtBootRecord_12_16) == 62 - 36, "ExtBootRecord_12_16 not packed");
// Extended Boot Record for FAT32, comes right after BPB
struct ExtBootRecord_32 {
uint32_t sectorsPerFAT; // number of sectors in per FAT
uint16_t flags; // flags
uint16_t version; // FAT version number. High byte is the major version and low byte is the minor. Can be zero.
uint32_t clusterRoot; // the cluster number (offset) of the root directory. Often 2.
uint16_t sectorFSinfo; // the sector number of the FSInfo structure
uint16_t sectorBackupBootsect; // the sector number of the backup boot sector
char reserved[12]; // reserved. Should be set to zero on format.
uint8_t driveNumber; // drive number, i.e., 0x00 for floppy and 0x80 for harddisk. Useless.
uint8_t ntFlags; // flags in Windows NT, reserved and zero.
uint8_t signature; // signature, must be 0x28 or 0x29.
uint32_t volumeID; // volume ID (partition UUID)
char label[11]; // label, padded with spaces
char type[8]; // system identifier string, always "FAT32 " with 3 spaces. Not to be trusted.
} PACKED;
static_assert(sizeof(ExtBootRecord_32) == 90 - 36, "ExtBootRecord_36 not packed");
static constexpr int FSInfo_Offset = 484;
// FSInfo structure for FAT32, starting at byte offset 484
struct FSInfo {
uint32_t signatureMid; // = 0x61417272
uint32_t freeClusterCount; // last known free cluster count, might be incorrect. 0xFFFFFFFF means the value is unknown and must be computed.
uint32_t freeClusterOffset; // the cluster number at which the driver should start looking for free clusters. 0xFFFFFFFF means there is no hint and the driver should start at 2. Also might be incorrect.
char reserved[12]; // reserved
uint32_t signatureTrail; // = 0xAA550000
} PACKED;
static_assert(sizeof(FSInfo) == 512 - 484, "FSInfo_off484 not packed");
static constexpr uint32_t
FSInfo_SignatureLead = 0x41615252, // Lead signature at the beginning of the FSInfo sector
FSInto_SignatureMid = 0x61417272, // Signature at offset 0x1e4
FSInfo_SignatureTrail = 0xaa550000; // Trail signature at the end of the sector
// Time in a Directory Entry
struct DirEntry_Time {
uint16_t hours : 5; // Hours in wall time.
uint16_t minutes : 6; // Minutes in wall time.
uint16_t seconds_div2 : 5; // Seconds in wall time divided by 2 (sec = div2*2 + 10th/100)
};
// Date in a Directory Entry
struct DirEntry_Date {
uint16_t year_1980 : 7; // Calendar year, offset since 1980
uint16_t month : 4; // Calendar month, 1-12
uint16_t day : 5; // Calendar day, 1-31
};
// Directory Entry
struct DirEntry {
char name[8], ext[3]; // File name and extension (standard 8.3 format)
uint8_t flags; // Flags of the file.
char reserved; // Reserved by Windows NT.
uint8_t createTime_Seconds_10th; // Tenths of a second, range 0-199 inclusive.
DirEntry_Time createTime; // Create time
DirEntry_Date createDate; // Create time
DirEntry_Date lastAccessDate; // Last access date. there are no last-access time.
uint16_t firstCluster_high; // High 16 bits of the first cluster number.
DirEntry_Time modifyTime; // Last modify time
DirEntry_Date modifyDate; // Last modify date
uint16_t firstCluster_low; // Low 16 bits of the first cluster number.
uint32_t size; // Size of the file in bytes
} PACKED;
static_assert(sizeof(DirEntry) == 32, "DirEntry not packed");
static constexpr uint8_t
DirEntry_Flags_ReadOnly = 0x01, // Read-Only
DirEntry_Flags_ReadOnly_Hidden = 0x02, // Hidden
DirEntry_Flags_ReadOnly_System = 0x04, // System
DirEntry_Flags_ReadOnly_VolumeID = 0x08, // A Volume-ID entry, only in the root folder
DirEntry_Flags_ReadOnly_Directory = 0x10, // Directory
DirEntry_Flags_ReadOnly_Archive = 0x20, // Archive
DirEntry_Flags_ReadOnly_LongFileName = 0x0f; // Part of a Long File Name entry
public:
~FAT();
// AllocateBlock allocates a new FAT driver from a block device.
//
// It only supports 512-byte-block devices.
virtual Filesystem *AllocateBlock(BlockDevice *block, Config *config) override;
public:
// Opendir opens a new directory.
//
// It places the cluster number of the directory into file->handle.
virtual int Opendir(const char *path, OpenFile *file) override;
// Readdir reads a whole directory in one go, calling the callback on each element.
virtual int Readdir(const char *path, void *user, Readdir_Callback callback, OpenFile *file) override;
// Closedir closes a open directory.
//
// Currently it does nothing.
virtual int Closedir(const char *path, OpenFile *file) override;
private:
// returns the next cluster number from this one, or 0 if end-of-chain
int __NextCluster(int cluster);
// reads a directory from a cluster number
virtual int __Readdir(int cluster, void *user, Readdir_Callback callback);
private:
FATType type;
// A copy of the BPB and EBR
BIOSParamBlock *bpb;
union {
ExtBootRecord_12_16 *ebr;
ExtBootRecord_32 * ebr32;
};
// A copy of the entire FAT
// TODO change this! this takes about 1/128 of the disk size in memory (in FAT32)
void *fat_table;
void *sector; // A buffer at the size of a sector. Allocated on creation
int numSectors; // Number of sectors in the entire partition.
};
} // namespace filesystem
} // namespace helos

View File

@ -0,0 +1,113 @@
Re: FAT12 vs FAT16
from the Linux kernel mailing list
Martin Mares (mj@atrey.karlin.mff.cuni.cz)
Fri, 22 Aug 1997 13:10:56 +0200
http://lkml.iu.edu/hypermail/linux/kernel/9708.2/0498.html
There are the following ways to determine FAT type:
(1) "FAT12" or "FAT16" string in the boot block. Not reliable and not supported
in all DOS versions.
(2) "Sector/cluster count rules" -- described for example in the Tech Help.
Varies between DOS versions. Even if you know the DOS version, you can still
be wrong as the disk might have been formatted on a different version.
(3) Partition type. Seems to be totally ignored by DOS and sometimes very
unreliable as DOS's format command doesn't alter this during reformatting
(if you create the partition by one DOS version and format it by another one,
an inconsistency might arise).
(4) Number of sectors per FAT -- this value must be correct as the root
directory position is calculated from it, but it sometimes allows both
possibilities (although such cases are very rare).
(5) Number of 0xff's at the start of the FAT. As you can remember, the first
two cluster numbers are reserved for special purposes and their FAT entries
are set to 0xff with an exception of the first FAT byte which contains a media
descriptor. If there are less than 3 0xff's, the FAT must be 12-bit. In other
case, both types are possible.
Richard B. Johnson (root@analogic.com)
Fri, 22 Aug 1997 15:53:45 -0400 (EDT)
http://lkml.iu.edu/hypermail/linux/kernel/9708.2/0527.html
No No No No! Page 3-7 of the MS-DOS Technical information (the real
system integrator's manual) states:
"For disks containing more than 4085 clusters (note that
4085 is the correct number), a 16-bit FAT entry is used."
The number of clusters is calculated from the BPB in the boot record from
the BYTE Custer size and the rest of the information about the size
of the media. The number of sectors entry is the total of the media.
You perform the indicated operations which even provides for the
reserved sectors (like for the boot record), and come up with the
number of clusters.
----- (some code resembling the BIOSParamBlock struct) -----
This will always work. It must work. This is how MS-DOS knows the size
of the media.
I used to get paid for making strange media work on MS-DOS. This will
always work. The media descriptor doesn't mean anything any more nor
does the OEM Name, etc. However the other information is updated in
this table when the disk is formatted. The boot record loading code
with a 360 kb floppy boot-record BPB is used by FORMAT, etc. It is
modified before being written to the physical media. This same boot
record is used for all MS-DOS media and is modified before being written.
You can copy the boot record from a 1.2 mb floppy to a 1.44 mb floppy
and you will find that MS-DOS thinks it is now a 1.2 mb floppy. You
can use format/u to reformat it "as-is", and it is now a 1.2 mb floppy!
Martin Mares (mj@atrey.karlin.mff.cuni.cz)
Fri, 22 Aug 1997 23:36:57 +0200
http://lkml.iu.edu/hypermail/linux/kernel/9708.2/0537.html
This is correct, but not sufficient for FAT type detection as you can
have a disk with _less_ than 4085 clusters and 16-bit FAT.
Have a nice fortnight
Richard B. Johnson (root@analogic.com)
Fri, 22 Aug 1997 18:12:30 -0400 (EDT)
http://lkml.iu.edu/hypermail/linux/kernel/9708.2/0542.html
Not if it's a disk supported by MS-DOS or created using MS-DOS tools.
MS-DOS format will not (read cannot) make such a disk. Even with its
media type/size/heads options, it doesn't have the code necessary to
produce a disk under any other rules because it calculates the
clusters not you, and it uses the 4085 cluster rule for writing the FAT.
Just because, in principle I can make a disk with one cluster and a 16-bit
FAT, does not mean that it is a MS-DOS disk. MS-DOS will not understand
such a disk so no other OS should bother with such a deviation either.
H. Peter Anvin (hpa@transmeta.com)
23 Aug 1997 00:04:49 GMT
http://lkml.iu.edu/hypermail/linux/kernel/9708.2/0549.html
I once came across a DOS floppy formatted with FAT16; I think some (OEM?)
version of DOS 2.x or 3.x always formatted FAT16. Surprised the
living daylight out of me...
Martin Mares (mj@atrey.karlin.mff.cuni.cz)
Sat, 23 Aug 1997 11:49:03 +0200
http://lkml.iu.edu/hypermail/linux/kernel/9708.2/0567.html
Unfortunately, the rules are not the same for different DOS versions :-(

View File

@ -1,14 +1,19 @@
#pragma once #pragma once
#include "../../cppruntime/runtime.hpp" #include "../../cppruntime/runtime.hpp"
#include "../block/blockdevice.hpp"
namespace helos { namespace helos {
namespace filesystem {
// Filesystems are created per-mount.
// Filesystems are created per-mount, or on filesystem register only for the Allocate functions.
// //
// Paths always begin with "/". // Paths always begin with "/".
// Functions return negative numbers on error. // Functions return negative numbers on error.
//
// This class does not facilitate utilties to format, check, or resize filesystems.
class Filesystem { class Filesystem {
public: public:
typedef uint16_t uid_t, gid_t; // User and group ID types typedef uint16_t uid_t, gid_t; // User and group ID types
@ -74,6 +79,8 @@ public:
bool setfmask, setdmask; // If true, file/directory permissions are masked with the given values. bool setfmask, setdmask; // If true, file/directory permissions are masked with the given values.
mode_t fmask, dmask; // (Corresponding bits cleared) mode_t fmask, dmask; // (Corresponding bits cleared)
bool readonly; // If true, the filesystem should be opened read-only, and no writes should be done.
}; };
// Information on an open file. // Information on an open file.
@ -86,6 +93,8 @@ public:
unsigned int padding : 31; unsigned int padding : 31;
unsigned int padding2 : 32; unsigned int padding2 : 32;
int errno; // errno of the last operation, only need to be set for Read()/Write()
// File handle. Filled in by Create(), Open(), Opendir(). // File handle. Filled in by Create(), Open(), Opendir().
uint64_t handle; uint64_t handle;
}; };
@ -116,13 +125,20 @@ public:
uint64_t TimeAccess, TimeModification; // UNIX time of last access/modification uint64_t TimeAccess, TimeModification; // UNIX time of last access/modification
}; };
// New is called to create a new Filesystem instance from a given source. // Allocate is called to create a new Filesystem instance from a given source.
// //
// The source paramater is quite special. // The source paramater is quite special.
// Beginning with "/" means a local file source (device or image). // Beginning with "/" means a local file source (device or image).
// Beginning with a "//" means a network target (URL). // Beginning with a "//" means a network target (URL).
// Otherwise, this string is non-canonical and its behavior is implementation-depedent. // Otherwise, this string is non-canonical and its behavior is implementation-depedent.
typedef Filesystem *(*New)(const char *source, Config *config); //
// On error, NULL is returned.
virtual Filesystem *Allocate(const char *source, Config *config) { return nullptr; }
// AllocateBlock is called to create a new Filesystem instance from a Block Device.
//
// On error, NULL is returned.
virtual Filesystem *AllocateBlock(BlockDevice *block, Config *config) { return nullptr; }
public: public:
// Filesystem implementation capabilitiy bits // Filesystem implementation capabilitiy bits
@ -256,4 +272,5 @@ public:
}; };
} // namespace filesystem
} // namespace helos } // namespace helos

View File

@ -51,6 +51,9 @@ bool operator!=(const kAllocator<T> &, const kAllocator<U> &) {
} // namespace helos } // namespace helos
// overload new/delete only in the real kernel, not in testing
#ifdef HELOS
// globally overload the new and delete operators // globally overload the new and delete operators
// so keep this header at the top of every source file // so keep this header at the top of every source file
// //
@ -76,3 +79,5 @@ void operator delete[](void *ptr, std::align_val_t align) noexcept;
void operator delete(void *ptr, std::size_t size, std::align_val_t align) noexcept; void operator delete(void *ptr, std::size_t size, std::align_val_t align) noexcept;
void operator delete[](void *ptr, std::size_t size, std::align_val_t align) noexcept; void operator delete[](void *ptr, std::size_t size, std::align_val_t align) noexcept;
#endif #endif
#endif // HELOS