fix(kernel/vfs): 🎉 a complete rewrite of the vfs

This is the fourth time re-writing the VFS, hope this will be the last. Tried to make it as modular as possible so this won't be necessary in the future. 🙏

This change required the entire kernel code to be modified.
This commit is contained in:
2025-05-13 15:59:12 +00:00
parent 83a7f83f81
commit 557c7e6235
83 changed files with 3252 additions and 2487 deletions

View File

@ -22,7 +22,8 @@
#include <interface/driver.h>
#include <interface/input.h>
#include <filesystem.hpp>
#include <interface/block.h>
#include <fs/vfs.hpp>
#include <unordered_map>
#include <memory.hpp>
#include <ints.hpp>
@ -120,17 +121,15 @@ namespace Driver
* 1 - input/... devices
*/
dev_t DriverIDCounter = 2;
FileNode *devNode = nullptr;
FileNode *devInputNode = nullptr;
Node devNode = nullptr;
Node devInputNode = nullptr;
Node devBlockNode = nullptr;
bool IsDriverTrusted(FileNode *File);
int LoadDriverFile(DriverObject &Drv, FileNode *File);
bool IsDriverTrusted(Node File);
int LoadDriverFile(DriverObject &Drv, Node File);
void ReloadDriver(dev_t driverID);
void InitializeDaemonFS();
dev_t RegisterInputDevice(std::unordered_map<dev_t, DriverHandlers> *, dev_t, size_t, const InodeOperations *);
dev_t RegisterBlockDevice(std::unordered_map<dev_t, DriverHandlers> *, dev_t, size_t, const InodeOperations *);
void InitializeDeviceDirectory();
public:
RingBuffer<KeyboardReport> GlobalKeyboardInputReports;
@ -138,11 +137,16 @@ namespace Driver
struct DeviceInode
{
struct Inode Node;
FileNode *Parent;
struct Inode inode;
Node Parent;
Inode *ParentInode;
std::string Name;
std::vector<DeviceInode *> Children;
size_t Size;
time_t AccessTime, ModifyTime, ChangeTime;
uint32_t BlockSize;
uint32_t Blocks;
};
std::unordered_map<dev_t, DriverObject> &
@ -185,6 +189,9 @@ namespace Driver
int ReportInputEvent(dev_t DriverID, InputReport *Report);
int UnregisterDevice(dev_t DriverID, dev_t Device);
dev_t RegisterBlockDevice(dev_t DriverID, struct BlockDevice *Device);
int UnregisterBlockDevice(dev_t DriverID, dev_t DeviceID);
void *AllocateMemory(dev_t DriverID, size_t Pages);
void FreeMemory(dev_t DriverID, void *Pointer, size_t Pages);
@ -193,8 +200,6 @@ namespace Driver
private:
~Manager();
};
void ManagerDaemonWrapper();
}
void *GetSymbolByName(const char *Name, int Version);
@ -215,9 +220,12 @@ namespace v0
int UnregisterInterruptHandler(dev_t DriverID, uint8_t IRQ, void *Handler);
int UnregisterAllInterruptHandlers(dev_t DriverID, void *Handler);
dev_t RegisterFileSystem(dev_t DriverID, FileSystemInfo *Info, struct Inode *Root);
dev_t RegisterFileSystem(dev_t DriverID, FileSystemInfo *Info);
int UnregisterFileSystem(dev_t DriverID, dev_t Device);
dev_t RegisterBlockDevice(dev_t DriverID, struct BlockDevice *Device);
int UnregisterBlockDevice(dev_t DriverID, dev_t DeviceID);
pid_t CreateKernelProcess(dev_t DriverID, const char *Name);
pid_t CreateKernelThread(dev_t DriverID, pid_t pId, const char *Name, void *EntryPoint, void *Argument);
pid_t GetCurrentProcess(dev_t DriverID);

View File

