From 964893b14ac7d871ca7192163f591c3ab2174035 Mon Sep 17 00:00:00 2001 From: Edgaru089 Date: Thu, 4 Nov 2021 18:05:19 +0800 Subject: [PATCH] filesystem/fat: WIP --- driver/filesystem/fat/fat.cpp | 84 ++++++++++++ driver/filesystem/fat/fat.hpp | 183 ++++++++++++++++++++++++++ driver/filesystem/fat/fat12_fat16.txt | 113 ++++++++++++++++ driver/filesystem/filesystem.hpp | 23 +++- memory/memory.hpp | 5 + 5 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 driver/filesystem/fat/fat.cpp create mode 100644 driver/filesystem/fat/fat.hpp create mode 100644 driver/filesystem/fat/fat12_fat16.txt diff --git a/driver/filesystem/fat/fat.cpp b/driver/filesystem/fat/fat.cpp new file mode 100644 index 0000000..ca984c3 --- /dev/null +++ b/driver/filesystem/fat/fat.cpp @@ -0,0 +1,84 @@ + +#include "fat.hpp" +#include + +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 diff --git a/driver/filesystem/fat/fat.hpp b/driver/filesystem/fat/fat.hpp new file mode 100644 index 0000000..14b5c24 --- /dev/null +++ b/driver/filesystem/fat/fat.hpp @@ -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 diff --git a/driver/filesystem/fat/fat12_fat16.txt b/driver/filesystem/fat/fat12_fat16.txt new file mode 100644 index 0000000..b4f09a1 --- /dev/null +++ b/driver/filesystem/fat/fat12_fat16.txt @@ -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 :-( diff --git a/driver/filesystem/filesystem.hpp b/driver/filesystem/filesystem.hpp index 3067a92..7cb814b 100644 --- a/driver/filesystem/filesystem.hpp +++ b/driver/filesystem/filesystem.hpp @@ -1,14 +1,19 @@ #pragma once #include "../../cppruntime/runtime.hpp" +#include "../block/blockdevice.hpp" 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 "/". // Functions return negative numbers on error. +// +// This class does not facilitate utilties to format, check, or resize filesystems. class Filesystem { public: 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. 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. @@ -86,6 +93,8 @@ public: unsigned int padding : 31; 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(). uint64_t handle; }; @@ -116,13 +125,20 @@ public: 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. // Beginning with "/" means a local file source (device or image). // Beginning with a "//" means a network target (URL). // 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: // Filesystem implementation capabilitiy bits @@ -256,4 +272,5 @@ public: }; +} // namespace filesystem } // namespace helos diff --git a/memory/memory.hpp b/memory/memory.hpp index de5fb5b..7e1cd13 100644 --- a/memory/memory.hpp +++ b/memory/memory.hpp @@ -51,6 +51,9 @@ bool operator!=(const kAllocator &, const kAllocator &) { } // namespace helos +// overload new/delete only in the real kernel, not in testing +#ifdef HELOS + // globally overload the new and delete operators // 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; #endif + +#endif // HELOS