diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index b9758b0..34ee96d 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 2 +#define MONTAUK_BUILD_NUMBER 4 diff --git a/kernel/src/Api/Filesystem.hpp b/kernel/src/Api/Filesystem.hpp index cab3bc6..37f8155 100644 --- a/kernel/src/Api/Filesystem.hpp +++ b/kernel/src/Api/Filesystem.hpp @@ -116,6 +116,23 @@ namespace montauk::abi { return Fs::Vfs::VfsDelete(resolved); } + static int Sys_Stat(const char* path, FileStat* out) { + if (out == nullptr) return -1; + char resolved[256]; + if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; + + Fs::Vfs::StatInfo info; + if (Fs::Vfs::VfsStat(resolved, info) != 0) return -1; + + out->size = info.size; + out->mtime = info.mtime; + out->ctime = info.ctime; + out->atime = info.atime; + out->mode = info.mode; + out->isDir = info.isDir ? 1u : 0u; + return 0; + } + static int Sys_FMkdir(const char* path) { char resolved[256]; if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 70410fb..1b45341 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -153,6 +153,10 @@ namespace montauk::abi { case SYS_GETEXECPATH: if (!UserMemory::Range(frame->arg2 ? frame->arg1 : frame->arg1, frame->arg2, true)) return -1; return Sys_GetExecPath((char*)frame->arg1, frame->arg2); + case SYS_STAT: + if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1; + if (!UserMemory::Writable(frame->arg2)) return -1; + return (int64_t)Sys_Stat((const char*)frame->arg1, (FileStat*)frame->arg2); case SYS_TERMSIZE: return (int64_t)Sys_TermSize(); case SYS_GETARGS: diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index a74f877..805bb41 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -285,6 +285,9 @@ namespace montauk::abi { // Absolute path of the running executable (for argv[0]). static constexpr uint64_t SYS_GETEXECPATH = 151; // (index, flags) -> new front index; index=-1 queries support (1/0); flags bit0 = wait vsync + // Path metadata (size, timestamps, mode). (const char* path, FileStat* out) -> 0, -1 on error/unsupported. + static constexpr uint64_t SYS_STAT = 152; + // Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM). static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz @@ -326,6 +329,17 @@ namespace montauk::abi { uint8_t Second; }; + // Path metadata returned by SYS_STAT. Timestamps are UTC unix seconds; + // a filesystem that does not record a given time reports it as 0. + struct FileStat { + uint64_t size; // file size in bytes + int64_t mtime; // last data modification time + int64_t ctime; // last inode (metadata) change time + int64_t atime; // last access time + uint32_t mode; // ext2/POSIX mode bits (type + permissions) + uint32_t isDir; // 1 if the entry is a directory, else 0 + }; + struct FbInfo { uint64_t width; uint64_t height; diff --git a/kernel/src/Fs/Ext2.cpp b/kernel/src/Fs/Ext2.cpp index 0b44208..25fb7c3 100644 --- a/kernel/src/Fs/Ext2.cpp +++ b/kernel/src/Fs/Ext2.cpp @@ -1189,6 +1189,24 @@ namespace Fs::Ext2 { return -1; } + static int StatImpl(int inst, const char* path, Vfs::StatInfo* out) { + if (!out) return -1; + if (inst < 0 || inst >= g_instanceCount || !g_instances[inst].active) return -1; + auto& self = g_instances[inst]; + + uint32_t inodeNum; + Inode inode; + if (!TraversePath(self, path, &inodeNum, &inode)) return -1; + + out->size = inode.i_size; + out->mtime = (int64_t)inode.i_mtime; + out->ctime = (int64_t)inode.i_ctime; + out->atime = (int64_t)inode.i_atime; + out->mode = inode.i_mode; + out->isDir = (inode.i_mode & IMODE_TYPE_MASK) == IMODE_DIR; + return 0; + } + static int ReadImpl(int inst, int handle, uint8_t* buffer, uint64_t offset, uint64_t size) { if (inst < 0 || inst >= g_instanceCount) return -1; @@ -1764,6 +1782,7 @@ namespace Fs::Ext2 { static int Mkdir(const char* p) { return MkdirImpl(N, p); } static int Rename(const char* o, const char* n) { return RenameImpl(N, o, n); } static const char* GetLabel() { return GetLabelImpl(N); } + static int Stat(const char* p, Vfs::StatInfo* o) { return StatImpl(N, p, o); } }; template @@ -1781,6 +1800,7 @@ namespace Fs::Ext2 { Thunks::Rename, Thunks::GetLabel, Thunks::ReadDirAt, + Thunks::Stat, }; } diff --git a/kernel/src/Fs/Vfs.cpp b/kernel/src/Fs/Vfs.cpp index 79c75bc..134518b 100644 --- a/kernel/src/Fs/Vfs.cpp +++ b/kernel/src/Fs/Vfs.cpp @@ -288,6 +288,25 @@ namespace Fs::Vfs { return result; } + int VfsStat(const char* path, StatInfo& out) { + int drive; + const char* localPath; + + out = StatInfo{}; + + if (!ParsePath(path, drive, localPath)) return -1; + if (drive < 0 || drive >= MaxDrives || !driveActive[drive] || driveTable[drive] == nullptr) return -1; + if (driveTable[drive]->Stat == nullptr) return -1; + + vfsLock.Acquire(); + FsDriver* driver = driveTable[drive]; + int result = (driveActive[drive] && driver && driver->Stat) + ? driver->Stat(localPath, &out) + : -1; + vfsLock.Release(); + return result; + } + int VfsMkdir(const char* path) { int drive; const char* localPath; diff --git a/kernel/src/Fs/Vfs.hpp b/kernel/src/Fs/Vfs.hpp index bbd0669..f884cf0 100644 --- a/kernel/src/Fs/Vfs.hpp +++ b/kernel/src/Fs/Vfs.hpp @@ -18,6 +18,17 @@ namespace Fs::Vfs { uint32_t generation; }; + // 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 { + uint64_t size; // file size in bytes + int64_t mtime; // last data modification time + int64_t ctime; // last inode (metadata) change time + int64_t atime; // last access time + uint32_t mode; // ext2/POSIX i_mode bits (type + permissions) + bool isDir; // true if the entry is a directory + }; + struct FsDriver { int (*Open)(const char* path); int (*Read)(int handle, uint8_t* buffer, uint64_t offset, uint64_t size); @@ -33,6 +44,9 @@ namespace Fs::Vfs { // Optional: paginated directory read returning entries [startIndex, startIndex+maxEntries). // Drivers that leave this null are read via ReadDir at startIndex 0 only. int (*ReadDirAt)(const char* path, const char** outNames, int maxEntries, int startIndex); + // Optional: fill metadata for a path. Drivers that leave this null do + // not support stat and VfsStat returns -1 for their paths. + int (*Stat)(const char* path, StatInfo* out); }; void Initialize(); @@ -51,6 +65,7 @@ namespace Fs::Vfs { void CloseBackendFile(BackendFile& file); int VfsDelete(const char* path); + int VfsStat(const char* path, StatInfo& out); int VfsReadDir(const char* path, const char** outNames, int maxEntries); int VfsReadDirAt(const char* path, const char** outNames, int maxEntries, int startIndex); int VfsMkdir(const char* path); diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index ed6c29a..5471905 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -209,6 +209,9 @@ namespace montauk::abi { // Absolute path of the running executable (for argv[0]). static constexpr uint64_t SYS_GETEXECPATH = 151; // (index, flags) -> new front index; index=-1 queries support (1/0); flags bit0 = wait vsync + // Path metadata (size, timestamps, mode). (const char* path, FileStat* out) -> 0, -1 on error/unsupported. + static constexpr uint64_t SYS_STAT = 152; + // Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM). static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz @@ -287,6 +290,17 @@ namespace montauk::abi { uint8_t Second; }; + // Path metadata returned by SYS_STAT. Timestamps are UTC unix seconds; + // a filesystem that does not record a given time reports it as 0. + struct FileStat { + uint64_t size; // file size in bytes + int64_t mtime; // last data modification time + int64_t ctime; // last inode (metadata) change time + int64_t atime; // last access time + uint32_t mode; // ext2/POSIX mode bits (type + permissions) + uint32_t isDir; // 1 if the entry is a directory, else 0 + }; + struct FbInfo { uint64_t width; uint64_t height; diff --git a/programs/include/libc/montauk.h b/programs/include/libc/montauk.h index 1ac8066..b4f1bcb 100644 --- a/programs/include/libc/montauk.h +++ b/programs/include/libc/montauk.h @@ -175,6 +175,7 @@ extern "C" { #define MTK_SYS_POWERINFO 149 #define MTK_SYS_FBFLIP 150 #define MTK_SYS_GETEXECPATH 151 +#define MTK_SYS_STAT 152 /* @SYSCALLS-END */ #define MTK_SOCK_TCP 1 diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index 88c44d6..eba2f2f 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -165,6 +165,11 @@ namespace montauk { inline int frename(const char* oldPath, const char* newPath) { return (int)syscall2(montauk::abi::SYS_FRENAME, (uint64_t)oldPath, (uint64_t)newPath); } + // Fill *out with metadata (size, timestamps, mode) for the path. + // Returns 0 on success, -1 on error or if the filesystem lacks stat support. + inline int stat(const char* path, montauk::abi::FileStat* out) { + return (int)syscall2(montauk::abi::SYS_STAT, (uint64_t)path, (uint64_t)out); + } inline int drivelist(int* outDrives, int max) { return (int)syscall2(montauk::abi::SYS_DRIVELIST, (uint64_t)outDrives, (uint64_t)max); } diff --git a/programs/libs/libprintersapplet/obj/src/libprintersapplet.o b/programs/libs/libprintersapplet/obj/src/libprintersapplet.o index 3b3f633..4679936 100644 Binary files a/programs/libs/libprintersapplet/obj/src/libprintersapplet.o and b/programs/libs/libprintersapplet/obj/src/libprintersapplet.o differ diff --git a/programs/libs/libtimezonesapplet/obj/src/libtimezonesapplet.o b/programs/libs/libtimezonesapplet/obj/src/libtimezonesapplet.o index fdd6dd9..26a0e26 100644 Binary files a/programs/libs/libtimezonesapplet/obj/src/libtimezonesapplet.o and b/programs/libs/libtimezonesapplet/obj/src/libtimezonesapplet.o differ