@ -20,7 +20,7 @@
#include <types.h>
#include <filesystem.hpp>
#include <fs/vfs.hpp>
#include <task.hpp>
#include <errno.h>
#include <vector>
@ -50,15 +50,15 @@ namespace Execute
void *ELFProgramHeaders;
void GenerateAuxiliaryVector(Memory::VirtualMemoryArea *vma,
FileNode *fd, Elf_Ehdr ELFHeader,
Node &fd, Elf_Ehdr ELFHeader,
uintptr_t EntryPoint,
uintptr_t BaseAddress);
void LoadSegments(FileNode *fd, Tasking::PCB *TargetProcess, Elf_Ehdr &ELFHeader, uintptr_t &BaseAddress);
void LoadSegments(Node &fd, Tasking::PCB *TargetProcess, Elf_Ehdr &ELFHeader, uintptr_t &BaseAddress);
void LoadExec(FileNode *fd, Tasking::PCB *TargetProcess);
void LoadDyn(FileNode *fd, Tasking::PCB *TargetProcess);
bool LoadInterpreter(FileNode *fd, Tasking::PCB *TargetProcess);
void LoadExec(Node &fd, Tasking::PCB *TargetProcess);
void LoadDyn(Node &fd, Tasking::PCB *TargetProcess);
bool LoadInterpreter(Node &fd, Tasking::PCB *TargetProcess);
public:
decltype(IsElfValid) &IsValid = IsElfValid;
@ -74,7 +74,7 @@ namespace Execute
~ELFObject();
};
BinaryType GetBinaryType(FileNode *Path);
BinaryType GetBinaryType(Node &Path);
BinaryType GetBinaryType(std::string Path);
int Spawn(const char *Path, const char **argv, const char **envp,
@ -88,12 +88,12 @@ namespace Execute
char *GetELFStringTable(Elf_Ehdr *Header);
char *ELFLookupString(Elf_Ehdr *Header, uintptr_t Offset);
Elf_Sym *ELFLookupSymbol(Elf_Ehdr *Header, std::string Name);
Elf_Sym ELFLookupSymbol(FileNode *fd, std::string Name);
Elf_Sym ELFLookupSymbol(Node &fd, std::string Name);
uintptr_t ELFGetSymbolValue(Elf_Ehdr *Header, uintptr_t Table, uintptr_t Index);
std::vector<Elf_Phdr> ELFGetSymbolType(FileNode *fd, SegmentTypes Tag);
std::vector<Elf_Shdr> ELFGetSections(FileNode *fd, std::string SectionName);
std::vector<Elf_Dyn> ELFGetDynamicTag(FileNode *fd, DynamicArrayTags Tag);
std::vector<Elf_Phdr> ELFGetSymbolType(Node &fd, SegmentTypes Tag);
std::vector<Elf_Shdr> ELFGetSections(Node &fd, std::string SectionName);
std::vector<Elf_Dyn> ELFGetDynamicTag(Node &fd, DynamicArrayTags Tag);
}
#endif // !__FENNIX_KERNEL_FILE_EXECUTE_H__

View File

