diff --git a/.gitignore b/.gitignore index 0a52be1..6bf18e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea cmake-build-debug -.qemu.pid \ No newline at end of file +.qemu.pid +disk.img \ No newline at end of file diff --git a/README.md b/README.md index af3488a..9a8dbd6 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ practices while remaining approachable to contributors and learners alike. ### Storage -- [ ] ATA driver -- [ ] Block device layer -- [ ] VFS +- [x] ATA driver +- [x] Block device layer +- [x] VFS - [ ] FAT32 - [ ] ext2 - [ ] Read & Write files diff --git a/include/drivers/ata.h b/include/drivers/ata.h new file mode 100644 index 0000000..cdea2d1 --- /dev/null +++ b/include/drivers/ata.h @@ -0,0 +1,44 @@ +/* +* ata.h +* As part of the Avery project +* Created by Max Van den Eynde in 2026 +* -------------------------------------- +* Description: ATA device manager +* Copyright (c) 2026 Max Van den Eynde +*/ + +#ifndef AVERY_ATA_H +#define AVERY_ATA_H +#include "driver.h" +#include "pci.h" + +class ATADiskDevice final : public BlockDevice { +public: + ATADiskDevice(PCIDevice* controller, u16 ioBase, u16 ctrlBase, u8 drive, u64 sectors); + bool readBlocks(u64 lba, u32 count, void* buffer) override; + bool writeBlocks(u64 lba, u32 count, const void* buffer) override; + +private: + bool access(bool write, u64 lba, u32 count, void* buffer); + + u16 ioBase; + u8 drive; +}; + +class ATADriver final : public Driver { +public: + string name() const override { + return "ATA Driver - Avery"; + } + + bool probe(Device& device) override; + bool start(Device& device) override; + bool stop(Device& device) override; +}; + +namespace ata { + static ATADriver* driver = nullptr; + void registerDriver(); +} + +#endif //AVERY_ATA_H diff --git a/include/drivers/driver.h b/include/drivers/driver.h index 3b20a5d..202356d 100644 --- a/include/drivers/driver.h +++ b/include/drivers/driver.h @@ -124,7 +124,6 @@ class DriverManager { static bool tryBind(Driver& driver); static void unbind(Device& device); -private: static constexpr usize MaxDrivers = 128; static Driver* drivers[MaxDrivers]; static usize driverCount; @@ -132,6 +131,7 @@ class DriverManager { namespace drivers { void init(); + BlockDevice* firstBlockDevice(); } namespace mmio { diff --git a/include/fs/fat32.h b/include/fs/fat32.h new file mode 100644 index 0000000..d73cf9b --- /dev/null +++ b/include/fs/fat32.h @@ -0,0 +1,317 @@ +/* +* fat32.h +* As part of the Avery project +* Created by Max Van den Eynde in 2026 +* -------------------------------------- +* Description: FAT32 systems +* Copyright (c) 2026 Max Van den Eynde +*/ + +#ifndef AVERY_FAT32_H +#define AVERY_FAT32_H +#include "vfs.h" +#include "../types.h" + +namespace fat32 { + constexpr u32 EndOfChain = 0x0FFFFFF8; + constexpr u32 BadCluster = 0x0FFFFFF7; + constexpr u32 FreeCluster = 0x00000000; + + constexpr u8 AttributeReadOnly = 0x01; + constexpr u8 AttributeHidden = 0x02; + constexpr u8 AttributeSystem = 0x04; + constexpr u8 AttributeVolumeID = 0x08; + constexpr u8 AttributeDirectory = 0x10; + constexpr u8 AttributeArchive = 0x20; + constexpr u8 AttributeLongName = 0x0F; + + constexpr u8 EntryDeleted = 0xE5; + + struct BIOSParameterBlock { + u8 jump[3]; + char oemName[8]; + + u16 bytesPerSector; + u8 sectorsPerCluster; + u16 reservedSectorCount; + u8 tableCount; + u16 rootEntryCount; + u16 totalSectors16; + u8 mediaType; + u16 tableSize16; + u16 sectorsPerTrack; + u16 headSideCount; + u32 hiddenSectorCount; + u32 totalSectors32; + + u32 tableSize32; + u16 extendedFlags; + u16 fatVersion; + u32 rootCluster; + u16 fsInfoSector; + u16 backupBootSector; + + u8 reserved0[12]; + u8 driveNumber; + u8 reserved1; + u8 bootSignature; + u32 volumeID; + char volumeLabel[11]; + char fatTypeLabel[8]; + } __attribute__((__packed__)); + + struct FSInfo { + u32 leadSignature; + u8 reserved0[480]; + u32 structureSignature; + u32 freeClusterCount; + u32 nextFreeCluster; + u8 reserved1[12]; + u32 trailSignature; + } __attribute__((packed)); + + struct DirectoryEntryRaw { + char name[11]; + u8 attributes; + u8 ntReserved; + u8 creationTimeTenths; + u16 creationTime; + u16 creationDate; + u16 lastAccessDate; + u16 firstClusterHigh; + u16 writeTime; + u16 writeDate; + u16 firstClusterLow; + u32 fileSize; + } __attribute__((packed)); + + struct LongDirectoryEntryRaw { + u8 order; + u16 name1[5]; + u8 attributes; + u8 type; + u8 checksum; + u16 name2[6]; + u16 firstClusterLow; + u16 name3[2]; + } __attribute__((packed)); + + struct DirectoryEntryInfo { + string name; + u8 attributes = 0; + + u32 firstCluster = 0; + u32 size = 0; + + u32 parentDirectoryCluster = 0; + u32 directoryEntryCluster = 0; + u32 directoryEntryOffset = 0; + u32 directoryEntryIndex = 0; + + bool dirty = false; + + bool isDirectory() const { + return attributes & AttributeDirectory; + } + + bool isFile() const { + return !isDirectory(); + } + }; +} + +class FAT32FileSystem; + +class FAT32VNode final : public VNode { +public: + FAT32VNode( + string name, + VFSNodeType type, + FAT32FileSystem* fs, + u32 firstCluster, + u64 size + ); + + bool stat(VFSStat& outStat) override; + + u32 firstCluster() const { return nodeFirstCluster; } + u64 size() const { return nodeSize; } + + void setFirstCluster(u32 firstCluster); + void setSize(u64 size); + + bool hasDirectoryEntry() const { return hasEntryInfo; } + const fat32::DirectoryEntryInfo& directoryEntry() const { return entryInfo; } + void setDirectoryEntry(const fat32::DirectoryEntryInfo& entry); + +private: + u32 nodeFirstCluster; + u64 nodeSize; + fat32::DirectoryEntryInfo entryInfo; + bool hasEntryInfo = false; +}; + +class FAT32FileHandle final : public FileHandle { +public: + FAT32FileHandle(FAT32VNode* node, VFSOpenMode mode); + + usize read(void* buffer, usize size) override; + usize write(const void* buffer, usize size) override; + + bool seek(u64 offset) override; + bool truncate(u64 size) override; + + u64 tell() const override; + u64 size() const override; + + bool flush() override; + bool close() override; + + bool dirty = false; + +private: + FAT32VNode* fatNode; + u64 offset = 0; + bool closed = false; +}; + +class FAT32DirectoryHandle final : public DirectoryHandle { +public: + FAT32DirectoryHandle( + FAT32FileSystem* fs, + Vector entries + ); + + bool readNext(DirectoryEntry& entry) override; + bool rewind() override; + bool close() override; + +private: + Vector entries; + usize index = 0; + bool closed = false; +}; + +class FAT32FileSystem final : public FileSystem { +public: + FAT32FileSystem() = default; + ~FAT32FileSystem() override = default; + + string name() const override { + return "FAT32"; + } + + bool mount(BlockDevice* device) override; + bool unmount() override; + + bool isMounted() const override { + return mounted; + } + + bool isReadOnly() const override { + return readOnly; + } + + VNode* rootNode() override; + + FileHandle* open(const string& path, VFSOpenMode mode, VFSError& error) override; + DirectoryHandle* openDirectory(const string& path, VFSError& error) override; + + bool stat(const string& path, VFSStat& outStat, VFSError& error) override; + + bool createFile(const string& path, VFSError& error) override; + bool createDirectory(const string& path, VFSError& error) override; + + bool remove(const string& path, VFSError& error) override; + bool rename(const string& oldPath, const string& newPath, VFSError& error) override; + + BlockDevice* device() const { + return backingDevice; + } + + u32 rootCluster() const { return bpb.rootCluster; } + u32 bytesPerSector() const { return bpb.bytesPerSector; } + u32 sectorsPerCluster() const { return bpb.sectorsPerCluster; } + u32 clusterSize() const { return bpb.bytesPerSector * bpb.sectorsPerCluster; } + + u32 firstDataSector() const { return fatFirstDataSector; } + u32 firstFATSector() const { return fatFirstSector; } + + u64 clusterToSector(u32 cluster) const; + bool readCluster(u32 cluster, void* buffer); + bool writeCluster(u32 cluster, const void* buffer); + + bool readFATEntry(u32 cluster, u32& nextCluster); + bool writeFATEntry(u32 cluster, u32 value); + + bool readDirectory(u32 firstCluster, Vector& entries); + bool findEntry(const string& path, fat32::DirectoryEntryInfo& entry); + + bool allocateCluster(u32& outCluster); + bool freeClusterChain(u32 firstCluster); + bool extendClusterChain(u32 startCluster, u32& newCluster); + + bool zeroCluster(u32 cluster); + + bool getClusterForOffset( + u32 firstCluster, + u64 offset, + bool allocateIfMissing, + u32& outCluster + ); + + bool updateDirectoryEntry(const fat32::DirectoryEntryInfo& entry); + bool writeDirectoryEntry( + u32 directoryCluster, + u32 entryOffset, + const fat32::DirectoryEntryRaw& raw + ); + + bool createDirectoryEntry( + u32 parentDirectoryCluster, + const string& name, + u8 attributes, + fat32::DirectoryEntryInfo& outEntry + ); + + bool findFreeDirectoryEntry( + u32 directoryCluster, + u32& outEntryCluster, + u32& outEntryOffset + ); + + bool syncFSInfo(); + +private: + bool readBootSector(); + bool validateBootSector() const; + + bool readFSInfo(); + bool loadRootNode(); + + bool readSectors(u64 sector, u32 count, void* buffer) const; + bool writeSectors(u64 sector, u32 count, const void* buffer) const; + + Vector splitPath(const string& path) const; + +private: + BlockDevice* backingDevice = nullptr; + + bool mounted = false; + bool readOnly = true; + + fat32::BIOSParameterBlock bpb{}; + fat32::FSInfo fsInfo{}; + + u32 fatFirstSector = 0; + u32 fatFirstDataSector = 0; + u32 totalSectors = 0; + u32 totalClusters = 0; + + u16 signature = 0; + + FAT32VNode* root = nullptr; +}; + + +#endif //AVERY_FAT32_H diff --git a/include/fs/vfs.h b/include/fs/vfs.h new file mode 100644 index 0000000..e015590 --- /dev/null +++ b/include/fs/vfs.h @@ -0,0 +1,221 @@ +/* +* vfs.h +* As part of the Avery project +* Created by Max Van den Eynde in 2026 +* -------------------------------------- +* Description: Virtual Filesystem defintions +* Copyright (c) 2026 Max Van den Eynde +*/ + +#ifndef AVERY_VFS_H +#define AVERY_VFS_H +#include "../types.h" +#include "../drivers/driver.h" +#include "kernel/debug.h" +#include "kernel/memory.h" + +enum class VFSNodeType { + Unknown, + File, + Directory, + Device, + Symlink +}; + +enum class VFSOpenMode : u32 { + Read = 1 << 0, + Write = 1 << 1, + Append = 1 << 2, + Create = 1 << 3, + Truncate = 1 << 4, +}; + +inline VFSOpenMode operator|(VFSOpenMode a, VFSOpenMode b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +struct VFSError { + enum Code { + None, + NotFound, + AlreadyExists, + NotDirectory, + IsDirectory, + InvalidPath, + PermissionDenied, + ReadOnly, + IOError, + Unsupported, + NoSpace, + }; + + Code code = None; + + [[nodiscard]] bool ok() const { + return code == None; + } +}; + +namespace vfs { + inline bool hasMode(VFSOpenMode mode, VFSOpenMode flag) { + return static_cast(mode) & static_cast(flag); + } +} + +struct VFSStat { + string name; + VFSNodeType type = VFSNodeType::Unknown; + + u64 size = 0; + u64 blockSize = 512; + u64 createdTime = 0; + u64 modifiedTime = 0; + u64 accessedTime = 0; +}; + +class FileSystem; +class VNode; +class FileHandle; + +class VNode { +public: + VNode(string name, VFSNodeType type, FileSystem* fs) : nodeName(memory::move(name)), nodeType(type), ownerFs(fs) { + } + + virtual ~VNode() = default; + + [[nodiscard]] string name() const { return nodeName; } + [[nodiscard]] VFSNodeType type() const { return nodeType; } + [[nodiscard]] FileSystem* fileSystem() const { return ownerFs; } + + virtual bool stat(VFSStat& outStat) = 0; + +protected: + string nodeName; + VFSNodeType nodeType; + FileSystem* ownerFs; +}; + +class FileHandle { +public: + FileHandle(VNode* node, VFSOpenMode mode) : handleNode(node), openMode(mode) { + } + + virtual ~FileHandle() = default; + + [[nodiscard]] VNode* node() const { return handleNode; } + [[nodiscard]] VFSOpenMode mode() const { return openMode; } + + virtual usize read(void* buffer, usize size) = 0; + virtual usize write(const void* buffer, usize size) = 0; + + virtual bool seek(u64 offset) = 0; + virtual bool truncate(u64 size) = 0; + + [[nodiscard]] virtual u64 tell() const = 0; + [[nodiscard]] virtual u64 size() const = 0; + + virtual bool flush() = 0; + virtual bool close() = 0; + +protected: + VNode* handleNode; + VFSOpenMode openMode; +}; + +struct DirectoryEntry { + string name; + VFSNodeType type = VFSNodeType::Unknown; + u64 size = 0; +}; + +class DirectoryHandle { +public: + virtual ~DirectoryHandle() = default; + + virtual bool readNext(DirectoryEntry& entry) = 0; + virtual bool rewind() = 0; + virtual bool close() = 0; +}; + +class FileSystem { +public: + virtual ~FileSystem() = default; + + [[nodiscard]] virtual string name() const = 0; + + virtual bool mount(BlockDevice* device) = 0; + virtual bool unmount() = 0; + + [[nodiscard]] virtual bool isMounted() const = 0; + [[nodiscard]] virtual bool isReadOnly() const = 0; + + virtual VNode* rootNode() = 0; + + virtual FileHandle* open(const string& path, VFSOpenMode mode, VFSError& error) = 0; + virtual DirectoryHandle* openDirectory(const string& path, VFSError& error) = 0; + + virtual bool stat(const string& path, VFSStat& outStat, VFSError& error) = 0; + + virtual bool createFile(const string& path, VFSError& error) = 0; + virtual bool createDirectory(const string& path, VFSError& error) = 0; + + virtual bool remove(const string& path, VFSError& error) = 0; + virtual bool rename(const string& oldPath, const string& newPath, VFSError& error) = 0; +}; + +struct MountPoint { + string path; + FileSystem* fs = nullptr; +}; + +namespace vfs { + bool init(); + + bool mount(const string& path, FileSystem* fs, BlockDevice* device); + bool unmount(const string& path); + + FileSystem* fileSystemForPath(const string& path); + string relativePathForMount(const string& path, const string& mountPath); + + FileHandle* open(const string& path, VFSOpenMode mode = VFSOpenMode::Read); + DirectoryHandle* openDirectory(const string& path); + + bool stat(const string& path, VFSStat& outStat); + + bool exists(const string& path); + bool isFile(const string& path); + bool isDirectory(const string& path); + + bool createFile(const string& path); + bool createDirectory(const string& path); + + bool remove(const string& path); + bool rename(const string& oldPath, const string& newPath); + + Vector mounts(); + + template + requires DerivedFrom + void mountRoot() { + init(); + + BlockDevice* device = drivers::firstBlockDevice(); + if (!device) { + debug::serialError("No disk is present!"); + return; + } + + auto* fs = new T(); + + if (!mount("/", fs, device)) { + debug::serialError("Could not mount root"); + return; + } + + debug::log("Mounted root device with the appropiate file system"); + } +} + + +#endif //AVERY_VFS_H diff --git a/include/io/io.h b/include/io/io.h index d63afe5..244c840 100644 --- a/include/io/io.h +++ b/include/io/io.h @@ -32,6 +32,10 @@ namespace io { return val; } + inline void wait() { + outb(0x80, 0); + } + inline void outl(u16 port, u32 value) { asm volatile( "outl %0, %1" @@ -40,6 +44,14 @@ namespace io { ); } + inline void outw(u16 port, u16 value) { + asm volatile( + "outw %0, %1" + : + : "a"(value), "Nd"(port) + ); + } + inline u32 inl(u16 port) { u32 val; @@ -51,5 +63,17 @@ namespace io { return val; } + + inline u16 inw(u16 port) { + u16 val; + + asm volatile( + "inw %1, %0" + : "=a"(val) + : "Nd"(port) + ); + + return val; + } } #endif //AVERY_IO_H diff --git a/include/kernel/console.h b/include/kernel/console.h index d3fa4df..964f08e 100644 --- a/include/kernel/console.h +++ b/include/kernel/console.h @@ -51,12 +51,7 @@ namespace out { template requires ByteNumber void print(T num) { - if constexpr (T(-1) < T(0)) { - printSigned(static_cast(num)); - } - else { - printNumber(static_cast(num)); - } + printNumber(static_cast(num)); } template diff --git a/include/kernel/debug.h b/include/kernel/debug.h index b6d7852..1de740d 100644 --- a/include/kernel/debug.h +++ b/include/kernel/debug.h @@ -101,12 +101,7 @@ namespace debug { template requires ByteNumber void writeValue(LogType logType, T value) { - if constexpr (T(-1) < T(0)) { - writeValue(logType, static_cast(value)); - } - else { - writeValue(logType, static_cast(value)); - } + writeValue(logType, static_cast(value)); } template diff --git a/include/types.h b/include/types.h index 74aeb2b..c0424aa 100644 --- a/include/types.h +++ b/include/types.h @@ -59,6 +59,9 @@ concept ByteNumber = requires(T a, T b) { a & b; }; +template +concept DerivedFrom = __is_base_of(Derived, Base); + template requires ByteNumber T alignUp(T x, T a) { @@ -138,21 +141,21 @@ class Option { if (!present) { debug::serialError("Tried to access an option that had a null value"); } - return storage; + return *storage; } const T& value() const { if (!present) { debug::serialError("Tried to access an option that had a null value"); } - return storage; + return *storage; } T valueOr(const T& fallback) const { if (!present) { return fallback; } - return storage; + return *storage; } static Option none() { @@ -179,6 +182,8 @@ class string { bool operator==(const string& other) const; bool operator!=(const string& other) const; + [[nodiscard]] bool startsWith(const string& other) const; + [[nodiscard]] cstring cStr() const; [[nodiscard]] usize length() const; [[nodiscard]] bool empty() const; @@ -186,8 +191,14 @@ class string { void clear(); void append(cstring text); void append(char c); + void prepend(cstring text); + void prepend(char c); char popBack(); + void removePrefix(cstring text); + void removeSuffix(cstring text); + void trim(); + Option operator[](usize index); Option operator[](usize index) const; @@ -337,8 +348,7 @@ class LinkedList { if (head == tail) { head = nullptr; tail = nullptr; - } - else { + } else { tail = tail->prev; tail->next = nullptr; } @@ -357,8 +367,7 @@ class LinkedList { if (head == tail) { head = nullptr; tail = nullptr; - } - else { + } else { head = head->next; head->prev = nullptr; } @@ -451,10 +460,65 @@ class Vector { cap = 0; } + Vector(const Vector& other) { + data = nullptr; + count = 0; + cap = 0; + + reserve(other.count); + + for (usize i = 0; i < other.count; i++) { + push(other.data[i]); + } + } + + Vector(Vector&& other) noexcept { + data = other.data; + count = other.count; + cap = other.cap; + + other.data = nullptr; + other.count = 0; + other.cap = 0; + } + ~Vector() { delete[] data; } + Vector& operator=(const Vector& other) { + if (this == &other) { + return *this; + } + + clear(); + reserve(other.count); + + for (usize i = 0; i < other.count; i++) { + push(other.data[i]); + } + + return *this; + } + + Vector& operator=(Vector&& other) noexcept { + if (this == &other) { + return *this; + } + + delete[] data; + + data = other.data; + count = other.count; + cap = other.cap; + + other.data = nullptr; + other.count = 0; + other.cap = 0; + + return *this; + } + void push(const T& value) { if (count >= cap) { grow(); @@ -481,6 +545,18 @@ class Vector { count--; } + void remove(usize index) { + if (index >= count) { + return; + } + + for (usize i = index; i + 1 < count; i++) { + data[i] = static_cast(data[i + 1]); + } + + count--; + } + Option operator[](usize index) { if (index >= count) { @@ -579,8 +655,7 @@ class Vector { void grow() { if (cap == 0) { reserve(4); - } - else { + } else { reserve(cap * 2); } } diff --git a/kernel/drivers/drivers.cpp b/kernel/drivers/drivers.cpp index 6342573..db64f5b 100644 --- a/kernel/drivers/drivers.cpp +++ b/kernel/drivers/drivers.cpp @@ -9,8 +9,10 @@ #include +#include "drivers/ata.h" #include "drivers/keyboard.h" #include "drivers/pit.h" +#include "kernel/debug.h" Device* DeviceManager::devices[MaxDevices] = {}; usize DeviceManager::s_deviceCount = 0; @@ -184,4 +186,21 @@ void drivers::init() { time::registerDevice(); keyboard::registerDriver(); keyboard::registerDevice(); + ata::registerDriver(); + + for (usize i = 0; i < DriverManager::driverCount; i++) { + auto* driver = DriverManager::drivers[i]; + debug::log("Registered driver ", i, ": ", driver->name()); + } +} + +BlockDevice* drivers::firstBlockDevice() { + for (usize i = 0; i < DeviceManager::deviceCount(); i++) { + auto* device = DeviceManager::deviceAt(i); + if (device && device->type() == DeviceType::Block) { + return reinterpret_cast(device); + } + } + + return nullptr; } diff --git a/kernel/drivers/input/keyboard.cpp b/kernel/drivers/input/keyboard.cpp index dbf497b..165634c 100644 --- a/kernel/drivers/input/keyboard.cpp +++ b/kernel/drivers/input/keyboard.cpp @@ -153,7 +153,7 @@ namespace { class KeyboardDriver final : public Driver { public: string name() const override { - return "ps2-keyboard"; + return "PS/2 Keyboard - Avery"; } bool probe(Device& device) override { diff --git a/kernel/drivers/storage/ata.cpp b/kernel/drivers/storage/ata.cpp new file mode 100644 index 0000000..2c0cf6c --- /dev/null +++ b/kernel/drivers/storage/ata.cpp @@ -0,0 +1,246 @@ +/* +* ata.cpp +* As part of the Avery project +* Created by Max Van den Eynde in 2026 +* -------------------------------------- +* Description: ATA implementation +* Copyright (c) 2026 Max Van den Eynde +*/ + +#include "drivers/ata.h" + +#include "types.h" +#include "io/io.h" +#include "kernel/debug.h" + +namespace { + constexpr u8 ATA_REG_DATA = 0x00; + constexpr u8 ATA_REG_SECCOUNT0 = 0x02; + constexpr u8 ATA_REG_LBA0 = 0x03; + constexpr u8 ATA_REG_LBA1 = 0x04; + constexpr u8 ATA_REG_LBA2 = 0x05; + constexpr u8 ATA_REG_HDDEVSEL = 0x06; + constexpr u8 ATA_REG_COMMAND = 0x07; + constexpr u8 ATA_REG_STATUS = 0x07; + + constexpr u8 ATA_SR_BSY = 0x80; + constexpr u8 ATA_SR_DF = 0x20; + constexpr u8 ATA_SR_DRQ = 0x08; + constexpr u8 ATA_SR_ERR = 0x01; + + constexpr u8 ATA_CMD_READ_PIO = 0x20; + constexpr u8 ATA_CMD_WRITE_PIO = 0x30; + constexpr u8 ATA_CMD_CACHE_FLUSH = 0xE7; + constexpr u8 ATA_CMD_IDENTIFY = 0xEC; + + bool waitBSY(u16 ioBase) { + for (usize i = 0; i < 100000; i++) { + if (!(io::inb(ioBase + ATA_REG_STATUS) & ATA_SR_BSY)) return true; + } + return false; + } + + bool waitDRQ(u16 ioBase) { + for (usize i = 0; i < 100000; i++) { + u8 status = io::inb(ioBase + ATA_REG_STATUS); + + if (status & (ATA_SR_ERR | ATA_SR_DF)) { + return false; + } + + if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) return true; + } + return false; + } + + void selectDrive(u16 ioBase, u8 drive, u64 lba) { + io::outb(ioBase + ATA_REG_HDDEVSEL, + static_cast(0xE0 | static_cast((drive & 1) << 4) | ((lba >> 24) & 0x0F))); + io::wait(); + } + + bool identifyDrive(u16 ioBase, u8 drive, u16* identify) { + selectDrive(ioBase, drive, 0); + + io::outb(ioBase + ATA_REG_SECCOUNT0, 0); + io::outb(ioBase + ATA_REG_LBA0, 0); + io::outb(ioBase + ATA_REG_LBA1, 0); + io::outb(ioBase + ATA_REG_LBA2, 0); + io::outb(ioBase + ATA_REG_COMMAND, ATA_CMD_IDENTIFY); + + io::wait(); + + if (io::inb(ioBase + ATA_REG_STATUS) == 0) { + return false; + } + + if (!waitDRQ(ioBase)) { + return false; + } + + for (usize i = 0; i < 256; i++) { + identify[i] = io::inw(ioBase + ATA_REG_DATA); + } + + return true; + } + + u64 sectorCountFromIdentify(const u16* id) { + return static_cast(id[60]) | (static_cast(id[61]) << 16); + } +} + + +ATADiskDevice::ATADiskDevice([[maybe_unused]] PCIDevice* controller, u16 ioBase, [[maybe_unused]] u16 ctrlBase, + u8 drive, u64 sectors) : + BlockDevice("Ata Disk", sectors, 512), + ioBase(ioBase), + drive(drive) { +} + +bool ATADiskDevice::readBlocks(u64 lba, u32 count, void* buffer) { + return access(false, lba, count, buffer); +} + +bool ATADiskDevice::writeBlocks(u64 lba, u32 count, const void* buffer) { + return access(true, lba, count, const_cast(buffer)); +} + +bool ATADiskDevice::access(bool write, u64 lba, u32 count, void* buffer) { + if (count == 0) { + return true; + } + + if (lba + count > blockCount()) { + debug::error("Tried to access region ", lba, " + ", count, " which resulted in an overflow."); + return false; + } + + if (lba > 0x0FFFFFFF) { + debug::error("For now LBA28 is not supported"); + return false; + } + + auto* words = reinterpret_cast(buffer); + + for (u32 sector = 0; sector < count; sector++) { + u64 currentLBA = lba + sector; + + if (!waitBSY(ioBase)) { + return false; + } + + selectDrive(ioBase, drive, currentLBA); + + io::outb(ioBase + ATA_REG_SECCOUNT0, 1); + io::outb(ioBase + ATA_REG_LBA0, currentLBA & 0xFF); + io::outb(ioBase + ATA_REG_LBA1, (currentLBA >> 8) & 0xFF); + io::outb(ioBase + ATA_REG_LBA2, (currentLBA >> 16) & 0xFF); + + io::outb(ioBase + ATA_REG_COMMAND, write ? ATA_CMD_WRITE_PIO : ATA_CMD_READ_PIO); + + if (!waitDRQ(ioBase)) { + return false; + } + + if (write) { + for (usize i = 0; i < 256; i++) { + io::outw(ioBase + ATA_REG_DATA, words[sector * 256 + i]); + } + + io::outb(ioBase + ATA_REG_COMMAND, ATA_CMD_CACHE_FLUSH); + + if (!waitBSY(ioBase)) { + return false; + } + } + else { + for (usize i = 0; i < 256; i++) { + words[sector * 256 + i] = io::inw(ioBase + ATA_REG_DATA); + } + } + } + + return true; +} + +bool ATADriver::probe(Device& device) { + if (device.type() != DeviceType::PCI) { + return false; + } + + auto& pciDevice = static_cast(device); + + return pciDevice.isClass(0x01, 0x01); +} + +bool ATADriver::start(Device& device) { + debug::log("Starting ATA Driver"); + + auto& pciDevice = static_cast(device); + + pciDevice.enableIOSpace(); + pciDevice.enableBusMastering(); + + struct Channel { + u16 ioBase; + u16 ctrlBase; + }; + + Channel channels[] = { + {0x1F0, 0x3F6}, + {0x170, 0x376} + }; + + bool foundAny = false; + + for (auto& channel : channels) { + for (u8 drive = 0; drive < 2; drive++) { + u16 identify[256]{}; + + if (!identifyDrive(channel.ioBase, drive, identify)) continue; + + u64 sectors = sectorCountFromIdentify(identify); + + if (sectors == 0) { + debug::warn("Found a drive with no sectors."); + } + + auto* disk = new ATADiskDevice( + &pciDevice, + channel.ioBase, + channel.ctrlBase, + drive, + sectors + ); + + disk->parent = &pciDevice; + disk->driver = this; + + DeviceManager::registerDevice(disk); + foundAny = true; + } + } + + if (!foundAny) { + return false; + } + + device.driver = this; + setState(DriverState::Active); + return true; +} + +bool ATADriver::stop(Device& device) { + if (device.driver == this) { + device.driver = nullptr; + } + + setState(DriverState::Stopping); + return true; +} + +void ata::registerDriver() { + driver = new ATADriver(); + DriverManager::registerDriver(driver); +} diff --git a/kernel/drivers/time/pit.cpp b/kernel/drivers/time/pit.cpp index c708854..a536776 100644 --- a/kernel/drivers/time/pit.cpp +++ b/kernel/drivers/time/pit.cpp @@ -26,7 +26,7 @@ namespace { class PitDriver final : public Driver { public: string name() const override { - return "pit"; + return "Programmable Interval Timer - Avery"; } bool probe(Device& device) override { diff --git a/kernel/fs/fat32.cpp b/kernel/fs/fat32.cpp new file mode 100644 index 0000000..256e625 --- /dev/null +++ b/kernel/fs/fat32.cpp @@ -0,0 +1,1564 @@ +#include +#include + +#include "kernel/debug.h" +#include "kernel/memory.h" + +namespace { + char toLower(char c) { + if (c >= 'A' && c <= 'Z') { + return static_cast(c - 'A' + 'a'); + } + + return c; + } + + char toUpper(char c) { + if (c >= 'a' && c <= 'z') { + return static_cast(c - 'a' + 'A'); + } + + return c; + } + + bool namesEqual(const string& a, const string& b) { + if (a.length() != b.length()) { + return false; + } + + for (usize i = 0; i < a.length(); i++) { + if (toLower(a[i].value()) != toLower(b[i].value())) { + return false; + } + } + + return true; + } + + bool isEndOfChain(u32 cluster) { + return cluster >= fat32::EndOfChain; + } + + bool isDotEntry(const string& name) { + return name == "." || name == ".."; + } + + bool makeShortName(const string& name, char out[11]) { + if (name.empty()) { + return false; + } + + for (usize i = 0; i < 11; i++) { + out[i] = ' '; + } + + usize dot = name.length(); + usize dotCount = 0; + + for (usize i = 0; i < name.length(); i++) { + char c = name[i].value(); + + if (c == '/') { + return false; + } + + if (c == '.') { + dot = i; + dotCount++; + } + } + + if (dotCount > 1) { + return false; + } + + usize baseLength = dot; + usize extLength = dotCount == 0 ? 0 : name.length() - dot - 1; + + if (baseLength == 0 || baseLength > 8 || extLength > 3) { + return false; + } + + for (usize i = 0; i < baseLength; i++) { + out[i] = toUpper(name[i].value()); + } + + for (usize i = 0; i < extLength; i++) { + out[8 + i] = toUpper(name[dot + 1 + i].value()); + } + + return true; + } + + string shortNameToString(const fat32::DirectoryEntryRaw& entry) { + char normalName[13]; + usize out = 0; + + usize baseEnd = 8; + while (baseEnd > 0 && entry.name[baseEnd - 1] == ' ') { + baseEnd--; + } + + for (usize j = 0; j < baseEnd; j++) { + normalName[out++] = toLower(entry.name[j]); + } + + usize extEnd = 11; + while (extEnd > 8 && entry.name[extEnd - 1] == ' ') { + extEnd--; + } + + if (extEnd > 8) { + normalName[out++] = '.'; + + for (usize j = 8; j < extEnd; j++) { + normalName[out++] = toLower(entry.name[j]); + } + } + + normalName[out] = '\0'; + return string(normalName); + } + + void appendLFNChars(string& out, const u16* chars, usize count) { + for (usize i = 0; i < count; i++) { + u16 c = chars[i]; + + if (c == 0x0000) return; + if (c == 0xFFFF) continue; + + out.append(static_cast(c & 0xFF)); + } + } + + string pathComponent(const string& path, usize start, usize end) { + string result; + + for (usize i = start; i < end; i++) { + result.append(path[i].value()); + } + + return result; + } + + string basenameOf(const string& path) { + if (path == "/") { + return "/"; + } + + usize end = path.length(); + while (end > 1 && path[end - 1].value() == '/') { + end--; + } + + usize start = end; + while (start > 0 && path[start - 1].value() != '/') { + start--; + } + + return pathComponent(path, start, end); + } + + string parentOf(const string& path) { + if (path == "/") { + return "/"; + } + + usize end = path.length(); + while (end > 1 && path[end - 1].value() == '/') { + end--; + } + + usize slash = end; + while (slash > 0 && path[slash - 1].value() != '/') { + slash--; + } + + if (slash <= 1) { + return "/"; + } + + return pathComponent(path, 0, slash - 1); + } + + void fillDotName(char out[11], bool parent) { + for (usize i = 0; i < 11; i++) { + out[i] = ' '; + } + + out[0] = '.'; + if (parent) { + out[1] = '.'; + } + } + + void markEntryAndLongNameDeleted(u8* clusterData, u32 entryOffset) { + auto* entries = reinterpret_cast(clusterData); + usize index = entryOffset / sizeof(fat32::DirectoryEntryRaw); + + entries[index].name[0] = static_cast(fat32::EntryDeleted); + + while (index > 0) { + index--; + + if (entries[index].attributes != fat32::AttributeLongName) { + break; + } + + entries[index].name[0] = static_cast(fat32::EntryDeleted); + } + } +} + +bool FAT32FileSystem::readSectors(u64 sector, u32 count, void* buffer) const { + return backingDevice != nullptr && backingDevice->readBlocks(sector, count, buffer); +} + +bool FAT32FileSystem::writeSectors(u64 sector, u32 count, const void* buffer) const { + return backingDevice != nullptr && backingDevice->writeBlocks(sector, count, buffer); +} + +bool FAT32FileSystem::readBootSector() { + u8* firstSector = new u8[512]; + + if (!readSectors(0, 1, firstSector)) { + delete[] firstSector; + debug::error("Could not read initial sector of FAT32 disk"); + return false; + } + + memory::copy( + reinterpret_cast(&bpb), + firstSector, + static_cast(sizeof(fat32::BIOSParameterBlock)) + ); + signature = static_cast(firstSector[510] | (firstSector[511] << 8)); + delete[] firstSector; + return true; +} + +bool FAT32FileSystem::validateBootSector() const { + if (bytesPerSector() != 512) { + debug::warn("Bytes per sector is not 512"); + return false; + } + + if (sectorsPerCluster() == 0 || (sectorsPerCluster() & (sectorsPerCluster() - 1)) != 0) { + debug::warn("Sectors per cluster is corrupted or not aligned"); + return false; + } + + if (bpb.reservedSectorCount == 0 || bpb.tableCount == 0 || bpb.tableSize32 == 0) { + debug::warn("Some sector information is corrupted because is null"); + return false; + } + + if (bpb.rootCluster < 2) { + debug::warn("The root cluster is not a valid cluster"); + return false; + } + + if (signature != 0xAA55) { + debug::error("The disk signature is ", signature, " instead of the correct ", 0xAA55); + return false; + } + + return true; +} + +bool FAT32FileSystem::readFSInfo() { + fsInfo.freeClusterCount = 0xFFFFFFFF; + fsInfo.nextFreeCluster = 2; + + if (bpb.fsInfoSector == 0) { + return true; + } + + u8* sector = new u8[512]; + if (!readSectors(bpb.fsInfoSector, 1, sector)) { + delete[] sector; + return true; + } + + memory::copy( + reinterpret_cast(&fsInfo), + sector, + static_cast(sizeof(fat32::FSInfo)) + ); + delete[] sector; + + if (fsInfo.leadSignature != 0x41615252 || + fsInfo.structureSignature != 0x61417272 || + fsInfo.trailSignature != 0xAA550000) { + fsInfo.freeClusterCount = 0xFFFFFFFF; + fsInfo.nextFreeCluster = 2; + } + + return true; +} + +bool FAT32FileSystem::loadRootNode() { + root = new FAT32VNode("/", VFSNodeType::Directory, this, bpb.rootCluster, 0); + return root != nullptr; +} + +bool FAT32FileSystem::mount(BlockDevice* device) { + if (mounted || device == nullptr) { + debug::warn("The device is null or the disk has already been mounted"); + return false; + } + + backingDevice = device; + + if (!readBootSector() || !validateBootSector()) { + debug::warn("The boot sector could not have been read or the boot sector is wrong. Disk corrupted"); + backingDevice = nullptr; + return false; + } + + totalSectors = bpb.totalSectors16 != 0 ? bpb.totalSectors16 : bpb.totalSectors32; + fatFirstSector = bpb.reservedSectorCount; + fatFirstDataSector = bpb.reservedSectorCount + bpb.tableCount * bpb.tableSize32; + + if (totalSectors <= fatFirstDataSector) { + backingDevice = nullptr; + debug::warn("Total sectors is less than the first sector"); + return false; + } + + totalClusters = (totalSectors - fatFirstDataSector) / bpb.sectorsPerCluster; + + if (totalClusters == 0) { + backingDevice = nullptr; + debug::warn("There are no clusters in the disk"); + return false; + } + + readFSInfo(); + + loadRootNode(); + + readOnly = false; + mounted = true; + return true; +} + +bool FAT32FileSystem::unmount() { + if (!mounted) { + return false; + } + + syncFSInfo(); + mounted = false; + backingDevice = nullptr; + return true; +} + +VNode* FAT32FileSystem::rootNode() { + return root; +} + +u64 FAT32FileSystem::clusterToSector(u32 cluster) const { + return fatFirstDataSector + (cluster - 2) * bpb.sectorsPerCluster; +} + +bool FAT32FileSystem::readCluster(u32 cluster, void* buffer) { + if (cluster < 2) { + return false; + } + + return readSectors(clusterToSector(cluster), bpb.sectorsPerCluster, buffer); +} + +bool FAT32FileSystem::writeCluster(u32 cluster, const void* buffer) { + if (cluster < 2) { + return false; + } + + return writeSectors(clusterToSector(cluster), bpb.sectorsPerCluster, buffer); +} + +bool FAT32FileSystem::readFATEntry(u32 cluster, u32& nextCluster) { + u8 sector[512]; + + u32 fatOffset = cluster * 4; + u32 fatSector = fatFirstSector + fatOffset / bpb.bytesPerSector; + u32 entryOffset = fatOffset % bpb.bytesPerSector; + + if (!readSectors(fatSector, 1, sector)) { + debug::error("Could not read FAT sector"); + return false; + } + + u32 rawEntry = + u32(sector[entryOffset]) | + (u32(sector[entryOffset + 1]) << 8) | + (u32(sector[entryOffset + 2]) << 16) | + (u32(sector[entryOffset + 3]) << 24); + + nextCluster = rawEntry & 0x0FFFFFFF; + return true; +} + +bool FAT32FileSystem::writeFATEntry(u32 cluster, u32 value) { + u8 sector[512]; + u32 fatOffset = cluster * 4; + u32 entryOffset = fatOffset % bpb.bytesPerSector; + + for (u8 table = 0; table < bpb.tableCount; table++) { + u32 fatSector = fatFirstSector + table * bpb.tableSize32 + fatOffset / bpb.bytesPerSector; + + if (!readSectors(fatSector, 1, sector)) { + return false; + } + + u32 oldValue = + u32(sector[entryOffset]) | + (u32(sector[entryOffset + 1]) << 8) | + (u32(sector[entryOffset + 2]) << 16) | + (u32(sector[entryOffset + 3]) << 24); + u32 rawValue = (oldValue & 0xF0000000) | (value & 0x0FFFFFFF); + + sector[entryOffset] = static_cast(rawValue & 0xFF); + sector[entryOffset + 1] = static_cast((rawValue >> 8) & 0xFF); + sector[entryOffset + 2] = static_cast((rawValue >> 16) & 0xFF); + sector[entryOffset + 3] = static_cast((rawValue >> 24) & 0xFF); + + if (!writeSectors(fatSector, 1, sector)) { + return false; + } + } + + return true; +} + +bool FAT32FileSystem::zeroCluster(u32 cluster) { + u32 size = clusterSize(); + u8* data = new u8[size]; + memory::set(data, static_cast(0), static_cast(size)); + bool ok = writeCluster(cluster, data); + delete[] data; + return ok; +} + +bool FAT32FileSystem::allocateCluster(u32& outCluster) { + u32 start = fsInfo.nextFreeCluster >= 2 && fsInfo.nextFreeCluster < totalClusters + 2 + ? fsInfo.nextFreeCluster + : 2; + + for (u32 i = 0; i < totalClusters; i++) { + u32 cluster = 2 + ((start - 2 + i) % totalClusters); + u32 value = 0; + + if (!readFATEntry(cluster, value)) { + return false; + } + + if (value != fat32::FreeCluster) { + continue; + } + + if (!writeFATEntry(cluster, 0x0FFFFFFF)) { + return false; + } + + if (!zeroCluster(cluster)) { + writeFATEntry(cluster, fat32::FreeCluster); + return false; + } + + outCluster = cluster; + fsInfo.nextFreeCluster = cluster + 1; + + if (fsInfo.nextFreeCluster >= totalClusters + 2) { + fsInfo.nextFreeCluster = 2; + } + + if (fsInfo.freeClusterCount != 0xFFFFFFFF && fsInfo.freeClusterCount > 0) { + fsInfo.freeClusterCount--; + } + + syncFSInfo(); + return true; + } + + return false; +} + +bool FAT32FileSystem::freeClusterChain(u32 firstCluster) { + if (firstCluster < 2) { + return true; + } + + u32 current = firstCluster; + + while (current >= 2 && !isEndOfChain(current)) { + u32 next = 0; + + if (!readFATEntry(current, next)) { + return false; + } + + if (!writeFATEntry(current, fat32::FreeCluster)) { + return false; + } + + if (fsInfo.freeClusterCount != 0xFFFFFFFF) { + fsInfo.freeClusterCount++; + } + + if (next == fat32::BadCluster || next == fat32::FreeCluster) { + break; + } + + current = next; + } + + syncFSInfo(); + return true; +} + +bool FAT32FileSystem::extendClusterChain(u32 startCluster, u32& newCluster) { + if (startCluster < 2) { + return allocateCluster(newCluster); + } + + u32 current = startCluster; + + while (true) { + u32 next = 0; + + if (!readFATEntry(current, next)) { + return false; + } + + if (isEndOfChain(next)) { + break; + } + + if (next == fat32::FreeCluster || next == fat32::BadCluster) { + return false; + } + + current = next; + } + + if (!allocateCluster(newCluster)) { + return false; + } + + return writeFATEntry(current, newCluster); +} + +bool FAT32FileSystem::getClusterForOffset(u32 firstCluster, u64 offset, bool allocateIfMissing, u32& outCluster) { + if (firstCluster < 2) { + return false; + } + + u32 cluster = firstCluster; + u64 index = offset / clusterSize(); + + while (index > 0) { + u32 next = 0; + + if (!readFATEntry(cluster, next)) { + return false; + } + + if (isEndOfChain(next)) { + if (!allocateIfMissing) { + return false; + } + + if (!extendClusterChain(firstCluster, next)) { + return false; + } + } + + if (next == fat32::FreeCluster || next == fat32::BadCluster) { + return false; + } + + cluster = next; + index--; + } + + outCluster = cluster; + return true; +} + +bool FAT32FileSystem::readDirectory(u32 firstCluster, Vector& entries) { + u32 size = clusterSize(); + u8* clusterData = new u8[size]; + u32 currentCluster = firstCluster; + u32 globalIndex = 0; + string lfn; + + while (true) { + if (!readCluster(currentCluster, clusterData)) { + delete[] clusterData; + debug::error("Could not read directory entry"); + return false; + } + + auto* rawEntries = reinterpret_cast(clusterData); + + for (usize i = 0; i < size / sizeof(fat32::DirectoryEntryRaw); i++) { + auto& entry = rawEntries[i]; + + if (entry.name[0] == 0x00) { + delete[] clusterData; + return true; + } + + if (static_cast(entry.name[0]) == fat32::EntryDeleted) { + lfn.clear(); + globalIndex++; + continue; + } + + if (entry.attributes == fat32::AttributeLongName) { + auto& lfnEntry = *reinterpret_cast(&entry); + string part; + + appendLFNChars(part, lfnEntry.name1, 5); + appendLFNChars(part, lfnEntry.name2, 6); + appendLFNChars(part, lfnEntry.name3, 2); + + lfn.prepend(part.cStr()); + globalIndex++; + continue; + } + + if (entry.attributes & fat32::AttributeVolumeID) { + lfn.clear(); + globalIndex++; + continue; + } + + fat32::DirectoryEntryInfo info; + info.name = lfn.empty() ? shortNameToString(entry) : lfn; + info.firstCluster = + (static_cast(entry.firstClusterHigh) << 16) | static_cast(entry.firstClusterLow); + info.size = entry.fileSize; + info.attributes = entry.attributes; + info.parentDirectoryCluster = firstCluster; + info.directoryEntryCluster = currentCluster; + info.directoryEntryOffset = static_cast(i * sizeof(fat32::DirectoryEntryRaw)); + info.directoryEntryIndex = globalIndex; + + entries.push(info); + lfn.clear(); + globalIndex++; + } + + u32 nextCluster = 0; + + if (!readFATEntry(currentCluster, nextCluster)) { + delete[] clusterData; + debug::error("Could not read directory entry"); + return false; + } + + if (isEndOfChain(nextCluster)) { + delete[] clusterData; + return true; + } + + if (nextCluster == fat32::FreeCluster || nextCluster == fat32::BadCluster) { + delete[] clusterData; + debug::error("Could not read directory entry"); + return false; + } + + currentCluster = nextCluster; + } +} + +Vector FAT32FileSystem::splitPath(const string& path) const { + Vector parts; + usize start = 0; + + while (start < path.length()) { + while (start < path.length() && path[start].value() == '/') { + start++; + } + + usize end = start; + while (end < path.length() && path[end].value() != '/') { + end++; + } + + if (end > start) { + parts.push(pathComponent(path, start, end)); + } + + start = end; + } + + return parts; +} + +bool FAT32FileSystem::findEntry(const string& path, fat32::DirectoryEntryInfo& entry) { + if (path.empty() || path == "/") { + entry.name = "/"; + entry.attributes = fat32::AttributeDirectory; + entry.firstCluster = bpb.rootCluster; + entry.size = 0; + entry.parentDirectoryCluster = bpb.rootCluster; + entry.directoryEntryCluster = 0; + entry.directoryEntryOffset = 0; + return true; + } + + Vector parts = splitPath(path); + u32 currentDirectory = bpb.rootCluster; + + for (usize i = 0; i < parts.size(); i++) { + Vector entries; + + if (!readDirectory(currentDirectory, entries)) { + return false; + } + + bool found = false; + + for (usize j = 0; j < entries.size(); j++) { + fat32::DirectoryEntryInfo& candidate = entries[j].value(); + + if (!namesEqual(candidate.name, parts[i].value())) { + continue; + } + + if (i + 1 == parts.size()) { + entry = candidate; + return true; + } + + if (!candidate.isDirectory()) { + return false; + } + + currentDirectory = candidate.firstCluster; + found = true; + break; + } + + if (!found) { + return false; + } + } + + return false; +} + +bool FAT32FileSystem::writeDirectoryEntry( + u32 directoryCluster, + u32 entryOffset, + const fat32::DirectoryEntryRaw& raw +) { + u32 size = clusterSize(); + u8* clusterData = new u8[size]; + + if (!readCluster(directoryCluster, clusterData)) { + delete[] clusterData; + return false; + } + + memory::copy( + clusterData + entryOffset, + reinterpret_cast(&raw), + static_cast(sizeof(fat32::DirectoryEntryRaw)) + ); + + bool ok = writeCluster(directoryCluster, clusterData); + delete[] clusterData; + return ok; +} + +bool FAT32FileSystem::updateDirectoryEntry(const fat32::DirectoryEntryInfo& entry) { + if (entry.directoryEntryCluster < 2) { + return true; + } + + u32 size = clusterSize(); + u8* clusterData = new u8[size]; + + if (!readCluster(entry.directoryEntryCluster, clusterData)) { + delete[] clusterData; + return false; + } + + auto* raw = reinterpret_cast(clusterData + entry.directoryEntryOffset); + raw->attributes = entry.attributes; + raw->firstClusterHigh = static_cast((entry.firstCluster >> 16) & 0xFFFF); + raw->firstClusterLow = static_cast(entry.firstCluster & 0xFFFF); + raw->fileSize = entry.isDirectory() ? 0 : entry.size; + + bool ok = writeCluster(entry.directoryEntryCluster, clusterData); + delete[] clusterData; + return ok; +} + +bool FAT32FileSystem::findFreeDirectoryEntry( + u32 directoryCluster, + u32& outEntryCluster, + u32& outEntryOffset +) { + u32 size = clusterSize(); + u8* clusterData = new u8[size]; + u32 currentCluster = directoryCluster; + + while (true) { + if (!readCluster(currentCluster, clusterData)) { + delete[] clusterData; + return false; + } + + auto* entries = reinterpret_cast(clusterData); + + for (usize i = 0; i < size / sizeof(fat32::DirectoryEntryRaw); i++) { + u8 first = static_cast(entries[i].name[0]); + + if (first == 0x00 || first == fat32::EntryDeleted) { + outEntryCluster = currentCluster; + outEntryOffset = static_cast(i * sizeof(fat32::DirectoryEntryRaw)); + delete[] clusterData; + return true; + } + } + + u32 next = 0; + + if (!readFATEntry(currentCluster, next)) { + delete[] clusterData; + return false; + } + + if (isEndOfChain(next)) { + u32 newCluster = 0; + + if (!extendClusterChain(directoryCluster, newCluster)) { + delete[] clusterData; + return false; + } + + outEntryCluster = newCluster; + outEntryOffset = 0; + delete[] clusterData; + return true; + } + + currentCluster = next; + } +} + +bool FAT32FileSystem::createDirectoryEntry( + u32 parentDirectoryCluster, + const string& name, + u8 attributes, + fat32::DirectoryEntryInfo& outEntry +) { + char shortName[11]; + + if (!makeShortName(name, shortName)) { + return false; + } + + u32 entryCluster = 0; + u32 entryOffset = 0; + + if (!findFreeDirectoryEntry(parentDirectoryCluster, entryCluster, entryOffset)) { + return false; + } + + fat32::DirectoryEntryRaw raw{}; + memory::copy(reinterpret_cast(raw.name), reinterpret_cast(shortName), 11); + raw.attributes = attributes; + + u32 firstCluster = 0; + + if (attributes & fat32::AttributeDirectory) { + if (!allocateCluster(firstCluster)) { + return false; + } + + fat32::DirectoryEntryRaw dot{}; + fillDotName(dot.name, false); + dot.attributes = fat32::AttributeDirectory; + dot.firstClusterHigh = static_cast((firstCluster >> 16) & 0xFFFF); + dot.firstClusterLow = static_cast(firstCluster & 0xFFFF); + + fat32::DirectoryEntryRaw dotDot{}; + fillDotName(dotDot.name, true); + dotDot.attributes = fat32::AttributeDirectory; + dotDot.firstClusterHigh = static_cast((parentDirectoryCluster >> 16) & 0xFFFF); + dotDot.firstClusterLow = static_cast(parentDirectoryCluster & 0xFFFF); + + u32 size = clusterSize(); + u8* directoryData = new u8[size]; + memory::set(directoryData, static_cast(0), static_cast(size)); + memory::copy(directoryData, reinterpret_cast(&dot), static_cast(sizeof(dot))); + memory::copy( + directoryData + sizeof(dot), + reinterpret_cast(&dotDot), + static_cast(sizeof(dotDot)) + ); + + if (!writeCluster(firstCluster, directoryData)) { + delete[] directoryData; + freeClusterChain(firstCluster); + return false; + } + + delete[] directoryData; + } + + raw.firstClusterHigh = static_cast((firstCluster >> 16) & 0xFFFF); + raw.firstClusterLow = static_cast(firstCluster & 0xFFFF); + raw.fileSize = 0; + + if (!writeDirectoryEntry(entryCluster, entryOffset, raw)) { + if (firstCluster >= 2) { + freeClusterChain(firstCluster); + } + + return false; + } + + outEntry.name = name; + outEntry.attributes = attributes; + outEntry.firstCluster = firstCluster; + outEntry.size = 0; + outEntry.parentDirectoryCluster = parentDirectoryCluster; + outEntry.directoryEntryCluster = entryCluster; + outEntry.directoryEntryOffset = entryOffset; + outEntry.directoryEntryIndex = entryOffset / sizeof(fat32::DirectoryEntryRaw); + + return true; +} + +bool FAT32FileSystem::syncFSInfo() { + if (bpb.fsInfoSector == 0) { + return true; + } + + fsInfo.leadSignature = 0x41615252; + fsInfo.structureSignature = 0x61417272; + fsInfo.trailSignature = 0xAA550000; + + u8 sector[512]; + memory::set(sector, static_cast(0), 512); + memory::copy(sector, reinterpret_cast(&fsInfo), static_cast(sizeof(fat32::FSInfo))); + return writeSectors(bpb.fsInfoSector, 1, sector); +} + +FileHandle* FAT32FileSystem::open(const string& path, VFSOpenMode mode, VFSError& error) { + error.code = VFSError::None; + + fat32::DirectoryEntryInfo entry; + + if (!findEntry(path, entry)) { + if (!vfs::hasMode(mode, VFSOpenMode::Create)) { + error.code = VFSError::NotFound; + return nullptr; + } + + if (!createFile(path, error)) { + return nullptr; + } + + if (!findEntry(path, entry)) { + error.code = VFSError::IOError; + return nullptr; + } + } + + if (entry.isDirectory()) { + error.code = VFSError::IsDirectory; + return nullptr; + } + + if (vfs::hasMode(mode, VFSOpenMode::Truncate)) { + if (entry.firstCluster >= 2 && !freeClusterChain(entry.firstCluster)) { + error.code = VFSError::IOError; + return nullptr; + } + + entry.firstCluster = 0; + entry.size = 0; + + if (!updateDirectoryEntry(entry)) { + error.code = VFSError::IOError; + return nullptr; + } + } + + auto* node = new FAT32VNode(entry.name, VFSNodeType::File, this, entry.firstCluster, entry.size); + node->setDirectoryEntry(entry); + + auto* handle = new FAT32FileHandle(node, mode); + + if (vfs::hasMode(mode, VFSOpenMode::Append)) { + handle->seek(node->size()); + } + + return handle; +} + +DirectoryHandle* FAT32FileSystem::openDirectory(const string& path, VFSError& error) { + error.code = VFSError::None; + + fat32::DirectoryEntryInfo entry; + if (!findEntry(path, entry)) { + error.code = VFSError::NotFound; + return nullptr; + } + + if (!entry.isDirectory()) { + error.code = VFSError::NotDirectory; + return nullptr; + } + + Vector entries; + if (!readDirectory(entry.firstCluster, entries)) { + error.code = VFSError::IOError; + return nullptr; + } + + return new FAT32DirectoryHandle(this, entries); +} + +bool FAT32FileSystem::stat(const string& path, VFSStat& outStat, VFSError& error) { + error.code = VFSError::None; + + fat32::DirectoryEntryInfo entry; + if (!findEntry(path, entry)) { + error.code = VFSError::NotFound; + return false; + } + + outStat.name = entry.name; + outStat.type = entry.isDirectory() ? VFSNodeType::Directory : VFSNodeType::File; + outStat.size = entry.size; + outStat.blockSize = bytesPerSector(); + return true; +} + +bool FAT32FileSystem::createFile(const string& path, VFSError& error) { + error.code = VFSError::None; + + fat32::DirectoryEntryInfo existing; + if (findEntry(path, existing)) { + error.code = VFSError::AlreadyExists; + return false; + } + + fat32::DirectoryEntryInfo parent; + string parentPath = parentOf(path); + string name = basenameOf(path); + + if (!findEntry(parentPath, parent) || !parent.isDirectory()) { + error.code = VFSError::NotDirectory; + return false; + } + + fat32::DirectoryEntryInfo created; + if (!createDirectoryEntry(parent.firstCluster, name, fat32::AttributeArchive, created)) { + error.code = VFSError::IOError; + return false; + } + + return true; +} + +bool FAT32FileSystem::createDirectory(const string& path, VFSError& error) { + error.code = VFSError::None; + + fat32::DirectoryEntryInfo existing; + if (findEntry(path, existing)) { + error.code = VFSError::AlreadyExists; + return false; + } + + fat32::DirectoryEntryInfo parent; + string parentPath = parentOf(path); + string name = basenameOf(path); + + if (!findEntry(parentPath, parent) || !parent.isDirectory()) { + error.code = VFSError::NotDirectory; + return false; + } + + fat32::DirectoryEntryInfo created; + if (!createDirectoryEntry(parent.firstCluster, name, fat32::AttributeDirectory, created)) { + error.code = VFSError::IOError; + return false; + } + + return true; +} + +bool FAT32FileSystem::remove(const string& path, VFSError& error) { + error.code = VFSError::None; + + fat32::DirectoryEntryInfo entry; + if (!findEntry(path, entry)) { + error.code = VFSError::NotFound; + return false; + } + + if (entry.directoryEntryCluster < 2 || isDotEntry(entry.name)) { + error.code = VFSError::InvalidPath; + return false; + } + + if (entry.isDirectory()) { + Vector entries; + + if (!readDirectory(entry.firstCluster, entries)) { + error.code = VFSError::IOError; + return false; + } + + for (usize i = 0; i < entries.size(); i++) { + if (!isDotEntry(entries[i].value().name)) { + error.code = VFSError::PermissionDenied; + return false; + } + } + } + + u32 size = clusterSize(); + u8* clusterData = new u8[size]; + + if (!readCluster(entry.directoryEntryCluster, clusterData)) { + delete[] clusterData; + error.code = VFSError::IOError; + return false; + } + + markEntryAndLongNameDeleted(clusterData, entry.directoryEntryOffset); + + bool wrote = writeCluster(entry.directoryEntryCluster, clusterData); + delete[] clusterData; + + if (!wrote) { + error.code = VFSError::IOError; + return false; + } + + if (entry.firstCluster >= 2 && !freeClusterChain(entry.firstCluster)) { + error.code = VFSError::IOError; + return false; + } + + return true; +} + +bool FAT32FileSystem::rename(const string& oldPath, const string& newPath, VFSError& error) { + error.code = VFSError::None; + + fat32::DirectoryEntryInfo oldEntry; + if (!findEntry(oldPath, oldEntry)) { + error.code = VFSError::NotFound; + return false; + } + + fat32::DirectoryEntryInfo existing; + if (findEntry(newPath, existing)) { + error.code = VFSError::AlreadyExists; + return false; + } + + string newName = basenameOf(newPath); + char shortName[11]; + if (!makeShortName(newName, shortName)) { + error.code = VFSError::InvalidPath; + return false; + } + + fat32::DirectoryEntryInfo oldParent; + fat32::DirectoryEntryInfo newParent; + + if (!findEntry(parentOf(oldPath), oldParent) || !findEntry(parentOf(newPath), newParent)) { + error.code = VFSError::NotDirectory; + return false; + } + + if (oldParent.firstCluster == newParent.firstCluster) { + u32 size = clusterSize(); + u8* clusterData = new u8[size]; + + if (!readCluster(oldEntry.directoryEntryCluster, clusterData)) { + delete[] clusterData; + error.code = VFSError::IOError; + return false; + } + + markEntryAndLongNameDeleted(clusterData, oldEntry.directoryEntryOffset); + auto* raw = reinterpret_cast(clusterData + oldEntry.directoryEntryOffset); + memory::copy(reinterpret_cast(raw->name), reinterpret_cast(shortName), 11); + raw->attributes = oldEntry.attributes; + raw->firstClusterHigh = static_cast((oldEntry.firstCluster >> 16) & 0xFFFF); + raw->firstClusterLow = static_cast(oldEntry.firstCluster & 0xFFFF); + raw->fileSize = oldEntry.isDirectory() ? 0 : oldEntry.size; + + bool ok = writeCluster(oldEntry.directoryEntryCluster, clusterData); + delete[] clusterData; + + if (!ok) { + error.code = VFSError::IOError; + return false; + } + + return true; + } + + fat32::DirectoryEntryInfo newEntry; + if (!createDirectoryEntry(newParent.firstCluster, newName, oldEntry.attributes, newEntry)) { + error.code = VFSError::IOError; + return false; + } + + newEntry.firstCluster = oldEntry.firstCluster; + newEntry.size = oldEntry.size; + + if (!updateDirectoryEntry(newEntry)) { + error.code = VFSError::IOError; + return false; + } + + u32 size = clusterSize(); + u8* clusterData = new u8[size]; + + if (!readCluster(oldEntry.directoryEntryCluster, clusterData)) { + delete[] clusterData; + newEntry.firstCluster = 0; + updateDirectoryEntry(newEntry); + remove(newPath, error); + error.code = VFSError::IOError; + return false; + } + + markEntryAndLongNameDeleted(clusterData, oldEntry.directoryEntryOffset); + + bool removedOldEntry = writeCluster(oldEntry.directoryEntryCluster, clusterData); + delete[] clusterData; + + if (!removedOldEntry) { + newEntry.firstCluster = 0; + updateDirectoryEntry(newEntry); + remove(newPath, error); + error.code = VFSError::IOError; + return false; + } + + return true; +} + +FAT32VNode::FAT32VNode(string name, VFSNodeType type, FAT32FileSystem* fs, u32 firstCluster, u64 size) + : VNode(name, type, fs), nodeFirstCluster(firstCluster), nodeSize(size) { +} + +bool FAT32VNode::stat(VFSStat& outStat) { + outStat.name = nodeName; + outStat.type = nodeType; + outStat.size = nodeSize; + outStat.blockSize = 512; + return true; +} + +void FAT32VNode::setFirstCluster(u32 firstCluster) { + nodeFirstCluster = firstCluster; + + if (hasEntryInfo) { + entryInfo.firstCluster = firstCluster; + } +} + +void FAT32VNode::setSize(u64 size) { + nodeSize = size; + + if (hasEntryInfo) { + entryInfo.size = static_cast(size); + } +} + +void FAT32VNode::setDirectoryEntry(const fat32::DirectoryEntryInfo& entry) { + entryInfo = entry; + hasEntryInfo = true; +} + +FAT32FileHandle::FAT32FileHandle(FAT32VNode* node, VFSOpenMode mode) + : FileHandle(node, mode), fatNode(node) { +} + +usize FAT32FileHandle::read(void* buffer, usize size) { + if (closed || !vfs::hasMode(openMode, VFSOpenMode::Read) || buffer == nullptr) { + return 0; + } + + if (offset >= fatNode->size()) { + return 0; + } + + FAT32FileSystem* fs = static_cast(fatNode->fileSystem()); + usize remaining = size; + u64 available = fatNode->size() - offset; + + if (remaining > available) { + remaining = static_cast(available); + } + + u8* out = static_cast(buffer); + usize totalRead = 0; + u32 clusterSize = fs->clusterSize(); + u8* clusterData = new u8[clusterSize]; + + while (remaining > 0) { + u32 cluster = 0; + + if (!fs->getClusterForOffset(fatNode->firstCluster(), offset, false, cluster)) { + break; + } + + if (!fs->readCluster(cluster, clusterData)) { + break; + } + + u32 clusterOffset = static_cast(offset % clusterSize); + usize chunk = clusterSize - clusterOffset; + + if (chunk > remaining) { + chunk = remaining; + } + + memory::copy(out + totalRead, clusterData + clusterOffset, static_cast(chunk)); + + offset += chunk; + totalRead += chunk; + remaining -= chunk; + } + + delete[] clusterData; + return totalRead; +} + +usize FAT32FileHandle::write(const void* buffer, usize size) { + if (closed || !vfs::hasMode(openMode, VFSOpenMode::Write) || buffer == nullptr) { + return 0; + } + + FAT32FileSystem* fs = static_cast(fatNode->fileSystem()); + + if (fatNode->firstCluster() < 2) { + u32 firstCluster = 0; + + if (!fs->allocateCluster(firstCluster)) { + return 0; + } + + fatNode->setFirstCluster(firstCluster); + dirty = true; + } + + const u8* in = static_cast(buffer); + usize remaining = size; + usize totalWritten = 0; + u32 clusterSize = fs->clusterSize(); + u8* clusterData = new u8[clusterSize]; + + while (remaining > 0) { + u32 cluster = 0; + + if (!fs->getClusterForOffset(fatNode->firstCluster(), offset, true, cluster)) { + break; + } + + u32 clusterOffset = static_cast(offset % clusterSize); + usize chunk = clusterSize - clusterOffset; + + if (chunk > remaining) { + chunk = remaining; + } + + if (chunk != clusterSize) { + if (!fs->readCluster(cluster, clusterData)) { + break; + } + } else { + memory::set(clusterData, static_cast(0), static_cast(clusterSize)); + } + + memory::copy(clusterData + clusterOffset, in + totalWritten, static_cast(chunk)); + + if (!fs->writeCluster(cluster, clusterData)) { + break; + } + + offset += chunk; + totalWritten += chunk; + remaining -= chunk; + + if (offset > fatNode->size()) { + fatNode->setSize(offset); + } + + dirty = true; + } + + delete[] clusterData; + return totalWritten; +} + +bool FAT32FileHandle::seek(u64 newOffset) { + if (closed) { + return false; + } + + offset = newOffset; + return true; +} + +bool FAT32FileHandle::truncate(u64 size) { + if (closed || !vfs::hasMode(openMode, VFSOpenMode::Write)) { + return false; + } + + FAT32FileSystem* fs = static_cast(fatNode->fileSystem()); + u32 oldFirstCluster = fatNode->firstCluster(); + + if (size == 0) { + if (oldFirstCluster >= 2 && !fs->freeClusterChain(oldFirstCluster)) { + return false; + } + + fatNode->setFirstCluster(0); + fatNode->setSize(0); + offset = 0; + dirty = true; + return flush(); + } + + u64 lastByte = size - 1; + u32 lastCluster = 0; + + if (oldFirstCluster < 2) { + u32 firstCluster = 0; + + if (!fs->allocateCluster(firstCluster)) { + return false; + } + + fatNode->setFirstCluster(firstCluster); + } + + if (!fs->getClusterForOffset(fatNode->firstCluster(), lastByte, true, lastCluster)) { + return false; + } + + u32 next = 0; + if (!fs->readFATEntry(lastCluster, next)) { + return false; + } + + if (!isEndOfChain(next) && next >= 2) { + if (!fs->freeClusterChain(next)) { + return false; + } + } + + if (!fs->writeFATEntry(lastCluster, 0x0FFFFFFF)) { + return false; + } + + fatNode->setSize(size); + + if (offset > size) { + offset = size; + } + + dirty = true; + return flush(); +} + +u64 FAT32FileHandle::tell() const { + return offset; +} + +u64 FAT32FileHandle::size() const { + return fatNode->size(); +} + +bool FAT32FileHandle::flush() { + if (closed) { + return false; + } + + if (!dirty) { + return true; + } + + if (!fatNode->hasDirectoryEntry()) { + dirty = false; + return true; + } + + FAT32FileSystem* fs = static_cast(fatNode->fileSystem()); + bool ok = fs->updateDirectoryEntry(fatNode->directoryEntry()); + + if (ok) { + dirty = false; + } + + return ok; +} + +bool FAT32FileHandle::close() { + if (closed) { + return true; + } + + bool ok = flush(); + closed = true; + return ok; +} + +FAT32DirectoryHandle::FAT32DirectoryHandle( + [[maybe_unused]] FAT32FileSystem* fs, + Vector entries +) : entries(entries) { +} + +bool FAT32DirectoryHandle::readNext(DirectoryEntry& entry) { + if (closed) { + return false; + } + + while (index < entries.size()) { + fat32::DirectoryEntryInfo& fatEntry = entries[index].value(); + index++; + + entry.name = fatEntry.name; + entry.type = fatEntry.isDirectory() ? VFSNodeType::Directory : VFSNodeType::File; + entry.size = fatEntry.size; + return true; + } + + return false; +} + +bool FAT32DirectoryHandle::rewind() { + if (closed) { + return false; + } + + index = 0; + return true; +} + +bool FAT32DirectoryHandle::close() { + closed = true; + return true; +} diff --git a/kernel/fs/vfs.cpp b/kernel/fs/vfs.cpp new file mode 100644 index 0000000..ef4954e --- /dev/null +++ b/kernel/fs/vfs.cpp @@ -0,0 +1,353 @@ +/* +* vfs.cpp +* As part of the Avery project +* Created by Max Van den Eynde in 2026 +* -------------------------------------- +* Description: Virtual File System implmenetation +* Copyright (c) 2026 Max Van den Eynde +*/ + +#include "fs/vfs.h" + +#include "kernel/debug.h" + +namespace { + Vector* mountStoragePointer = nullptr; + + Vector& mountsStorage() { + if (mountStoragePointer == nullptr) { + mountStoragePointer = new Vector(); + } + + return *mountStoragePointer; + } + + bool isValidMountPath(const string& path) { + if (path.empty()) return false; + if (path[0].value() != '/') return false; + + return true; + } + + bool pathStartsWithMount(const string& path, const string& mountPath) { + if (mountPath == "/") return path.startsWith("/"); + + if (!path.startsWith(mountPath)) return false; + + if (path.length() == mountPath.length()) return true; + + return path[mountPath.length()].value() == '/'; + } + + usize mountMatchLength(const string& path, const string& mountPath) { + if (!pathStartsWithMount(path, mountPath)) return 0; + + return mountPath.length(); + } +} + +namespace vfs { + bool init() { + mountsStorage().clear(); + return true; + } + + + bool mount(const string& path, FileSystem* fs, BlockDevice* device) { + ASSERT(fs != nullptr); + ASSERT(device != nullptr); + ASSERT(isValidMountPath(path)); + + for (usize i = 0; i < mountsStorage().size(); i++) { + if (mountsStorage()[i].value().path == path) { + debug::warn("Cannot mount the filesystem because a disk has already been mounted"); + return false; + }; + } + + if (!fs->mount(device)) { + debug::warn("The filesystem did not allow the mounting of the disk"); + return false; + } + + MountPoint mountPoint; + mountPoint.path = path; + mountPoint.fs = fs; + + mountsStorage().push(mountPoint); + return true; + } + + bool unmount(const string& path) { + for (usize i = 0; i < mountsStorage().size(); i++) { + if (mountsStorage()[i].value().path != path) continue; + + FileSystem* fs = mountsStorage()[i].value().fs; + + if (fs && !fs->unmount()) { + return false; + } + + mountsStorage().remove(i); + return true; + } + + return false; + } + + FileSystem* fileSystemForPath(const string& path) { + FileSystem* best = nullptr; + usize bestLength = 0; + + for (usize i = 0; i < mountsStorage().size(); i++) { + const MountPoint& mountPoint = mountsStorage()[i].value(); + + usize length = mountMatchLength(path, mountPoint.path); + + if (length > bestLength) { + best = mountPoint.fs; + bestLength = length; + } + } + + return best; + } + + string relativePathForMount(const string& path, const string& mountPath) { + if (mountPath == "/") return path; + + if (!pathStartsWithMount(path, mountPath)) return path; + + if (path.length() == mountPath.length()) return "/"; + + string relative; + + for (usize i = mountPath.length(); i < path.length(); i++) { + relative.append(path[i].value()); + } + + if (relative.length() == 0) return "/"; + + return relative; + } + + FileHandle* open(const string& path, VFSOpenMode mode) { + FileSystem* fs = fileSystemForPath(path); + + ASSERT(fs != nullptr); + + string mounthPath = "/"; + + usize bestLength = 0; + for (usize i = 0; i < mountsStorage().size(); i++) { + usize length = mountMatchLength(path, mountsStorage()[i].value().path); + + if (length > bestLength) { + bestLength = length; + mounthPath = mountsStorage()[i].value().path; + } + } + + string relativePath = relativePathForMount(path, mounthPath); + + VFSError error; + FileHandle* handle = fs->open(relativePath, mode, error); + + if (!error.ok()) { + debug::error("Error while opening file of type ", error.code); + return nullptr; + } + + return handle; + } + + DirectoryHandle* openDirectory(const string& path) { + FileSystem* fs = fileSystemForPath(path); + + ASSERT(fs != nullptr); + + string mounthPath = "/"; + + usize bestLength = 0; + for (usize i = 0; i < mountsStorage().size(); i++) { + usize length = mountMatchLength(path, mountsStorage()[i].value().path); + + if (length > bestLength) { + bestLength = length; + mounthPath = mountsStorage()[i].value().path; + } + } + + string relativePath = relativePathForMount(path, mounthPath); + + VFSError error; + DirectoryHandle* handle = fs->openDirectory(relativePath, error); + + if (!error.ok()) { + debug::error("Error while opening directory of type ", error.code); + return nullptr; + } + + return handle; + } + + bool stat(const string& path, VFSStat& outStat) { + FileSystem* fs = fileSystemForPath(path); + + ASSERT(fs != nullptr); + + string mountPath = "/"; + usize bestLength = 0; + for (usize i = 0; i < mountsStorage().size(); i++) { + usize length = mountMatchLength(path, mountsStorage()[i].value().path); + + if (length > bestLength) { + bestLength = length; + mountPath = mountsStorage()[i].value().path; + } + } + + string relativePath = relativePathForMount(path, mountPath); + + VFSError error; + bool result = fs->stat(relativePath, outStat, error); + + return result && error.ok(); + } + + bool exists(const string& path) { + VFSStat st; + return stat(path, st); + } + + bool isFile(const string& path) { + VFSStat st; + + if (!stat(path, st)) return false; + + return st.type == VFSNodeType::File; + } + + bool isDirectory(const string& path) { + VFSStat st; + + if (!stat(path, st)) return false; + + return st.type == VFSNodeType::Directory; + } + + bool createFile(const string& path) { + FileSystem* fs = fileSystemForPath(path); + + if (!fs) + return false; + + string mountPath = "/"; + + usize bestLength = 0; + for (usize i = 0; i < mountsStorage().size(); i++) { + usize length = mountMatchLength(path, mountsStorage()[i].value().path); + + if (length > bestLength) { + bestLength = length; + mountPath = mountsStorage()[i].value().path; + } + } + + string relativePath = relativePathForMount(path, mountPath); + + VFSError error; + return fs->createFile(relativePath, error) && error.ok(); + } + + bool createDirectory(const string& path) { + FileSystem* fs = fileSystemForPath(path); + + if (!fs) + return false; + + string mountPath = "/"; + + usize bestLength = 0; + for (usize i = 0; i < mountsStorage().size(); i++) { + usize length = mountMatchLength(path, mountsStorage()[i].value().path); + + if (length > bestLength) { + bestLength = length; + mountPath = mountsStorage()[i].value().path; + } + } + + string relativePath = relativePathForMount(path, mountPath); + + VFSError error; + return fs->createDirectory(relativePath, error) && error.ok(); + } + + bool remove(const string& path) { + FileSystem* fs = fileSystemForPath(path); + + if (!fs) + return false; + + string mountPath = "/"; + + usize bestLength = 0; + for (usize i = 0; i < mountsStorage().size(); i++) { + usize length = mountMatchLength(path, mountsStorage()[i].value().path); + + if (length > bestLength) { + bestLength = length; + mountPath = mountsStorage()[i].value().path; + } + } + + string relativePath = relativePathForMount(path, mountPath); + + VFSError error; + return fs->remove(relativePath, error) && error.ok(); + } + + bool rename(const string& oldPath, const string& newPath) { + FileSystem* oldFs = fileSystemForPath(oldPath); + FileSystem* newFs = fileSystemForPath(newPath); + + if (!oldFs || !newFs) + return false; + + if (oldFs != newFs) + return false; + + string oldMountPath = "/"; + string newMountPath = "/"; + + usize bestOldLength = 0; + usize bestNewLength = 0; + + for (usize i = 0; i < mountsStorage().size(); i++) { + usize oldLength = mountMatchLength(oldPath, mountsStorage()[i].value().path); + + if (oldLength > bestOldLength) { + bestOldLength = oldLength; + oldMountPath = mountsStorage()[i].value().path; + } + + usize newLength = mountMatchLength(newPath, mountsStorage()[i].value().path); + + if (newLength > bestNewLength) { + bestNewLength = newLength; + newMountPath = mountsStorage()[i].value().path; + } + } + + string relativeOldPath = relativePathForMount(oldPath, oldMountPath); + string relativeNewPath = relativePathForMount(newPath, newMountPath); + + VFSError error; + return oldFs->rename(relativeOldPath, relativeNewPath, error) && error.ok(); + } + + Vector mounts() { + return mountsStorage(); + } +} diff --git a/kernel/main.cpp b/kernel/main.cpp index 8c9dd42..f330e32 100644 --- a/kernel/main.cpp +++ b/kernel/main.cpp @@ -5,6 +5,8 @@ #include "core/regs.h" #include "core/systems.h" #include "drivers/driver.h" +#include "fs/fat32.h" +#include "fs/vfs.h" #include "graphics/framebuffer.h" #include "io/serial.h" #include "kernel/debug.h" @@ -40,6 +42,18 @@ static volatile uint64_t limine_requests_start_marker[] = LIMINE_REQUESTS_START_ __attribute__((used, section(".limine_requests_end"))) static volatile uint64_t limine_requests_end_marker[] = LIMINE_REQUESTS_END_MARKER; +void ls(const string& path) { + DirectoryHandle* dir = vfs::openDirectory(path); + if (!dir) return; + + DirectoryEntry entry; + while (dir->readNext(entry)) { + out::println(entry.name, entry.type == VFSNodeType::Directory ? "/" : ""); + } + + dir->close(); +} + extern "C" [[noreturn]] void _start() { if (LIMINE_BASE_REVISION_SUPPORTED(limine_base_revision) == false) { while (true) { @@ -54,6 +68,8 @@ extern "C" [[noreturn]] void _start() { drivers::init(); debug::log("Drivers initialized"); + vfs::mountRoot(); + Framebuffer framebuffer = Framebuffer::createFromLimineRequest(framebuffer_request); out::initFramebufferConsole(framebuffer); @@ -62,12 +78,19 @@ extern "C" [[noreturn]] void _start() { out::println("The Avery Kernel"); out::println("Version Alpha 1 (Development Edition)"); out::println("Made by Max Van den Eynde in 2026"); + while (true) { string input = in::getLine("> "); if (input == "clear") { out::clear(); continue; + } else if (input.startsWith("ls ")) { + input.removePrefix("ls "); + ls(input); + } else { + out::setColor(Color::red, Color::black); + out::println("Incorrect command, try again"); + out::setColor(Color::white, Color::black); } - out::println(input); } } diff --git a/kernel/utils/types.cpp b/kernel/utils/types.cpp index ff0b3a2..022e663 100644 --- a/kernel/utils/types.cpp +++ b/kernel/utils/types.cpp @@ -205,4 +205,136 @@ extern "C" [[noreturn]] void __cxa_pure_virtual() { } } +extern "C" void* memset(void* dest, int value, usize count) { + auto* p = static_cast(dest); + for (usize i = 0; i < count; i++) { + p[i] = static_cast(value); + } + + return dest; +} + +bool string::startsWith(const string& other) const { + if (len < other.len) return false; + + for (usize i = 0; i < other.len; i++) { + if (data[i] != other.data[i]) return false; + } + + return true; +} + +void string::prepend(char c) { + reserve(len + 1); + + for (usize i = len; i > 0; i--) { + data[i] = data[i - 1]; + } + + data[0] = c; + len++; + data[len] = '\0'; +} + +void string::prepend(cstring text) { + usize textLength = 0; + + while (text[textLength] != '\0') { + textLength++; + } + + if (textLength == 0) { + return; + } + + reserve(len + textLength); + + for (usize i = len; i > 0; i--) { + data[i + textLength - 1] = data[i - 1]; + } + + memory::copy( + reinterpret_cast(data), + reinterpret_cast(text), + static_cast(textLength) + ); + + len += textLength; + data[len] = '\0'; +} + +void string::removePrefix(cstring text) { + if (!text || !data) + return; + + usize prefixLength = 0; + while (text[prefixLength] != '\0') + prefixLength++; + + if (prefixLength == 0 || prefixLength > len) + return; + + for (usize i = 0; i < prefixLength; i++) { + if (data[i] != text[i]) + return; + } + + for (usize i = 0; i <= len - prefixLength; i++) + data[i] = data[i + prefixLength]; + + len -= prefixLength; +} + +void string::removeSuffix(cstring text) { + if (!text || !data) + return; + + usize suffixLength = 0; + while (text[suffixLength] != '\0') + suffixLength++; + + if (suffixLength == 0 || suffixLength > len) + return; + + usize start = len - suffixLength; + + for (usize i = 0; i < suffixLength; i++) { + if (data[start + i] != text[i]) + return; + } + + data[start] = '\0'; + len -= suffixLength; +} + +void string::trim() { + if (!data || len == 0) + return; + + auto isSpace = [](char c) + { + return c == ' ' || + c == '\n' || + c == '\r' || + c == '\t' || + c == '\v' || + c == '\f'; + }; + + usize start = 0; + while (start < len && isSpace(data[start])) + start++; + + usize end = len; + while (end > start && isSpace(data[end - 1])) + end--; + + usize newLength = end - start; + + for (usize i = 0; i < newLength; i++) + data[i] = data[start + i]; + + data[newLength] = '\0'; + len = newLength; +} diff --git a/scripts/makeDisk.sh b/scripts/makeDisk.sh new file mode 100644 index 0000000..1da7ab2 --- /dev/null +++ b/scripts/makeDisk.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +rm disk.img +qemu-img create -f raw disk.img 64M + +mkfs.fat -F 32 disk.img +mdir -i disk.img :: diff --git a/scripts/run.sh b/scripts/run.sh index 6106034..164dcab 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -9,4 +9,5 @@ QEMU="$HOME/opt/qemu-custom/bin/qemu-system-x86_64" -boot d \ -m 256M \ -debugcon stdio \ - -display cocoa \ No newline at end of file + -display cocoa \ + -drive file=disk.img,format=raw,if=ide,index=0,media=disk \ No newline at end of file