feat: real stat metadata across all filesystems, plus mkdir and rmdir
This commit is contained in:
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 42
|
||||
#define MONTAUK_BUILD_NUMBER 46
|
||||
|
||||
+18
-1
@@ -69,6 +69,23 @@ namespace Fs {
|
||||
const char* RdGetLabel(void*) { return Ramdisk::GetLabel(); }
|
||||
int RdReadDirAt(void*, const char* p, const char** o, int m, int s) { return Ramdisk::ReadDirAt(p, o, m, s); }
|
||||
|
||||
int RdStat(void*, const char* p, Vfs::StatInfo* out) {
|
||||
if (out == nullptr) return -1;
|
||||
|
||||
Ramdisk::StatResult st;
|
||||
if (Ramdisk::Stat(p, &st) != 0) return -1;
|
||||
|
||||
out->size = st.size;
|
||||
// The USTAR header carries a single timestamp; report it for all
|
||||
// three rather than leaving ctime/atime at the epoch.
|
||||
out->mtime = st.mtime;
|
||||
out->ctime = st.mtime;
|
||||
out->atime = st.mtime;
|
||||
out->mode = (st.isDirectory ? Vfs::ModeDir : Vfs::ModeReg) | (st.mode & 07777);
|
||||
out->isDir = st.isDirectory;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Vfs::FsDriver g_ramdiskDriver = {
|
||||
.ctx = nullptr,
|
||||
.Open = RdOpen,
|
||||
@@ -83,7 +100,7 @@ namespace Fs {
|
||||
.Rename = RdRename,
|
||||
.GetLabel = RdGetLabel,
|
||||
.ReadDirAt = RdReadDirAt,
|
||||
.Stat = nullptr,
|
||||
.Stat = RdStat,
|
||||
// Statically allocated: nothing to release.
|
||||
.Unmount = nullptr,
|
||||
};
|
||||
|
||||
+71
-1
@@ -11,6 +11,7 @@
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <CppLib/Vector.hpp>
|
||||
#include <Timekeeping/Time.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
@@ -107,6 +108,13 @@ namespace Fs::Fat32 {
|
||||
// Location of the SFN entry on disk (for write support)
|
||||
uint64_t sfnPartSector;
|
||||
uint32_t sfnOffInSector;
|
||||
// Packed FAT date/time words straight from the SFN entry, decoded
|
||||
// on demand by Stat. Zero means "not recorded".
|
||||
uint16_t writeTime;
|
||||
uint16_t writeDate;
|
||||
uint16_t createTime;
|
||||
uint16_t createDate;
|
||||
uint16_t accessDate;
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
@@ -853,6 +861,13 @@ namespace Fs::Fat32 {
|
||||
out->firstCluster = ((uint32_t)clHi << 16) | (uint32_t)clLo;
|
||||
memcpy(&out->fileSize, e + 28, 4);
|
||||
out->attributes = attr;
|
||||
// SFN timestamp words: creation at 14/16, last access
|
||||
// date at 18, last write at 22/24.
|
||||
memcpy(&out->createTime, e + 14, 2);
|
||||
memcpy(&out->createDate, e + 16, 2);
|
||||
memcpy(&out->accessDate, e + 18, 2);
|
||||
memcpy(&out->writeTime, e + 22, 2);
|
||||
memcpy(&out->writeDate, e + 24, 2);
|
||||
int j = 0;
|
||||
while (entryName[j] && j < MaxNameLen - 1) {
|
||||
out->name[j] = entryName[j]; j++;
|
||||
@@ -888,6 +903,12 @@ namespace Fs::Fat32 {
|
||||
out->attributes = ATTR_DIRECTORY;
|
||||
out->name[0] = '/';
|
||||
out->name[1] = '\0';
|
||||
// The root has no directory entry, so it has no timestamps.
|
||||
out->writeTime = 0;
|
||||
out->writeDate = 0;
|
||||
out->createTime = 0;
|
||||
out->createDate = 0;
|
||||
out->accessDate = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1578,6 +1599,54 @@ namespace Fs::Fat32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Stat — metadata for a single path
|
||||
// =========================================================================
|
||||
|
||||
// Decode a packed FAT date/time pair into a Unix timestamp.
|
||||
// date: bits 15-9 year since 1980, 8-5 month (1-12), 4-0 day (1-31)
|
||||
// time: bits 15-11 hour, 10-5 minute, 4-0 seconds/2
|
||||
// FAT stores local time with no recorded offset, so it is taken as UTC.
|
||||
// A zero date means the field was never written; report 0 rather than
|
||||
// inventing 1980-00-00.
|
||||
static int64_t FatDateTimeToEpoch(uint16_t date, uint16_t time) {
|
||||
if (date == 0) return 0;
|
||||
|
||||
int year = 1980 + ((date >> 9) & 0x7F);
|
||||
int month = (date >> 5) & 0x0F;
|
||||
int day = date & 0x1F;
|
||||
|
||||
int hour = (time >> 11) & 0x1F;
|
||||
int minute = (time >> 5) & 0x3F;
|
||||
int second = (time & 0x1F) * 2;
|
||||
|
||||
return Timekeeping::DateToUnixTimestamp(year, month, day, hour, minute, second);
|
||||
}
|
||||
|
||||
static int StatImpl(int inst, const char* path, Vfs::StatInfo* out) {
|
||||
if (out == nullptr) return -1;
|
||||
if (InstanceAt(inst) == nullptr) return -1;
|
||||
|
||||
ParsedEntry entry;
|
||||
if (!TraversePath(inst, path, &entry)) return -1;
|
||||
|
||||
bool isDir = (entry.attributes & ATTR_DIRECTORY) != 0;
|
||||
|
||||
out->size = isDir ? 0 : entry.fileSize;
|
||||
out->mtime = FatDateTimeToEpoch(entry.writeDate, entry.writeTime);
|
||||
out->ctime = FatDateTimeToEpoch(entry.createDate, entry.createTime);
|
||||
// Access date has no time-of-day component on FAT.
|
||||
out->atime = FatDateTimeToEpoch(entry.accessDate, 0);
|
||||
// FAT carries no POSIX permissions; synthesize them from the
|
||||
// read-only attribute so callers see a plausible mode.
|
||||
uint32_t perms = (entry.attributes & ATTR_READ_ONLY)
|
||||
? (isDir ? 0555u : 0444u)
|
||||
: (isDir ? 0755u : 0644u);
|
||||
out->mode = (isDir ? Vfs::ModeDir : Vfs::ModeReg) | perms;
|
||||
out->isDir = isDir;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Rename — atomic directory entry move
|
||||
// =========================================================================
|
||||
@@ -1821,6 +1890,7 @@ namespace Fs::Fat32 {
|
||||
static int DrvMkdir(void* c, const char* p) { return MkdirImpl(CtxToInst(c), p); }
|
||||
static int DrvRename(void* c, const char* o, const char* n) { return RenameImpl(CtxToInst(c), o, n); }
|
||||
static const char* DrvGetLabel(void* c) { return GetLabelImpl(CtxToInst(c)); }
|
||||
static int DrvStat(void* c, const char* p, Vfs::StatInfo* o) { return StatImpl(CtxToInst(c), p, o); }
|
||||
|
||||
// Release everything the mount owns. The driver is either not registered
|
||||
// yet, or the VFS has deactivated its drive and drained dispatches.
|
||||
@@ -2028,7 +2098,7 @@ namespace Fs::Fat32 {
|
||||
.Rename = DrvRename,
|
||||
.GetLabel = DrvGetLabel,
|
||||
.ReadDirAt = DrvReadDirAt,
|
||||
.Stat = nullptr,
|
||||
.Stat = DrvStat,
|
||||
.Unmount = DrvUnmount,
|
||||
};
|
||||
|
||||
|
||||
@@ -9,12 +9,20 @@
|
||||
#include <Libraries/String.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Timekeeping/Time.hpp>
|
||||
|
||||
namespace Fs::Ramdisk {
|
||||
|
||||
static FileEntry fileTable[MaxFiles];
|
||||
static int fileCount = 0;
|
||||
|
||||
// Wall-clock stamp for entries created after boot. Negative (clock not yet
|
||||
// available) collapses to 0 rather than a nonsense pre-epoch timestamp.
|
||||
static int64_t RamdiskNow() {
|
||||
int64_t ts = Timekeeping::GetUnixTimestamp();
|
||||
return ts < 0 ? 0 : ts;
|
||||
}
|
||||
|
||||
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++) {
|
||||
@@ -114,8 +122,12 @@ namespace Fs::Ramdisk {
|
||||
// the name lands as a bogus root-level entry.
|
||||
const char* name = (const char*)ptr;
|
||||
const char* namePrefix = (const char*)(ptr + 345);
|
||||
// File mode at offset 100 (8 bytes, octal ASCII)
|
||||
uint32_t mode = (uint32_t)OctalToUint((const char*)(ptr + 100), 8);
|
||||
// File size at offset 124 (12 bytes, octal ASCII)
|
||||
uint64_t size = OctalToUint((const char*)(ptr + 124), 12);
|
||||
// Modification time at offset 136 (12 bytes, octal ASCII Unix epoch)
|
||||
int64_t mtime = (int64_t)OctalToUint((const char*)(ptr + 136), 12);
|
||||
// Type flag at offset 156
|
||||
char typeFlag = (char)ptr[156];
|
||||
|
||||
@@ -154,6 +166,13 @@ namespace Fs::Ramdisk {
|
||||
entry.size = size;
|
||||
entry.capacity = size;
|
||||
entry.heapAllocated = false;
|
||||
// Keep permission bits only; some archivers stash type bits here
|
||||
// too, and the type is already carried by isDirectory.
|
||||
entry.mode = mode & 07777;
|
||||
if (entry.mode == 0) {
|
||||
entry.mode = entry.isDirectory ? DefaultDirMode : DefaultFileMode;
|
||||
}
|
||||
entry.mtime = mtime;
|
||||
|
||||
// Data starts at next 512-byte block
|
||||
entry.data = ptr + 512;
|
||||
@@ -396,6 +415,7 @@ namespace Fs::Ramdisk {
|
||||
}
|
||||
entry.size = 0;
|
||||
entry.isDirectory = false;
|
||||
entry.mtime = RamdiskNow();
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -418,6 +438,8 @@ namespace Fs::Ramdisk {
|
||||
entry.capacity = 256;
|
||||
entry.isDirectory = false;
|
||||
entry.heapAllocated = true;
|
||||
entry.mode = DefaultFileMode;
|
||||
entry.mtime = RamdiskNow();
|
||||
|
||||
return fileCount++;
|
||||
}
|
||||
@@ -504,11 +526,27 @@ namespace Fs::Ramdisk {
|
||||
entry.capacity = 0;
|
||||
entry.isDirectory = true;
|
||||
entry.heapAllocated = false;
|
||||
entry.mode = DefaultDirMode;
|
||||
entry.mtime = RamdiskNow();
|
||||
|
||||
fileCount++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Stat(const char* path, StatResult* out) {
|
||||
if (path == nullptr || out == nullptr) return -1;
|
||||
|
||||
int idx = FindEntryByPath(path);
|
||||
if (idx < 0) return -1;
|
||||
|
||||
const FileEntry& entry = fileTable[idx];
|
||||
out->size = entry.isDirectory ? 0 : entry.size;
|
||||
out->mtime = entry.mtime;
|
||||
out->mode = entry.mode;
|
||||
out->isDirectory = entry.isDirectory;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Rename(const char* oldPath, const char* newPath) {
|
||||
char oldNorm[MaxNameLen];
|
||||
char newNorm[MaxNameLen];
|
||||
|
||||
@@ -21,10 +21,20 @@ namespace Fs::Ramdisk {
|
||||
uint8_t* data;
|
||||
uint64_t size;
|
||||
uint64_t capacity;
|
||||
// POSIX permission bits (no type bits) and modification time, both
|
||||
// taken from the USTAR header so shipped files report real metadata.
|
||||
// Runtime-created entries get sensible defaults instead.
|
||||
uint32_t mode;
|
||||
int64_t mtime;
|
||||
bool isDirectory;
|
||||
bool heapAllocated;
|
||||
};
|
||||
|
||||
// Default permissions for entries created at runtime, where the archive
|
||||
// has no header to read them from.
|
||||
static constexpr uint32_t DefaultFileMode = 0644;
|
||||
static constexpr uint32_t DefaultDirMode = 0755;
|
||||
|
||||
void Initialize(void* moduleData, uint64_t moduleSize);
|
||||
|
||||
int Open(const char* path);
|
||||
@@ -42,4 +52,17 @@ namespace Fs::Ramdisk {
|
||||
const char* GetLabel();
|
||||
int GetFileCount();
|
||||
|
||||
// Metadata for a single path. Deliberately free of VFS types so the
|
||||
// ramdisk stays independent of the layer above it; the caller maps this
|
||||
// onto Vfs::StatInfo and adds the POSIX type bits.
|
||||
struct StatResult {
|
||||
uint64_t size;
|
||||
int64_t mtime;
|
||||
uint32_t mode; // permission bits only, no type bits
|
||||
bool isDirectory;
|
||||
};
|
||||
|
||||
// Returns 0 and fills *out on success, -1 if the path does not exist.
|
||||
int Stat(const char* path, StatResult* out);
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,12 @@ namespace Fs::Vfs {
|
||||
uint32_t generation;
|
||||
};
|
||||
|
||||
// POSIX file-type bits carried in StatInfo::mode. These match the ext2
|
||||
// on-disk i_mode encoding, which drivers without native POSIX modes
|
||||
// (ramdisk, FAT32) synthesize so every filesystem reports mode alike.
|
||||
static constexpr uint32_t ModeDir = 0x4000;
|
||||
static constexpr uint32_t ModeReg = 0x8000;
|
||||
|
||||
// Metadata for a single path, filled by FsDriver::Stat. Timestamps are UTC
|
||||
// unix seconds; a filesystem that does not record a given time leaves it 0.
|
||||
struct StatInfo {
|
||||
|
||||
@@ -84,6 +84,13 @@ int64_t Timekeeping::GetUnixTimestamp() {
|
||||
return g_bootEpoch + (int64_t)(Timekeeping::GetMilliseconds() / 1000);
|
||||
}
|
||||
|
||||
int64_t Timekeeping::DateToUnixTimestamp(int year, int month, int day,
|
||||
int hour, int minute, int second) {
|
||||
if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31) return 0;
|
||||
if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 60) return 0;
|
||||
return DateToEpoch(year, month, day, hour, minute, second);
|
||||
}
|
||||
|
||||
bool Timekeeping::SetUnixTimestamp(int64_t unixSeconds) {
|
||||
if (unixSeconds < 0 || unixSeconds > 4102444799LL)
|
||||
return false;
|
||||
|
||||
@@ -52,6 +52,12 @@ namespace Timekeeping {
|
||||
|
||||
void Init(uint16_t Year, uint8_t Month, uint8_t Day, uint8_t Hour, uint8_t Minute, uint8_t Second);
|
||||
int64_t GetUnixTimestamp();
|
||||
|
||||
// Convert a UTC calendar date to a Unix timestamp. Returns 0 for dates
|
||||
// outside the representable range. Used by filesystem drivers that store
|
||||
// on-disk timestamps as calendar fields (e.g. FAT32).
|
||||
int64_t DateToUnixTimestamp(int year, int month, int day, int hour, int minute, int second);
|
||||
|
||||
DateTime GetDateTime();
|
||||
bool SetUnixTimestamp(int64_t unixSeconds);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user