@ -1,260 +0,0 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
Fennix Kernel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef __FENNIX_KERNEL_FILESYSTEM_H__
#define __FENNIX_KERNEL_FILESYSTEM_H__
#include <types.h>
#include <interface/fs.h>
#include <unordered_map>
#include <lock.hpp>
#include <errno.h>
#include <atomic>
#include <string>
#include <list>
static_assert(DTTOIF(DT_FIFO) == S_IFIFO);
static_assert(IFTODT(S_IFCHR) == DT_CHR);
/**
* This macro is used to check if a filesystem operation is available.
*
* TL;DR
*
* @code
* if FileSystemInfo.Ops.op == nullptr
* return -err
* else
* return FileSystemInfo.Ops.op(this->Node, ...);
* @endcode
*
* @param op The operation to check.
* @param err The error to return if the operation is not available.
* @param ... The arguments to pass to the operation.
*
* @return The result of the operation.
*/
#define __check_op(op, err, ...) \
if (fsi->Ops.op == nullptr) \
return -err; \
else \
return fsi->Ops.op(this->Node, ##__VA_ARGS__)
#define FSROOT(num) "\x06root-" #num "\x06"
class FileNode
{
public:
std::string Name, Path;
FileNode *Parent;
std::vector<FileNode *> Children;
Inode *Node;
FileSystemInfo *fsi;
std::string GetName();
std::string GetPath();
bool IsDirectory() { return S_ISDIR(Node->Mode); }
bool IsCharacterDevice() { return S_ISCHR(Node->Mode); }
bool IsBlockDevice() { return S_ISBLK(Node->Mode); }
bool IsRegularFile() { return S_ISREG(Node->Mode); }
bool IsFIFO() { return S_ISFIFO(Node->Mode); }
bool IsSymbolicLink() { return S_ISLNK(Node->Mode); }
bool IsSocket() { return S_ISSOCK(Node->Mode); }
int Lookup(const char *Name, Inode **Node) { __check_op(Lookup, ENOTSUP, Name, Node); }
int Create(const char *Name, mode_t Mode, Inode **Node) { __check_op(Create, EROFS, Name, Mode, Node); }
int Remove(const char *Name) { __check_op(Remove, EROFS, Name); }
int Rename(const char *OldName, const char *NewName) { __check_op(Rename, EROFS, OldName, NewName); }
ssize_t Read(auto Buffer, size_t Size, off_t Offset) { __check_op(Read, ENOTSUP, (void *)Buffer, Size, Offset); }
ssize_t Write(const auto Buffer, size_t Size, off_t Offset) { __check_op(Write, EROFS, (const void *)Buffer, Size, Offset); }
int Truncate(off_t Size) { __check_op(Truncate, EROFS, Size); }
int Open(int Flags, mode_t Mode) { __check_op(Open, ENOTSUP, Flags, Mode); }
int Close() { __check_op(Close, ENOTSUP); }
int Ioctl(unsigned long Request, void *Argp) { __check_op(Ioctl, ENOTSUP, Request, Argp); }
ssize_t ReadDir(struct kdirent *Buffer, size_t Size, off_t Offset, off_t Entries) { __check_op(ReadDir, ENOTSUP, Buffer, Size, Offset, Entries); }
int MkDir(const char *Name, mode_t Mode, struct Inode **Result) { __check_op(MkDir, EROFS, Name, Mode, Result); }
int RmDir(const char *Name) { __check_op(RmDir, EROFS, Name); }
int SymLink(const char *Name, const char *Target, struct Inode **Result) { __check_op(SymLink, EROFS, Name, Target, Result); }
ssize_t ReadLink(auto Buffer, size_t Size) { __check_op(ReadLink, ENOTSUP, (char *)Buffer, Size); }
off_t Seek(off_t Offset) { __check_op(Seek, ENOTSUP, Offset); }
int Stat(struct kstat *Stat) { __check_op(Stat, ENOTSUP, Stat); }
~FileNode() = delete;
};
#undef __check_op
namespace vfs
{
struct vfsInode
{
Inode Node;
std::string Name;
std::string FriendlyName;
std::vector<Inode *> Children;
};
class Virtual
{
private:
NewLock(VirtualLock);
struct FSMountInfo
{
FileSystemInfo *fsi;
Inode *Root;
};
struct CacheNode
{
FileNode *fn;
std::atomic_int References;
};
std::unordered_map<dev_t, FSMountInfo> DeviceMap;
std::atomic_bool RegisterLock = false;
FileNode *CacheSearchReturnLast(FileNode *Parent, const char **Path);
FileNode *CacheRecursiveSearch(FileNode *Root, const char *NameOrPath, bool IsName);
FileNode *CacheLookup(FileNode *Parent, const char *Path);
FileNode *CreateCacheNode(FileNode *Parent, Inode *Node, const char *Name, mode_t Mode);
int RemoveCacheNode(FileNode *Node);
public:
vfsInode *FileSystemRoots = nullptr;
/**
* Default reserved roots:
*
* 0 - Native
* 1 - Linux
* 2 - Windows
*/
std::unordered_map<ino_t, FileNode *> FileRoots;
bool PathIsRelative(const char *Path);
bool PathIsAbsolute(const char *Path) { return !PathIsRelative(Path); }
/**
* Reserve a device number for a filesystem
*
* @note After this function is called, the filesystem must
* call LateRegisterFileSystem to release the lock
*/
dev_t EarlyReserveDevice();
/**
* Register a filesystem after the device number has been reserved
*/
int LateRegisterFileSystem(dev_t Device, FileSystemInfo *fsi, Inode *Root);
dev_t RegisterFileSystem(FileSystemInfo *fsi, Inode *Root);
int UnregisterFileSystem(dev_t Device);
void AddRoot(Inode *Root);
void AddRootAt(Inode *Root, size_t Index);
bool SetRootAt(Inode *Root, size_t Index);
void RemoveRoot(Inode *Root);
FileNode *GetRoot(size_t Index);
bool RootExists(size_t Index);
FileNode *Create(FileNode *Parent, const char *Name, mode_t Mode);
FileNode *ForceCreate(FileNode *Parent, const char *Name, mode_t Mode);
FileNode *Mount(FileNode *Parent, Inode *Node, const char *Path);
int Unmount(const char *Path);
FileNode *GetByPath(const char *Path, FileNode *Parent);
std::string GetByNode(FileNode *Node);
FileNode *CreateLink(const char *Path, FileNode *Parent, const char *Target);
FileNode *CreateLink(const char *Path, FileNode *Parent, FileNode *Target);
bool PathExists(const char *Path, FileNode *Parent);
int Remove(FileNode *Node);
void Initialize();
Virtual();
~Virtual();
};
class FileDescriptorTable
{
public:
struct Fildes
{
enum FildesType
{
FD_INODE,
FD_PIPE,
FD_SOCKET,
} Type;
mode_t Mode = 0;
int Flags = 0;
FileNode *Node = nullptr;
int References = 0;
off_t Offset = 0;
int operator==(const Fildes &other)
{
return Type == other.Type &&
Mode == other.Mode &&
Flags == other.Flags &&
Node == other.Node &&
References == other.References &&
Offset == other.Offset;
}
};
private:
FileNode *fdDir = nullptr;
void *Owner;
int AddFileDescriptor(const char *AbsolutePath, mode_t Mode, int Flags);
int RemoveFileDescriptor(int FileDescriptor);
int GetFreeFileDescriptor();
public:
std::unordered_map<int, Fildes> FileMap;
int GetFlags(int FileDescriptor);
int SetFlags(int FileDescriptor, int Flags);
void Fork(FileDescriptorTable *Parent);
int usr_open(const char *pathname, int flags, mode_t mode);
int usr_creat(const char *pathname, mode_t mode);
ssize_t usr_read(int fd, void *buf, size_t count);
ssize_t usr_write(int fd, const void *buf, size_t count);
ssize_t usr_pread(int fd, void *buf, size_t count, off_t offset);
ssize_t usr_pwrite(int fd, const void *buf, size_t count, off_t offset);
int usr_close(int fd);
off_t usr_lseek(int fd, off_t offset, int whence);
int usr_stat(const char *pathname, struct kstat *statbuf);
int usr_fstat(int fd, struct kstat *statbuf);
int usr_lstat(const char *pathname, struct kstat *statbuf);
int usr_dup(int oldfd);
int usr_dup2(int oldfd, int newfd);
int usr_ioctl(int fd, unsigned long request, void *argp);
FileDescriptorTable(void *Owner);
~FileDescriptorTable() = default;
};
}
#endif // !__FENNIX_KERNEL_FILESYSTEM_H__

View File

@ -1,91 +0,0 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
Fennix Kernel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef __FENNIX_KERNEL_FILESYSTEM_EXT2_H__
#define __FENNIX_KERNEL_FILESYSTEM_EXT2_H__
#include <types.h>
#include <filesystem.hpp>
namespace vfs
{
class EXT2
{
public:
struct SuperBlock
{
uint32_t Inodes;
uint32_t Blocks;
uint32_t ReservedBlocks;
uint32_t FreeBlock;
uint32_t FreeInodes;
uint32_t FirstDataBlock;
uint32_t LogBlockSize;
uint32_t LogFragSize;
uint32_t BlocksPerGroup;
uint32_t FragsPerGroup;
uint32_t InodesPerGroup;
uint32_t LastMountTime;
uint32_t LastWrittenTime;
uint16_t MountedTimes;
uint16_t MaximumMountedTimes;
uint16_t Magic;
uint16_t State;
uint16_t Errors;
uint16_t MinorRevLevel;
uint32_t LastCheck;
uint32_t CheckInternval;
uint32_t SystemID;
uint32_t RevLevel;
uint16_t ReservedBlocksUserID;
uint16_t ReservedBlocksGroupID;
uint32_t FirstInode;
uint16_t InodeSize;
uint16_t BlockGroups;
uint32_t FeatureCompatibility;
uint32_t FeatureIncompatibility;
uint32_t FeatureRoCompatibility;
uint8_t UUID[16];
char VolumeName[16];
char LastMounted[64];
uint32_t BitmapAlogrithm;
uint8_t PreallocatedBlocks;
uint8_t PreallocatedDirectoryBlocks;
uint16_t Padding;
uint8_t JournalUUID[16];
uint32_t JournalInum;
uint32_t JournalDev;
uint32_t LastOrphan;
uint32_t HashSeed[4];
uint8_t DefHashVersion;
uint8_t ReservedCharPad;
uint16_t ReservedWordPad;
uint32_t DefaultMountOptions;
uint32_t FirstMetaBg;
uint32_t Reserved[190];
};
EXT2(void *partition);
~EXT2();
};
}
#endif // !__FENNIX_KERNEL_FILESYSTEM_EXT2_H__

View File

@ -1,48 +0,0 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
Fennix Kernel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef __FENNIX_KERNEL_FILESYSTEM_INITRD_H__
#define __FENNIX_KERNEL_FILESYSTEM_INITRD_H__
#include <types.h>
#include <filesystem.hpp>
namespace vfs
{
class Initrd
{
public:
struct InitrdHeader
{
uint32_t nfiles;
};
struct InitrdFileHeader
{
uint8_t magic;
char name[64];
uint32_t offset;
uint32_t length;
};
Initrd(uintptr_t Address);
~Initrd();
};
}
#endif // !__FENNIX_KERNEL_FILESYSTEM_INITRD_H__

86
Kernel/include/fs/fdt.hpp Normal file
View File

@ -0,0 +1,86 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
Fennix Kernel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <fs/node.hpp>
#include <unordered_map>
namespace vfs
{
class FileDescriptorTable
{
public:
struct Fildes
{
enum FildesType
{
FD_INODE,
FD_PIPE,
FD_SOCKET,
} Type;
mode_t Mode = 0;
int Flags = 0;
Node node = nullptr;
int References = 0;
off_t Offset = 0;
int operator==(const Fildes &other)
{
return Type == other.Type &&
Mode == other.Mode &&
Flags == other.Flags &&
node.get() == other.node.get() &&
References == other.References &&
Offset == other.Offset;
}
};
private:
Node fdDir = nullptr;
void *Owner;
int AddFileDescriptor(const char *AbsolutePath, mode_t Mode, int Flags);
int RemoveFileDescriptor(int FileDescriptor);
int GetFreeFileDescriptor();
public:
std::unordered_map<int, Fildes> FileMap;
int GetFlags(int FileDescriptor);
int SetFlags(int FileDescriptor, int Flags);
void Fork(FileDescriptorTable *Parent);
int usr_open(const char *pathname, int flags, mode_t mode);
int usr_creat(const char *pathname, mode_t mode);
ssize_t usr_read(int fd, void *buf, size_t count);
ssize_t usr_write(int fd, const void *buf, size_t count);
ssize_t usr_pread(int fd, void *buf, size_t count, off_t offset);
ssize_t usr_pwrite(int fd, const void *buf, size_t count, off_t offset);
int usr_close(int fd);
off_t usr_lseek(int fd, off_t offset, int whence);
int usr_stat(const char *pathname, kstat *statbuf);
int usr_fstat(int fd, kstat *statbuf);
int usr_lstat(const char *pathname, kstat *statbuf);
int usr_dup(int oldfd);
int usr_dup2(int oldfd, int newfd);
int usr_ioctl(int fd, unsigned long request, void *argp);
FileDescriptorTable(void *Owner);
~FileDescriptorTable() = default;
};
}

207
Kernel/include/fs/node.hpp Normal file
View File

@ -0,0 +1,207 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
Fennix Kernel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <interface/fs.h>
#include <errno.h>
#include <string>
#include <vector>
/**
* This macro is used to check if a filesystem operation is available.
*
* TL;DR
*
* @code
* if FileSystemInfo.Ops.op == nullptr
* return -err
* else
* return FileSystemInfo.Ops.op(this->Node, ...);
* @endcode
*
* @param op The operation to check.
* @param err The error to return if the operation is not available.
* @param ... The arguments to pass to the operation.
*
* @return The result of the operation.
*/
#define __check_op(op, err, ...) \
if (unlikely(fsi->Ops.op == nullptr)) \
return -err; \
else \
return fsi->Ops.op(this->inode, ##__VA_ARGS__)
namespace vfs
{
class NodeObject
{
public:
Inode *inode;
FileSystemInfo *fsi;
union
{
struct
{
uint8_t MountPoint : 1;
uint8_t __reserved : 7;
};
uint8_t raw;
} Flags;
int __Lookup(const char *Name, Inode **Node) { __check_op(Lookup, ENOTSUP, Name, Node); }
int __Create(const char *Name, mode_t Mode, Inode **Node) { __check_op(Create, ENOTSUP, Name, Mode, Node); }
int __Remove(const char *Name) { __check_op(Remove, ENOTSUP, Name); }
int __Rename(const char *OldName, const char *NewName) { __check_op(Rename, ENOTSUP, OldName, NewName); }
ssize_t __Read(auto Buffer, size_t Size, off_t Offset) { __check_op(Read, ENOTSUP, (void *)Buffer, Size, Offset); }
ssize_t __Write(const auto Buffer, size_t Size, off_t Offset) { __check_op(Write, ENOTSUP, (const void *)Buffer, Size, Offset); }
int __Truncate(off_t Size) { __check_op(Truncate, ENOTSUP, Size); }
int __Open(int Flags, mode_t Mode) { __check_op(Open, ENOTSUP, Flags, Mode); }
int __Close() { __check_op(Close, ENOTSUP); }
int __Ioctl(unsigned long Request, void *Argp) { __check_op(Ioctl, ENOTSUP, Request, Argp); }
ssize_t __ReadDir(struct kdirent *Buffer, size_t Size, off_t Offset, off_t Entries) { __check_op(ReadDir, ENOTSUP, Buffer, Size, Offset, Entries); }
int __MkDir(const char *Name, mode_t Mode, struct Inode **Result) { __check_op(MkDir, ENOTSUP, Name, Mode, Result); }
int __RmDir(const char *Name) { __check_op(RmDir, ENOTSUP, Name); }
int __SymLink(const char *Name, const char *Target, struct Inode **Result) { __check_op(SymLink, ENOTSUP, Name, Target, Result); }
ssize_t __ReadLink(auto Buffer, size_t Size) { __check_op(ReadLink, ENOTSUP, (char *)Buffer, Size); }
off_t __Seek(off_t Offset) { __check_op(Seek, ENOTSUP, Offset); }
int __Stat(struct kstat *Stat) { __check_op(Stat, ENOTSUP, Stat); }
~NodeObject()
{
debug("%#lx destructor called", this);
}
};
}
class NodeCache;
/**
* @brief Node is a type that represents a filesystem node.
* It is a shared pointer to a NodeCache object.
*
* If the refcount of the NodeCache object goes to zero, the data is synced to disk.
*/
typedef std::shared_ptr<NodeCache> Node;
/**
* @brief NodeResult is a type that represents the result of a filesystem operation.
* It contains a Node object and an error code.
*/
typedef struct NodeResult
{
/* Value must be the first member of the struct to allow for implicit conversion */
Node Value;
int Error;
operator bool() const { return Error == 0 && Value.get() != nullptr; }
operator Node() const { return Value; }
const char *what() const { return strerror(Error); }
} eNode;
class NodeCache : public vfs::NodeObject
{
public:
std::string Name, Path, Link;
/**
* @brief Parent of this node
*
* Maximum depth is 1, otherwise undefined behavior.
*/
Node Parent;
/**
* @brief Childrens of this node
*
* On access, children are loaded, but not children of children!
* Accessing children of children is undefined behavior.
*/
std::vector<Node> Children;
std::string GetName() { return Name; }
std::string GetPath() { return Path; }
std::string GetLink() { return Link; }
bool IsDirectory() { return S_ISDIR(inode->Mode); }
bool IsCharacterDevice() { return S_ISCHR(inode->Mode); }
bool IsBlockDevice() { return S_ISBLK(inode->Mode); }
bool IsRegularFile() { return S_ISREG(inode->Mode); }
bool IsFIFO() { return S_ISFIFO(inode->Mode); }
bool IsSymbolicLink() { return S_ISLNK(inode->Mode); }
bool IsSocket() { return S_ISSOCK(inode->Mode); }
bool IsMountPoint() { return Flags.MountPoint; }
/**
* @brief Search through the cached children of this node
*
* Searches through the cached children of this node.
*
* @param Name The name to search for
* @return 0 and a Node on success, ENOENT on failure
*/
eNode CachedSearch(std::string Name)
{
for (auto it = Children.begin(); it != Children.end(); ++it)
{
if ((*it).get() == nullptr)
{
Children.erase(it);
continue;
}
debug("comparing \"%s\" with \"%s\"", (*it)->Name.c_str(), Name.c_str());
if ((*it)->Name == Name)
{
debug("\"%s\" found", Name.c_str());
return {*it, 0};
}
}
return {nullptr, ENOENT};
}
/**
* @brief Get the allocation size of this object
*
* @return The allocated size
*/
size_t GetAllocationSize()
{
size_t size = sizeof(NodeCache);
size += Name.capacity();
size += Path.capacity();
size += Link.capacity();
size += Children.capacity();
return size;
}
~NodeCache()
{
debug("%#lx\"%s\" destructor called", this, Name.c_str());
if (this->Parent)
this->Parent->Children.erase(std::remove(this->Parent->Children.begin(), this->Parent->Children.end(), Node(this)), this->Parent->Children.end());
if (fsi->SuperOps.Synchronize)
fsi->SuperOps.Synchronize(this->fsi, this->inode);
// FIXME: recursive deletion of nodes children
}
};
#undef __check_op

View File

@ -17,7 +17,7 @@
#pragma once
#include <filesystem.hpp>
#include <fs/vfs.hpp>
#include <memory.hpp>
namespace vfs
@ -147,4 +147,4 @@ namespace vfs
};
}
bool MountRAMFS(FileNode *Parent, const char *Name, size_t Index);
bool MountAndRootRAMFS(Node Parent, const char *Name, size_t Index);

