From 3e6a0bef15db39f0d9ca08ee5f419dad754bbc50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Carter=20=E7=A5=81=E6=98=8E=E6=80=9D?= Date: Thu, 20 Aug 2026 16:55:13 +0800 Subject: [PATCH] feat(personalities): real NTSTATUS/LastError + Darwin errno maps Windows failures previously flattened every VFS return code to ERROR_FILE_NOT_FOUND or ERROR_INVALID_HANDLE, and NTSTATUS existed only as an unused helper. Darwin had an errno table with no caller and no errno slot. - windows.in: paired win-last-error/win-last-status slots, winerror.h + NTSTATUS tables, RtlNtStatusToDosError subset and its inverse, single-writer setters used by every failure exit, existence probe to refine 0/-1 fs failures into ALREADY_EXISTS / DIR_NOT_EMPTY / PATH_NOT_FOUND, and calls 34-36 (RtlGetLastNtStatus, RtlNtStatusToDosError, RtlGetLastWin32Error). - darwin.in: darwin-dispatch wraps darwin-dispatch-call and records negative results in darwin-errno-last (success leaves it alone, per BSD); __error() shims 0x2000/0x2001; darwin-errno covers ESRCH/ENOMEM/ENOSYS. - Both demos assert the mapping; check scripts grep the exact codes and fail on the FAILED marker. Verified: check-personalities.sh green (SCI contract + both QEMU demos). Co-Authored-By: Claude Opus 5 (1M context) --- components/darwin.in | 174 ++++++++- components/windows.in | 558 ++++++++++++++++++++++----- docs/personalities-roadmap.md | 43 ++- scripts/check-darwin-personality.sh | 12 +- scripts/check-windows-personality.sh | 13 +- 5 files changed, 703 insertions(+), 97 deletions(-) diff --git a/components/darwin.in b/components/darwin.in index bd858be..e954847 100644 --- a/components/darwin.in +++ b/components/darwin.in @@ -12,6 +12,7 @@ package space.kernel // mkdir=136, rmdir=137, stat=188, fstat=189, getcwd=192, mmap=197, lseek=199, munmap=73 // Open flags: BSD/Darwin O_CREAT=0x200, O_TRUNC=0x400 (mapped onto VFS-O-*). // Optional Mach stubs: task_self trap 0x1000 → 1; mach_msg 0x1001 → -1. +// errno: negative dispatch results land in darwin-errno-last; read via 0x2000 (__error()). // Pipe lite: max 4 pipes, 4KB ring; vfs fd type field (+24) = 2, pipe idx at +8. const DARWIN-SYS-EXIT = 1 @@ -67,6 +68,12 @@ const DARWIN-AF-INET = 2 const DARWIN-MACH-TASK-SELF = 0x1000 const DARWIN-MACH-MSG = 0x1001 +// Space-local errno shims (not BSD syscall numbers): __error() read/write. +const DARWIN-SYS-ERRNO = 0x2000 +const DARWIN-SYS-SETERRNO = 0x2001 + +var darwin-errno-last: Int = 0 + const DARWIN-ESRCH = -3 const DARWIN-EBADF = -9 const DARWIN-ENOENT = -2 @@ -141,6 +148,9 @@ fn darwin-errno(vfs-rc: Int) -> Int { if vfs-rc == -2 { return DARWIN-ENOENT } + if vfs-rc == -3 { + return DARWIN-ESRCH + } if vfs-rc == -5 { return DARWIN-EIO } @@ -159,7 +169,13 @@ fn darwin-errno(vfs-rc: Int) -> Int { if vfs-rc == -28 { return DARWIN-ENOSPC } - return DARWIN-ENOSYS + if vfs-rc == -12 { + return DARWIN-ENOMEM + } + if vfs-rc == -38 { + return DARWIN-ENOSYS + } + return DARWIN-EINVAL } // Map BSD/Darwin open flags onto Space VFS flags (Linux-shaped bits under the hood). @@ -651,7 +667,7 @@ fn darwin-sys-recvfrom(fd: Int, buf: Int, len: Int) -> Int { } // BSD/Mach-ish dispatch. a0..a3 carry classic arg slots (open mode in a2, kill sig in a1). -fn darwin-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int { +fn darwin-dispatch-call(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int { if darwin-ready == 0 { darwin-init() } @@ -763,6 +779,158 @@ fn darwin-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> I return DARWIN-ENOSYS } +// BSD errno discipline: every negative dispatch result lands in the per-personality +// errno slot, readable through the __error() shim. Success never clears it. +fn darwin-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int { + if darwin-ready == 0 { + darwin-init() + } + if num == DARWIN-SYS-ERRNO { + return darwin-errno-last + } + if num == DARWIN-SYS-SETERRNO { + darwin-errno-last = a0 + return 0 + } + let rc = darwin-dispatch-call(port, num, a0, a1, a2, a3) + if rc < 0 { + darwin-errno-last = 0 - darwin-errno(rc) + } + return rc +} + +// errno number → short name, so serial logs are readable without a lookup table. +fn darwin-errno-name(err: Int) -> Int { + if err == 0 { + return "0" + } + if err == 0 - DARWIN-ENOENT { + return "ENOENT" + } + if err == 0 - DARWIN-ESRCH { + return "ESRCH" + } + if err == 0 - DARWIN-EIO { + return "EIO" + } + if err == 0 - DARWIN-EBADF { + return "EBADF" + } + if err == 0 - DARWIN-ENOMEM { + return "ENOMEM" + } + if err == 0 - DARWIN-EACCES { + return "EACCES" + } + if err == 0 - DARWIN-EEXIST { + return "EEXIST" + } + if err == 0 - DARWIN-EINVAL { + return "EINVAL" + } + if err == 0 - DARWIN-ENOSPC { + return "ENOSPC" + } + if err == 0 - DARWIN-ENOSYS { + return "ENOSYS" + } + return "EUNKNOWN" +} + +fn darwin-errno-demo(port: Int) -> void { + let checks = 0 + let ok = 0 + + // open(missing, O_RDONLY) → ENOENT. + darwin-dispatch(port, DARWIN-SYS-OPEN, "darwin-err-nosuch.txt", DARWIN-O-RDONLY, 0, 0) + let e1 = darwin-dispatch(port, DARWIN-SYS-ERRNO, 0, 0, 0, 0) + serial-write-cstr(port, "darwin: errno after open(missing) = ") + serial-write-dec-wide(port, e1) + serial-write-cstr(port, " (") + serial-write-cstr(port, darwin-errno-name(e1)) + serial-write-cstr(port, ")") + serial-nl(port) + checks = checks + 1 + if e1 == 0 - DARWIN-ENOENT { + ok = ok + 1 + } + + // read on an unopened fd → EBADF. + let rbuf = alloc(16) + darwin-dispatch(port, DARWIN-SYS-READ, 15, rbuf, 8, 0) + let e2 = darwin-dispatch(port, DARWIN-SYS-ERRNO, 0, 0, 0, 0) + serial-write-cstr(port, "darwin: errno after read(bad fd) = ") + serial-write-dec-wide(port, e2) + serial-write-cstr(port, " (") + serial-write-cstr(port, darwin-errno-name(e2)) + serial-write-cstr(port, ")") + serial-nl(port) + checks = checks + 1 + if e2 == 0 - DARWIN-EBADF { + ok = ok + 1 + } + + // kill on a pid that does not exist → ESRCH. + darwin-dispatch(port, DARWIN-SYS-KILL, 99999, SIGKILL, 0, 0) + let e3 = darwin-dispatch(port, DARWIN-SYS-ERRNO, 0, 0, 0, 0) + serial-write-cstr(port, "darwin: errno after kill(invalid) = ") + serial-write-dec-wide(port, e3) + serial-write-cstr(port, " (") + serial-write-cstr(port, darwin-errno-name(e3)) + serial-write-cstr(port, ")") + serial-nl(port) + checks = checks + 1 + if e3 == 0 - DARWIN-ESRCH { + ok = ok + 1 + } + + // Unknown syscall number → ENOSYS. + darwin-dispatch(port, 60000, 0, 0, 0, 0) + let e4 = darwin-dispatch(port, DARWIN-SYS-ERRNO, 0, 0, 0, 0) + serial-write-cstr(port, "darwin: errno after unknown syscall = ") + serial-write-dec-wide(port, e4) + serial-write-cstr(port, " (") + serial-write-cstr(port, darwin-errno-name(e4)) + serial-write-cstr(port, ")") + serial-nl(port) + checks = checks + 1 + if e4 == 0 - DARWIN-ENOSYS { + ok = ok + 1 + } + + // A successful call must leave errno untouched (BSD semantics). + darwin-dispatch(port, DARWIN-SYS-GETPID, 0, 0, 0, 0) + let e5 = darwin-dispatch(port, DARWIN-SYS-ERRNO, 0, 0, 0, 0) + serial-write-cstr(port, "darwin: errno after getpid() = ") + serial-write-dec-wide(port, e5) + serial-nl(port) + checks = checks + 1 + if e5 == e4 { + ok = ok + 1 + } + + // Explicit reset through the shim. + darwin-dispatch(port, DARWIN-SYS-SETERRNO, 0, 0, 0, 0) + let e6 = darwin-dispatch(port, DARWIN-SYS-ERRNO, 0, 0, 0, 0) + checks = checks + 1 + if e6 == 0 { + ok = ok + 1 + } + + serial-write-cstr(port, "darwin: errno scenarios ") + serial-write-dec-wide(port, ok) + serial-write-cstr(port, "/") + serial-write-dec-wide(port, checks) + serial-nl(port) + if ok == checks { + serial-write-cstr(port, "darwin: errno consistency OK") + } else { + serial-write-cstr(port, "darwin: errno consistency FAILED") + } + serial-nl(port) + return +} + fn darwin-demo(port: Int) -> void { if darwin-ready == 0 { darwin-init() @@ -1070,6 +1238,8 @@ fn darwin-demo(port: Int) -> void { } serial-nl(port) + darwin-errno-demo(port) + let task = darwin-dispatch(port, DARWIN-MACH-TASK-SELF, 0, 0, 0, 0) serial-write-cstr(port, "darwin: mach_task_self stub = ") serial-write-dec-wide(port, task) diff --git a/components/windows.in b/components/windows.in index 42c4a3f..b06cef5 100644 --- a/components/windows.in +++ b/components/windows.in @@ -9,7 +9,7 @@ package space.kernel // 2 WIN-GetCurrentProcessId // 3 WIN-ExitProcess // 4 WIN-CreateFileA (path, access: 0=read 1=write/create) -// 5 WIN-ReadFile +// 5 WIN-ReadFile (0 bytes at EOF → STATUS_END_OF_FILE / ERROR_HANDLE_EOF) // 6 WIN-CloseHandle // 7 WIN-GetFileSize // 8 WIN-Sleep → short pause loop (no thr-yield from shell) @@ -35,6 +35,9 @@ package space.kernel // 28 WIN-CopyFileA → fs-read-file src + fs-write-file dst; return 1/0 // 29 WIN-GetEnvironmentVariableA → PATH→"/", else empty; return len // 30 WIN-OutputDebugStringA → serial-write-cstr +// 34 WIN-RtlGetLastNtStatus → win-last-status +// 35 WIN-RtlNtStatusToDosError → a0 NTSTATUS → Win32 error +// 36 WIN-RtlGetLastWin32Error → win-last-error (alias of GetLastError) // // Honest: not full Win32, not PE loader, not CSRSS, not real NT objects. @@ -71,14 +74,32 @@ const WIN-CALL-OUTPUTDEBUGSTRINGA = 30 const WIN-CALL-DUPLICATEHANDLE = 31 const WIN-CALL-GETFILETYPE = 32 const WIN-CALL-SETENDOFFILE = 33 +const WIN-CALL-RTLGETLASTNTSTATUS = 34 +const WIN-CALL-RTLNTSTATUSTODOSERROR = 35 +const WIN-CALL-RTLGETLASTWIN32ERROR = 36 const WIN-MAX-HANDLES = 16 const WIN-HANDLE-STRIDE = 16 const WIN-INVALID-HANDLE = 0 const WIN-ACCESS-READ = 0 const WIN-ACCESS-WRITE = 1 +// Win32 error codes (winerror.h subset). +const WIN-ERROR-SUCCESS = 0 const WIN-ERROR-FILE-NOT-FOUND = 2 +const WIN-ERROR-PATH-NOT-FOUND = 3 +const WIN-ERROR-TOO-MANY-OPEN-FILES = 4 +const WIN-ERROR-ACCESS-DENIED = 5 const WIN-ERROR-INVALID-HANDLE = 6 +const WIN-ERROR-NOT-ENOUGH-MEMORY = 8 +const WIN-ERROR-GEN-FAILURE = 31 +const WIN-ERROR-HANDLE-EOF = 38 +const WIN-ERROR-NOT-SUPPORTED = 50 +const WIN-ERROR-FILE-EXISTS = 80 +const WIN-ERROR-INVALID-PARAMETER = 87 +const WIN-ERROR-INSUFFICIENT-BUFFER = 122 +const WIN-ERROR-DISK-FULL = 112 +const WIN-ERROR-DIR-NOT-EMPTY = 145 +const WIN-ERROR-ALREADY-EXISTS = 183 const WIN-HANDLE-TYPE-FREE = 0 const WIN-HANDLE-TYPE-CONSOLE = 1 const WIN-HANDLE-TYPE-FILE = 2 @@ -96,6 +117,19 @@ const WIN-STATUS-FILE-IS-A-DIRECTORY = -1073741810 const WIN-STATUS-NO-MEDIA = -1073741768 const WIN-STATUS-INVALID-HANDLE = -1073741816 const WIN-STATUS-OBJECT-NAME-NOT-FOUND = -1073741772 +const WIN-STATUS-UNSUCCESSFUL = -1073741823 +const WIN-STATUS-NOT-IMPLEMENTED = -1073741822 +const WIN-STATUS-INVALID-PARAMETER = -1073741811 +const WIN-STATUS-ACCESS-DENIED = -1073741790 +const WIN-STATUS-NO-MEMORY = -1073741801 +const WIN-STATUS-OBJECT-NAME-COLLISION = -1073741771 +const WIN-STATUS-OBJECT-PATH-NOT-FOUND = -1073741766 +const WIN-STATUS-DISK-FULL = -1073741697 +const WIN-STATUS-INSUFFICIENT-RESOURCES = -1073741670 +const WIN-STATUS-DIRECTORY-NOT-EMPTY = -1073741567 +const WIN-STATUS-TOO-MANY-OPENED-FILES = -1073741537 +const WIN-STATUS-BUFFER-TOO-SMALL = -1073741789 +const WIN-STATUS-IO-DEVICE-ERROR = -1073741435 // Typed handle table: 16-byte records, slots 0..15. // offset 0: type (0=free, 1=console, 2=file, 3=process) @@ -104,6 +138,7 @@ const WIN-STATUS-OBJECT-NAME-NOT-FOUND = -1073741772 var win-ready: Int = 0 var win-handles: Int = 0 var win-last-error: Int = 0 +var win-last-status: Int = 0 var win-cmdline: Int = 0 fn win-handle-base(handle: Int) -> Int { @@ -143,24 +178,232 @@ fn win-handle-value(h: Int) -> Int { return load64(win-handle-base(h) + 8) } -// Map VFS return codes to NTSTATUS values. +// Map VFS/errno-shaped return codes to NTSTATUS values. fn win-ntstatus(vfs-rc: Int) -> Int { - if vfs-rc == 0 { + if vfs-rc >= 0 { return WIN-STATUS-SUCCESS } if vfs-rc == -2 { return WIN-STATUS-OBJECT-NAME-NOT-FOUND } if vfs-rc == -5 { + return WIN-STATUS-IO-DEVICE-ERROR + } + if vfs-rc == -6 { return WIN-STATUS-NO-MEDIA } if vfs-rc == -9 { return WIN-STATUS-INVALID-HANDLE } + if vfs-rc == -12 { + return WIN-STATUS-NO-MEMORY + } + if vfs-rc == -13 { + return WIN-STATUS-ACCESS-DENIED + } + if vfs-rc == -16 { + return WIN-STATUS-TOO-MANY-OPENED-FILES + } + if vfs-rc == -17 { + return WIN-STATUS-OBJECT-NAME-COLLISION + } + if vfs-rc == -21 { + return WIN-STATUS-FILE-IS-A-DIRECTORY + } + if vfs-rc == -22 { + return WIN-STATUS-INVALID-PARAMETER + } if vfs-rc == -28 { + return WIN-STATUS-DISK-FULL + } + if vfs-rc == -38 { + return WIN-STATUS-NOT-IMPLEMENTED + } + if vfs-rc == -39 { + return WIN-STATUS-DIRECTORY-NOT-EMPTY + } + return WIN-STATUS-UNSUCCESSFUL +} + +// RtlNtStatusToDosError subset: NTSTATUS → Win32 error code. +fn win-dos-error(status: Int) -> Int { + if status == WIN-STATUS-SUCCESS { + return WIN-ERROR-SUCCESS + } + if status == WIN-STATUS-OBJECT-NAME-NOT-FOUND { + return WIN-ERROR-FILE-NOT-FOUND + } + if status == WIN-STATUS-OBJECT-PATH-NOT-FOUND { + return WIN-ERROR-PATH-NOT-FOUND + } + if status == WIN-STATUS-OBJECT-NAME-COLLISION { + return WIN-ERROR-ALREADY-EXISTS + } + if status == WIN-STATUS-INVALID-HANDLE { + return WIN-ERROR-INVALID-HANDLE + } + if status == WIN-STATUS-ACCESS-DENIED { + return WIN-ERROR-ACCESS-DENIED + } + if status == WIN-STATUS-NO-MEMORY { + return WIN-ERROR-NOT-ENOUGH-MEMORY + } + if status == WIN-STATUS-INSUFFICIENT-RESOURCES { + return WIN-ERROR-NOT-ENOUGH-MEMORY + } + if status == WIN-STATUS-TOO-MANY-OPENED-FILES { + return WIN-ERROR-TOO-MANY-OPEN-FILES + } + if status == WIN-STATUS-DISK-FULL { + return WIN-ERROR-DISK-FULL + } + if status == WIN-STATUS-DIRECTORY-NOT-EMPTY { + return WIN-ERROR-DIR-NOT-EMPTY + } + if status == WIN-STATUS-END-OF-FILE { + return WIN-ERROR-HANDLE-EOF + } + if status == WIN-STATUS-FILE-IS-A-DIRECTORY { + return WIN-ERROR-ACCESS-DENIED + } + if status == WIN-STATUS-INVALID-PARAMETER { + return WIN-ERROR-INVALID-PARAMETER + } + if status == WIN-STATUS-BUFFER-TOO-SMALL { + return WIN-ERROR-INSUFFICIENT-BUFFER + } + if status == WIN-STATUS-NOT-IMPLEMENTED { + return WIN-ERROR-NOT-SUPPORTED + } + if status == WIN-STATUS-NO-MEDIA { + return WIN-ERROR-GEN-FAILURE + } + if status == WIN-STATUS-IO-DEVICE-ERROR { + return WIN-ERROR-GEN-FAILURE + } + return WIN-ERROR-GEN-FAILURE +} + +// Inverse of win-dos-error for the SetLastError path, so GetLastError and +// RtlGetLastNtStatus never disagree. +fn win-status-from-error(err: Int) -> Int { + if err == WIN-ERROR-SUCCESS { + return WIN-STATUS-SUCCESS + } + if err == WIN-ERROR-FILE-NOT-FOUND { + return WIN-STATUS-OBJECT-NAME-NOT-FOUND + } + if err == WIN-ERROR-PATH-NOT-FOUND { + return WIN-STATUS-OBJECT-PATH-NOT-FOUND + } + if err == WIN-ERROR-ALREADY-EXISTS { + return WIN-STATUS-OBJECT-NAME-COLLISION + } + if err == WIN-ERROR-FILE-EXISTS { + return WIN-STATUS-OBJECT-NAME-COLLISION + } + if err == WIN-ERROR-INVALID-HANDLE { + return WIN-STATUS-INVALID-HANDLE + } + if err == WIN-ERROR-ACCESS-DENIED { + return WIN-STATUS-ACCESS-DENIED + } + if err == WIN-ERROR-NOT-ENOUGH-MEMORY { + return WIN-STATUS-NO-MEMORY + } + if err == WIN-ERROR-TOO-MANY-OPEN-FILES { + return WIN-STATUS-TOO-MANY-OPENED-FILES + } + if err == WIN-ERROR-DISK-FULL { + return WIN-STATUS-DISK-FULL + } + if err == WIN-ERROR-DIR-NOT-EMPTY { + return WIN-STATUS-DIRECTORY-NOT-EMPTY + } + if err == WIN-ERROR-HANDLE-EOF { return WIN-STATUS-END-OF-FILE } - return WIN-STATUS-INVALID-HANDLE + if err == WIN-ERROR-INVALID-PARAMETER { + return WIN-STATUS-INVALID-PARAMETER + } + if err == WIN-ERROR-INSUFFICIENT-BUFFER { + return WIN-STATUS-BUFFER-TOO-SMALL + } + if err == WIN-ERROR-NOT-SUPPORTED { + return WIN-STATUS-NOT-IMPLEMENTED + } + return WIN-STATUS-UNSUCCESSFUL +} + +// Single writer for both last-error slots: keeps LastError and NTSTATUS paired. +fn win-set-error(err: Int) -> void { + win-last-error = err + win-last-status = win-status-from-error(err) + return +} + +fn win-set-status(status: Int) -> void { + win-last-status = status + win-last-error = win-dos-error(status) + return +} + +// Record a VFS/errno-shaped failure code as both NTSTATUS and Win32 error. +fn win-set-error-rc(rc: Int) -> void { + win-set-status(win-ntstatus(rc)) + return +} + +fn win-clear-error() -> void { + win-last-error = WIN-ERROR-SUCCESS + win-last-status = WIN-STATUS-SUCCESS + return +} + +// Existence probe used to refine 0/-1 filesystem failures into real codes. +fn win-path-exists(path: Int) -> Int { + if path == 0 { + return 0 + } + let sbuf = alloc(256) + if fs-stat(path, sbuf) == 0 { + return 1 + } + return 0 +} + +// mkdir failed: collision if the name is taken, otherwise a bad parent path. +fn win-set-error-mkdir(path: Int) -> void { + if win-path-exists(path) != 0 { + win-set-status(WIN-STATUS-OBJECT-NAME-COLLISION) + return + } + win-set-status(WIN-STATUS-OBJECT-PATH-NOT-FOUND) + return +} + +// unlink/rmdir failed: missing name vs a directory that still has entries. +fn win-set-error-unlink(path: Int) -> void { + if win-path-exists(path) == 0 { + win-set-status(WIN-STATUS-OBJECT-NAME-NOT-FOUND) + return + } + win-set-status(WIN-STATUS-DIRECTORY-NOT-EMPTY) + return +} + +// rename failed: missing source vs occupied destination. +fn win-set-error-rename(oldp: Int, newp: Int) -> void { + if win-path-exists(oldp) == 0 { + win-set-status(WIN-STATUS-OBJECT-NAME-NOT-FOUND) + return + } + if win-path-exists(newp) != 0 { + win-set-status(WIN-STATUS-OBJECT-NAME-COLLISION) + return + } + win-set-status(WIN-STATUS-OBJECT-PATH-NOT-FOUND) + return } fn win-init() -> void { @@ -192,7 +435,7 @@ fn win-init() -> void { store8(win-cmdline + 11, 119) store8(win-cmdline + 12, 115) store8(win-cmdline + 13, 0) - win-last-error = 0 + win-clear-error() win-ready = 1 return } @@ -257,20 +500,20 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int serial-put(port, load8(buf + i)) i = i + 1 } - win-last-error = 0 + win-clear-error() return len } if htype != WIN-HANDLE-TYPE-FILE { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } let vfd = win-handle-value(handle) let n = vfs-write(vfd, buf, len) if n < 0 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error-rc(n) return 0 } - win-last-error = 0 + win-clear-error() return n } if num == WIN-CALL-GETCURRENTPROCESSID { @@ -289,16 +532,16 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int } let vfd = vfs-open(path, flags) if vfd < 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error-rc(vfd) return WIN-INVALID-HANDLE } let h = win-handle-alloc(vfd) if h == WIN-INVALID-HANDLE { vfs-close(vfd) - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-TOO-MANY-OPEN-FILES) return WIN-INVALID-HANDLE } - win-last-error = 0 + win-clear-error() return h } if num == WIN-CALL-READFILE { @@ -306,59 +549,63 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int let buf = a1 let len = a2 if win-handle-type(handle) != WIN-HANDLE-TYPE-FILE { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } let vfd = win-handle-value(handle) let n = vfs-read(vfd, buf, len) if n < 0 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error-rc(n) + return 0 + } + if n == 0 && len > 0 { + win-set-status(WIN-STATUS-END-OF-FILE) return 0 } - win-last-error = 0 + win-clear-error() return n } if num == WIN-CALL-CLOSEHANDLE { let handle = a0 let htype = win-handle-type(handle) if htype == WIN-HANDLE-TYPE-CONSOLE || handle == 1 || handle == 2 { - win-last-error = 0 + win-clear-error() return 1 } if htype == WIN-HANDLE-TYPE-PROCESS { win-handle-free(handle) - win-last-error = 0 + win-clear-error() return 1 } if htype != WIN-HANDLE-TYPE-FILE { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } let vfd = win-handle-value(handle) vfs-close(vfd) win-handle-free(handle) - win-last-error = 0 + win-clear-error() return 1 } if num == WIN-CALL-GETFILESIZE { let handle = a0 if win-handle-type(handle) != WIN-HANDLE-TYPE-FILE { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } let vfd = win-handle-value(handle) if vfs-fd-valid(vfd) == 0 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } let base = vfs-fd-table + vfd * 32 let path = load64(base + 0) let sz = vfs-sparkfs-file-size(path) if sz < 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error(WIN-ERROR-FILE-NOT-FOUND) return 0 } - win-last-error = 0 + win-clear-error() return sz } if num == WIN-CALL-SLEEP { @@ -384,10 +631,10 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int let path = a0 let rc = fs-delete(path) if rc != 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error-unlink(path) return 0 } - win-last-error = 0 + win-clear-error() return 1 } if num == WIN-CALL-SETFILEPOINTER { @@ -395,28 +642,28 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int let offset = a1 let whence = a2 if win-handle-type(handle) != WIN-HANDLE-TYPE-FILE { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return -1 } let vfd = win-handle-value(handle) - win-last-error = 0 + win-clear-error() return vfs-lseek(vfd, offset, whence) } if num == WIN-CALL-GETSTDHANDLE { // Space-local: -10 stdin→0, -11 stdout→1, -12 stderr→2 if a0 == -10 { - win-last-error = 0 + win-clear-error() return 0 } if a0 == -11 { - win-last-error = 0 + win-clear-error() return 1 } if a0 == -12 { - win-last-error = 0 + win-clear-error() return 2 } - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return WIN-INVALID-HANDLE } if num == WIN-CALL-MOVEFILEA { @@ -424,67 +671,76 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int let newp = a1 let rc = fs-rename(oldp, newp) if rc != 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error-rename(oldp, newp) return 0 } - win-last-error = 0 + win-clear-error() return 1 } if num == WIN-CALL-FLUSHFILEBUFFERS { let handle = a0 let htype = win-handle-type(handle) if htype == WIN-HANDLE-TYPE-CONSOLE || handle == 1 || handle == 2 { - win-last-error = 0 + win-clear-error() return 1 } if htype != WIN-HANDLE-TYPE-FILE { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } // No-op success: VFS writes are already durable enough for this subset. - win-last-error = 0 + win-clear-error() return 1 } if num == WIN-CALL-CREATEDIRECTORYA { let path = a0 let rc = fs-mkdir(path) if rc != 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error-mkdir(path) return 0 } - win-last-error = 0 + win-clear-error() return 1 } if num == WIN-CALL-REMOVEDIRECTORYA { let path = a0 let rc = fs-rmdir(path) if rc != 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error-unlink(path) return 0 } - win-last-error = 0 + win-clear-error() return 1 } if num == WIN-CALL-GETLASTERROR { return win-last-error } + if num == WIN-CALL-RTLGETLASTWIN32ERROR { + return win-last-error + } + if num == WIN-CALL-RTLGETLASTNTSTATUS { + return win-last-status + } + if num == WIN-CALL-RTLNTSTATUSTODOSERROR { + return win-dos-error(a0) + } if num == WIN-CALL-SETLASTERROR { - win-last-error = a0 + win-set-error(a0) return 0 } if num == WIN-CALL-VIRTUALALLOC { // a0 preferred addr ignored; a1 size. Not real VirtualAlloc/MEM_*. let size = a1 if size < 1 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-PARAMETER) return 0 } let p = posix-sys-mmap(0, size, 0, 0, -1, 0) if p == 0 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-NOT-ENOUGH-MEMORY) return 0 } - win-last-error = 0 + win-clear-error() return p } if num == WIN-CALL-VIRTUALFREE { @@ -492,30 +748,30 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int let addr = a0 let size = a1 if addr == 0 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-PARAMETER) return 0 } if size < 1 { size = 4096 } posix-sys-munmap(addr, size) - win-last-error = 0 + win-clear-error() return 1 } if num == WIN-CALL-CREATEPROCESSA { // a0 app path only. Not real CreateProcessA / PE spawn. let path = a0 if path == 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error(WIN-ERROR-FILE-NOT-FOUND) return 0 } if load8(path) == 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error(WIN-ERROR-FILE-NOT-FOUND) return 0 } let status = proc-spawn-sci(port, path) if status < 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error(WIN-ERROR-FILE-NOT-FOUND) return 0 } let pid = proc-count - 1 @@ -524,33 +780,33 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int } let h = win-handle-alloc-process(pid) if h == WIN-INVALID-HANDLE { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } - win-last-error = 0 + win-clear-error() return h } if num == WIN-CALL-WAITFORSINGLEOBJECT { // Demo: process handles only. Not real wait objects / timeouts. let handle = a0 if win-handle-type(handle) != WIN-HANDLE-TYPE-PROCESS { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } let pid = win-handle-value(handle) let code = proc-wait(pid) if code < 0 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } - win-last-error = 0 + win-clear-error() return 0 } if num == WIN-CALL-GETCOMMANDLINEA { if win-cmdline == 0 { win-init() } - win-last-error = 0 + win-clear-error() return win-cmdline } if num == WIN-CALL-WRITECONSOLEA { @@ -559,7 +815,7 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int let buf = a1 let len = a2 if win-handle-type(handle) != WIN-HANDLE-TYPE-CONSOLE && handle != 1 && handle != 2 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } return win-dispatch(port, WIN-CALL-WRITEFILE, handle, buf, len, 0) @@ -569,7 +825,7 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int let buf = a1 let size = a2 if buf == 0 || size < 2 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-PARAMETER) return 0 } let name = "space.exe" @@ -578,7 +834,7 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int nlen = nlen + 1 } if size <= nlen { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INSUFFICIENT-BUFFER) return 0 } let i = 0 @@ -587,19 +843,19 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int i = i + 1 } store8(buf + nlen, 0) - win-last-error = 0 + win-clear-error() return nlen } if num == WIN-CALL-HEAPALLOC { // a0 heap ignored; a1 flags ignored; a2 size. Bump alloc + zero. let size = a2 if size < 1 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-PARAMETER) return 0 } let p = alloc(size) if p == 0 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-NOT-ENOUGH-MEMORY) return 0 } let i = 0 @@ -607,16 +863,16 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int store8(p + i, 0) i = i + 1 } - win-last-error = 0 + win-clear-error() return p } if num == WIN-CALL-HEAPFREE { // a0 heap a1 flags a2 ptr. Bump heap has no free — success only. if a2 == 0 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } - win-last-error = 0 + win-clear-error() return 1 } if num == WIN-CALL-COPYFILEA { @@ -624,21 +880,21 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int let src = a0 let dst = a1 if src == 0 || dst == 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error(WIN-ERROR-FILE-NOT-FOUND) return 0 } let cbuf = alloc(4096) let n = fs-read-file(src, cbuf) if n < 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error-unlink(src) return 0 } let wrc = fs-write-file(dst, cbuf, n) if wrc != 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-status(WIN-STATUS-DISK-FULL) return 0 } - win-last-error = 0 + win-clear-error() return 1 } if num == WIN-CALL-GETENVIRONMENTVARIABLEA { @@ -647,7 +903,7 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int let buf = a1 let size = a2 if buf == 0 || size < 1 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-PARAMETER) return 0 } let is-path = 0 @@ -666,26 +922,26 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int } if is-path != 0 { if size < 2 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INSUFFICIENT-BUFFER) return 0 } store8(buf + 0, 47) store8(buf + 1, 0) - win-last-error = 0 + win-clear-error() return 1 } store8(buf + 0, 0) - win-last-error = 0 + win-clear-error() return 0 } if num == WIN-CALL-OUTPUTDEBUGSTRINGA { let s = a0 if s == 0 { - win-last-error = 0 + win-clear-error() return 0 } serial-write-cstr(port, s) - win-last-error = 0 + win-clear-error() return 0 } if num == WIN-CALL-DUPLICATEHANDLE { @@ -693,7 +949,7 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int let src = a0 let stype = win-handle-type(src) if stype == WIN-HANDLE-TYPE-FREE { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return WIN-INVALID-HANDLE } let sval = win-handle-value(src) @@ -718,58 +974,58 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int } } if nh < 0 || nh >= WIN-MAX-HANDLES { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return WIN-INVALID-HANDLE } - win-last-error = 0 + win-clear-error() return nh } if num == WIN-CALL-GETFILETYPE { let handle = a0 let htype = win-handle-type(handle) if htype == WIN-HANDLE-TYPE-CONSOLE { - win-last-error = 0 + win-clear-error() return WIN-FILE-TYPE-CHAR } if htype == WIN-HANDLE-TYPE-FILE { - win-last-error = 0 + win-clear-error() return WIN-FILE-TYPE-DISK } if handle == 1 || handle == 2 { - win-last-error = 0 + win-clear-error() return WIN-FILE-TYPE-CHAR } - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return WIN-FILE-TYPE-UNKNOWN } if num == WIN-CALL-SETENDOFFILE { // a0 = file handle. Truncate at current VFS position. let handle = a0 if win-handle-type(handle) != WIN-HANDLE-TYPE-FILE { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } let vfd = win-handle-value(handle) if vfs-fd-valid(vfd) == 0 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } // Get current position from VFS fd table (offset 8 = pos). let pos = load64(vfs-fd-table + vfd * 32 + 8) let fpath = load64(vfs-fd-table + vfd * 32 + 0) if fpath == -1 || fpath == 0 { - win-last-error = WIN-ERROR-INVALID-HANDLE + win-set-error(WIN-ERROR-INVALID-HANDLE) return 0 } // Read existing file content, truncate to pos, write back. let fullsz = vfs-sparkfs-file-size(fpath) if fullsz < 0 { - win-last-error = WIN-ERROR-FILE-NOT-FOUND + win-set-error(WIN-ERROR-FILE-NOT-FOUND) return 0 } if pos >= fullsz { // Nothing to truncate. - win-last-error = 0 + win-clear-error() return 1 } let truncbuf = alloc(pos) @@ -782,12 +1038,134 @@ fn win-dispatch(port: Int, num: Int, a0: Int, a1: Int, a2: Int, a3: Int) -> Int } } fs-write-file(fpath, truncbuf, pos) - win-last-error = 0 + win-clear-error() return 1 } return 0 } +// Print "