diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 64d8e93..c91ba51 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 94 +#define MONTAUK_BUILD_NUMBER 95 diff --git a/kernel/src/Api/Filesystem.hpp b/kernel/src/Api/Filesystem.hpp index 37f8155..7671901 100644 --- a/kernel/src/Api/Filesystem.hpp +++ b/kernel/src/Api/Filesystem.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "Path.hpp" namespace montauk::abi { @@ -133,6 +134,18 @@ namespace montauk::abi { return 0; } + static int Sys_Utime(const char* path, int64_t atime, int64_t mtime, + bool useCurrent) { + char resolved[256]; + if (!ResolveProcessPath(path, resolved, sizeof(resolved))) return -1; + if (useCurrent) { + int64_t now = Timekeeping::GetUnixTimestamp(); + atime = now; + mtime = now; + } + return Fs::Vfs::VfsSetTimes(resolved, atime, mtime); + } + 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 60f4ce8..129535a 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -174,6 +174,12 @@ namespace montauk::abi { 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_UTIME: + if (!UserMemory::String(frame->arg1, kMaxPathBytes)) return -1; + return (int64_t)Sys_Utime((const char*)frame->arg1, + (int64_t)frame->arg2, + (int64_t)frame->arg3, + frame->arg4 != 0); 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 a6d7707..6750498 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -310,6 +310,9 @@ namespace montauk::abi { /* Keyboard.hpp */ static constexpr uint64_t SYS_GETCHAR_NB = 166; // () -> ascii, 0 if nothing pending; never blocks + // Set path access/modification times. (path, atime, mtime, useCurrent) + static constexpr uint64_t SYS_UTIME = 167; + // 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 diff --git a/kernel/src/Fs/Boot.cpp b/kernel/src/Fs/Boot.cpp index 326bffb..06a53f2 100644 --- a/kernel/src/Fs/Boot.cpp +++ b/kernel/src/Fs/Boot.cpp @@ -101,6 +101,7 @@ namespace Fs { .GetLabel = RdGetLabel, .ReadDirAt = RdReadDirAt, .Stat = RdStat, + .SetTimes = nullptr, // Statically allocated: nothing to release. .Unmount = nullptr, }; diff --git a/kernel/src/Fs/Ext2.cpp b/kernel/src/Fs/Ext2.cpp index 4f5ee5f..9ae8d1f 100644 --- a/kernel/src/Fs/Ext2.cpp +++ b/kernel/src/Fs/Ext2.cpp @@ -1234,6 +1234,31 @@ namespace Fs::Ext2 { return 0; } + static int SetTimesImpl(int inst, const char* path, int64_t atime, int64_t mtime) { + if (InstanceAt(inst) == nullptr) return -4; + if (atime < 0 || atime > 0xFFFFFFFFLL || + mtime < 0 || mtime > 0xFFFFFFFFLL) return -3; + auto& self = g_instances[inst]; + + uint32_t inodeNum; + Inode inode; + if (!TraversePath(self, path, &inodeNum, &inode)) return -1; + + inode.i_atime = (uint32_t)atime; + inode.i_mtime = (uint32_t)mtime; + inode.i_ctime = Ext2Now(); + if (!WriteInode(self, inodeNum, &inode)) return -4; + + // Open files cache their inode. Keep every alias coherent so a later + // write through an existing handle cannot restore stale metadata. + for (int i = 0; i < MaxFilesPerInstance; i++) { + if (self.files[i].inUse && self.files[i].inodeNum == inodeNum) { + self.files[i].inode = inode; + } + } + return 0; + } + static int ReadImpl(int inst, int handle, uint8_t* buffer, uint64_t offset, uint64_t size) { if (InstanceAt(inst) == nullptr) return -1; @@ -1830,6 +1855,7 @@ namespace Fs::Ext2 { 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); } + static int DrvSetTimes(void* c, const char* p, int64_t a, int64_t m) { return SetTimesImpl(CtxToInst(c), p, a, m); } // Release everything the mount owns. The driver is either not registered // yet, or the VFS has deactivated its drive and drained dispatches. @@ -2040,6 +2066,7 @@ namespace Fs::Ext2 { .GetLabel = DrvGetLabel, .ReadDirAt = DrvReadDirAt, .Stat = DrvStat, + .SetTimes = DrvSetTimes, .Unmount = DrvUnmount, }; diff --git a/kernel/src/Fs/Fat32.cpp b/kernel/src/Fs/Fat32.cpp index 5a8c56b..8ed8ac5 100644 --- a/kernel/src/Fs/Fat32.cpp +++ b/kernel/src/Fs/Fat32.cpp @@ -2099,6 +2099,7 @@ namespace Fs::Fat32 { .GetLabel = DrvGetLabel, .ReadDirAt = DrvReadDirAt, .Stat = DrvStat, + .SetTimes = nullptr, .Unmount = DrvUnmount, }; diff --git a/kernel/src/Fs/Vfs.cpp b/kernel/src/Fs/Vfs.cpp index 7647eca..e925806 100644 --- a/kernel/src/Fs/Vfs.cpp +++ b/kernel/src/Fs/Vfs.cpp @@ -307,6 +307,21 @@ namespace Fs::Vfs { return result; } + int VfsSetTimes(const char* path, int64_t atime, int64_t mtime) { + int drive; + const char* localPath; + + if (!ParsePath(path, drive, localPath)) return -1; + + vfsLock.Acquire(); + FsDriver* driver = DriverForLocked(drive); + int result = (driver != nullptr && driver->SetTimes != nullptr) + ? driver->SetTimes(driver->ctx, localPath, atime, mtime) + : -2; + 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 57f854e..2fded13 100644 --- a/kernel/src/Fs/Vfs.hpp +++ b/kernel/src/Fs/Vfs.hpp @@ -65,6 +65,10 @@ namespace Fs::Vfs { // Optional: fill metadata for a path. Drivers that leave this null do // not support stat and VfsStat returns -1 for their paths. int (*Stat)(void* ctx, const char* path, StatInfo* out); + // Optional: set access and modification times for a path. Return 0 on + // success, -1 for a missing path, -3 for an unrepresentable time, or + // -4 for an I/O error. A null hook yields -2 (unsupported). + int (*SetTimes)(void* ctx, const char* path, int64_t atime, int64_t mtime); // Optional: release everything this mount owns, including the FsDriver // itself. Called either when registration fails, before the driver has // become reachable, or by UnregisterDrive once the slot is deactivated @@ -90,6 +94,7 @@ namespace Fs::Vfs { int VfsDelete(const char* path); int VfsStat(const char* path, StatInfo& out); + int VfsSetTimes(const char* path, int64_t atime, int64_t mtime); 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/libc/limits.h b/programs/include/libc/limits.h index c9e9c6b..984e9a8 100644 --- a/programs/include/libc/limits.h +++ b/programs/include/libc/limits.h @@ -27,5 +27,6 @@ #define PATH_MAX 4096 #define NAME_MAX 256 +#define OPEN_MAX 128 #endif /* _LIBC_LIMITS_H */ diff --git a/programs/include/libc/utime.h b/programs/include/libc/utime.h index 00327b7..61d56c5 100644 --- a/programs/include/libc/utime.h +++ b/programs/include/libc/utime.h @@ -14,8 +14,7 @@ struct utimbuf { time_t modtime; }; -/* Accepted for POSIX compatibility; the Montauk VFS does not support - setting file times. */ +/* Set access and modification times. Currently supported on ext2. */ int utime(const char *path, const struct utimbuf *times); #ifdef __cplusplus diff --git a/programs/lib/libc/libc.c b/programs/lib/libc/libc.c index 6450081..85c01f2 100644 --- a/programs/lib/libc/libc.c +++ b/programs/lib/libc/libc.c @@ -118,6 +118,7 @@ static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) { #define SYS_DUPHANDLE 98 #define SYS_KILL 62 #define SYS_STAT 152 +#define SYS_UTIME 167 /* ======================================================================== errno @@ -2019,10 +2020,15 @@ done: fcntl.h / unistd.h POSIX-style file I/O ======================================================================== */ -/* fd position tracking for POSIX read/write (MontaukOS uses explicit offsets) */ -#define _FD_POS_MAX 64 +/* fd position tracking for POSIX read/write (MontaukOS uses explicit offsets). + OPEN_MAX is part of the userspace ABI and matches the kernel handle table. */ +#define _FD_POS_MAX OPEN_MAX static unsigned long _fd_pos[_FD_POS_MAX]; +static int _fd_pos_valid(int fd) { + return fd >= 0 && fd < _FD_POS_MAX; +} + int access(const char *path, int mode) { if (path == NULL) { errno = EINVAL; @@ -2069,7 +2075,12 @@ int open(const char *path, int flags, ...) { h = (int)_zos_syscall1(SYS_FCREATE, (long)path); } - if (h >= 0 && h < _FD_POS_MAX) { + if (h >= _FD_POS_MAX) { + _zos_syscall1(SYS_CLOSE, (long)h); + errno = EMFILE; + return -1; + } + if (h >= 0) { _fd_pos[h] = 0; if (flags & O_APPEND) { _fd_pos[h] = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)h); @@ -2110,9 +2121,14 @@ int read(int fd, void *buf, size_t count) { return (int)i; } - unsigned long pos = (fd >= 0 && fd < _FD_POS_MAX) ? _fd_pos[fd] : 0; + if (!_fd_pos_valid(fd)) { + errno = EBADF; + return -1; + } + + unsigned long pos = _fd_pos[fd]; int ret = (int)_zos_syscall4(SYS_READ, (long)fd, (long)buf, (long)pos, (long)count); - if (ret > 0 && fd >= 0 && fd < _FD_POS_MAX) + if (ret > 0) _fd_pos[fd] += (unsigned long)ret; return ret; } @@ -2127,9 +2143,14 @@ int write(int fd, const void *buf, size_t count) { return (int)count; } - unsigned long pos = (fd >= 0 && fd < _FD_POS_MAX) ? _fd_pos[fd] : 0; + if (!_fd_pos_valid(fd)) { + errno = EBADF; + return -1; + } + + unsigned long pos = _fd_pos[fd]; int ret = (int)_zos_syscall4(SYS_FWRITE, (long)fd, (long)buf, (long)pos, (long)count); - if (ret > 0 && fd >= 0 && fd < _FD_POS_MAX) + if (ret > 0) _fd_pos[fd] += (unsigned long)ret; return ret; } @@ -2184,20 +2205,37 @@ int ftruncate(int fd, long length) { * offset is genuinely left alone. */ ssize_t pread(int fd, void *buf, size_t count, off_t offset) { - if (buf == NULL || offset < 0) return -1; + if (!_fd_pos_valid(fd)) { + errno = EBADF; + return -1; + } + if (buf == NULL || offset < 0) { + errno = EINVAL; + return -1; + } return (ssize_t)_zos_syscall4(SYS_READ, (long)fd, (long)buf, (long)offset, (long)count); } ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) { - if (buf == NULL || offset < 0) return -1; + if (!_fd_pos_valid(fd)) { + errno = EBADF; + return -1; + } + if (buf == NULL || offset < 0) { + errno = EINVAL; + return -1; + } return (ssize_t)_zos_syscall4(SYS_FWRITE, (long)fd, (long)buf, (long)offset, (long)count); } int close(int fd) { - if (fd >= 0 && fd < _FD_POS_MAX) - _fd_pos[fd] = 0; + if (!_fd_pos_valid(fd)) { + errno = EBADF; + return -1; + } + _fd_pos[fd] = 0; if ((int)_zos_syscall1(SYS_CLOSE, (long)fd) < 0) { errno = EBADF; return -1; @@ -2206,7 +2244,12 @@ int close(int fd) { } long lseek(int fd, long offset, int whence) { - unsigned long pos = (fd >= 0 && fd < _FD_POS_MAX) ? _fd_pos[fd] : 0; + if (!_fd_pos_valid(fd)) { + errno = EBADF; + return -1; + } + + unsigned long pos = _fd_pos[fd]; unsigned long newpos; switch (whence) { case 0: /* SEEK_SET */ @@ -2219,10 +2262,10 @@ long lseek(int fd, long offset, int whence) { newpos = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)fd) + (unsigned long)offset; break; default: + errno = EINVAL; return -1; } - if (fd >= 0 && fd < _FD_POS_MAX) - _fd_pos[fd] = newpos; + _fd_pos[fd] = newpos; return (long)newpos; } @@ -3322,12 +3365,21 @@ int fchmod(int fd, mode_t mode) { } int utime(const char *path, const struct utimbuf *times) { - (void)times; if (path == NULL) { errno = EINVAL; return -1; } - return 0; /* file times cannot be set through the VFS */ + + long result = _zos_syscall4(SYS_UTIME, (long)path, + times != NULL ? (long)times->actime : 0, + times != NULL ? (long)times->modtime : 0, + times == NULL); + if (result == 0) return 0; + if (result == -2) errno = ENOTSUP; + else if (result == -3) errno = EOVERFLOW; + else if (result == -4) errno = EIO; + else errno = ENOENT; + return -1; } void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, @@ -3740,7 +3792,7 @@ pid_t getpid(void) { allocation usually land there. The oldfd == newfd form (the common validity probe) is handled exactly. */ int dup2(int oldfd, int newfd) { - if (oldfd < 0 || newfd < 0) { + if (!_fd_pos_valid(oldfd) || !_fd_pos_valid(newfd)) { errno = EBADF; return -1; } @@ -3764,9 +3816,7 @@ int dup2(int oldfd, int newfd) { errno = EBUSY; return -1; } - if (h < _FD_POS_MAX) { - _fd_pos[h] = (oldfd < _FD_POS_MAX) ? _fd_pos[oldfd] : 0; - } + _fd_pos[h] = _fd_pos[oldfd]; return newfd; } @@ -3812,7 +3862,7 @@ int getpagesize(void) { long sysconf(int name) { switch (name) { case 4: /* _SC_OPEN_MAX */ - return 64; + return OPEN_MAX; case 30: /* _SC_PAGESIZE */ return 4096; case 84: /* _SC_NPROCESSORS_ONLN */ @@ -4066,14 +4116,21 @@ pid_t wait(int *status) { } int dup(int fd) { + if (!_fd_pos_valid(fd)) { + errno = EBADF; + return -1; + } int h = (int)_zos_syscall1(SYS_DUPHANDLE, (long)fd); if (h < 0) { errno = EBADF; return -1; } - if (h < _FD_POS_MAX) { - _fd_pos[h] = (fd >= 0 && fd < _FD_POS_MAX) ? _fd_pos[fd] : 0; + if (!_fd_pos_valid(h)) { + _zos_syscall1(SYS_CLOSE, (long)h); + errno = EMFILE; + return -1; } + _fd_pos[h] = _fd_pos[fd]; return h; } @@ -4089,7 +4146,7 @@ int fileno(FILE *stream) { } FILE *fdopen(int fd, const char *mode) { - if (fd < 0 || mode == NULL) { + if (!_fd_pos_valid(fd) || mode == NULL) { errno = EBADF; return NULL; } @@ -4099,7 +4156,7 @@ FILE *fdopen(int fd, const char *mode) { return NULL; } f->handle = fd; - f->pos = (fd < _FD_POS_MAX) ? _fd_pos[fd] : 0; + f->pos = _fd_pos[fd]; f->size = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)fd); f->eof = 0; f->error = 0; diff --git a/programs/lib/libc/obj/libc.o b/programs/lib/libc/obj/libc.o index 7677457..f9ef7a7 100644 Binary files a/programs/lib/libc/obj/libc.o and b/programs/lib/libc/obj/libc.o differ diff --git a/template/sysroot/include/libc/limits.h b/template/sysroot/include/libc/limits.h index c9e9c6b..984e9a8 100644 --- a/template/sysroot/include/libc/limits.h +++ b/template/sysroot/include/libc/limits.h @@ -27,5 +27,6 @@ #define PATH_MAX 4096 #define NAME_MAX 256 +#define OPEN_MAX 128 #endif /* _LIBC_LIMITS_H */ diff --git a/template/sysroot/include/libc/utime.h b/template/sysroot/include/libc/utime.h index 00327b7..61d56c5 100644 --- a/template/sysroot/include/libc/utime.h +++ b/template/sysroot/include/libc/utime.h @@ -14,8 +14,7 @@ struct utimbuf { time_t modtime; }; -/* Accepted for POSIX compatibility; the Montauk VFS does not support - setting file times. */ +/* Set access and modification times. Currently supported on ext2. */ int utime(const char *path, const struct utimbuf *times); #ifdef __cplusplus