View File

@ -18,7 +18,7 @@
#ifndef __FENNIX_KERNEL_FILESYSTEM_USTAR_H__
#define __FENNIX_KERNEL_FILESYSTEM_USTAR_H__
#include <filesystem.hpp>
#include <fs/vfs.hpp>
namespace vfs
{

179
Kernel/include/fs/vfs.hpp Normal file
View File

@ -0,0 +1,179 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
Fennix Kernel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <fs/fdt.hpp>
#include <errno.h>
#include <cwalk.h>
#include <atomic>
#include <string>
#include <list>
/* sanity checks */
static_assert(DTTOIF(DT_FIFO) == S_IFIFO);
static_assert(IFTODT(S_IFCHR) == DT_CHR);
namespace vfs
{
class Virtual
{
private:
std::unordered_map<dev_t, Node> Roots;
std::unordered_map<dev_t, FileSystemInfo *> FileSystems;
public:
#pragma region Utilities
inline bool PathIsRelative(const char *Path) { return cwk_path_is_relative(Path); }
inline bool PathIsAbsolute(const char *Path) { return !PathIsRelative(Path); }
inline bool PathIsRelative(std::string &Path) { return cwk_path_is_relative(Path.c_str()); }
inline bool PathIsAbsolute(std::string &Path) { return !PathIsRelative(Path.c_str()); }
eNode Convert(Node node) { return {node, 0}; }
/**
* @brief Converts an Inode to a Node
*
* Result of this function will be empty.
* You are responsible to populate the Node.
*
* @param inode Inode to convert
* @return eNode{Node, errno}
*/
eNode Convert(Inode *inode);
/**
* @brief Converts an Inode to a Node
*
* Result of this function will be populated using the Parent Node.
* This function will automatically assign the FSI, parents and children.
*
* @note Name and Path won't be set.
*
* @param Parent Parent Node
* @param inode Inode to convert
* @return eNode{Node, errno}
*/
eNode Convert(Node &Parent, Inode *inode);
/**
* @brief Normalizes a path
*
* If the path is relative, it will be normalized using the Parent Node.
* Example if Join is true:
* Parent = /home/user/Desktop
* Path = ../Documents
* Result = /home/user/Documents
*
* If Join is false:
* Path = /var/foo/bar/../../
* Result = /var
*
* @param Parent Parent Node
* @param Path Path to normalize
* @param Join If true, the path will be joined with the Parent Node
* @return Normalized Path
*/
std::string NormalizePath(Node &Parent, std::string Path, bool Join = false);
#pragma endregion Utilities
#pragma region Roots
bool RootExists(dev_t Index);
eNode GetRoot(dev_t Index);
ssize_t GetRoot(Node Index);
int AddRoot(dev_t Index, Node Root, bool Replace = false);
int AddRoot(dev_t Index, eNode Root, bool Replace = false) { return AddRoot(Index, Root.Value, Replace); }
#pragma endregion Roots
#pragma region Registrations
dev_t RegisterFileSystem(FileSystemInfo *fsi);
int UnregisterFileSystem(dev_t Device);
#pragma endregion Registrations
#pragma region Node Operations
eNode Lookup(Node &Parent, std::string Path);
eNode Create(Node &Parent, std::string Name, mode_t Mode, bool ErrorIfExists = true);
int Remove(Node &Parent, std::string Name);
int Remove(Node &node);
int Rename(Node &node, std::string NewName);
ssize_t Read(Node &Target, void *Buffer, size_t Size, off_t Offset);
ssize_t Write(Node &Target, const void *Buffer, size_t Size, off_t Offset);
int Truncate(Node &Target, off_t Size);
/**
* @brief Read directory entries
*
* @note This function includes "." and ".."
*
* @param Target
* @param Buffer
* @param Size
* @param Offset
* @param Entries
* @return
*/
ssize_t ReadDirectory(Node &Target, kdirent *Buffer, size_t Size, off_t Offset, off_t Entries);
/**
* @brief Read directory entries
*
* @note This function does NOT include "." and ".."
*
* @param Target
* @return
*/
std::list<Node> ReadDirectory(Node &Target);
eNode CreateLink(Node &Parent, std::string Name, std::string Target);
eNode CreateLink(Node &Parent, std::string Name, Node &Target) { return this->CreateLink(Parent, Name, Target->Path); }
int Stat(Node &Target, kstat *Stat);
int ReadLink(Node &Target, char *Buffer, size_t Size) { return Target->__ReadLink(Buffer, Size); }
off_t Seek(Node &Target, off_t Offset);
int Open(Node &Target, int Flags, mode_t Mode);
int Close(Node &Target);
int Ioctl(Node &Target, unsigned long Request, void *Argp) { return Target->__Ioctl(Request, Argp); }
#pragma endregion Node Operations
#pragma region Mounting
eNode Mount(Node &Parent, Inode *inode, std::string Name, FileSystemInfo *fsi);
int Umount(Node &node);
int Umount(Node &Parent, std::string Name);
#pragma endregion Mounting
void Initialize();
Virtual();
~Virtual();
};
}

View File

@ -0,0 +1,110 @@
/*
This file is part of Fennix Kernel.
Fennix Kernel is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
Fennix Kernel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef __FENNIX_API_BLOCK_H__
#define __FENNIX_API_BLOCK_H__
#include <types.h>
#if __has_include(<interface/fs.h>)
#include <interface/fs.h>
#else
#include <fs.h>
#endif
struct BlockDevice
{
/**
* @brief Base name of the device.
*
* This name is used to identify the device in the system. It should be unique
* across all block devices. The kernel may append a number to this name to
* create a unique device name (e.g., "ahci0", "ahci1").
*/
const char *Name;
/**
* @brief Total size of the device in bytes.
*
* This value represents the total addressable storage capacity of the device.
* It is used for bounds checking and partitioning.
*/
size_t Size;
/**
* @brief Size of a single block in bytes.
*
* All read and write operations are performed in multiples of this block size.
* Typical values are 512 or 4096 bytes.
*/
uint32_t BlockSize;
/**
* @brief Number of blocks in the device.
*
* This value is calculated as Size / BlockSize. It represents the total number
* of addressable blocks on the device.
*/
size_t BlockCount;
/**
* @brief Pointer to the block device operations structure.
*
* This structure contains function pointers for various operations that can
* be performed on the block device, such as read, write, and ioctl.
*
* Yea, inode operations are used for block devices too.
*/
const InodeOperations *Ops;
/**
* @brief Opaque pointer to driver-specific or hardware-specific data.
*
* This field allows the driver to associate private context or state with the
* device, such as controller registers or internal buffers.
*/
void *PrivateData;
};
#ifndef __kernel__
/**
* @brief Registers a block device with the kernel block subsystem.
*
* This function should be called by block device drivers after initializing
* a device. The kernel will take ownership of the device structure and assign
* it a unique device ID. The device will then be accessible for filesystem
* mounting and I/O operations.
*
* @param Device Pointer to a fully initialized BlockDevice structure. All required fields must be set and valid for the lifetime of the device.
* @return Device ID (dev_t) assigned by the kernel on success, or an error code on failure.
*/
dev_t RegisterBlockDevice(struct BlockDevice *Device);
/**
* @brief Unregisters a block device from the kernel block subsystem.
*
* This function should be called by drivers when a device is being removed
* or is no longer available. The kernel will release any resources associated
* with the device and invalidate its device ID.
*
* @param DeviceID The device ID (dev_t) previously returned by RegisterBlockDevice().
* @return 0 on success, or an error code.
*/
int UnregisterBlockDevice(dev_t DeviceID);
#endif // __kernel__
#endif // __FENNIX_API_BLOCK_H__

