feat: scheduling, usermode, shell
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Ramdisk.cpp
|
||||
* USTAR tar-based ramdisk filesystem backed by Limine modules
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Ramdisk.hpp"
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Libraries/String.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
|
||||
namespace Fs::Ramdisk {
|
||||
|
||||
static FileEntry fileTable[MaxFiles];
|
||||
static int fileCount = 0;
|
||||
|
||||
static uint64_t OctalToUint(const char* str, int len) {
|
||||
uint64_t result = 0;
|
||||
for (int i = 0; i < len && str[i] != '\0' && str[i] != ' '; i++) {
|
||||
result = result * 8 + (str[i] - '0');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool StrEqual(const char* a, const char* b) {
|
||||
while (*a && *b) {
|
||||
if (*a != *b) return false;
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
static int StrLen(const char* s) {
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
static bool StartsWith(const char* str, const char* prefix) {
|
||||
while (*prefix) {
|
||||
if (*str != *prefix) return false;
|
||||
str++;
|
||||
prefix++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Initialize(void* moduleData, uint64_t moduleSize) {
|
||||
Kt::KernelLogStream(Kt::OK, "Ramdisk") << "Parsing USTAR archive (" << moduleSize << " bytes)";
|
||||
|
||||
uint8_t* ptr = (uint8_t*)moduleData;
|
||||
uint8_t* end = ptr + moduleSize;
|
||||
fileCount = 0;
|
||||
|
||||
while (ptr + 512 <= end && fileCount < MaxFiles) {
|
||||
// Check for end-of-archive (two consecutive zero blocks)
|
||||
bool allZero = true;
|
||||
for (int i = 0; i < 512; i++) {
|
||||
if (ptr[i] != 0) {
|
||||
allZero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allZero) break;
|
||||
|
||||
// Verify USTAR magic at offset 257
|
||||
const char* magic = (const char*)(ptr + 257);
|
||||
if (magic[0] != 'u' || magic[1] != 's' || magic[2] != 't' ||
|
||||
magic[3] != 'a' || magic[4] != 'r') {
|
||||
Kt::KernelLogStream(Kt::WARNING, "Ramdisk") << "Invalid USTAR magic, stopping parse";
|
||||
break;
|
||||
}
|
||||
|
||||
// File name at offset 0 (100 bytes)
|
||||
const char* name = (const char*)ptr;
|
||||
// File size at offset 124 (12 bytes, octal ASCII)
|
||||
uint64_t size = OctalToUint((const char*)(ptr + 124), 12);
|
||||
// Type flag at offset 156
|
||||
char typeFlag = (char)ptr[156];
|
||||
|
||||
FileEntry& entry = fileTable[fileCount];
|
||||
|
||||
// Copy name
|
||||
int nameLen = 0;
|
||||
while (nameLen < MaxNameLen - 1 && name[nameLen] != '\0') {
|
||||
entry.name[nameLen] = name[nameLen];
|
||||
nameLen++;
|
||||
}
|
||||
entry.name[nameLen] = '\0';
|
||||
|
||||
// Strip leading "./" if present
|
||||
if (entry.name[0] == '.' && entry.name[1] == '/') {
|
||||
char temp[MaxNameLen];
|
||||
int srcIdx = 2;
|
||||
int dstIdx = 0;
|
||||
while (entry.name[srcIdx] && dstIdx < MaxNameLen - 1) {
|
||||
temp[dstIdx++] = entry.name[srcIdx++];
|
||||
}
|
||||
temp[dstIdx] = '\0';
|
||||
for (int i = 0; i <= dstIdx; i++) {
|
||||
entry.name[i] = temp[i];
|
||||
}
|
||||
}
|
||||
|
||||
entry.isDirectory = (typeFlag == '5');
|
||||
entry.size = size;
|
||||
|
||||
// Data starts at next 512-byte block
|
||||
entry.data = ptr + 512;
|
||||
|
||||
// Skip entries that are just the root "." or empty name
|
||||
if (entry.name[0] == '\0' || (entry.name[0] == '.' && entry.name[1] == '\0')) {
|
||||
// Advance past header + data blocks
|
||||
uint64_t dataBlocks = (size + 511) / 512;
|
||||
ptr += 512 + dataBlocks * 512;
|
||||
continue;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::INFO, "Ramdisk") << " " << entry.name
|
||||
<< " (" << entry.size << " bytes"
|
||||
<< (entry.isDirectory ? ", dir" : "") << ")";
|
||||
|
||||
fileCount++;
|
||||
|
||||
// Advance past header + data (rounded up to 512-byte blocks)
|
||||
uint64_t dataBlocks = (size + 511) / 512;
|
||||
ptr += 512 + dataBlocks * 512;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Ramdisk") << "Loaded " << fileCount << " entries";
|
||||
}
|
||||
|
||||
int Open(const char* path) {
|
||||
// Normalize: skip leading '/'
|
||||
if (path[0] == '/') path++;
|
||||
|
||||
for (int i = 0; i < fileCount; i++) {
|
||||
if (StrEqual(fileTable[i].name, path)) {
|
||||
return i;
|
||||
}
|
||||
// Also try matching with trailing slash stripped from table entry
|
||||
int entryLen = StrLen(fileTable[i].name);
|
||||
if (entryLen > 0 && fileTable[i].name[entryLen - 1] == '/') {
|
||||
// Compare without trailing slash
|
||||
bool match = true;
|
||||
int pathLen = StrLen(path);
|
||||
if (pathLen == entryLen - 1) {
|
||||
for (int j = 0; j < pathLen; j++) {
|
||||
if (path[j] != fileTable[i].name[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) {
|
||||
if (handle < 0 || handle >= fileCount) return -1;
|
||||
|
||||
const FileEntry& entry = fileTable[handle];
|
||||
if (offset >= entry.size) return 0;
|
||||
|
||||
uint64_t bytesToRead = size;
|
||||
if (offset + bytesToRead > entry.size) {
|
||||
bytesToRead = entry.size - offset;
|
||||
}
|
||||
|
||||
memcpy(buffer, entry.data + offset, bytesToRead);
|
||||
return (int)bytesToRead;
|
||||
}
|
||||
|
||||
uint64_t GetSize(int handle) {
|
||||
if (handle < 0 || handle >= fileCount) return 0;
|
||||
return fileTable[handle].size;
|
||||
}
|
||||
|
||||
void Close(int handle) {
|
||||
// No-op for ramdisk: files are memory-mapped and read-only
|
||||
(void)handle;
|
||||
}
|
||||
|
||||
int ReadDir(const char* path, const char** outNames, int maxEntries) {
|
||||
// Normalize path: skip leading '/'
|
||||
if (path[0] == '/') path++;
|
||||
|
||||
int pathLen = StrLen(path);
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < fileCount && count < maxEntries; i++) {
|
||||
const char* entryName = fileTable[i].name;
|
||||
|
||||
if (pathLen == 0) {
|
||||
// Root directory: find entries without '/' in them (or only trailing '/')
|
||||
bool hasSlash = false;
|
||||
int entryLen = StrLen(entryName);
|
||||
for (int j = 0; j < entryLen; j++) {
|
||||
if (entryName[j] == '/' && j < entryLen - 1) {
|
||||
hasSlash = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasSlash) {
|
||||
outNames[count++] = entryName;
|
||||
}
|
||||
} else {
|
||||
// Subdirectory: match entries starting with "path/"
|
||||
// and that are direct children (no additional '/' beyond the prefix)
|
||||
if (!StartsWith(entryName, path)) continue;
|
||||
|
||||
// Check that path prefix is followed by '/'
|
||||
char separator = entryName[pathLen];
|
||||
if (separator != '/') continue;
|
||||
|
||||
// Check it's a direct child (no more '/' except trailing)
|
||||
const char* rest = entryName + pathLen + 1;
|
||||
int restLen = StrLen(rest);
|
||||
bool hasDeepSlash = false;
|
||||
for (int j = 0; j < restLen; j++) {
|
||||
if (rest[j] == '/' && j < restLen - 1) {
|
||||
hasDeepSlash = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasDeepSlash && restLen > 0) {
|
||||
outNames[count++] = entryName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int GetFileCount() {
|
||||
return fileCount;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Ramdisk.hpp
|
||||
* USTAR tar-based ramdisk filesystem backed by Limine modules
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace Fs::Ramdisk {
|
||||
|
||||
static constexpr int MaxFiles = 128;
|
||||
static constexpr int MaxNameLen = 100;
|
||||
|
||||
struct FileEntry {
|
||||
char name[MaxNameLen];
|
||||
uint8_t* data;
|
||||
uint64_t size;
|
||||
bool isDirectory;
|
||||
};
|
||||
|
||||
void Initialize(void* moduleData, uint64_t moduleSize);
|
||||
|
||||
int Open(const char* path);
|
||||
int Read(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
uint64_t GetSize(int handle);
|
||||
void Close(int handle);
|
||||
|
||||
int ReadDir(const char* path, const char** outNames, int maxEntries);
|
||||
int GetFileCount();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Vfs.cpp
|
||||
* Virtual File System with numerical logical drive identifiers
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Vfs.hpp"
|
||||
#include <Terminal/Terminal.hpp>
|
||||
|
||||
namespace Fs::Vfs {
|
||||
|
||||
struct HandleEntry {
|
||||
bool inUse;
|
||||
int driveNumber;
|
||||
int localHandle;
|
||||
};
|
||||
|
||||
static FsDriver* driveTable[MaxDrives];
|
||||
static HandleEntry handleTable[MaxHandles];
|
||||
|
||||
// Parse "N:/path" into drive number and local path.
|
||||
// Returns true on success, sets outDrive and outPath.
|
||||
static bool ParsePath(const char* path, int& outDrive, const char*& outPath) {
|
||||
if (path == nullptr) return false;
|
||||
|
||||
// Parse decimal drive number before ':'
|
||||
int drive = 0;
|
||||
int i = 0;
|
||||
bool hasDigit = false;
|
||||
|
||||
while (path[i] >= '0' && path[i] <= '9') {
|
||||
drive = drive * 10 + (path[i] - '0');
|
||||
hasDigit = true;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!hasDigit) return false;
|
||||
if (path[i] != ':') return false;
|
||||
|
||||
// Everything after "N:" is the local path
|
||||
outDrive = drive;
|
||||
outPath = &path[i + 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
static int AllocHandle() {
|
||||
for (int i = 0; i < MaxHandles; i++) {
|
||||
if (!handleTable[i].inUse) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
for (int i = 0; i < MaxDrives; i++) {
|
||||
driveTable[i] = nullptr;
|
||||
}
|
||||
for (int i = 0; i < MaxHandles; i++) {
|
||||
handleTable[i].inUse = false;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "VFS") << "Initialized (" << MaxDrives << " drives, " << MaxHandles << " handles)";
|
||||
}
|
||||
|
||||
int RegisterDrive(int driveNumber, FsDriver* driver) {
|
||||
if (driveNumber < 0 || driveNumber >= MaxDrives) return -1;
|
||||
if (driver == nullptr) return -1;
|
||||
|
||||
driveTable[driveNumber] = driver;
|
||||
Kt::KernelLogStream(Kt::OK, "VFS") << "Registered drive " << driveNumber;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int VfsOpen(const char* path) {
|
||||
int drive;
|
||||
const char* localPath;
|
||||
|
||||
if (!ParsePath(path, drive, localPath)) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Invalid path format";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Drive " << drive << " not registered";
|
||||
return -1;
|
||||
}
|
||||
|
||||
int localHandle = driveTable[drive]->Open(localPath);
|
||||
if (localHandle < 0) return -1;
|
||||
|
||||
int globalHandle = AllocHandle();
|
||||
if (globalHandle < 0) {
|
||||
driveTable[drive]->Close(localHandle);
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "No free handles";
|
||||
return -1;
|
||||
}
|
||||
|
||||
handleTable[globalHandle].inUse = true;
|
||||
handleTable[globalHandle].driveNumber = drive;
|
||||
handleTable[globalHandle].localHandle = localHandle;
|
||||
|
||||
return globalHandle;
|
||||
}
|
||||
|
||||
int VfsRead(int handle, uint8_t* buffer, uint64_t offset, uint64_t size) {
|
||||
if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) return -1;
|
||||
|
||||
HandleEntry& entry = handleTable[handle];
|
||||
return driveTable[entry.driveNumber]->Read(entry.localHandle, buffer, offset, size);
|
||||
}
|
||||
|
||||
uint64_t VfsGetSize(int handle) {
|
||||
if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) return 0;
|
||||
|
||||
HandleEntry& entry = handleTable[handle];
|
||||
return driveTable[entry.driveNumber]->GetSize(entry.localHandle);
|
||||
}
|
||||
|
||||
void VfsClose(int handle) {
|
||||
if (handle < 0 || handle >= MaxHandles || !handleTable[handle].inUse) return;
|
||||
|
||||
HandleEntry& entry = handleTable[handle];
|
||||
driveTable[entry.driveNumber]->Close(entry.localHandle);
|
||||
entry.inUse = false;
|
||||
}
|
||||
|
||||
int VfsReadDir(const char* path, const char** outNames, int maxEntries) {
|
||||
int drive;
|
||||
const char* localPath;
|
||||
|
||||
if (!ParsePath(path, drive, localPath)) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Invalid path format for ReadDir";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (drive < 0 || drive >= MaxDrives || driveTable[drive] == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "VFS") << "Drive " << drive << " not registered";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return driveTable[drive]->ReadDir(localPath, outNames, maxEntries);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Vfs.hpp
|
||||
* Virtual File System with numerical logical drive identifiers
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace Fs::Vfs {
|
||||
|
||||
static constexpr int MaxDrives = 16;
|
||||
static constexpr int MaxHandles = 64;
|
||||
|
||||
struct FsDriver {
|
||||
int (*Open)(const char* path);
|
||||
int (*Read)(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
uint64_t (*GetSize)(int handle);
|
||||
void (*Close)(int handle);
|
||||
int (*ReadDir)(const char* path, const char** outNames, int maxEntries);
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
int RegisterDrive(int driveNumber, FsDriver* driver);
|
||||
|
||||
int VfsOpen(const char* path);
|
||||
int VfsRead(int handle, uint8_t* buffer, uint64_t offset, uint64_t size);
|
||||
uint64_t VfsGetSize(int handle);
|
||||
void VfsClose(int handle);
|
||||
int VfsReadDir(const char* path, const char** outNames, int maxEntries);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user