View File

@ -322,10 +322,6 @@ struct InodeOperations
int (*Stat)(struct Inode *Node, struct kstat *Stat);
} __attribute__((packed));
#define I_FLAG_ROOT 0x1
#define I_FLAG_MOUNTPOINT 0x2
#define I_FLAG_CACHE_KEEP 0x4
struct FileSystemInfo;
struct SuperBlockOperations
{
@ -337,8 +333,8 @@ struct SuperBlockOperations
*
* Write all pending changes to the disk.
*
* @param Info Inode to synchronize. If NULL, synchronize all inodes.
* @param Node Inode to synchronize.
* @param Info Inode to synchronize.
* @param Node Inode to synchronize. If NULL, synchronize all inodes.
*
* @return Zero on success, otherwise an error code.
*/
@ -354,13 +350,50 @@ struct SuperBlockOperations
* @return Zero on success, otherwise an error code.
*/
int (*Destroy)(struct FileSystemInfo *Info);
/**
* Probe the filesystem.
*
* Check if the filesystem is supported by the driver.
*
* @param Device Device to probe.
*
* @return Zero on success, otherwise an error code.
*/
int (*Probe)(void *Device);
/**
* Mount the filesystem.
*
* Mount the filesystem on the given device.
*
* @param FS Filesystem to mount.
* @param Root Pointer to the root inode.
* @param Device Device to mount.
*
* @return Zero on success, otherwise an error code.
*/
int (*Mount)(struct FileSystemInfo *FS, struct Inode **Root, void *Device);
/**
* Unmount the filesystem.
*
* Unmount the filesystem from the given device.
*
* @param FS Filesystem to unmount.
*
* @return Zero on success, otherwise an error code.
*/
int (*Unmount)(struct FileSystemInfo *FS);
} __attribute__((packed));
struct FileSystemInfo
{
const char *Name;
const char *RootName;
int Flags;
int Capabilities;
struct SuperBlockOperations SuperOps;
struct InodeOperations Ops;
@ -368,6 +401,9 @@ struct FileSystemInfo
} __attribute__((packed));
#ifndef __kernel__
dev_t RegisterMountPoint(FileSystemInfo *fsi, Inode *Root);
int UnregisterMountPoint(dev_t Device);
dev_t RegisterFileSystem(struct FileSystemInfo *Info, struct Inode *Root);
int UnregisterFileSystem(dev_t Device);
#endif // !__kernel__

View File

@ -19,7 +19,7 @@
#define __FENNIX_KERNEL_INTERNAL_MEMORY_H__
#ifdef __cplusplus
#include <filesystem.hpp>
#include <fs/vfs.hpp>
#include <boot/binfo.h>
#include <bitmap.hpp>
#include <lock.hpp>

View File

@ -19,7 +19,7 @@
#define __FENNIX_KERNEL_MEMORY_VMA_H__
#include <types.h>
#include <filesystem.hpp>
#include <fs/vfs.hpp>
#include <bitmap.hpp>
#include <lock.hpp>
#include <list>

View File

@ -20,7 +20,7 @@
#include <types.h>
#include <filesystem.hpp>
#include <fs/vfs.hpp>
#include <memory/va.hpp>
#include <symbols.hpp>
#include <memory.hpp>
@ -219,7 +219,7 @@ namespace Tasking
TaskArchitecture Architecture = TaskArchitecture::UnknownArchitecture;
TaskCompatibility Compatibility = TaskCompatibility::UnknownPlatform;
cwk_path_style PathStyle = CWK_STYLE_UNIX;
FileNode *RootNode = nullptr;
Node RootNode = nullptr;
};
struct ThreadLocalStorage
@ -445,7 +445,7 @@ namespace Tasking
PID ID = -1;
const char *Name = nullptr;
PCB *Parent = nullptr;
FileNode *ProcDirectory = nullptr;
Node ProcDirectory = nullptr;
/* Statuses */
std::atomic_int ExitCode;
@ -489,14 +489,14 @@ namespace Tasking
} Linux{};
/* Filesystem */
FileNode *CWD;
FileNode *Executable;
Node CWD;
Node Executable;
FileDescriptorTable *FileDescriptors;
/* stdio */
FileNode *stdin;
FileNode *stdout;
FileNode *stderr;
Node stdin;
Node stdout;
Node stderr;
/*TTY::TeletypeDriver*/ void *tty;
/* Memory */
@ -521,7 +521,7 @@ namespace Tasking
void SetState(TaskState state);
void SetExitCode(int code);
void Rename(const char *name);
void SetWorkingDirectory(FileNode *node);
void SetWorkingDirectory(Node node);
void SetExe(const char *path);
size_t GetSize();
TCB *GetThread(TID ID);

View File

@ -458,6 +458,10 @@ typedef uint48_t uint_fast48_t;
#define hot __attribute__((hot))
#define cold __attribute__((cold))
#define OPTIONAL
#define IN
#define OUT
#define NoSecurityAnalysis __no_stack_protector __no_sanitize_address __no_sanitize_undefined __no_sanitize_thread
#define nsa NoSecurityAnalysis