From e1248f4afea124b146caa4850302e40d756f1782 Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Tue, 26 May 2026 10:38:11 +0200 Subject: [PATCH] refactor syscall ABI and FAT32 driver to use request structs and string views natively, enforce handle access rights on object lookup, add scheduler statistics tracking, fix KiSchedule to prevent 50% idle throttling on CPU-bound threads, better document x64 initial user stack alignment, fix process teardown to properly signal exit events and manage thread counts, optimize doom file I/O with a custom handle struct to internally track position and size, add SubsystemData to TEB for generic thread-local state, apply PAGE_NX to non-executable PE sections in the loader, update native apps, tests, and shells to match new APIs, update kernel32 directory retrieval. --- OVMF/OVMF_VARS_4M_d.fd | Bin 540672 -> 540672 bytes code/async_apc/async_apc.c | 12 +- code/async_cancel/async_cancel.c | 12 +- code/boot.ini | 2 +- code/doom/doom.c | 89 +- code/drivers/fat32/fat32.c | 392 ++++--- code/edit/edit.c | 13 +- code/hal/hal_timer.c | 39 +- code/httpget/httpget.c | 10 +- code/init/init.c | 28 +- code/kernel/io_file.c | 321 ++--- code/kernel/io_file.h | 31 +- code/kernel/ke_process.c | 217 +++- code/kernel/ke_process.h | 40 +- code/kernel/ke_sched.c | 31 + code/kernel/ke_sched.h | 79 +- code/kernel/ke_selftest.c | 46 +- code/kernel/ke_settings.c | 2 +- code/kernel/ke_syscall.c | 551 +++++---- code/kernel/ke_thread.c | 25 +- code/kernel/ke_userenv.h | 22 +- code/kernel/kmain.c | 15 +- code/kernel/ldr_image.c | 58 +- code/kernel/ldr_image.h | 79 +- code/kernel/mm_virt.c | 89 +- code/kernel/mm_virt.h | 13 +- code/ping/ping.c | 10 +- code/posix/posix_dir.c | 11 +- code/posix/posix_fd.c | 2 +- code/posix/posix_io.c | 14 +- code/posix/posix_proc.c | 7 +- code/posix/posix_socket.c | 3 +- code/printf.c | 1872 +++++++++++++++--------------- code/resolve/resolve.c | 10 +- code/shell_native/shell_native.c | 46 +- code/shell_posix/shell_posix.c | 2 +- code/syscall/sys_string.h | 112 ++ code/syscall/syscall.h | 107 +- code/tcpserve/tcpserve.c | 7 +- code/udp/udp.c | 7 +- code/win32/kernel32.c | 147 ++- code/win32/user32.c | 145 +-- readme.md | 31 +- 43 files changed, 2892 insertions(+), 1857 deletions(-) create mode 100644 code/syscall/sys_string.h diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index f0ac529422d060331ab45e0fae2f992c64af862e..3cb17543e9b0db35ed810e2453a506e9d08da5f5 100644 GIT binary patch delta 106 zcmZo@P;3CA7RD`1|CUbn3v$}-u#D*v*XDP*yI2_;H%|=e7GP|GP|Xmk1wyq#s5S`I m4x~HYStatus = iosb ? iosb->Status : 0xFFFFFFFFu; st->Information = information; st->Delivered++; - SysDebugPrint("async_apc: user APC callback ran\n"); + do { SYS_DEBUG_PRINT_REQUEST req = { SYS_SV_LITERAL("async_apc: user APC callback ran\n") }; SysDebugPrint(&req); } while (0); } int main(void) { @@ -59,7 +65,7 @@ int main(void) { } uint32_t file = 0; - s = SysOpenFile("system/init.exe", &file); + s = open_file("system/init.exe", &file); if (s != SYS_STATUS_SUCCESS) { print("async_apc: open failed status="); print_u64(s); diff --git a/code/async_cancel/async_cancel.c b/code/async_cancel/async_cancel.c index 92e4d52..2b4e98c 100644 --- a/code/async_cancel/async_cancel.c +++ b/code/async_cancel/async_cancel.c @@ -6,7 +6,13 @@ #define EVENT_AUTO_RESET 0u static void print(const char* s) { - SysWriteConsole(s, strlen(s)); + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); +} + +static uint32_t open_file(const char* path, uint32_t* handleOut) { + SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; + return SysOpenFile(&req); } static void print_u64(uint64_t v) { @@ -22,7 +28,7 @@ static void print_u64(uint64_t v) { } while (i--) { char c = buf[i]; - SysWriteConsole(&c, 1); + do { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; SysWriteConsole(&req); } while (0); } } @@ -33,7 +39,7 @@ int main(void) { uint32_t event = 0; uint32_t request = 0; - uint32_t s = SysOpenFile("system/init.exe", &file); + uint32_t s = open_file("system/init.exe", &file); if (s != SYS_STATUS_SUCCESS) { print("async_cancel: open failed status="); print_u64(s); print("\n"); SysExitProcess(1); diff --git a/code/boot.ini b/code/boot.ini index 8c0cd3c..9d59f32 100644 --- a/code/boot.ini +++ b/code/boot.ini @@ -1,5 +1,5 @@ [kernel] ; bitmask: 1 = debug prints, 2 = self check -debug = 1 +debug = 3 [init] commandline = apps/shell_native.exe diff --git a/code/doom/doom.c b/code/doom/doom.c index 0c847b2..037bbf5 100644 --- a/code/doom/doom.c +++ b/code/doom/doom.c @@ -19,7 +19,8 @@ int _fltused; // --------------------------------------------------------------------------- static void doom_print_wrapper(const char* msg) { - SysWriteConsole(msg, strlen(msg)); + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(msg) }; + SysWriteConsole(&req); } // --------------------------------------------------------------------------- @@ -42,74 +43,108 @@ static void doom_free_wrapper(void* ptr) // --------------------------------------------------------------------------- // File I/O wrappers // --------------------------------------------------------------------------- +typedef struct DOOM_FILE_HANDLE { + uint32_t Handle; + uint64_t Pos; + uint64_t Size; +} DOOM_FILE_HANDLE; + static void* doom_open_wrapper(const char* path, const char* mode) { uint32_t handle = 0; uint32_t result; + SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), &handle }; if (mode[0] == 'w') { - result = SysCreateFile(path, &handle); + result = SysCreateFile(&req); } else { - result = SysOpenFile(path, &handle); + result = SysOpenFile(&req); + } + if (result != 0) return 0; + + DOOM_FILE_HANDLE* f = (DOOM_FILE_HANDLE*)SysAlloc(sizeof(DOOM_FILE_HANDLE)); + if (!f) { + SysCloseHandle(handle); + return 0; } - return (result == 0) ? (void*)(uintptr_t)handle : 0; + f->Handle = handle; + f->Pos = 0; + f->Size = SysGetFileSize(handle); + return f; } static void doom_close_wrapper(void* handle) { - SysCloseHandle((uint32_t)(uintptr_t)handle); + if (!handle) return; + DOOM_FILE_HANDLE* f = (DOOM_FILE_HANDLE*)handle; + SysCloseHandle(f->Handle); + SysFree(f); } static int doom_read_wrapper(void* handle, void* buf, int count) { + if (!handle || count <= 0) return 0; + DOOM_FILE_HANDLE* f = (DOOM_FILE_HANDLE*)handle; size_t read = 0; - SysReadFile((uint32_t)(uintptr_t)handle, buf, (size_t)count, &read); + SysReadFile(f->Handle, buf, (size_t)count, &read); + f->Pos += read; + if (f->Pos > f->Size) f->Size = f->Pos; return (int)read; } static int doom_write_wrapper(void* handle, const void* buf, int count) { + if (!handle || count <= 0) return 0; + DOOM_FILE_HANDLE* f = (DOOM_FILE_HANDLE*)handle; size_t written = 0; - SysWriteFile((uint32_t)(uintptr_t)handle, buf, (size_t)count, &written); + SysWriteFile(f->Handle, buf, (size_t)count, &written); + f->Pos += written; + if (f->Pos > f->Size) f->Size = f->Pos; return (int)written; } static int doom_seek_wrapper(void* handle, int offset, doom_seek_t origin) { - uint32_t h = (uint32_t)(uintptr_t)handle; - uint64_t absolute_offset = 0; + if (!handle) return -1; + DOOM_FILE_HANDLE* f = (DOOM_FILE_HANDLE*)handle; + int64_t base = 0; switch (origin) { - case DOOM_SEEK_SET: - absolute_offset = (uint64_t)offset; + case DOOM_SEEK_SET: + base = 0; break; - case DOOM_SEEK_CUR: - absolute_offset = SysGetFilePos(h) + offset; + case DOOM_SEEK_CUR: + base = (int64_t)f->Pos; break; - case DOOM_SEEK_END: - absolute_offset = SysGetFileSize(h) + offset; + case DOOM_SEEK_END: + base = (int64_t)f->Size; break; - default: - absolute_offset = (uint64_t)offset; + default: + base = 0; break; } - - SysSeekFile(h, absolute_offset); + + int64_t target = base + (int64_t)offset; + if (target < 0) target = 0; + + uint32_t s = SysSeekFile(f->Handle, (uint64_t)target); + if (s != 0) return -1; + f->Pos = (uint64_t)target; return 0; } static int doom_tell_wrapper(void* handle) { - return (int)SysGetFilePos((uint32_t)(uintptr_t)handle); + if (!handle) return -1; + return (int)((DOOM_FILE_HANDLE*)handle)->Pos; } static int doom_eof_wrapper(void* handle) { - uint32_t h = (uint32_t)(uintptr_t)handle; - uint64_t size = SysGetFileSize(h); - uint64_t pos = SysGetFilePos(h); - return (pos >= size) ? 1 : 0; + if (!handle) return 1; + DOOM_FILE_HANDLE* f = (DOOM_FILE_HANDLE*)handle; + return (f->Pos >= f->Size) ? 1 : 0; } // --------------------------------------------------------------------------- @@ -305,9 +340,9 @@ static int build_argv_from_command_line(const char* cmdline, char* storage, size // --------------------------------------------------------------------------- int main(void) { - const char* cmdline = SysGetPeb()->CommandLine; + const char* cmdline = SysGetPeb()->CommandLine.Data; - SysWriteConsole("doom: starting\n", 15); + do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: starting\n") }; SysWriteConsole(&req); } while (0); // Wire up PureDOOM core mechanics doom_set_print(doom_print_wrapper); @@ -339,7 +374,7 @@ int main(void) int argc = build_argv_from_command_line(cmdline, argvStorage, sizeof(argvStorage), argv, 32); doom_init(argc, argv, 0); - SysWriteConsole("doom: entering game loop\n", 25); + do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: entering game loop\n") }; SysWriteConsole(&req); } while (0); // Core execution loop while (1) { diff --git a/code/drivers/fat32/fat32.c b/code/drivers/fat32/fat32.c index 5837089..2fbb087 100644 --- a/code/drivers/fat32/fat32.c +++ b/code/drivers/fat32/fat32.c @@ -425,26 +425,6 @@ static KSTATUS DirentUpdateFirstCluster(FAT32_VOL *v, uint64_t lba, // --------------------------------------------------------------------------- // 8.3 name matching / assembly // --------------------------------------------------------------------------- -static int MatchName83(const char *dirName, const char *dirExt, - const char *want) { - char buf[13]; int n = 0; - for (int i = 0; i < 8 && dirName[i] != ' '; i++) - buf[n++] = (char)(dirName[i] >= 'A' && dirName[i] <= 'Z' ? dirName[i]+32 : dirName[i]); - if (dirExt[0] != ' ') { - buf[n++] = '.'; - for (int i = 0; i < 3 && dirExt[i] != ' '; i++) - buf[n++] = (char)(dirExt[i] >= 'A' && dirExt[i] <= 'Z' ? dirExt[i]+32 : dirExt[i]); - } - buf[n] = '\0'; - const char *p = buf; - while (*want && *p) { - char a = *p++, b = *want++; - if (a >= 'A' && a <= 'Z') a += 32; - if (b >= 'A' && b <= 'Z') b += 32; - if (a != b) return 0; - } - return *want == '\0' && *p == '\0'; -} static int MatchRaw83(const char dirName[8], const char dirExt[3], const char wantName[8], const char wantExt[3]) { @@ -452,34 +432,35 @@ static int MatchRaw83(const char dirName[8], const char dirExt[3], memcmp(dirExt, wantExt, 3) == 0; } -static void Make83Name(const char *src, char name8[8], char ext3[3]) { - memset(name8, ' ', 8); memset(ext3, ' ', 3); +static char FatUpperChar(char c); +static int FatIsLowerChar(char c); +static int FatIsValidShortChar(char c); - // A leading dot is not a FAT 8.3 extension separator. The old version - // turned ".doomrc" into an invalid empty-base short name with extension - // "DOO", making .doomrc, .doom and .doo all alias the same entry. Treat - // leading-dot names as a basename here; the create path will usually emit - // a VFAT long-name entry for them anyway. - const char *base = src; - const char *dot = NULL; - if (src && src[0] == '.' && src[1]) { - base = src + 1; +static void Make83NameV(SYS_STRING_VIEW src, char name8[8], char ext3[3]) { + memset(name8, ' ', 8); + memset(ext3, ' ', 3); + if (!SysSvIsValid(src) || src.Length == 0) return; + + size_t baseStart = 0; + size_t dot = (size_t)-1; + if (src.Data[0] == '.' && src.Length > 1) { + baseStart = 1; } else { - for (const char *p = src; p && *p; p++) if (*p == '.') dot = p; + for (size_t i = 0; i < src.Length; i++) + if (src.Data[i] == '.') dot = i; } + size_t end = (dot != (size_t)-1) ? dot : src.Length; int ni = 0; - const char *end = dot ? dot : base + strlen(base); - for (const char *p = base; p < end && ni < 8; p++) { - char c = *p; - if (c >= 'a' && c <= 'z') c -= 32; + for (size_t i = baseStart; i < end && ni < 8; i++) { + char c = FatUpperChar(src.Data[i]); name8[ni++] = c; } - if (dot) { + + if (dot != (size_t)-1) { int ei = 0; - for (const char *p = dot + 1; *p && ei < 3; p++) { - char c = *p; - if (c >= 'a' && c <= 'z') c -= 32; + for (size_t i = dot + 1; i < src.Length && ei < 3; i++) { + char c = FatUpperChar(src.Data[i]); ext3[ei++] = c; } } @@ -515,30 +496,31 @@ static int FatIsValidShortChar(char c) { // True when the user spelling can refer to a normal FAT 8.3 entry. This is // intentionally structural and case-insensitive: "doom1.wad" must still open // an existing short entry named DOOM1.WAD. Leading-dot names are not 8.3. -static int FatNameIsStructurallyShort83(const char *name) { - if (!name || !name[0]) return 0; - if (name[0] == '.') return 0; +static int FatNameIsStructurallyShort83V(SYS_STRING_VIEW name) { + if (!SysSvIsValid(name) || name.Length == 0) return 0; + if (name.Data[0] == '.') return 0; - const char *dot = NULL; - for (const char *p = name; *p; p++) { - if (*p == '.') { - if (dot) return 0; // multiple dots need LFN - dot = p; - } else if (!FatIsValidShortChar(*p)) { + size_t dot = (size_t)-1; + for (size_t i = 0; i < name.Length; i++) { + char c = name.Data[i]; + if (c == '.') { + if (dot != (size_t)-1) return 0; + dot = i; + } else if (!FatIsValidShortChar(c)) { return 0; } } - size_t baseLen = dot ? (size_t)(dot - name) : strlen(name); - size_t extLen = dot ? strlen(dot + 1) : 0; + size_t baseLen = (dot != (size_t)-1) ? dot : name.Length; + size_t extLen = (dot != (size_t)-1) ? (name.Length - dot - 1) : 0; return baseLen >= 1 && baseLen <= 8 && extLen <= 3; } // Creation uses LFN when needed for structure, or when lowercase would be lost. -static int FatNameNeedsLfnForCreate(const char *name) { - if (!FatNameIsStructurallyShort83(name)) return 1; - for (const char *p = name; *p; p++) - if (FatIsLowerChar(*p)) return 1; +static int FatNameNeedsLfnForCreateV(SYS_STRING_VIEW name) { + if (!FatNameIsStructurallyShort83V(name)) return 1; + for (size_t i = 0; i < name.Length; i++) + if (FatIsLowerChar(name.Data[i])) return 1; return 0; } @@ -624,33 +606,35 @@ static void FatAliasAppendChar(char *dst, int *len, int maxLen, char c) { if (*len < maxLen) dst[(*len)++] = c; } -static void BuildAliasStemExt(const char *src, char stem[9], char ext3[3]) { +static void BuildAliasStemExtV(SYS_STRING_VIEW src, char stem[9], char ext3[3]) { memset(stem, 0, 9); memset(ext3, ' ', 3); + if (!SysSvIsValid(src)) return; - const char *base = src; - const char *dot = NULL; - if (src[0] == '.' && src[1]) { + size_t baseStart = 0; + size_t dot = (size_t)-1; + if (src.Length > 1 && src.Data[0] == '.') { // Hidden/Unix-style names like .doomrc become alias stem DOOMRC. - base = src + 1; + baseStart = 1; } else { - for (const char *p = src; *p; p++) if (*p == '.') dot = p; + for (size_t i = 0; i < src.Length; i++) + if (src.Data[i] == '.') dot = i; } int slen = 0; - const char *end = dot ? dot : src + strlen(src); - for (const char *p = base; p < end && *p; p++) - FatAliasAppendChar(stem, &slen, 8, *p); + size_t end = (dot != (size_t)-1) ? dot : src.Length; + for (size_t i = baseStart; i < end; i++) + FatAliasAppendChar(stem, &slen, 8, src.Data[i]); if (slen == 0) { memcpy(stem, "FILE", 4); slen = 4; } stem[slen] = '\0'; - if (dot && dot[1]) { + if (dot != (size_t)-1 && dot + 1 < src.Length) { int ei = 0; - for (const char *p = dot + 1; *p && ei < 3; p++) { - char c = FatUpperChar(*p); + for (size_t i = dot + 1; i < src.Length && ei < 3; i++) { + char c = FatUpperChar(src.Data[i]); if (!FatIsValidShortChar(c)) continue; ext3[ei++] = c; } @@ -683,11 +667,11 @@ static void MakeAliasAttempt(const char stem[9], const char extIn[3], memcpy(name8 + keep, suffix, suffixLen); } -static int MakeUniqueShortAlias(FAT32_VOL *v, uint32_t dirCluster, - const char *longName, - char name8[8], char ext3[3]) { +static int MakeUniqueShortAliasV(FAT32_VOL *v, uint32_t dirCluster, + SYS_STRING_VIEW longName, + char name8[8], char ext3[3]) { char stem[9], extCandidate[3]; - BuildAliasStemExt(longName, stem, extCandidate); + BuildAliasStemExtV(longName, stem, extCandidate); for (uint32_t attempt = 1; attempt < 1000000; attempt++) { MakeAliasAttempt(stem, extCandidate, attempt, name8, ext3); @@ -696,12 +680,25 @@ static int MakeUniqueShortAlias(FAT32_VOL *v, uint32_t dirCluster, return 0; } -static KSTATUS DeleteLegacyBrokenDotAlias(FAT32_VOL *v, uint32_t dirCluster, - const char *longName) { - if (!longName || longName[0] != '.' || !longName[1]) return STATUS_SUCCESS; +static void MakeLegacyBrokenDot83V(SYS_STRING_VIEW src, char name8[8], char ext3[3]) { + // This exactly models the pre-fix bug for ".doomrc": empty name + "DOO". + memset(name8, ' ', 8); + memset(ext3, ' ', 3); + if (!SysSvIsValid(src)) return; + size_t dot = (size_t)-1; + for (size_t i = 0; i < src.Length; i++) if (src.Data[i] == '.') dot = i; + if (dot == (size_t)-1) return; + int ei = 0; + for (size_t i = dot + 1; i < src.Length && ei < 3; i++) + ext3[ei++] = FatUpperChar(src.Data[i]); +} + +static KSTATUS DeleteLegacyBrokenDotAliasV(FAT32_VOL *v, uint32_t dirCluster, + SYS_STRING_VIEW longName) { + if (!SysSvIsValid(longName) || longName.Length < 2 || longName.Data[0] != '.') return STATUS_SUCCESS; char badName[8], badExt[3]; - MakeLegacyBrokenDot83(longName, badName, badExt); + MakeLegacyBrokenDot83V(longName, badName, badExt); uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return STATUS_NO_MEMORY; @@ -754,29 +751,71 @@ static void LfnAppend(char *lfn, const LFN_ENTRY *e) { } } -static int LfnMatch(const char *lfn, const char *want) { - while (*lfn && *want) { - char a = *lfn++, b = *want++; + +// --------------------------------------------------------------------------- +// Directory search and view-native path traversal +// --------------------------------------------------------------------------- +static int Fat32IsPathSep(char c) { + return c == '\\' || c == '/'; +} + +static int Fat32NameViewIsValid(SYS_STRING_VIEW name) { + if (!SysSvIsValid(name)) return 0; + if (name.Length == 0 || name.Length >= MAX_LFN) return 0; + for (size_t i = 0; i < name.Length; i++) { + char c = name.Data[i]; + if (c == '\0' || Fat32IsPathSep(c)) return 0; + } + return 1; +} + +static int LfnMatchV(const char *lfn, SYS_STRING_VIEW want) { + if (!lfn || !SysSvIsValid(want)) return 0; + size_t i = 0; + while (lfn[i] && i < want.Length) { + char a = lfn[i], b = want.Data[i]; + if (a >= 'A' && a <= 'Z') a += 32; + if (b >= 'A' && b <= 'Z') b += 32; + if (a != b) return 0; + i++; + } + return lfn[i] == '\0' && i == want.Length; +} + +static int MatchName83V(const char *dirName, const char *dirExt, + SYS_STRING_VIEW want) { + char buf[13]; + int n = 0; + for (int i = 0; i < 8 && dirName[i] != ' '; i++) + buf[n++] = (char)(dirName[i] >= 'A' && dirName[i] <= 'Z' ? dirName[i]+32 : dirName[i]); + if (dirExt[0] != ' ') { + buf[n++] = '.'; + for (int i = 0; i < 3 && dirExt[i] != ' '; i++) + buf[n++] = (char)(dirExt[i] >= 'A' && dirExt[i] <= 'Z' ? dirExt[i]+32 : dirExt[i]); + } + + if ((size_t)n != want.Length) return 0; + for (int i = 0; i < n; i++) { + char a = buf[i], b = want.Data[i]; if (a >= 'A' && a <= 'Z') a += 32; if (b >= 'A' && b <= 'Z') b += 32; if (a != b) return 0; } - return *lfn == '\0' && *want == '\0'; + return 1; } -// --------------------------------------------------------------------------- -// Directory search -// --------------------------------------------------------------------------- -static int SearchDir(FAT32_VOL *v, uint32_t dirCluster, const char *name, - uint32_t *outCluster, uint32_t *outSize, uint8_t *outAttr, - uint64_t *outDirentLba, uint32_t *outDirentOffset) { +static int SearchDirV(FAT32_VOL *v, uint32_t dirCluster, SYS_STRING_VIEW name, + uint32_t *outCluster, uint32_t *outSize, uint8_t *outAttr, + uint64_t *outDirentLba, uint32_t *outDirentOffset) { + if (!Fat32NameViewIsValid(name)) return 0; + uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return 0; char lfn[MAX_LFN]; int hasLfn = 0; memset(lfn, 0, sizeof(lfn)); char wantName83[8], wantExt83[3]; - int allowShortMatch = FatNameIsStructurallyShort83(name); - Make83Name(name, wantName83, wantExt83); + int allowShortMatch = FatNameIsStructurallyShort83V(name); + Make83NameV(name, wantName83, wantExt83); uint32_t cluster = dirCluster; while (cluster >= 2 && cluster < FAT32_EOC) { @@ -792,9 +831,9 @@ static int SearchDir(FAT32_VOL *v, uint32_t dirCluster, const char *name, if (dir[i].Attr == ATTR_LFN) { LfnAppend(lfn, (LFN_ENTRY *)&dir[i]); hasLfn = 1; continue; } int match = 0; - if (hasLfn && lfn[0]) match = LfnMatch(lfn, name); + if (hasLfn && lfn[0]) match = LfnMatchV(lfn, name); if (!match && allowShortMatch) - match = MatchName83(dir[i].Name, dir[i].Ext, name); + match = MatchName83V(dir[i].Name, dir[i].Ext, name); if (!match && allowShortMatch) match = MatchRaw83(dir[i].Name, dir[i].Ext, wantName83, wantExt83); hasLfn = 0; memset(lfn, 0, sizeof(lfn)); @@ -819,58 +858,92 @@ done: return 0; } -// --------------------------------------------------------------------------- -// Path traversal -// --------------------------------------------------------------------------- -static KSTATUS Fat32LookupPath(FAT32_VOL *v, const char *path, - uint32_t *outCluster, uint32_t *outSize, - uint64_t *outDirentLba, uint32_t *outDirentOffset) { +static int Fat32PathNextComponent(SYS_STRING_VIEW path, size_t* cursor, + SYS_STRING_VIEW* component) { + if (!cursor || !component || !SysSvIsValid(path)) return 0; + size_t pos = *cursor; + while (pos < path.Length && Fat32IsPathSep(path.Data[pos])) pos++; + if (pos >= path.Length) { + *cursor = pos; + *component = SYS_SV_NULL; + return 0; + } + + size_t start = pos; + while (pos < path.Length && !Fat32IsPathSep(path.Data[pos])) pos++; + *cursor = pos; + *component = SysSvFromParts(path.Data + start, pos - start); + return 1; +} + +static KSTATUS Fat32LookupPathV(FAT32_VOL *v, SYS_STRING_VIEW path, + uint32_t *outCluster, uint32_t *outSize, + uint64_t *outDirentLba, uint32_t *outDirentOffset) { + if (!v || !outCluster || !outSize || !SysSvIsValid(path)) + return STATUS_INVALID_PARAMETER; + if (SysSvContainsByte(path, '\0')) + return STATUS_INVALID_PARAMETER; + uint32_t cluster = v->RootCluster, size = 0; uint8_t attr = ATTR_DIRECTORY; if (outDirentLba) *outDirentLba = 0; if (outDirentOffset) *outDirentOffset = 0; - char component[256]; const char *p = path; - while (*p) { - while (*p == '\\' || *p == '/') p++; - if (!*p) break; - int len = 0; - while (*p && *p != '\\' && *p != '/' && len < 255) component[len++] = *p++; - component[len] = '\0'; - if (!len) break; + size_t cursor = 0; + SYS_STRING_VIEW component; + while (Fat32PathNextComponent(path, &cursor, &component)) { + if (component.Length >= MAX_LFN) return STATUS_BUFFER_TOO_SMALL; + if (!Fat32NameViewIsValid(component)) return STATUS_INVALID_PARAMETER; if (!(attr & ATTR_DIRECTORY)) return STATUS_NOT_FOUND; uint32_t childCluster, childSize; uint8_t childAttr; uint64_t childLba = 0; uint32_t childOffset = 0; - if (!SearchDir(v, cluster, component, &childCluster, &childSize, - &childAttr, &childLba, &childOffset)) + if (!SearchDirV(v, cluster, component, &childCluster, &childSize, + &childAttr, &childLba, &childOffset)) return STATUS_NOT_FOUND; cluster = childCluster; size = childSize; attr = childAttr; if (outDirentLba) *outDirentLba = childLba; if (outDirentOffset) *outDirentOffset = childOffset; } - *outCluster = cluster; *outSize = size; + + *outCluster = cluster; + *outSize = size; return STATUS_SUCCESS; } -static KSTATUS Traverse(FAT32_VOL *v, const char *path, +static KSTATUS TraverseV(FAT32_VOL *v, SYS_STRING_VIEW path, uint32_t *outCluster, uint32_t *outSize) { - return Fat32LookupPath(v, path, outCluster, outSize, NULL, NULL); + return Fat32LookupPathV(v, path, outCluster, outSize, NULL, NULL); } -// --------------------------------------------------------------------------- -// Path helpers for Fat32Create -// --------------------------------------------------------------------------- -static void SplitPath(const char *path, char parent[256], char name[256]) { - const char *last = NULL; - for (const char *p = path; *p; p++) if (*p == '\\' || *p == '/') last = p; - if (!last) { parent[0] = '\0'; strncpy(name, path, 255); name[255] = '\0'; } - else { - size_t len = (size_t)(last - path); if (len > 255) len = 255; - memcpy(parent, path, len); parent[len] = '\0'; - strncpy(name, last+1, 255); name[255] = '\0'; +static KSTATUS SplitPathV(SYS_STRING_VIEW path, SYS_STRING_VIEW* parent, + SYS_STRING_VIEW* name) { + if (!parent || !name || !SysSvIsValid(path)) return STATUS_INVALID_PARAMETER; + *parent = SYS_SV_NULL; + *name = SYS_SV_NULL; + + while (path.Length && Fat32IsPathSep(path.Data[path.Length - 1])) + path.Length--; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; + + if (SysSvContainsByte(path, '\0')) return STATUS_INVALID_PARAMETER; + + size_t lastSep = (size_t)-1; + for (size_t i = 0; i < path.Length; i++) { + if (Fat32IsPathSep(path.Data[i])) lastSep = i; } + + if (lastSep == (size_t)-1) { + *parent = SYS_SV_NULL; + *name = path; + } else { + *parent = SysSvFromParts(path.Data, lastSep); + *name = SysSvFromParts(path.Data + lastSep + 1, path.Length - lastSep - 1); + } + + if (!Fat32NameViewIsValid(*name)) return STATUS_INVALID_PARAMETER; + return STATUS_SUCCESS; } // --------------------------------------------------------------------------- @@ -969,11 +1042,15 @@ static int FindFreeDirentRun(FAT32_VOL *v, uint32_t dirCluster, uint32_t needed, // --------------------------------------------------------------------------- // Open / Close // --------------------------------------------------------------------------- -static KSTATUS Fat32Open(IO_FILESYSTEM *fs, const char *path, void **fileCtx) { +static KSTATUS Fat32Open(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) { + if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0) + return STATUS_INVALID_PARAMETER; + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; - KSTATUS s = Fat32LookupPath(v, path, &cluster, &size, &direntLba, &direntOffset); + KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; + FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); if (!f) return STATUS_NO_MEMORY; f->StartCluster = cluster; f->FileSize = size; f->Vol = v; @@ -1013,11 +1090,19 @@ static KSTATUS Fat32Read(IO_FILESYSTEM *fs, void *fileCtx, FAT32_FILE *f = (FAT32_FILE *)fileCtx; FAT32_VOL *v = f->Vol; if (offset >= f->FileSize || size == 0) return STATUS_SUCCESS; - if (offset + size > f->FileSize) size = (size_t)(f->FileSize - offset); + if (size > (size_t)(f->FileSize - offset)) size = (size_t)(f->FileSize - offset); size_t wanted = size; - // Single allocation for the largest possible coalesced run. - uint8_t *runBuf = (uint8_t *)MmAllocatePool(MAX_RUN_SECTORS * 512u); + // Allocate only as much bounce space as this read can actually use. + // The old code allocated 64 KiB and could read a 64 KiB contiguous run even + // for tiny random reads; Doom does lots of seek/read pairs for small lumps. + uint32_t initialOffsetInCluster = (uint32_t)(offset % v->BytesPerCluster); + uint64_t initialBytesCovered = (uint64_t)initialOffsetInCluster + (uint64_t)size; + uint32_t maxUsefulSectors = (uint32_t)((initialBytesCovered + 511u) / 512u); + if (maxUsefulSectors < v->SectorsPerCluster) maxUsefulSectors = v->SectorsPerCluster; + if (maxUsefulSectors > MAX_RUN_SECTORS) maxUsefulSectors = MAX_RUN_SECTORS; + + uint8_t *runBuf = (uint8_t *)MmAllocatePool((size_t)maxUsefulSectors * 512u); if (!runBuf) return STATUS_NO_MEMORY; size_t totalRead = 0; @@ -1031,13 +1116,21 @@ static KSTATUS Fat32Read(IO_FILESYSTEM *fs, void *fileCtx, cluster = FatEntry(v, cluster); while (size > 0 && cluster >= 2 && cluster < FAT32_EOC) { - // Build the longest physically-contiguous run we can. + // Build the longest physically-contiguous run that can contribute to + // this caller's remaining read. Do not prefetch an unrelated 64 KiB run + // for a tiny random read. + uint64_t bytesCovered = (uint64_t)offsetInCluster + (uint64_t)size; + uint32_t usefulSectors = (uint32_t)((bytesCovered + 511u) / 512u); + if (usefulSectors < v->SectorsPerCluster) usefulSectors = v->SectorsPerCluster; + if (usefulSectors > MAX_RUN_SECTORS) usefulSectors = MAX_RUN_SECTORS; + if (usefulSectors > maxUsefulSectors) usefulSectors = maxUsefulSectors; + uint32_t runStart = cluster; uint32_t runSectors = v->SectorsPerCluster; uint32_t next = FatEntry(v, cluster); while (next == cluster + 1 && next < FAT32_EOC - && runSectors + v->SectorsPerCluster <= MAX_RUN_SECTORS) { + && runSectors + v->SectorsPerCluster <= usefulSectors) { cluster = next; next = FatEntry(v, cluster); runSectors += v->SectorsPerCluster; @@ -1188,21 +1281,25 @@ static KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx, // --------------------------------------------------------------------------- static KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize); -static KSTATUS Fat32Create(IO_FILESYSTEM *fs, const char *path, void **fileCtx) { +static KSTATUS Fat32Create(IO_FILESYSTEM *fs, SYS_STRING_VIEW path, void **fileCtx) { + if (!fs || !fileCtx || !SysSvIsValid(path) || path.Length == 0) + return STATUS_INVALID_PARAMETER; + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; - char parentPath[256], filename[256]; - SplitPath(path, parentPath, filename); - if (!filename[0]) return STATUS_INVALID_PARAMETER; + SYS_STRING_VIEW parentPath; + SYS_STRING_VIEW filenameView; + KSTATUS s = SplitPathV(path, &parentPath, &filenameView); + if (!KSUCCESS(s)) return s; uint32_t parentCluster, parentSize; - KSTATUS s = Traverse(v, parentPath, &parentCluster, &parentSize); + s = TraverseV(v, parentPath, &parentCluster, &parentSize); if (!KSUCCESS(s)) return s; { uint32_t ec, es; uint8_t ea; uint64_t existingLba = 0; uint32_t existingOffset = 0; - if (SearchDir(v, parentCluster, filename, &ec, &es, &ea, - &existingLba, &existingOffset)) { + if (SearchDirV(v, parentCluster, filenameView, &ec, &es, &ea, + &existingLba, &existingOffset)) { if (ea & ATTR_DIRECTORY) return STATUS_OBJECT_NAME_COLLISION; @@ -1224,18 +1321,18 @@ static KSTATUS Fat32Create(IO_FILESYSTEM *fs, const char *path, void **fileCtx) } } - int useLfn = FatNameNeedsLfnForCreate(filename); + int useLfn = FatNameNeedsLfnForCreateV(filenameView); char shortName[8], shortExt[3]; if (useLfn) { - s = DeleteLegacyBrokenDotAlias(v, parentCluster, filename); + s = DeleteLegacyBrokenDotAliasV(v, parentCluster, filenameView); if (!KSUCCESS(s)) return s; - if (!MakeUniqueShortAlias(v, parentCluster, filename, shortName, shortExt)) + if (!MakeUniqueShortAliasV(v, parentCluster, filenameView, shortName, shortExt)) return STATUS_OBJECT_NAME_COLLISION; } else { - Make83Name(filename, shortName, shortExt); + Make83NameV(filenameView, shortName, shortExt); } - size_t nameLen = strlen(filename); + size_t nameLen = filenameView.Length; if (nameLen == 0 || nameLen >= MAX_LFN) return STATUS_INVALID_PARAMETER; uint32_t lfnCount = useLfn ? (uint32_t)((nameLen + 12u) / 13u) : 0; uint32_t neededEntries = lfnCount + 1u; @@ -1258,7 +1355,7 @@ static KSTATUS Fat32Create(IO_FILESYSTEM *fs, const char *path, void **fileCtx) uint8_t checksum = LfnChecksum(shortName, shortExt); for (uint32_t slot = 0; slot < lfnCount; slot++) { uint32_t ordinal = lfnCount - slot; - BuildLfnEntry((LFN_ENTRY *)&dir[firstIndex + slot], filename, + BuildLfnEntry((LFN_ENTRY *)&dir[firstIndex + slot], filenameView.Data, nameLen, ordinal, lfnCount, checksum); } } @@ -1348,10 +1445,13 @@ update_size: // --------------------------------------------------------------------------- // Delete // --------------------------------------------------------------------------- -static KSTATUS Fat32Delete(IO_FILESYSTEM *fs, const char *path) { +static KSTATUS Fat32Delete(IO_FILESYSTEM *fs, SYS_STRING_VIEW path) { + if (!fs || !SysSvIsValid(path) || path.Length == 0) + return STATUS_INVALID_PARAMETER; + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; - KSTATUS s = Fat32LookupPath(v, path, &cluster, &size, &direntLba, &direntOffset); + KSTATUS s = Fat32LookupPathV(v, path, &cluster, &size, &direntLba, &direntOffset); if (!KSUCCESS(s)) return s; if (!direntLba) return STATUS_ACCESS_DENIED; @@ -1389,13 +1489,17 @@ typedef struct { uint8_t *ClusterBuf; // allocated once in OpenDir, freed in CloseDir } FAT32_DIR_CTX; -static KSTATUS Fat32OpenDir(IO_FILESYSTEM *fs, const char *dirPath, void **dirCtx) { +static KSTATUS Fat32OpenDir(IO_FILESYSTEM *fs, SYS_STRING_VIEW dirPath, void **dirCtx) { + if (!fs || !dirCtx || !SysSvIsValid(dirPath)) + return STATUS_INVALID_PARAMETER; + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; uint32_t cluster; - if (!dirPath || dirPath[0] == '\0') { + if (dirPath.Length == 0) { cluster = v->RootCluster; } else { - uint32_t size; KSTATUS s = Traverse(v, dirPath, &cluster, &size); + uint32_t size; + KSTATUS s = TraverseV(v, dirPath, &cluster, &size); if (!KSUCCESS(s)) return s; } diff --git a/code/edit/edit.c b/code/edit/edit.c index dba61bb..54db77c 100644 --- a/code/edit/edit.c +++ b/code/edit/edit.c @@ -302,8 +302,9 @@ static void move_down(void) { static void save_file(void) { uint32_t h = 0; - uint32_t st = SysCreateFile(gFilename, &h); - if (st != 0) st = SysOpenFile(gFilename, &h); + SYS_PATH_HANDLE_REQUEST pathReq = { SysSvFromCstr(gFilename), &h }; + uint32_t st = SysCreateFile(&pathReq); + if (st != 0) st = SysOpenFile(&pathReq); if (st != 0) { msg_set("save failed: cannot open file"); return; } SysTruncateFile(h, 0); @@ -339,7 +340,8 @@ static void load_file(void) { reset_buffer(); uint32_t h = 0; - if (SysOpenFile(gFilename, &h) != 0) { + SYS_PATH_HANDLE_REQUEST pathReq = { SysSvFromCstr(gFilename), &h }; + if (SysOpenFile(&pathReq) != 0) { msg_set("new file"); return; } @@ -411,7 +413,7 @@ static bool read_token(const char* s, char* out, size_t cap) { static void parse_filename_from_cmdline(void) { SYS_PEB* peb = SysGetPeb(); - const char* cmd = peb ? peb->CommandLine : NULL; + const char* cmd = (peb && peb->CommandLine.Data) ? peb->CommandLine.Data : NULL; const char* arg = skip_token(cmd); if (read_token(arg, gFilename, sizeof(gFilename))) return; strcpy(gFilename, "untitled.txt"); @@ -476,7 +478,8 @@ static void editor_loop(void) { } static void print(const char* s) { - SysWriteConsole(s, strlen(s)); + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; + SysWriteConsole(&req); } int main(void) { diff --git a/code/hal/hal_timer.c b/code/hal/hal_timer.c index afd95f9..90482d2 100644 --- a/code/hal/hal_timer.c +++ b/code/hal/hal_timer.c @@ -23,6 +23,7 @@ static volatile uint32_t* gLapic = NULL; static uint32_t gApicTicksPerUs = 0; static uint32_t gApicTicksPerQuantum = 0; static uint64_t gHpetTicksPerUs = 0; +static uint64_t gHpetHz = 0; static volatile uint64_t* gHpet = NULL; #define LAPIC_REG(off) (*(volatile uint32_t *)((uint8_t *)gLapic + (off))) @@ -39,11 +40,28 @@ static void TimerIsr(void) { } static uint64_t QueryTicks(void) { - uint64_t baseTicks = gTicks; // The 10ms "coarse" ticks - - // The LAPIC timer counts DOWN. - // Ticks elapsed = (Initial Value) - (Current Value) - uint32_t current = LAPIC_REG(LAPIC_TIMER_CUR); + // Prefer HPET when present: it is already a monotonically increasing main + // counter and avoids races between the LAPIC periodic tick and the current + // countdown register. + if (gHpet && gHpetHz) { + uint64_t counter = gHpet[30]; // offset 0xF0: main counter + uint64_t seconds = counter / gHpetHz; + uint64_t rem = counter % gHpetHz; + return seconds * 1000000ull + (rem * 1000000ull) / gHpetHz; + } + + // Fallback: combine the coarse periodic tick with the current LAPIC + // countdown. Read the coarse tick twice so an interrupt between gTicks and + // LAPIC_TIMER_CUR cannot make time jump backwards by almost one quantum. + uint64_t baseTicks1; + uint64_t baseTicks2; + uint32_t current; + do { + baseTicks1 = gTicks; + current = LAPIC_REG(LAPIC_TIMER_CUR); + baseTicks2 = gTicks; + } while (baseTicks1 != baseTicks2); + uint32_t initial = gApicTicksPerQuantum; uint32_t elapsedApicTicks = 0; @@ -51,11 +69,11 @@ static uint64_t QueryTicks(void) { elapsedApicTicks = initial - current; } - // Convert base 10ms ticks to microseconds + the remainder - // gTicks * 10,000 (ms to us) + (elapsed / ticks_per_us) - uint64_t totalUs = (baseTicks * QUANTUM_MS * 1000) + (elapsedApicTicks / gApicTicksPerUs); - - return totalUs; + uint64_t subQuantumUs = 0; + if (gApicTicksPerUs) + subQuantumUs = elapsedApicTicks / gApicTicksPerUs; + + return (baseTicks1 * QUANTUM_MS * 1000ull) + subQuantumUs; } static void Stall(uint32_t us) { @@ -181,6 +199,7 @@ KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *o if (hpetPhys) { gHpet = (volatile uint64_t*)gApi.PhysToVirt(hpetPhys); uint64_t hpetHz = HpetInit(hpetPhys); + gHpetHz = hpetHz; gHpetTicksPerUs = hpetHz / 1000000; } diff --git a/code/httpget/httpget.c b/code/httpget/httpget.c index 6e2a654..0395d48 100644 --- a/code/httpget/httpget.c +++ b/code/httpget/httpget.c @@ -5,11 +5,12 @@ #include static void print(const char* s) { - if (s) SysWriteConsole(s, strlen(s)); + if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } } static void print_char(char c) { - SysWriteConsole(&c, 1); + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); } static void print_u64(uint64_t v) { @@ -115,8 +116,7 @@ static uint32_t resolve_host(const char* host, uint8_t ip[4]) { SYS_RESOLVE_HOST_REQUEST req; memset(&resolved, 0, sizeof(resolved)); memset(&req, 0, sizeof(req)); - req.Name = host; - req.NameLength = strlen(host); + req.Name = SysSvFromCstr(host); req.Family = SYS_AF_INET; req.AddressOut = &resolved; req.AddressSizeInOut = &resolvedSize; @@ -142,7 +142,7 @@ int main(void) { path[0] = '/'; path[1] = 0; - const char* cmd = SysGetPeb()->CommandLine; + const char* cmd = SysGetPeb()->CommandLine.Data; char tok[256]; cmd = next_token(cmd, tok, sizeof(tok)); // program path if (cmd) { diff --git a/code/init/init.c b/code/init/init.c index 6a1071f..839fec5 100644 --- a/code/init/init.c +++ b/code/init/init.c @@ -48,32 +48,38 @@ void itoa(int num, char* str) { } } +static void debug_print(const char* s) { + SYS_DEBUG_PRINT_REQUEST req = { SysSvFromCstr(s) }; + (void)SysDebugPrint(&req); +} + void _start(void) { - const char* args = SysGetPeb()->CommandLine; + SYS_STRING_VIEW args = SysGetPeb()->CommandLine; uint32_t h = 0; uint32_t s = 0; while (true) { - SysDebugPrint("[init] starting\n"); - s = SysCreateProcess(args, &h, NULL); + debug_print("[init] starting\n"); + SYS_CREATE_PROCESS_REQUEST cp = { args, SysSvFromParts(0, 0), &h }; + s = SysCreateProcess(&cp); if (s != STATUS_SUCCESS) { - SysDebugPrint("[init] FATAL: failed to start!\n"); + debug_print("[init] FATAL: failed to start!\n"); char buffer[12]; itoa(s, buffer); - SysDebugPrint(buffer); - SysDebugPrint("\n"); + debug_print(buffer); + debug_print("\n"); SysExitProcess(1); } - SysDebugPrint("[init] waiting\n"); + debug_print("[init] waiting\n"); s = SysWaitForProcess(h); if (s != STATUS_SUCCESS) - SysDebugPrint("[init] WARNING: SysWaitForProcess returned error\n"); + debug_print("[init] WARNING: SysWaitForProcess returned error\n"); int exitCode = 0; if (SysGetProcessExitCode(h, &exitCode) != STATUS_SUCCESS) { - SysDebugPrint("[init] WARNING: failed to read exit code\n"); + debug_print("[init] WARNING: failed to read exit code\n"); exitCode = 1; } @@ -82,9 +88,9 @@ void _start(void) if (exitCode == 0) break; - SysDebugPrint("[init] child crashed, restarting\n"); + debug_print("[init] child crashed, restarting\n"); } - SysDebugPrint("[init] done\n"); + debug_print("[init] done\n"); SysExitProcess(0); } diff --git a/code/kernel/io_file.c b/code/kernel/io_file.c index 175b959..59c30c0 100644 --- a/code/kernel/io_file.c +++ b/code/kernel/io_file.c @@ -387,22 +387,26 @@ __declspec(dllexport) KSTATUS IoQueryAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJE // Stored as (CurrentDrive, drive-relative path with no trailing backslash). // Exported as native "N:/path" so personalities can round-trip volumes. // --------------------------------------------------------------------------- -#define CWD_MAX 512 +#define CWD_MAX PROCESS_CWD_MAX -static size_t KiFormatCwd(uint8_t drive, const char* rel, char* buf, size_t size) { - if (!buf || size == 0) return 0; +typedef struct KRESOLVED_PATH { + uint8_t Drive; + char Storage[CWD_MAX]; + SYS_STRING_VIEW Path; +} KRESOLVED_PATH; + +static size_t KiFormatCwd(uint8_t drive, SYS_STRING_VIEW rel, char* buf, size_t size) { + if (!buf || size == 0 || !SysSvIsValid(rel)) return 0; if (drive > 7) drive = 0; - if (!rel) rel = ""; - size_t relLen = strlen(rel); - size_t needed = 3 + relLen + 1; + size_t needed = 3 + rel.Length + 1; if (needed > size) return needed; buf[0] = (char)('0' + drive); buf[1] = ':'; buf[2] = '/'; - memcpy(buf + 3, rel, relLen); - buf[3 + relLen] = '\0'; + if (rel.Length) memcpy(buf + 3, rel.Data, rel.Length); + buf[3 + rel.Length] = '\0'; return needed; } @@ -413,17 +417,57 @@ __declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) { if (!proc) return STATUS_ACCESS_DENIED; uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - size_t needed = KiFormatCwd(proc->CurrentDrive, proc->CurrentDirectory, buf, size); + size_t needed = KiFormatCwd(proc->CurrentDrive, SysSvFromParts(proc->CurrentDirectory, proc->CurrentDirectoryLength), buf, size); KeReleaseSpinlock(&proc->CwdLock, flags); return needed && needed <= size ? STATUS_SUCCESS : STATUS_BUFFER_TOO_SMALL; } -// Resolve a possibly-relative path component against the CWD. -// Writes the fully-resolved, drive-stripped relative path into 'resolved' -// (caller supplies buf of at least CWD_MAX bytes). -// Returns the drive index. -static uint8_t KiResolvePath(const char* path, char* resolved, size_t resolvedSize) { +static int KiSvHasDriveRoot(SYS_STRING_VIEW path) { + return path.Length >= 3 && + path.Data[0] >= '0' && path.Data[0] <= '7' && + path.Data[1] == ':' && + (path.Data[2] == '\\' || path.Data[2] == '/'); +} + +static int KiAppendBytes(char* work, size_t* workLen, size_t cap, + const char* data, size_t len) { + if (!work || !workLen || (!data && len)) return 0; + if (*workLen + len >= cap) return 0; + if (len) memcpy(work + *workLen, data, len); + *workLen += len; + work[*workLen] = '\0'; + return 1; +} + +static int KiAppendComponent(char* work, size_t* workLen, size_t cap, + const char* data, size_t len) { + if (!work || !workLen || (!data && len)) return 0; + if (*workLen && work[*workLen - 1] != '\\' && work[*workLen - 1] != '/') { + if (!KiAppendBytes(work, workLen, cap, "\\", 1)) return 0; + } + return KiAppendBytes(work, workLen, cap, data, len); +} + +static void KiPopComponent(char* work, size_t* workLen) { + if (!work || !workLen || *workLen == 0) return; + size_t i = *workLen; + while (i > 0 && work[i - 1] != '\\' && work[i - 1] != '/') i--; + if (i > 0) i--; + *workLen = i; + work[i] = '\0'; +} + +// Resolve a possibly-relative string view against the CWD. +// The returned view is backed by out->Storage and remains valid while 'out' lives. +static KSTATUS KiResolvePathV(SYS_STRING_VIEW input, KRESOLVED_PATH* out) { + if (!out) return STATUS_INVALID_PARAMETER; + memset(out, 0, sizeof(*out)); + + if (!SysSvIsValid(input)) return STATUS_INVALID_PARAMETER; + if (SysSvContainsByte(input, '\0')) return STATUS_INVALID_PARAMETER; + if (input.Length >= CWD_MAX) return STATUS_BUFFER_TOO_SMALL; + uint8_t drive = 0; PROCESS* proc = KeGetCurrentProcess(); if (proc) { @@ -432,128 +476,98 @@ static uint8_t KiResolvePath(const char* path, char* resolved, size_t resolvedSi KeReleaseSpinlock(&proc->CwdLock, flags); } - // Strip explicit drive prefix "N:\" or "N:/". + size_t pos = 0; bool fromDriveRoot = false; - if (path[0] >= '0' && path[0] <= '7' && - path[1] == ':' && (path[2] == '\\' || path[2] == '/')) { - drive = (uint8_t)(path[0] - '0'); - path = path + 3; + if (KiSvHasDriveRoot(input)) { + drive = (uint8_t)(input.Data[0] - '0'); + pos = 3; fromDriveRoot = true; } char work[CWD_MAX * 2]; + size_t workLen = 0; work[0] = '\0'; - // Initialize our working base - if (path[0] == '\\' || path[0] == '/' || fromDriveRoot) { - // Absolute path: start from the root (empty work buffer) - // The segment loop below will automatically skip the leading separators. - } else { - // Relative path: start from the current process's CWD - PROCESS* proc = KeGetCurrentProcess(); - if (proc) { - uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - size_t wlen = strlen(proc->CurrentDirectory); - if (wlen >= sizeof(work)) wlen = sizeof(work) - 1; - memcpy(work, proc->CurrentDirectory, wlen); - work[wlen] = '\0'; - KeReleaseSpinlock(&proc->CwdLock, flags); - } + bool absolute = fromDriveRoot || + (pos < input.Length && (input.Data[pos] == '\\' || input.Data[pos] == '/')); + if (!absolute && proc) { + uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); + size_t cwdLen = proc->CurrentDirectoryLength; + if (cwdLen >= sizeof(work)) cwdLen = sizeof(work) - 1; + memcpy(work, proc->CurrentDirectory, cwdLen); + work[cwdLen] = '\0'; + workLen = cwdLen; + KeReleaseSpinlock(&proc->CwdLock, flags); } - // Segment-by-segment canonicalization - const char* p = path; - while (*p) { - // Skip separators - while (*p == '\\' || *p == '/') p++; - if (!*p) break; + while (pos < input.Length) { + while (pos < input.Length && (input.Data[pos] == '\\' || input.Data[pos] == '/')) pos++; + if (pos >= input.Length) break; - // Read next component - char comp[256]; - int clen = 0; - while (*p && *p != '\\' && *p != '/' && clen < 255) - comp[clen++] = *p++; - comp[clen] = '\0'; + size_t compStart = pos; + while (pos < input.Length && input.Data[pos] != '\\' && input.Data[pos] != '/') pos++; + size_t compLen = pos - compStart; + if (compLen == 0) continue; - if (clen == 0) continue; - - if (strcmp(comp, ".") == 0) { - // Stay in place - } else if (strcmp(comp, "..") == 0) { - // Remove last component from work - int i = (int)strlen(work) - 1; - while (i >= 0 && work[i] != '\\' && work[i] != '/') i--; - if (i < 0) i = 0; - work[i] = '\0'; - } else { - // Append separator + component - size_t wl = strlen(work); - if (wl > 0 && work[wl-1] != '\\' && work[wl-1] != '/') { - if (wl + 1 < sizeof(work)) { work[wl] = '\\'; work[wl+1] = '\0'; } - } - size_t wl2 = strlen(work); - if (wl2 + clen + 1 < sizeof(work)) { - memcpy(work + wl2, comp, clen); - work[wl2 + clen] = '\0'; - } + if (compLen == 1 && input.Data[compStart] == '.') { + continue; } + if (compLen == 2 && input.Data[compStart] == '.' && input.Data[compStart + 1] == '.') { + KiPopComponent(work, &workLen); + continue; + } + if (compLen >= 256) return STATUS_BUFFER_TOO_SMALL; + if (!KiAppendComponent(work, &workLen, sizeof(work), input.Data + compStart, compLen)) + return STATUS_BUFFER_TOO_SMALL; } - // Output the final canonicalized, drive-stripped path - size_t finalLen = strlen(work); - if (finalLen + 1 > resolvedSize) finalLen = resolvedSize - 1; - memcpy(resolved, work, finalLen); - resolved[finalLen] = '\0'; - - return drive; + if (workLen + 1 > sizeof(out->Storage)) return STATUS_BUFFER_TOO_SMALL; + memcpy(out->Storage, work, workLen); + out->Storage[workLen] = '\0'; + out->Drive = drive; + out->Path = SysSvFromParts(out->Storage, workLen); + return STATUS_SUCCESS; } -__declspec(dllexport) KSTATUS IoSetCwd(const char* path) { - if (!path) return STATUS_INVALID_PARAMETER; +__declspec(dllexport) KSTATUS IoSetCwd(SYS_STRING_VIEW path) { + if (path.Length && !path.Data) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; PROCESS* proc = KeGetCurrentProcess(); if (!proc) return STATUS_ACCESS_DENIED; - // Resolve the new path (this also validates ".." navigation). - // Note: KiResolvePath already uses proc->CurrentDirectory internally! - char resolved[CWD_MAX]; - uint8_t drive = KiResolvePath(path, resolved, sizeof(resolved)); + KRESOLVED_PATH resolved; + KSTATUS s = KiResolvePathV(path, &resolved); + if (!KSUCCESS(s)) return s; - // Validate: try to open the directory to confirm it actually exists on disk - IO_FILESYSTEM* fs = gVolumes[drive].Fs; + IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->OpenDir) return STATUS_NOT_SUPPORTED; void* dirCtx = NULL; - KSTATUS s = fs->OpenDir(fs, resolved, &dirCtx); + s = fs->OpenDir(fs, resolved.Path, &dirCtx); if (!KSUCCESS(s)) return s; fs->CloseDir(fs, dirCtx); - // Commit the new CWD to the process block - size_t len = strlen(resolved); - if (len + 1 > sizeof(proc->CurrentDirectory)) return STATUS_BUFFER_TOO_SMALL; + if (resolved.Path.Length + 1 > sizeof(proc->CurrentDirectory)) return STATUS_BUFFER_TOO_SMALL; uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - proc->CurrentDrive = drive; - memcpy(proc->CurrentDirectory, resolved, len + 1); + proc->CurrentDrive = resolved.Drive; + proc->CurrentDirectoryLength = resolved.Path.Length; + memcpy(proc->CurrentDirectory, resolved.Path.Data, resolved.Path.Length); + proc->CurrentDirectory[resolved.Path.Length] = '\0'; - // Keep the user-visible PEB mirror in sync. The PEB's CWD buffer is a - // fixed SYS_CWD_MAX allocation made at process creation, so the pointer - // remains stable for personalities that cache SYS_PEB*. if (proc->UserCwdBase && proc->UserCwdSize) { - size_t formatted = KiFormatCwd(drive, resolved, (char*)proc->UserCwdBase, proc->UserCwdSize); + size_t formatted = KiFormatCwd(resolved.Drive, resolved.Path, (char*)proc->UserCwdBase, proc->UserCwdSize); size_t copyLen = (formatted && formatted <= proc->UserCwdSize) ? formatted - 1 : proc->UserCwdSize - 1; SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase; if (peb && peb->Magic == SYS_PEB_MAGIC) { - peb->CurrentDirectory = (const char*)proc->UserCwdBase; - peb->CurrentDirectoryLength = copyLen; + peb->CurrentDirectory = SysSvFromParts((const char*)proc->UserCwdBase, copyLen); } } KeReleaseSpinlock(&proc->CwdLock, flags); - return STATUS_SUCCESS; } @@ -583,18 +597,20 @@ static void KiFileDestroy(OBJECT* obj) { // --------------------------------------------------------------------------- // IoOpenFile // --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) { - if (!path || !out) return STATUS_INVALID_PARAMETER; +__declspec(dllexport) KSTATUS IoOpenFile(SYS_STRING_VIEW path, FILE_OBJECT **out) { + if ((!path.Data && path.Length) || !out) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; - char relPath[CWD_MAX]; - uint8_t drive = KiResolvePath(path, relPath, sizeof(relPath)); + KRESOLVED_PATH resolved; + KSTATUS s = KiResolvePathV(path, &resolved); + if (!KSUCCESS(s)) return s; - IO_FILESYSTEM* fs = gVolumes[drive].Fs; + IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; void* fileCtx; - KSTATUS s = fs->Open(fs, relPath, &fileCtx); + s = fs->Open(fs, resolved.Path, &fileCtx); if (!KSUCCESS(s)) return s; FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile /* reuse slot */, @@ -603,6 +619,7 @@ __declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) { f->Header.Destroy = KiFileDestroy; f->Fs = fs; f->FileCtx = fileCtx; + f->Lock = (SPINLOCK)SPINLOCK_INIT; f->Size = fs->GetSize(fs, fileCtx); f->Offset = 0; @@ -701,19 +718,20 @@ __declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf, // --------------------------------------------------------------------------- // IoCreateFile - create a new file and return it open for writing // --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out) { - if (!path || !out) return STATUS_INVALID_PARAMETER; +__declspec(dllexport) KSTATUS IoCreateFile(SYS_STRING_VIEW path, FILE_OBJECT** out) { + if ((!path.Data && path.Length) || !out || path.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; - char relPath[CWD_MAX]; - uint8_t drive = KiResolvePath(path, relPath, sizeof(relPath)); + KRESOLVED_PATH resolved; + KSTATUS s = KiResolvePathV(path, &resolved); + if (!KSUCCESS(s)) return s; - IO_FILESYSTEM* fs = gVolumes[drive].Fs; + IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->Create) return STATUS_NOT_SUPPORTED; void* fileCtx; - KSTATUS s = fs->Create(fs, relPath, &fileCtx); + s = fs->Create(fs, resolved.Path, &fileCtx); if (!KSUCCESS(s)) return s; FILE_OBJECT* f = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile, @@ -733,63 +751,69 @@ __declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out) // --------------------------------------------------------------------------- // IoDeleteFile - delete a file by path (does not require an open handle) // --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoDeleteFile(const char* path) { - if (!path) return STATUS_INVALID_PARAMETER; +__declspec(dllexport) KSTATUS IoDeleteFile(SYS_STRING_VIEW path) { + if ((!path.Data && path.Length) || path.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; - char relPath[CWD_MAX]; - uint8_t drive = KiResolvePath(path, relPath, sizeof(relPath)); + KRESOLVED_PATH resolved; + KSTATUS s = KiResolvePathV(path, &resolved); + if (!KSUCCESS(s)) return s; - IO_FILESYSTEM* fs = gVolumes[drive].Fs; + IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->Delete) return STATUS_NOT_SUPPORTED; - return fs->Delete(fs, relPath); + return fs->Delete(fs, resolved.Path); } -__declspec(dllexport) KSTATUS IoRenameFile(const char* oldPath, const char* newPath) { - if (!oldPath || !newPath) return STATUS_INVALID_PARAMETER; +__declspec(dllexport) KSTATUS IoRenameFile(SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath) { + if ((!oldPath.Data && oldPath.Length) || (!newPath.Data && newPath.Length) || + oldPath.Length == 0 || newPath.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; - char oldRel[CWD_MAX]; - char newRel[CWD_MAX]; - uint8_t oldDrive = KiResolvePath(oldPath, oldRel, sizeof(oldRel)); - uint8_t newDrive = KiResolvePath(newPath, newRel, sizeof(newRel)); - if (oldDrive != newDrive) return STATUS_NOT_SUPPORTED; + KRESOLVED_PATH oldResolved; + KRESOLVED_PATH newResolved; + KSTATUS s = KiResolvePathV(oldPath, &oldResolved); + if (!KSUCCESS(s)) return s; + s = KiResolvePathV(newPath, &newResolved); + if (!KSUCCESS(s)) return s; + if (oldResolved.Drive != newResolved.Drive) return STATUS_NOT_SUPPORTED; - IO_FILESYSTEM* fs = gVolumes[oldDrive].Fs; + IO_FILESYSTEM* fs = gVolumes[oldResolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->Rename) return STATUS_NOT_SUPPORTED; - return fs->Rename(fs, oldRel, newRel); + return fs->Rename(fs, oldResolved.Path, newResolved.Path); } -__declspec(dllexport) KSTATUS IoCreateDir(const char* path) { - if (!path) return STATUS_INVALID_PARAMETER; +__declspec(dllexport) KSTATUS IoCreateDir(SYS_STRING_VIEW path) { + if ((!path.Data && path.Length) || path.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; - char relPath[CWD_MAX]; - uint8_t drive = KiResolvePath(path, relPath, sizeof(relPath)); + KRESOLVED_PATH resolved; + KSTATUS s = KiResolvePathV(path, &resolved); + if (!KSUCCESS(s)) return s; - IO_FILESYSTEM* fs = gVolumes[drive].Fs; + IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->CreateDir) return STATUS_NOT_SUPPORTED; - return fs->CreateDir(fs, relPath); + return fs->CreateDir(fs, resolved.Path); } -__declspec(dllexport) KSTATUS IoRemoveDir(const char* path) { - if (!path) return STATUS_INVALID_PARAMETER; +__declspec(dllexport) KSTATUS IoRemoveDir(SYS_STRING_VIEW path) { + if ((!path.Data && path.Length) || path.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; - char relPath[CWD_MAX]; - uint8_t drive = KiResolvePath(path, relPath, sizeof(relPath)); + KRESOLVED_PATH resolved; + KSTATUS s = KiResolvePathV(path, &resolved); + if (!KSUCCESS(s)) return s; - IO_FILESYSTEM* fs = gVolumes[drive].Fs; + IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->RemoveDir) return STATUS_NOT_SUPPORTED; - return fs->RemoveDir(fs, relPath); + return fs->RemoveDir(fs, resolved.Path); } // --------------------------------------------------------------------------- @@ -840,37 +864,44 @@ static void KiDirDestroy(OBJECT* obj) { if (d->Fs && d->DirCtx) d->Fs->CloseDir(d->Fs, d->DirCtx); } -__declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) { +__declspec(dllexport) KSTATUS IoOpenDir(SYS_STRING_VIEW dirPath, DIR_OBJECT** out) { if (!out) return STATUS_INVALID_PARAMETER; + if (dirPath.Length && !dirPath.Data) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; - // NULL or empty path means the CWD itself - char relPath[CWD_MAX]; - uint8_t drive; + KRESOLVED_PATH resolved; + KSTATUS s; - if (!dirPath || dirPath[0] == '\0') { + if (dirPath.Length == 0) { PROCESS* proc = KeGetCurrentProcess(); if (!proc) return STATUS_ACCESS_DENIED; uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - memcpy(relPath, proc->CurrentDirectory, strlen(proc->CurrentDirectory) + 1); - drive = proc->CurrentDrive; + size_t cwdLen = proc->CurrentDirectoryLength; + if (cwdLen + 1 > sizeof(resolved.Storage)) { + KeReleaseSpinlock(&proc->CwdLock, flags); + return STATUS_BUFFER_TOO_SMALL; + } + memcpy(resolved.Storage, proc->CurrentDirectory, cwdLen + 1); + resolved.Drive = proc->CurrentDrive; + resolved.Path = SysSvFromParts(resolved.Storage, cwdLen); KeReleaseSpinlock(&proc->CwdLock, flags); } else { - drive = KiResolvePath(dirPath, relPath, sizeof(relPath)); + s = KiResolvePathV(dirPath, &resolved); + if (!KSUCCESS(s)) return s; } - IO_FILESYSTEM* fs = gVolumes[drive].Fs; + IO_FILESYSTEM* fs = gVolumes[resolved.Drive].Fs; if (!fs) return STATUS_NOT_FOUND; if (!fs->OpenDir) return STATUS_NOT_SUPPORTED; void* dirCtx = NULL; - KSTATUS s = fs->OpenDir(fs, relPath, &dirCtx); + s = fs->OpenDir(fs, resolved.Path, &dirCtx); if (!KSUCCESS(s)) return s; DIR_OBJECT* d = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, sizeof(DIR_OBJECT)); if (!d) { fs->CloseDir(fs, dirCtx); return STATUS_NO_MEMORY; } - + d->Header.Destroy = KiDirDestroy; d->Fs = fs; d->DirCtx = dirCtx; diff --git a/code/kernel/io_file.h b/code/kernel/io_file.h index 086246b..06f103b 100644 --- a/code/kernel/io_file.h +++ b/code/kernel/io_file.h @@ -7,6 +7,7 @@ #include #include #include +#include // --------------------------------------------------------------------------- // KDIR_ENTRY - one directory entry returned by ReadDir / SYS_READ_DIR @@ -35,7 +36,7 @@ typedef struct IO_FILESYSTEM { // Open a file by absolute path (e.g. "apps\\shell.exe"). // Returns an opaque per-file driver cookie in* fileCtx. KSTATUS (*Open)(struct IO_FILESYSTEM* fs, - const char* path, void **fileCtx); + SYS_STRING_VIEW path, void **fileCtx); // Read up to 'size' bytes at 'offset' into 'buf'. // Sets* bytesRead to actual bytes copied. @@ -58,19 +59,19 @@ typedef struct IO_FILESYSTEM { void* completeContext); // Create a new file at 'path'; returns an open fileCtx on success. - KSTATUS (*Create)(struct IO_FILESYSTEM* fs, const char* path, + KSTATUS (*Create)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path, void** fileCtx); // Delete the file at 'path'. - KSTATUS (*Delete)(struct IO_FILESYSTEM* fs, const char* path); + KSTATUS (*Delete)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path); // Rename or move a file/directory within the same filesystem. KSTATUS (*Rename)(struct IO_FILESYSTEM* fs, - const char* oldPath, const char* newPath); + SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath); // Create or remove directories. - KSTATUS (*CreateDir)(struct IO_FILESYSTEM* fs, const char* path); - KSTATUS (*RemoveDir)(struct IO_FILESYSTEM* fs, const char* path); + KSTATUS (*CreateDir)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path); + KSTATUS (*RemoveDir)(struct IO_FILESYSTEM* fs, SYS_STRING_VIEW path); // Truncate or extend the file to exactly 'newSize' bytes. KSTATUS (*Truncate)(struct IO_FILESYSTEM* fs, void* fileCtx, @@ -92,7 +93,7 @@ typedef struct IO_FILESYSTEM { // Open a directory iterator for 'dirPath' (relative or drive-prefixed). // Returns an opaque per-iteration driver cookie in *dirCtx. KSTATUS (*OpenDir)(struct IO_FILESYSTEM* fs, - const char* dirPath, void** dirCtx); + SYS_STRING_VIEW dirPath, void** dirCtx); // Advance to the next entry. // Fills 'out' with name, attributes, and size. @@ -153,7 +154,7 @@ __declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t driveIndex, IO_FILESY // --------------------------------------------------------------------------- // Kernel-side API // --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out); +__declspec(dllexport) KSTATUS IoOpenFile(SYS_STRING_VIEW path, FILE_OBJECT **out); __declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead); __declspec(dllexport) KSTATUS IoReadFileAt(FILE_OBJECT* file, uint64_t offset, @@ -175,11 +176,11 @@ __declspec(dllexport) OBJECT* IoAsyncFileRequestObject(IO_ASYNC_FILE_REQUEST_OB __declspec(dllexport) KSTATUS IoCancelAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJECT* request); __declspec(dllexport) KSTATUS IoQueryAsyncFileRequest(IO_ASYNC_FILE_REQUEST_OBJECT* request, IO_STATUS_BLOCK* iosbOut); -__declspec(dllexport) KSTATUS IoCreateFile (const char* path, FILE_OBJECT** out); -__declspec(dllexport) KSTATUS IoDeleteFile (const char* path); -__declspec(dllexport) KSTATUS IoRenameFile (const char* oldPath, const char* newPath); -__declspec(dllexport) KSTATUS IoCreateDir (const char* path); -__declspec(dllexport) KSTATUS IoRemoveDir (const char* path); +__declspec(dllexport) KSTATUS IoCreateFile (SYS_STRING_VIEW path, FILE_OBJECT** out); +__declspec(dllexport) KSTATUS IoDeleteFile (SYS_STRING_VIEW path); +__declspec(dllexport) KSTATUS IoRenameFile (SYS_STRING_VIEW oldPath, SYS_STRING_VIEW newPath); +__declspec(dllexport) KSTATUS IoCreateDir (SYS_STRING_VIEW path); +__declspec(dllexport) KSTATUS IoRemoveDir (SYS_STRING_VIEW path); __declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize); __declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset); __declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file); @@ -188,12 +189,12 @@ __declspec(dllexport) void IoCloseFile(FILE_OBJECT* file); // --------------------------------------------------------------------------- // Directory enumeration API // --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoOpenDir (const char* dirPath, DIR_OBJECT** out); +__declspec(dllexport) KSTATUS IoOpenDir (SYS_STRING_VIEW dirPath, DIR_OBJECT** out); __declspec(dllexport) KSTATUS IoReadDir (DIR_OBJECT* dir, KDIR_ENTRY* entryOut); __declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir); // --------------------------------------------------------------------------- // Current Working Directory // --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoSetCwd(const char* path); +__declspec(dllexport) KSTATUS IoSetCwd(SYS_STRING_VIEW path); __declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size); diff --git a/code/kernel/ke_process.c b/code/kernel/ke_process.c index f7a9021..b12595e 100644 --- a/code/kernel/ke_process.c +++ b/code/kernel/ke_process.c @@ -88,15 +88,17 @@ static void KiProcessDestroy(OBJECT* obj) { // Release all remaining handles. for (int i = 1; i < HANDLE_TABLE_SIZE; i++) { - if (proc->Handles[i]) { - ObDereferenceObject(proc->Handles[i]); - proc->Handles[i] = (OBJECT *)0; + if (proc->Handles[i].Object) { + ObDereferenceObject(proc->Handles[i].Object); + proc->Handles[i].Object = (OBJECT*)0; + proc->Handles[i].Rights = 0; + proc->Handles[i].Flags = 0; } } // Release the main executable image. if (proc->ImageBase && proc->ImageSize) { - MmFreeUserMemory((void *)proc->ImageBase); + MmFreeUserImage((void *)proc->ImageBase, proc->ImageSize); proc->ImageBase = 0; proc->ImageSize = 0; } @@ -104,7 +106,7 @@ static void KiProcessDestroy(OBJECT* obj) { // Release per-process modules. for (uint32_t i = 0; i < proc->ModuleCount; i++) { if (proc->Modules[i].Base && proc->Modules[i].Size) { - MmFreeUserMemory((void *)proc->Modules[i].Base); + MmFreeUserImage((void *)proc->Modules[i].Base, proc->Modules[i].Size); } proc->Modules[i].Base = 0; proc->Modules[i].Size = 0; @@ -170,6 +172,8 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) { proc->ThreadCount = 0; proc->HandleLock = (SPINLOCK)SPINLOCK_INIT; proc->ExitCode = 0; + proc->Terminating = 0; + proc->Exited = 0; proc->ImageBase = 0; proc->ImageSize = 0; proc->UserPebBase = 0; @@ -182,6 +186,7 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) { proc->UserCwdSize = 0; proc->ModuleCount = 0; proc->CurrentDirectory[0] = '\0'; + proc->CurrentDirectoryLength = 0; proc->CurrentDrive = 0; proc->CwdLock = (SPINLOCK)SPINLOCK_INIT; @@ -208,33 +213,47 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) { // --------------------------------------------------------------------------- // KeSetupUserProcessEnvironment // --------------------------------------------------------------------------- -static KSTATUS KiCopyEnvironmentString(const char* src, uintptr_t* baseOut, size_t* sizeOut) { +static KSTATUS KiCopyEnvironmentStringView(SYS_STRING_VIEW src, + uintptr_t* baseOut, + size_t* sizeOut) { if (!baseOut || !sizeOut) return STATUS_INVALID_PARAMETER; - if (!src) src = ""; + *baseOut = 0; + *sizeOut = 0; - size_t len = strlen(src) + 1; + if (!SysSvIsValid(src)) return STATUS_INVALID_PARAMETER; + if (SysSvContainsByte(src, '\0')) return STATUS_INVALID_PARAMETER; + if (src.Length == (size_t)-1) return STATUS_BUFFER_TOO_SMALL; + + size_t len = src.Length + 1; char* dst = (char*)MmAllocateUserMemory(len); if (!dst) return STATUS_NO_MEMORY; - memcpy(dst, src, len); + if (src.Length) memcpy(dst, src.Data, src.Length); + dst[src.Length] = '\0'; + *baseOut = (uintptr_t)dst; *sizeOut = len; return STATUS_SUCCESS; } -static KSTATUS KiCopyEnvironmentStringFixed(const char* src, size_t capacity, - uintptr_t* baseOut, size_t* sizeOut) { +static KSTATUS KiCopyEnvironmentStringViewFixed(SYS_STRING_VIEW src, + size_t capacity, + uintptr_t* baseOut, + size_t* sizeOut) { if (!baseOut || !sizeOut || capacity == 0) return STATUS_INVALID_PARAMETER; - if (!src) src = ""; + *baseOut = 0; + *sizeOut = 0; + + if (!SysSvIsValid(src)) return STATUS_INVALID_PARAMETER; + if (SysSvContainsByte(src, '\0')) return STATUS_INVALID_PARAMETER; + if (src.Length + 1 > capacity) return STATUS_BUFFER_TOO_SMALL; char* dst = (char*)MmAllocateUserMemory(capacity); if (!dst) return STATUS_NO_MEMORY; memset(dst, 0, capacity); - size_t len = strlen(src); - if (len >= capacity) len = capacity - 1; - memcpy(dst, src, len); - dst[len] = '\0'; + if (src.Length) memcpy(dst, src.Data, src.Length); + dst[src.Length] = '\0'; *baseOut = (uintptr_t)dst; *sizeOut = capacity; @@ -266,43 +285,68 @@ static void KiFreePartialProcessEnvironment(PROCESS* proc) { } } -static void KiFormatNativeCwd(const PROCESS* proc, const char* currentDirectory, - char* out, size_t outSize) { - if (!out || outSize == 0) return; - const char* rel = currentDirectory ? currentDirectory : ""; +static KSTATUS KiFormatNativeCwdV(const PROCESS* proc, + SYS_STRING_VIEW currentDirectory, + char* out, + size_t outSize, + size_t* lengthOut) { + if (lengthOut) *lengthOut = 0; + if (!out || outSize == 0) return STATUS_INVALID_PARAMETER; + out[0] = '\0'; + + if (!SysSvIsValid(currentDirectory)) return STATUS_INVALID_PARAMETER; + if (SysSvContainsByte(currentDirectory, '\0')) return STATUS_INVALID_PARAMETER; + uint8_t drive = proc ? proc->CurrentDrive : 0; if (drive > 7) drive = 0; - if (outSize < 4) { out[0] = '\0'; return; } + if (currentDirectory.Length + 4 > outSize) return STATUS_BUFFER_TOO_SMALL; + out[0] = (char)('0' + drive); out[1] = ':'; out[2] = '/'; - size_t len = strlen(rel); - if (len + 4 > outSize) len = outSize - 4; - memcpy(out + 3, rel, len); - out[3 + len] = '\0'; + if (currentDirectory.Length) + memcpy(out + 3, currentDirectory.Data, currentDirectory.Length); + out[3 + currentDirectory.Length] = '\0'; + + if (lengthOut) *lengthOut = 3 + currentDirectory.Length; + return STATUS_SUCCESS; } KSTATUS KeSetupUserProcessEnvironment(PROCESS* proc, - const char* imagePath, - const char* commandLine, - const char* currentDirectory) { - if (!proc || !imagePath) return STATUS_INVALID_PARAMETER; + SYS_STRING_VIEW imagePath, + SYS_STRING_VIEW commandLine, + SYS_STRING_VIEW currentDirectory) { + if (!proc || imagePath.Length == 0) return STATUS_INVALID_PARAMETER; + if (!SysSvIsValid(imagePath) || !SysSvIsValid(commandLine) || !SysSvIsValid(currentDirectory)) + return STATUS_INVALID_PARAMETER; + if (SysSvContainsByte(imagePath, '\0') || + SysSvContainsByte(commandLine, '\0') || + SysSvContainsByte(currentDirectory, '\0')) + return STATUS_INVALID_PARAMETER; + + SYS_STRING_VIEW finalCommandLine = commandLine.Length ? commandLine : imagePath; + SYS_STRING_VIEW finalCwdRel = currentDirectory; + if (finalCwdRel.Length == 0 && proc->CurrentDirectoryLength) + finalCwdRel = SysSvFromParts(proc->CurrentDirectory, proc->CurrentDirectoryLength); - const char* finalCommandLine = (commandLine && commandLine[0]) ? commandLine : imagePath; - const char* finalCwdRel = currentDirectory ? currentDirectory : proc->CurrentDirectory; char finalCwd[SYS_CWD_MAX]; - KiFormatNativeCwd(proc, finalCwdRel, finalCwd, sizeof(finalCwd)); + size_t finalCwdLength = 0; + KSTATUS s = KiFormatNativeCwdV(proc, finalCwdRel, finalCwd, sizeof(finalCwd), &finalCwdLength); + if (!KSUCCESS(s)) return s; KiFreePartialProcessEnvironment(proc); - KSTATUS s = KiCopyEnvironmentString(imagePath, &proc->UserImagePathBase, &proc->UserImagePathSize); + s = KiCopyEnvironmentStringView(imagePath, &proc->UserImagePathBase, &proc->UserImagePathSize); if (!KSUCCESS(s)) goto fail; - s = KiCopyEnvironmentString(finalCommandLine, &proc->UserCmdlineBase, &proc->UserCmdlineSize); + s = KiCopyEnvironmentStringView(finalCommandLine, &proc->UserCmdlineBase, &proc->UserCmdlineSize); if (!KSUCCESS(s)) goto fail; - s = KiCopyEnvironmentStringFixed(finalCwd, SYS_CWD_MAX, &proc->UserCwdBase, &proc->UserCwdSize); + s = KiCopyEnvironmentStringViewFixed(SysSvFromParts(finalCwd, finalCwdLength), + SYS_CWD_MAX, + &proc->UserCwdBase, + &proc->UserCwdSize); if (!KSUCCESS(s)) goto fail; SYS_PEB* peb = (SYS_PEB*)MmAllocateUserMemory(sizeof(SYS_PEB)); @@ -312,12 +356,9 @@ KSTATUS KeSetupUserProcessEnvironment(PROCESS* proc, peb->Magic = SYS_PEB_MAGIC; peb->Version = SYS_ENV_VERSION; peb->ProcessId = proc->ProcessId; - peb->ImagePath = (const char*)proc->UserImagePathBase; - peb->ImagePathLength = proc->UserImagePathSize ? proc->UserImagePathSize - 1 : 0; - peb->CommandLine = (const char*)proc->UserCmdlineBase; - peb->CommandLineLength = proc->UserCmdlineSize ? proc->UserCmdlineSize - 1 : 0; - peb->CurrentDirectory = (const char*)proc->UserCwdBase; - peb->CurrentDirectoryLength = strlen(finalCwd); + peb->ImagePath = SysSvFromParts((const char*)proc->UserImagePathBase, imagePath.Length); + peb->CommandLine = SysSvFromParts((const char*)proc->UserCmdlineBase, finalCommandLine.Length); + peb->CurrentDirectory = SysSvFromParts((const char*)proc->UserCwdBase, finalCwdLength); proc->UserPebBase = (uintptr_t)peb; proc->UserPebSize = sizeof(SYS_PEB); @@ -329,16 +370,56 @@ fail: } // --------------------------------------------------------------------------- -// KeExitProcess +// Process exit / termination // --------------------------------------------------------------------------- +static void KiCompleteProcessExitIfLastThread(PROCESS* process) { + if (!process) return; + + uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); + uint32_t threadCount = process->ThreadCount; + KeReleaseSpinlock(&process->HandleLock, flags); + + if (threadCount == 0 && _InterlockedExchange(&process->Exited, 1) == 0) { + if (process->ExitEvent) KeSignalEvent(process->ExitEvent); + } +} + void KeExitProcess(PROCESS* process, int exitCode) { if (!process) return; process->ExitCode = exitCode; - // Wake any threads waiting for this process to finish. - if (process->ExitEvent) KeSignalEvent(process->ExitEvent); - // The last thread exiting via KeExitThread will drop the process refcount. - // This function is a policy hook; actual cleanup is in KiProcessDestroy. - ObDereferenceObject(&process->Header); + _InterlockedExchange(&process->Terminating, 1); + + // The calling thread exits immediately after this in SysExitProcess. Do + // not drop the process object here: each thread owns one process reference, + // and KiThreadDestroy releases that reference after unlinking the thread. + KiCompleteProcessExitIfLastThread(process); +} + +KSTATUS KeTerminateProcess(PROCESS* process, int exitCode) { + if (!process) return STATUS_INVALID_PARAMETER; + + process->ExitCode = exitCode; + _InterlockedExchange(&process->Terminating, 1); + + // Wake alertable waiters so they can notice Terminating at the syscall/ + // wait boundary. This is intentionally conservative: fully asynchronous + // forced thread teardown needs object-specific wait-list locking and is a + // larger process-lifecycle project. + uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); + LIST_ENTRY* head = &process->ThreadList; + for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { + THREAD* t = CONTAINING_RECORD(node, THREAD, ProcessLink); + if (t != KeGetCurrentThread()) + (void)KeAlertThread(t); + } + KeReleaseSpinlock(&process->HandleLock, flags); + + KiCompleteProcessExitIfLastThread(process); + return STATUS_SUCCESS; +} + +int KeIsProcessTerminating(PROCESS* process) { + return process && process->Terminating != 0; } // --------------------------------------------------------------------------- @@ -353,15 +434,17 @@ KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut) { // --------------------------------------------------------------------------- // Handle table // --------------------------------------------------------------------------- -KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) { - if (!process || !object || !handleOut) return STATUS_INVALID_PARAMETER; +KSTATUS KeAllocateHandleWithRights(PROCESS* process, OBJECT* object, uint32_t rights, uint32_t* handleOut) { + if (!process || !object || !handleOut || rights == 0) return STATUS_INVALID_PARAMETER; uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); for (int i = 1; i < HANDLE_TABLE_SIZE; i++) { - if (!process->Handles[i]) { + if (!process->Handles[i].Object) { ObReferenceObject(object); - process->Handles[i] = object; + process->Handles[i].Object = object; + process->Handles[i].Rights = rights; + process->Handles[i].Flags = 0; *handleOut = (uint32_t)i; KeReleaseSpinlock(&process->HandleLock, flags); return STATUS_SUCCESS; @@ -372,13 +455,19 @@ KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) return STATUS_NO_MEMORY; } +KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) { + return KeAllocateHandleWithRights(process, object, HANDLE_RIGHT_ALL, handleOut); +} + void KeCloseHandle(PROCESS* process, uint32_t handle) { if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return; uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); - OBJECT* obj = process->Handles[handle]; + OBJECT* obj = process->Handles[handle].Object; if (obj) { - process->Handles[handle] = (OBJECT *)0; + process->Handles[handle].Object = (OBJECT*)0; + process->Handles[handle].Rights = 0; + process->Handles[handle].Flags = 0; KeReleaseSpinlock(&process->HandleLock, flags); ObDereferenceObject(obj); return; @@ -386,17 +475,33 @@ void KeCloseHandle(PROCESS* process, uint32_t handle) { KeReleaseSpinlock(&process->HandleLock, flags); } -OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) { +OBJECT* KeLookupHandleWithRights(PROCESS* process, uint32_t handle, uint32_t requiredRights) { if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) - return (OBJECT *)0; + return (OBJECT*)0; uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); - OBJECT* obj = process->Handles[handle]; + HANDLE_ENTRY* entry = &process->Handles[handle]; + OBJECT* obj = entry->Object; + if (obj && requiredRights && ((entry->Rights & requiredRights) != requiredRights)) + obj = (OBJECT*)0; if (obj) ObReferenceObject(obj); KeReleaseSpinlock(&process->HandleLock, flags); return obj; } +OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) { + return KeLookupHandleWithRights(process, handle, 0); +} + +uint32_t KeGetHandleRights(PROCESS* process, uint32_t handle) { + if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return 0; + + uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); + uint32_t rights = process->Handles[handle].Object ? process->Handles[handle].Rights : 0; + KeReleaseSpinlock(&process->HandleLock, flags); + return rights; +} + PROCESS* KeGetProcessById(uint32_t pid) { KiProcessInit(); uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); diff --git a/code/kernel/ke_process.h b/code/kernel/ke_process.h index 6fd1b85..f280438 100644 --- a/code/kernel/ke_process.h +++ b/code/kernel/ke_process.h @@ -8,12 +8,27 @@ #include #include #include +#include #define PROCESS_NAME_MAX 64 #define HANDLE_TABLE_SIZE 256 - - #define PROCESS_MODULE_MAX 16 +#define PROCESS_CWD_MAX 512 + +#define HANDLE_RIGHT_READ 0x00000001u +#define HANDLE_RIGHT_WRITE 0x00000002u +#define HANDLE_RIGHT_WAIT 0x00000004u +#define HANDLE_RIGHT_SIGNAL 0x00000008u +#define HANDLE_RIGHT_DUPLICATE 0x00000010u +#define HANDLE_RIGHT_QUERY 0x00000020u +#define HANDLE_RIGHT_TERMINATE 0x00000040u +#define HANDLE_RIGHT_ALL 0xFFFFFFFFu + +typedef struct HANDLE_ENTRY { + OBJECT* Object; + uint32_t Rights; + uint32_t Flags; +} HANDLE_ENTRY; typedef struct PROCESS_MODULE { char Name[32]; @@ -27,7 +42,8 @@ typedef struct PROCESS { uint32_t ProcessId; char Name[PROCESS_NAME_MAX]; /* UTF-8 */ - char CurrentDirectory[512]; + char CurrentDirectory[PROCESS_CWD_MAX]; + size_t CurrentDirectoryLength; uint8_t CurrentDrive; SPINLOCK CwdLock; @@ -36,12 +52,14 @@ typedef struct PROCESS { uint32_t ThreadCount; /* Handle table. - Index 0 is always NULL (the invalid handle). - Entries are OBJECT pointers, each holding a reference. */ - OBJECT* Handles[HANDLE_TABLE_SIZE]; + Index 0 is always empty (the invalid handle). + Each live entry holds one object reference plus rights metadata. */ + HANDLE_ENTRY Handles[HANDLE_TABLE_SIZE]; SPINLOCK HandleLock; int ExitCode; + volatile long Terminating; + volatile long Exited; /* Main executable image. Used for cleanup. */ uintptr_t ImageBase; @@ -70,14 +88,22 @@ typedef struct PROCESS { KSTATUS KeCreateProcess(const char* name, PROCESS **out); KSTATUS KeCreateSystemProcess(PROCESS** out); -KSTATUS KeSetupUserProcessEnvironment(PROCESS* process, const char* imagePath, const char* commandLine, const char* currentDirectory); +KSTATUS KeSetupUserProcessEnvironment(PROCESS* process, + SYS_STRING_VIEW imagePath, + SYS_STRING_VIEW commandLine, + SYS_STRING_VIEW currentDirectory); void KeExitProcess(PROCESS* process, int exitCode); +KSTATUS KeTerminateProcess(PROCESS* process, int exitCode); +int KeIsProcessTerminating(PROCESS* process); KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut); /* Handle table operations. */ KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut); +KSTATUS KeAllocateHandleWithRights(PROCESS* process, OBJECT* object, uint32_t rights, uint32_t* handleOut); void KeCloseHandle(PROCESS* process, uint32_t handle); OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle); +OBJECT* KeLookupHandleWithRights(PROCESS* process, uint32_t handle, uint32_t requiredRights); +uint32_t KeGetHandleRights(PROCESS* process, uint32_t handle); /* Return the global process list head (kernel-internal). */ LIST_ENTRY* KeGetProcessList(void); diff --git a/code/kernel/ke_sched.c b/code/kernel/ke_sched.c index c2c819b..a334060 100644 --- a/code/kernel/ke_sched.c +++ b/code/kernel/ke_sched.c @@ -20,10 +20,18 @@ static THREAD* gIdleThread; static THREAD* gOutgoingThread; static uint32_t gAgingCounter = 0; static volatile long gReschedulePending = 0; +static KE_SCHED_STATS gSchedStats; THREAD* KeGetCurrentThread(void) { return gCurrentThread; } void KeSetCurrentThread(THREAD* t) { gCurrentThread = t; } +void KeGetSchedulerStats(KE_SCHED_STATS* out) { + if (!out) return; + uintptr_t flags = KeAcquireSpinlock(&gSchedLock); + *out = gSchedStats; + KeReleaseSpinlock(&gSchedLock, flags); +} + static void KiIdleLoop(void) { while (true) { __halt(); } } @@ -121,6 +129,14 @@ void KeUnreadyThread(THREAD* thread) { KeReleaseSpinlock(&gSchedLock, flags); } +static int KiAnyRunnableLocked(void) { + for (int p = 0; p < PriorityCount; p++) { + if (!LIST_IS_EMPTY(&gRunQueue[p])) + return 1; + } + return 0; +} + static THREAD* KiPickNext(void) { for (int p = PriorityCount - 1; p >= 0; p--) { if (!LIST_IS_EMPTY(&gRunQueue[p])) { @@ -149,7 +165,18 @@ static void KiSchedule(void) { uintptr_t flags = KeAcquireSpinlock(&gSchedLock); cur->SchedLockFlags = flags; + // The currently running thread is not present in a run queue. If it is + // still runnable and nobody else is ready, keep executing it. Otherwise a + // lone CPU-bound thread gets alternated with the idle thread on every timer + // tick, effectively turning a periodic preemption check into a 50% throttle. + if (cur != gIdleThread && cur->State == ThreadRunning && !KiAnyRunnableLocked()) { + gSchedStats.KeepCurrentDecisions++; + KeReleaseSpinlock(&gSchedLock, flags); + return; + } + THREAD* next = KiPickNext(); + if (next == gIdleThread) gSchedStats.IdleSelections++; if (next == cur) { // Only re-queue if still running normally; a ThreadWaiting/ThreadDead // caller has already placed SchedulerLink elsewhere (or is discarding @@ -174,6 +201,7 @@ static void KiSchedule(void) { } gOutgoingThread = cur; + gSchedStats.ContextSwitches++; KeSwitchContext(cur, next); KeSchedulerPostSwitch(); } @@ -190,12 +218,14 @@ void KeSchedulerTick(void) { uintptr_t flags = KeAcquireSpinlock(&gSchedLock); cur->SchedLockFlags = flags; // Issue 5 fix + gSchedStats.Ticks++; cur->QuantaConsumed++; if (cur != gIdleThread && cur->QuantaConsumed >= DEMOTION_QUANTA) { cur->QuantaConsumed = 0; if (cur->Priority > PriorityLow) { cur->Priority = (THREAD_PRIORITY)(cur->Priority - 1); + gSchedStats.Demotions++; } } @@ -208,6 +238,7 @@ void KeSchedulerTick(void) { LIST_ENTRY* node = ListPopHead(&gRunQueue[p]); THREAD* t = CONTAINING_RECORD(node, THREAD, SchedulerLink); t->Priority = PriorityHigh; + gSchedStats.AgingPromotions++; ListInsertTail(&gRunQueue[PriorityHigh], &t->SchedulerLink); } } diff --git a/code/kernel/ke_sched.h b/code/kernel/ke_sched.h index 2182951..e3ef0bb 100644 --- a/code/kernel/ke_sched.h +++ b/code/kernel/ke_sched.h @@ -1,34 +1,45 @@ -#pragma once -// ke_sched.h - Multi-Level Feedback Queue scheduler -// Three run queues (Low / Normal / High). IRQ 0 drives KeSchedulerTick. -#include - -#define QUANTUM_MS 10 // base time slice in milliseconds - -// Called once by KernelMain before any threads are created. -void KeInitializeScheduler(void); - -// Add / remove a thread from the run queues. -void KeReadyThread(THREAD* thread); -void KeUnreadyThread(THREAD* thread); - -// Called from the timer ISR every QUANTUM_MS. This only performs timer -// accounting and requests a reschedule; the actual context switch happens from -// KiExitInterrupt after the IRQ handler has returned and EOI has been sent. -void KeSchedulerTick(void); - -// Internal scheduler hooks used by the interrupt-exit path. KiRequestReschedule -// is cheap and can be called from interrupt context. KiScheduleFromInterrupt -// consumes the pending request and performs the context switch, if needed. -void KiRequestReschedule(void); -int KiIsReschedulePending(void); -void KiScheduleFromInterrupt(void); - -// Voluntarily yield the CPU. Picks the next runnable thread immediately. -__declspec(dllexport) void KeYield(void); - -// Returns the thread currently running on this CPU (reads GS:[0]). -THREAD* KeGetCurrentThread(void); - -// Set the current-thread GS slot. Called once per CPU at boot. -void KeSetCurrentThread(THREAD* thread); +#pragma once +// ke_sched.h - Multi-Level Feedback Queue scheduler +// Three run queues (Low / Normal / High). IRQ 0 drives KeSchedulerTick. +#include + +#define QUANTUM_MS 10 // base time slice in milliseconds + +typedef struct KE_SCHED_STATS { + uint64_t Ticks; + uint64_t ContextSwitches; + uint64_t KeepCurrentDecisions; + uint64_t IdleSelections; + uint64_t Demotions; + uint64_t AgingPromotions; +} KE_SCHED_STATS; + +// Called once by KernelMain before any threads are created. +void KeInitializeScheduler(void); + +// Add / remove a thread from the run queues. +void KeReadyThread(THREAD* thread); +void KeUnreadyThread(THREAD* thread); + +// Called from the timer ISR every QUANTUM_MS. This only performs timer +// accounting and requests a reschedule; the actual context switch happens from +// KiExitInterrupt after the IRQ handler has returned and EOI has been sent. +void KeSchedulerTick(void); + +// Internal scheduler hooks used by the interrupt-exit path. KiRequestReschedule +// is cheap and can be called from interrupt context. KiScheduleFromInterrupt +// consumes the pending request and performs the context switch, if needed. +void KiRequestReschedule(void); +int KiIsReschedulePending(void); +void KiScheduleFromInterrupt(void); + +// Voluntarily yield the CPU. Picks the next runnable thread immediately. +__declspec(dllexport) void KeYield(void); + +void KeGetSchedulerStats(KE_SCHED_STATS* out); + +// Returns the thread currently running on this CPU (reads GS:[0]). +THREAD* KeGetCurrentThread(void); + +// Set the current-thread GS slot. Called once per CPU at boot. +void KeSetCurrentThread(THREAD* thread); diff --git a/code/kernel/ke_selftest.c b/code/kernel/ke_selftest.c index 51508de..6a0b392 100644 --- a/code/kernel/ke_selftest.c +++ b/code/kernel/ke_selftest.c @@ -476,6 +476,43 @@ static void KiRunIrpKernelApcSelfTest(const HAL_API* hal) { } + +// ------------------------------------------------------------------------- +// String-view ABI self-test +// ------------------------------------------------------------------------- +static void KiRunStringViewSelfTest(void) { + DbgPrint("STRINGVIEW selftest: begin\n"); + + SYS_STRING_VIEW hello = SYS_SV_LITERAL("Hello"); + if (hello.Length != 5 || !SysSvEqualLit(hello, "Hello")) + KePanic("STRINGVIEW selftest: literal/equality failed\n"); + + static const char embedded[] = { 'a', 'b', '\0', 'c', 'd' }; + SYS_STRING_VIEW emb = SysSvFromParts(embedded, sizeof(embedded)); + if (emb.Length != 5 || !SysSvContainsByte(emb, '\0')) + KePanic("STRINGVIEW selftest: embedded-NUL detection failed\n"); + + char copy[6]; + if (!SysSvCopyZ(copy, sizeof(copy), emb)) + KePanic("STRINGVIEW selftest: bounded copy failed\n"); + if (memcmp(copy, embedded, sizeof(embedded)) != 0 || copy[5] != '\0') + KePanic("STRINGVIEW selftest: bounded copy contents wrong\n"); + + if (SysSvCopyZ(copy, 4, emb)) + KePanic("STRINGVIEW selftest: undersized copy unexpectedly succeeded\n"); + + // Paths are string views at the ABI boundary, but FAT/VFS path names do not + // accept embedded NUL bytes. This must fail before reaching any filesystem. + FILE_OBJECT* f = (FILE_OBJECT*)0; + KSTATUS s = IoOpenFile(SysSvFromParts("system/init.exe\0junk", 20), &f); + if (s != STATUS_INVALID_PARAMETER) + KePanic("STRINGVIEW selftest: embedded-NUL path status=%u\n", s); + + DbgPrint("STRINGVIEW selftest: sample view='%v' len=%llu\n", + hello, (uint64_t)hello.Length); + DbgPrint("STRINGVIEW selftest: PASS\n"); +} + // ------------------------------------------------------------------------- // Async file I/O self-test // ------------------------------------------------------------------------- @@ -534,7 +571,7 @@ static void KiRunAsyncFileIoSelfTest(void) { memset(&st, 0, sizeof(st)); st.Iosb.Status = STATUS_FAILURE; - KSTATUS s = IoOpenFile("system/init.exe", &st.File); + KSTATUS s = IoOpenFile(SYS_SV_LITERAL("system/init.exe"), &st.File); if (!KSUCCESS(s)) { DbgPrint("FILE/ASYNC selftest: SKIP, system/init.exe open failed %u\n", s); return; @@ -614,7 +651,7 @@ static void KiRunAsyncFileIoSelfTest(void) { wiosb.Status = STATUS_FAILURE; wiosb.Information = 0; - s = IoCreateFile("asyncio.tmp", &wf); + s = IoCreateFile(SYS_SV_LITERAL("asyncio.tmp"), &wf); if (!KSUCCESS(s)) KePanic("FILE/ASYNC selftest: create write temp failed %u\n", s); @@ -651,7 +688,7 @@ static void KiRunAsyncFileIoSelfTest(void) { IoCloseFile(wf); wf = (FILE_OBJECT*)0; - s = IoOpenFile("asyncio.tmp", &rf); + s = IoOpenFile(SYS_SV_LITERAL("asyncio.tmp"), &rf); if (!KSUCCESS(s)) KePanic("FILE/ASYNC selftest: reopen write temp failed %u\n", s); @@ -664,7 +701,7 @@ static void KiRunAsyncFileIoSelfTest(void) { IoCloseFile(rf); ObDereferenceObject(&wev->Header); - s = IoDeleteFile("asyncio.tmp"); + s = IoDeleteFile(SYS_SV_LITERAL("asyncio.tmp")); if (!KSUCCESS(s)) KePanic("FILE/ASYNC selftest: delete temp failed %u\n", s); @@ -695,6 +732,7 @@ static void KiRunTrapFrameSelfTest(void) { void KiRunKernelSelfTests(const HAL_API* hal) { DbgPrint("Kernel selftests: begin\n"); KiRunKernelApcSelfTest(); + KiRunStringViewSelfTest(); KiRunIrpKernelApcSelfTest(hal); KiRunAsyncFileIoSelfTest(); KiRunTrapFrameSelfTest(); diff --git a/code/kernel/ke_settings.c b/code/kernel/ke_settings.c index 9189118..9cca58c 100644 --- a/code/kernel/ke_settings.c +++ b/code/kernel/ke_settings.c @@ -54,7 +54,7 @@ __declspec(dllexport) KSTATUS KeGetSettingString(const char* section, buffer[0] = '\0'; FILE_OBJECT* file = NULL; - KSTATUS st = IoOpenFile(KE_SETTINGS_PATH, &file); + KSTATUS st = IoOpenFile(SYS_SV_LITERAL(KE_SETTINGS_PATH), &file); if (!KSUCCESS(st)) return st; diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index 8585336..ad44c98 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -49,6 +49,7 @@ #include #include #include +#include // --------------------------------------------------------------------------- // HAL pointer - set once at boot by KeSyscallSetHal() @@ -94,13 +95,13 @@ void KeSyscallInit(const HAL_API* hal) { // Bumps the refcount on success; caller must ObDereferenceObject when done. // Returns NULL if the handle is invalid or the type does not match. // --------------------------------------------------------------------------- -static OBJECT* KiHandleToObject(uint32_t handle, OBJECT_TYPE expected) { +static OBJECT* KiHandleToObject(uint32_t handle, OBJECT_TYPE expected, uint32_t requiredRights) { PROCESS* proc = KeGetCurrentProcess(); if (!proc) return NULL; - OBJECT* obj = KeLookupHandle(proc, handle); // bumps refcount + OBJECT* obj = KeLookupHandleWithRights(proc, handle, requiredRights); if (!obj) return NULL; if (obj->Type != expected) { - ObDereferenceObject(obj); // undo the bump + ObDereferenceObject(obj); return NULL; } return obj; @@ -111,35 +112,76 @@ static OBJECT* KiHandleToObject(uint32_t handle, OBJECT_TYPE expected) { // Takes one reference on behalf of the handle slot. // On failure, drops the reference it would have stored. // --------------------------------------------------------------------------- -static KSTATUS KiAllocHandleForCurrent(OBJECT* obj, uint32_t* handleOut) { +static KSTATUS KiAllocHandleForCurrentWithRights(OBJECT* obj, uint32_t rights, uint32_t* handleOut) { PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return STATUS_FAILURE; - KSTATUS s = KeAllocateHandle(proc, obj, handleOut); - if (!KSUCCESS(s)) ObDereferenceObject(obj); + if (!proc) { + ObDereferenceObject(obj); + return STATUS_FAILURE; + } + + KSTATUS s = KeAllocateHandleWithRights(proc, obj, rights, handleOut); + + // Most syscall create/open paths receive a fresh creator reference from the + // subsystem. The handle table takes its own reference; this helper always + // drops the creator reference so the object is owned only by the returned + // handle (and any subsystem-internal refs). + ObDereferenceObject(obj); return s; } +static KSTATUS KiAllocHandleForCurrent(OBJECT* obj, uint32_t* handleOut) { + return KiAllocHandleForCurrentWithRights(obj, HANDLE_RIGHT_ALL, handleOut); +} + #define SYSCALL_STRING_MAX 1024 +#define SYSCALL_TEXT_MAX_BYTES (64ULL * 1024ULL) #define SYSCALL_IO_MAX_BYTES (16ULL * 1024ULL * 1024ULL) #define SYSCALL_BLIT_MAX_BYTES (16ULL * 1024ULL * 1024ULL) -static KSTATUS KiCopyUserStringArg(uint64_t arg, char* dst, size_t dstSize) { - if (!arg) return STATUS_INVALID_PARAMETER; - return CopyStringFromUser(dst, dstSize, (const char*)(uintptr_t)arg); +static KSTATUS KiCopyUserRequest(uint64_t arg, void* dst, size_t size) { + if (!arg || !dst || size == 0) return STATUS_INVALID_PARAMETER; + return CopyFromUser(dst, (const void*)(uintptr_t)arg, size); } -static KSTATUS KiCopyOptionalUserStringArg(uint64_t arg, char* dst, size_t dstSize, const char** out) { +static KSTATUS KiImportUserStringView(SYS_STRING_VIEW userView, + char* dst, + size_t dstSize, + SYS_STRING_VIEW* out, + bool nulTerminate) { if (!out) return STATUS_INVALID_PARAMETER; - if (!arg) { - *out = (const char*)0; - if (dstSize) dst[0] = '\0'; + *out = SysSvFromParts((const char*)0, 0); + + if (userView.Length == 0) { + if (nulTerminate) { + if (!dst || dstSize == 0) return STATUS_BUFFER_TOO_SMALL; + dst[0] = '\0'; + } return STATUS_SUCCESS; } - KSTATUS s = CopyStringFromUser(dst, dstSize, (const char*)(uintptr_t)arg); + if (!userView.Data || !dst) return STATUS_INVALID_PARAMETER; + if (userView.Length > SYSCALL_STRING_MAX) return STATUS_BUFFER_TOO_SMALL; + + size_t needed = userView.Length + (nulTerminate ? 1 : 0); + if (needed < userView.Length || needed > dstSize) return STATUS_BUFFER_TOO_SMALL; + + KSTATUS s = CopyFromUser(dst, userView.Data, userView.Length); if (!KSUCCESS(s)) return s; - *out = dst; + if (nulTerminate) dst[userView.Length] = '\0'; + + *out = SysSvFromParts(dst, userView.Length); + return STATUS_SUCCESS; +} + +static KSTATUS KiImportUserTextView(SYS_STRING_VIEW userView, + char* dst, + size_t dstSize, + SYS_STRING_VIEW* out, + bool nulTerminate) { + KSTATUS s = KiImportUserStringView(userView, dst, dstSize, out, nulTerminate); + if (!KSUCCESS(s)) return s; + if (out && SysSvContainsByte(*out, '\0')) return STATUS_INVALID_PARAMETER; return STATUS_SUCCESS; } @@ -342,13 +384,12 @@ static uint64_t SyscallMutexCreate(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = KeMutexCreate(&mx); if (!KSUCCESS(s)) return s; uint32_t h = 0; - s = KiAllocHandleForCurrent(&mx->Header, &h); // takes ownership of the ref + s = KiAllocHandleForCurrentWithRights(&mx->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, &h); // takes ownership of the ref if (!KSUCCESS(s)) return s; s = KiCopyHandleToUser(a0, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); - ObDereferenceObject(&mx->Header); } return s; } @@ -359,7 +400,7 @@ static uint64_t SyscallMutexCreate(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallMutexAcquire(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - MUTEX* mx = (MUTEX*)KiHandleToObject((uint32_t)a0, ObjectTypeMutex); + MUTEX* mx = (MUTEX*)KiHandleToObject((uint32_t)a0, ObjectTypeMutex, HANDLE_RIGHT_WAIT); if (!mx) return STATUS_NOT_FOUND; KSTATUS s = KeAcquireMutex(mx); ObDereferenceObject(&mx->Header); @@ -372,7 +413,7 @@ static uint64_t SyscallMutexAcquire(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallMutexTryAcquire(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - MUTEX* mx = (MUTEX*)KiHandleToObject((uint32_t)a0, ObjectTypeMutex); + MUTEX* mx = (MUTEX*)KiHandleToObject((uint32_t)a0, ObjectTypeMutex, HANDLE_RIGHT_WAIT); if (!mx) return STATUS_NOT_FOUND; KSTATUS s = KeTryAcquireMutex(mx); ObDereferenceObject(&mx->Header); @@ -385,7 +426,7 @@ static uint64_t SyscallMutexTryAcquire(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallMutexRelease(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - MUTEX* mx = (MUTEX*)KiHandleToObject((uint32_t)a0, ObjectTypeMutex); + MUTEX* mx = (MUTEX*)KiHandleToObject((uint32_t)a0, ObjectTypeMutex, HANDLE_RIGHT_SIGNAL); if (!mx) return STATUS_NOT_FOUND; KeReleaseMutex(mx); ObDereferenceObject(&mx->Header); @@ -403,13 +444,12 @@ static uint64_t SyscallSemCreate(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = KeSemaphoreCreate((int32_t)a0, (int32_t)a1, &sem); if (!KSUCCESS(s)) return s; uint32_t h = 0; - s = KiAllocHandleForCurrent(&sem->Header, &h); + s = KiAllocHandleForCurrentWithRights(&sem->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, &h); if (!KSUCCESS(s)) return s; s = KiCopyHandleToUser(a2, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); - ObDereferenceObject(&sem->Header); } return s; } @@ -420,7 +460,7 @@ static uint64_t SyscallSemCreate(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallSemWait(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject((uint32_t)a0, ObjectTypeSemaphore); + SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject((uint32_t)a0, ObjectTypeSemaphore, HANDLE_RIGHT_WAIT); if (!sem) return STATUS_NOT_FOUND; KSTATUS s = KeWaitSemaphore(sem); ObDereferenceObject(&sem->Header); @@ -433,7 +473,7 @@ static uint64_t SyscallSemWait(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallSemTryWait(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject((uint32_t)a0, ObjectTypeSemaphore); + SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject((uint32_t)a0, ObjectTypeSemaphore, HANDLE_RIGHT_WAIT); if (!sem) return STATUS_NOT_FOUND; KSTATUS s = KeTrySemaphore(sem); ObDereferenceObject(&sem->Header); @@ -446,7 +486,7 @@ static uint64_t SyscallSemTryWait(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallSemSignal(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject((uint32_t)a0, ObjectTypeSemaphore); + SEMAPHORE* sem = (SEMAPHORE*)KiHandleToObject((uint32_t)a0, ObjectTypeSemaphore, HANDLE_RIGHT_SIGNAL); if (!sem) return STATUS_NOT_FOUND; KeSignalSemaphore(sem); ObDereferenceObject(&sem->Header); @@ -464,13 +504,12 @@ static uint64_t SyscallEventCreate(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = KeEventCreate((EVENT_TYPE)a0, (uint32_t)a1, &ev); if (!KSUCCESS(s)) return s; uint32_t h = 0; - s = KiAllocHandleForCurrent(&ev->Header, &h); + s = KiAllocHandleForCurrentWithRights(&ev->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_SIGNAL | HANDLE_RIGHT_QUERY, &h); if (!KSUCCESS(s)) return s; s = KiCopyHandleToUser(a2, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); - ObDereferenceObject(&ev->Header); } return s; } @@ -481,7 +520,7 @@ static uint64_t SyscallEventCreate(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallEventSignal(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - EVENT* ev = (EVENT*)KiHandleToObject((uint32_t)a0, ObjectTypeEvent); + EVENT* ev = (EVENT*)KiHandleToObject((uint32_t)a0, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL); if (!ev) return STATUS_NOT_FOUND; KeSignalEvent(ev); ObDereferenceObject(&ev->Header); @@ -494,7 +533,7 @@ static uint64_t SyscallEventSignal(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallEventClear(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - EVENT* ev = (EVENT*)KiHandleToObject((uint32_t)a0, ObjectTypeEvent); + EVENT* ev = (EVENT*)KiHandleToObject((uint32_t)a0, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL); if (!ev) return STATUS_NOT_FOUND; KeClearEvent(ev); ObDereferenceObject(&ev->Header); @@ -507,7 +546,7 @@ static uint64_t SyscallEventClear(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallEventWait(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; - EVENT* ev = (EVENT*)KiHandleToObject((uint32_t)a0, ObjectTypeEvent); + EVENT* ev = (EVENT*)KiHandleToObject((uint32_t)a0, ObjectTypeEvent, HANDLE_RIGHT_WAIT); if (!ev) return STATUS_NOT_FOUND; uint8_t alertable = a1 ? 1 : 0; KSTATUS s = KeWaitForEvent(ev, alertable); @@ -531,13 +570,12 @@ static uint64_t SyscallQueueCreate(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = KeQueueCreate(&q); if (!KSUCCESS(s)) return s; uint32_t h = 0; - s = KiAllocHandleForCurrent(&q->Header, &h); + s = KiAllocHandleForCurrentWithRights(&q->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_DUPLICATE | HANDLE_RIGHT_QUERY, &h); if (!KSUCCESS(s)) return s; s = KiCopyHandleToUser(a0, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); - ObDereferenceObject(&q->Header); } return s; } @@ -554,7 +592,7 @@ static uint64_t SyscallQueueSend(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = CopyFromUser(&msg, (const void*)(uintptr_t)a1, sizeof(msg)); if (!KSUCCESS(s)) return s; - QUEUE* q = (QUEUE*)KiHandleToObject((uint32_t)a0, ObjectTypeQueue); + QUEUE* q = (QUEUE*)KiHandleToObject((uint32_t)a0, ObjectTypeQueue, HANDLE_RIGHT_WRITE); if (!q) return STATUS_NOT_FOUND; s = KeSendMessage(q, &msg, (bool)a2); ObDereferenceObject(&q->Header); @@ -569,7 +607,7 @@ static uint64_t SyscallQueueRecv(uint64_t a0, uint64_t a1, uint64_t a2, (void)a3; (void)a4; (void)a5; if (!a1) return STATUS_INVALID_PARAMETER; - QUEUE* q = (QUEUE*)KiHandleToObject((uint32_t)a0, ObjectTypeQueue); + QUEUE* q = (QUEUE*)KiHandleToObject((uint32_t)a0, ObjectTypeQueue, HANDLE_RIGHT_READ); if (!q) return STATUS_NOT_FOUND; QUEUE_MESSAGE msg; @@ -602,26 +640,31 @@ static uint64_t SyscallCloseHandle(uint64_t a0, uint64_t a1, uint64_t a2, // --------------------------------------------------------------------------- static uint64_t SyscallOpenFile(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0 || !a1) return STATUS_INVALID_PARAMETER; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - char path[SYSCALL_STRING_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, path, sizeof(path)); + SYS_PATH_HANDLE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; + if (!req.HandleOut) return STATUS_INVALID_PARAMETER; + + char pathBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); + if (!KSUCCESS(s)) return s; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; FILE_OBJECT* file = NULL; s = IoOpenFile(path, &file); if (!KSUCCESS(s)) return s; uint32_t h = 0; - s = KiAllocHandleForCurrent(&file->Header, &h); + s = KiAllocHandleForCurrentWithRights(&file->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &h); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser(a1, h); + s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); - ObDereferenceObject(&file->Header); } return s; } @@ -647,7 +690,7 @@ static uint64_t SyscallReadFile(uint64_t a0, uint64_t a1, uint64_t a2, void* kbuf = MmAllocatePool(size); if (!kbuf) return STATUS_NO_MEMORY; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile); + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_READ); if (!file) { MmFreePool(kbuf); return STATUS_NOT_FOUND; } size_t got = 0; @@ -684,7 +727,7 @@ static uint64_t SyscallWriteFile(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = CopyFromUser(kbuf, (const void*)(uintptr_t)a1, size); if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; } - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile); + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_WRITE); if (!file) { MmFreePool(kbuf); return STATUS_NOT_FOUND; } size_t written = 0; @@ -782,12 +825,12 @@ static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) { } FILE_OBJECT* file = - (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile); + (FILE_OBJECT*)KiHandleToObject(req.FileHandle, ObjectTypeFile, write ? HANDLE_RIGHT_WRITE : HANDLE_RIGHT_READ); if (!file) return STATUS_NOT_FOUND; EVENT* event = (EVENT*)0; if (req.EventHandle) { - event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent); + event = (EVENT*)KiHandleToObject(req.EventHandle, ObjectTypeEvent, HANDLE_RIGHT_SIGNAL); if (!event) { ObDereferenceObject(&file->Header); return STATUS_NOT_FOUND; @@ -839,20 +882,31 @@ static uint64_t SyscallAsyncFileCommon(uint64_t a0, bool write) { } if (s == STATUS_PENDING && req.RequestHandleOut && asyncReq) { - uint32_t requestHandle = 0; - KSTATUS hs = KiAllocHandleForCurrent(IoAsyncFileRequestObject(asyncReq), &requestHandle); - if (!KSUCCESS(hs)) { - // KiAllocHandleForCurrent consumes the creator reference on failure. + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) { (void)IoCancelAsyncFileRequest(asyncReq); + ObDereferenceObject(IoAsyncFileRequestObject(asyncReq)); + if (event) ObDereferenceObject(&event->Header); + ObDereferenceObject(&file->Header); + return STATUS_FAILURE; + } + + uint32_t requestHandle = 0; + KSTATUS hs = KeAllocateHandleWithRights(proc, IoAsyncFileRequestObject(asyncReq), + HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE, + &requestHandle); + if (!KSUCCESS(hs)) { + (void)IoCancelAsyncFileRequest(asyncReq); + ObDereferenceObject(IoAsyncFileRequestObject(asyncReq)); if (event) ObDereferenceObject(&event->Header); ObDereferenceObject(&file->Header); return hs; } + KSTATUS cs = CopyToUser(req.RequestHandleOut, &requestHandle, sizeof(requestHandle)); if (!KSUCCESS(cs)) { - PROCESS* proc = KeGetCurrentProcess(); - if (proc) KeCloseHandle(proc, requestHandle); (void)IoCancelAsyncFileRequest(asyncReq); + KeCloseHandle(proc, requestHandle); ObDereferenceObject(IoAsyncFileRequestObject(asyncReq)); if (event) ObDereferenceObject(&event->Header); ObDereferenceObject(&file->Header); @@ -907,7 +961,7 @@ static uint64_t SyscallCancelIo(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; IO_ASYNC_FILE_REQUEST_OBJECT* req = - (IO_ASYNC_FILE_REQUEST_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeAsyncIo); + (IO_ASYNC_FILE_REQUEST_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeAsyncIo, HANDLE_RIGHT_TERMINATE); if (!req) return STATUS_INVALID_HANDLE; KSTATUS s = IoCancelAsyncFileRequest(req); @@ -920,26 +974,31 @@ static uint64_t SyscallCancelIo(uint64_t a0, uint64_t a1, uint64_t a2, // --------------------------------------------------------------------------- static uint64_t SyscallCreateFile(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0 || !a1) return STATUS_INVALID_PARAMETER; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - char path[SYSCALL_STRING_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, path, sizeof(path)); + SYS_PATH_HANDLE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; + if (!req.HandleOut) return STATUS_INVALID_PARAMETER; + + char pathBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); + if (!KSUCCESS(s)) return s; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; FILE_OBJECT* file = NULL; s = IoCreateFile(path, &file); if (!KSUCCESS(s)) return s; uint32_t h = 0; - s = KiAllocHandleForCurrent(&file->Header, &h); + s = KiAllocHandleForCurrentWithRights(&file->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &h); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser(a1, h); + s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, h); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, h); - ObDereferenceObject(&file->Header); } return s; } @@ -950,11 +1009,17 @@ static uint64_t SyscallCreateFile(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallDeleteFile(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0) return STATUS_INVALID_PARAMETER; - char path[SYSCALL_STRING_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, path, sizeof(path)); + SYS_PATH_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; + + char pathBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); + if (!KSUCCESS(s)) return s; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; + return IoDeleteFile(path); } @@ -964,7 +1029,7 @@ static uint64_t SyscallDeleteFile(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallTruncateFile(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile); + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_WRITE); if (!file) return STATUS_NOT_FOUND; KSTATUS s = IoTruncateFile(file, (uint64_t)a1); ObDereferenceObject(&file->Header); @@ -977,7 +1042,7 @@ static uint64_t SyscallTruncateFile(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallGetFilePos(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile); + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY); if (!file) return (uint64_t)-1; uintptr_t flags = KeAcquireSpinlock(&file->Lock); uint64_t pos = file->Offset; @@ -992,7 +1057,7 @@ static uint64_t SyscallGetFilePos(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallSeekFile(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile); + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY); if (!file) return STATUS_NOT_FOUND; KSTATUS s = IoSeekFile(file, (uint64_t)a1); ObDereferenceObject(&file->Header); @@ -1005,7 +1070,7 @@ static uint64_t SyscallSeekFile(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallGetFileSize(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile); + FILE_OBJECT* file = (FILE_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeFile, HANDLE_RIGHT_QUERY); if (!file) return 0; uint64_t sz = IoGetFileSize(file); ObDereferenceObject(&file->Header); @@ -1198,12 +1263,24 @@ static uint64_t SyscallGetTick(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallDebugPrint(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0) return STATUS_SUCCESS; - char msg[SYSCALL_STRING_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, msg, sizeof(msg)); + SYS_DEBUG_PRINT_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - DbgPrint("%s", msg); + if (req.Text.Length == 0) return STATUS_SUCCESS; + if (!req.Text.Data) return STATUS_INVALID_PARAMETER; + if (req.Text.Length > SYSCALL_TEXT_MAX_BYTES) return STATUS_BUFFER_TOO_SMALL; + + char chunk[512]; + size_t offset = 0; + while (offset < req.Text.Length) { + size_t todo = req.Text.Length - offset; + if (todo > sizeof(chunk)) todo = sizeof(chunk); + s = CopyFromUser(chunk, req.Text.Data + offset, todo); + if (!KSUCCESS(s)) return s; + DbgPrint("%v", SysSvFromParts(chunk, todo)); + offset += todo; + } return STATUS_SUCCESS; } @@ -1235,21 +1312,25 @@ static uint64_t SyscallQueryTrapFrame(uint64_t a0, uint64_t a1, uint64_t a2, // --------------------------------------------------------------------------- static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0 || !a1) return STATUS_INVALID_PARAMETER; + SYS_CREATE_PROCESS_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (!req.HandleOut) return STATUS_INVALID_PARAMETER; - char path[SYSCALL_STRING_MAX]; + char pathBuf[SYSCALL_STRING_MAX]; char cmdlineBuf[SYSCALL_STRING_MAX]; - const char* cmdline = NULL; + SYS_STRING_VIEW path; + SYS_STRING_VIEW cmdlineView; - KSTATUS s = KiCopyUserStringArg(a0, path, sizeof(path)); + s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); if (!KSUCCESS(s)) return s; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; - s = KiCopyOptionalUserStringArg(a2, cmdlineBuf, sizeof(cmdlineBuf), &cmdline); + s = KiImportUserTextView(req.CommandLine, cmdlineBuf, sizeof(cmdlineBuf), &cmdlineView, true); if (!KSUCCESS(s)) return s; - - s = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(uint32_t)); + s = MmProbeUserWrite(req.HandleOut, sizeof(uint32_t)); if (!KSUCCESS(s)) return s; // Read the executable from disk. @@ -1268,20 +1349,26 @@ static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, // Create the process object. PROCESS* proc; - s = KeCreateProcess(path, &proc); + s = KeCreateProcess(pathBuf, &proc); if (!KSUCCESS(s)) { MmFreePool(mem); return s; } // Inherit CWD from parent PROCESS* parent = KeGetCurrentProcess(); if (parent) { uintptr_t flags = KeAcquireSpinlock(&parent->CwdLock); - strncpy(proc->CurrentDirectory, parent->CurrentDirectory, sizeof(proc->CurrentDirectory) - 1); - proc->CurrentDirectory[sizeof(proc->CurrentDirectory) - 1] = '\0'; + size_t cwdLen = parent->CurrentDirectoryLength; + if (cwdLen >= sizeof(proc->CurrentDirectory)) cwdLen = sizeof(proc->CurrentDirectory) - 1; + memcpy(proc->CurrentDirectory, parent->CurrentDirectory, cwdLen); + proc->CurrentDirectory[cwdLen] = '\0'; + proc->CurrentDirectoryLength = cwdLen; proc->CurrentDrive = parent->CurrentDrive; KeReleaseSpinlock(&parent->CwdLock, flags); } - s = KeSetupUserProcessEnvironment(proc, path, cmdline, proc->CurrentDirectory); + s = KeSetupUserProcessEnvironment(proc, + path, + cmdlineView, + SysSvFromParts(proc->CurrentDirectory, proc->CurrentDirectoryLength)); if (!KSUCCESS(s)) { MmFreePool(mem); ObDereferenceObject(&proc->Header); return s; } // Map the PE image into memory. @@ -1292,7 +1379,14 @@ static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, s = LdrResolveImportsForProcess(proc, imageBase); if (!KSUCCESS(s)) { - MmFreeUserMemory((void*)imageBase); + MmFreeUserImage((void*)imageBase, LdrGetImageSize(imageBase)); + ObDereferenceObject(&proc->Header); + return s; + } + + s = LdrProtectImage(imageBase, true); + if (!KSUCCESS(s)) { + MmFreeUserImage((void*)imageBase, LdrGetImageSize(imageBase)); ObDereferenceObject(&proc->Header); return s; } @@ -1300,43 +1394,43 @@ static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, proc->ImageBase = imageBase; proc->ImageSize = LdrGetImageSize(imageBase); - // Give the caller a handle before making the child runnable. Otherwise a - // fast crash/exit can destroy the PROCESS while the syscall is still trying - // to finish wiring it up. PROCESS* caller = KeGetCurrentProcess(); if (!caller) { - MmFreeUserMemory((void*)imageBase); + MmFreeUserImage((void*)imageBase, LdrGetImageSize(imageBase)); ObDereferenceObject(&proc->Header); return STATUS_FAILURE; } uint32_t handle = 0; - s = KeAllocateHandle(caller, &proc->Header, &handle); + s = KeAllocateHandleWithRights(caller, &proc->Header, HANDLE_RIGHT_WAIT | HANDLE_RIGHT_QUERY | HANDLE_RIGHT_TERMINATE, &handle); if (!KSUCCESS(s)) { proc->ImageBase = 0; proc->ImageSize = 0; - MmFreeUserMemory((void*)imageBase); + MmFreeUserImage((void*)imageBase, LdrGetImageSize(imageBase)); ObDereferenceObject(&proc->Header); return s; } - // Spin up the initial thread last. KeCreateThread queues the thread before - // returning, so everything visible from exit/fault/wait paths must already - // be valid at this point. THREAD* thread; uintptr_t entryPoint = LdrGetEntryPoint(imageBase); s = KeCreateThread(proc, entryPoint, 0, PriorityNormal, &thread); if (!KSUCCESS(s)) { - MmFreeUserMemory((void*)imageBase); + KeCloseHandle(caller, handle); ObDereferenceObject(&proc->Header); return s; } - s = KiCopyHandleToUser(a1, handle); + s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, handle); if (!KSUCCESS(s)) { + KeTerminateProcess(proc, -1); KeCloseHandle(caller, handle); + ObDereferenceObject(&proc->Header); return s; } + + // Drop the creator's local reference. The returned handle and live thread + // references now own the process lifetime. + ObDereferenceObject(&proc->Header); return STATUS_SUCCESS; } // --------------------------------------------------------------------------- @@ -1347,7 +1441,7 @@ static uint64_t SyscallWaitForProcess(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess); + PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_WAIT); if (!proc) return STATUS_INVALID_HANDLE; KSTATUS s = KeWaitForEvent(proc->ExitEvent, 0); @@ -1363,7 +1457,7 @@ static uint64_t SyscallGetProcessExitCode(uint64_t a0, uint64_t a1, uint64_t a2, (void)a2; (void)a3; (void)a4; (void)a5; if (!a1) return STATUS_INVALID_PARAMETER; - PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess); + PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_QUERY); if (!proc) return STATUS_INVALID_HANDLE; int exitCode = 0; @@ -1379,18 +1473,21 @@ static uint64_t SyscallGetProcessExitCode(uint64_t a0, uint64_t a1, uint64_t a2, // --------------------------------------------------------------------------- static uint64_t SyscallWriteConsole(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - const char* userBuf = (const char*)(uintptr_t)a0; - size_t len = (size_t)a1; - if (!len) return STATUS_SUCCESS; - if (!userBuf) return STATUS_INVALID_PARAMETER; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + SYS_WRITE_CONSOLE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (req.Text.Length == 0) return STATUS_SUCCESS; + if (!req.Text.Data) return STATUS_INVALID_PARAMETER; + if (req.Text.Length > SYSCALL_TEXT_MAX_BYTES) return STATUS_BUFFER_TOO_SMALL; char chunk[512]; size_t offset = 0; - while (offset < len) { - size_t todo = len - offset; + while (offset < req.Text.Length) { + size_t todo = req.Text.Length - offset; if (todo > sizeof(chunk)) todo = sizeof(chunk); - KSTATUS s = CopyFromUser(chunk, userBuf + offset, todo); + s = CopyFromUser(chunk, req.Text.Data + offset, todo); if (!KSUCCESS(s)) return s; KePrintConsole(chunk, todo); offset += todo; @@ -1401,17 +1498,21 @@ static uint64_t SyscallWriteConsole(uint64_t a0, uint64_t a1, uint64_t a2, // --------------------------------------------------------------------------- // SYS_OPEN_DIR (54) a0=*dirPath a1=*handleOut // --------------------------------------------------------------------------- -static uint64_t SyscallOpenDir(uint64_t a0, uint64_t a1, uint64_t a2, +static uint64_t SyscallOpenDir(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - if (!a1) return STATUS_INVALID_PARAMETER; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + SYS_PATH_HANDLE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (!req.HandleOut) return STATUS_INVALID_PARAMETER; char dirPathBuf[SYSCALL_STRING_MAX]; - const char* dirPath = NULL; - KSTATUS s = KiCopyOptionalUserStringArg(a0, dirPathBuf, sizeof(dirPathBuf), &dirPath); + SYS_STRING_VIEW dirPath; + s = KiImportUserTextView(req.Path, dirPathBuf, sizeof(dirPathBuf), &dirPath, true); if (!KSUCCESS(s)) return s; - s = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(uint32_t)); + s = MmProbeUserWrite(req.HandleOut, sizeof(uint32_t)); if (!KSUCCESS(s)) return s; DIR_OBJECT* dir = NULL; @@ -1419,13 +1520,13 @@ static uint64_t SyscallOpenDir(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) return s; uint32_t handle = 0; - s = KiAllocHandleForCurrent(&dir->Header, &handle); + s = KiAllocHandleForCurrentWithRights(&dir->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_QUERY, &handle); if (!KSUCCESS(s)) return s; - // Drop IoOpenDir's creator reference; the handle now owns the object. - ObDereferenceObject(&dir->Header); + // KiAllocHandleForCurrentWithRights consumed IoOpenDir's creator reference; + // the returned process handle now owns the directory object. - s = KiCopyHandleToUser(a1, handle); + s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, handle); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, handle); @@ -1443,7 +1544,7 @@ static uint64_t SyscallReadDir(uint64_t a0, uint64_t a1, uint64_t a2, (void)a2; (void)a3; (void)a4; (void)a5; if (!a1) return STATUS_INVALID_PARAMETER; - DIR_OBJECT* dir = (DIR_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeDir); + DIR_OBJECT* dir = (DIR_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeDir, HANDLE_RIGHT_READ); if (!dir) return STATUS_INVALID_PARAMETER; KDIR_ENTRY entry; @@ -1460,9 +1561,17 @@ static uint64_t SyscallReadDir(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallSetCwd(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - char path[SYSCALL_STRING_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, path, sizeof(path)); + + SYS_PATH_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; + + char pathBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); + if (!KSUCCESS(s)) return s; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; + return IoSetCwd(path); } @@ -1471,17 +1580,25 @@ static uint64_t SyscallSetCwd(uint64_t a0, uint64_t a1, uint64_t a2, // --------------------------------------------------------------------------- static uint64_t SyscallGetCwd(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - size_t size = (size_t)a1; - if (!a0 || size == 0) return STATUS_INVALID_PARAMETER; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + SYS_GET_CWD_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (!req.Buffer && req.Capacity) return STATUS_INVALID_PARAMETER; char cwd[SYSCALL_STRING_MAX]; - KSTATUS s = IoGetCwd(cwd, sizeof(cwd)); + s = IoGetCwd(cwd, sizeof(cwd)); if (!KSUCCESS(s)) return s; - size_t len = strlen(cwd) + 1; - if (len > size) return STATUS_BUFFER_TOO_SMALL; - return CopyToUser((void*)(uintptr_t)a0, cwd, len); + size_t len = strlen(cwd); + if (req.LengthOut) { + s = CopyToUser(req.LengthOut, &len, sizeof(len)); + if (!KSUCCESS(s)) return s; + } + if (len > req.Capacity) return STATUS_BUFFER_TOO_SMALL; + if (len) return CopyToUser(req.Buffer, cwd, len); + return STATUS_SUCCESS; } // --------------------------------------------------------------------------- @@ -1538,7 +1655,7 @@ static uint64_t SyscallGetProcessPid(uint64_t a0, uint64_t a1, uint64_t a2, (void)a2; (void)a3; (void)a4; (void)a5; if (!a1) return STATUS_INVALID_PARAMETER; - PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess); + PROCESS* proc = (PROCESS*)KiHandleToObject((uint32_t)a0, ObjectTypeProcess, HANDLE_RIGHT_QUERY); if (!proc) return STATUS_INVALID_HANDLE; uint32_t pid = proc->ProcessId; @@ -1563,6 +1680,7 @@ static uint64_t SyscallGetProcessPid(uint64_t a0, uint64_t a1, uint64_t a2, typedef struct { char Name[NAMED_QUEUE_NAME_MAX]; + size_t NameLength; QUEUE* Queue; // NULL = slot free; non-NULL = occupied uint32_t PublisherPid; // ProcessId of whoever called SysPublishQueue } NAMED_QUEUE_ENTRY; @@ -1570,6 +1688,12 @@ typedef struct { static NAMED_QUEUE_ENTRY gNamedQueues[NAMED_QUEUE_MAX]; static SPINLOCK gNamedQueueLock; +static int KiNamedQueueNameEquals(const NAMED_QUEUE_ENTRY* entry, + SYS_STRING_VIEW name) { + if (!entry || !entry->Queue) return 0; + return SysSvEqual(SysSvFromParts(entry->Name, entry->NameLength), name); +} + // --------------------------------------------------------------------------- // SYS_PUBLISH_QUEUE (60) a0=*name a1=queueHandle // @@ -1579,36 +1703,36 @@ static SPINLOCK gNamedQueueLock; // --------------------------------------------------------------------------- static uint64_t SyscallPublishQueue(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + SYS_PUBLISH_QUEUE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; char name[NAMED_QUEUE_NAME_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, name, sizeof(name)); + SYS_STRING_VIEW nameView; + s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, false); if (!KSUCCESS(s)) return s; - if (!name[0]) return STATUS_INVALID_PARAMETER; + if (nameView.Length == 0) return STATUS_INVALID_PARAMETER; - // Resolve the handle to a QUEUE object. KiHandleToObject bumps the - // refcount by one; we keep that ref in the registry slot. - QUEUE* q = (QUEUE*)KiHandleToObject((uint32_t)a1, ObjectTypeQueue); + QUEUE* q = (QUEUE*)KiHandleToObject(req.QueueHandle, ObjectTypeQueue, HANDLE_RIGHT_DUPLICATE); if (!q) return STATUS_INVALID_HANDLE; uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); - // Reject duplicate names. for (int i = 0; i < NAMED_QUEUE_MAX; i++) { - if (gNamedQueues[i].Queue && - strncmp(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX) == 0) { + if (KiNamedQueueNameEquals(&gNamedQueues[i], nameView)) { KeReleaseSpinlock(&gNamedQueueLock, flags); - ObDereferenceObject(&q->Header); // drop the ref we just took + ObDereferenceObject(&q->Header); return STATUS_ALREADY_EXISTS; } } - // Find a free slot. for (int i = 0; i < NAMED_QUEUE_MAX; i++) { if (!gNamedQueues[i].Queue) { - strncpy(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX - 1); - gNamedQueues[i].Name[NAMED_QUEUE_NAME_MAX - 1] = '\0'; - gNamedQueues[i].Queue = q; // ref transferred to registry + memcpy(gNamedQueues[i].Name, name, nameView.Length); + gNamedQueues[i].NameLength = nameView.Length; + gNamedQueues[i].Queue = q; gNamedQueues[i].PublisherPid = KeGetCurrentProcess()->ProcessId; KeReleaseSpinlock(&gNamedQueueLock, flags); return STATUS_SUCCESS; @@ -1616,7 +1740,7 @@ static uint64_t SyscallPublishQueue(uint64_t a0, uint64_t a1, uint64_t a2, } KeReleaseSpinlock(&gNamedQueueLock, flags); - ObDereferenceObject(&q->Header); // no room - drop the ref + ObDereferenceObject(&q->Header); return STATUS_NO_MEMORY; } @@ -1629,26 +1753,29 @@ static uint64_t SyscallPublishQueue(uint64_t a0, uint64_t a1, uint64_t a2, // --------------------------------------------------------------------------- static uint64_t SyscallOpenNamedQueue(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!a1) return STATUS_INVALID_PARAMETER; + SYS_OPEN_NAMED_QUEUE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (!req.HandleOut) return STATUS_INVALID_PARAMETER; char name[NAMED_QUEUE_NAME_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, name, sizeof(name)); + SYS_STRING_VIEW nameView; + s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, false); if (!KSUCCESS(s)) return s; - if (!name[0]) return STATUS_INVALID_PARAMETER; + if (nameView.Length == 0) return STATUS_INVALID_PARAMETER; - s = MmProbeUserWrite((void*)(uintptr_t)a1, sizeof(uint32_t)); + s = MmProbeUserWrite(req.HandleOut, sizeof(uint32_t)); if (!KSUCCESS(s)) return s; uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); QUEUE* q = NULL; for (int i = 0; i < NAMED_QUEUE_MAX; i++) { - if (gNamedQueues[i].Queue && - strncmp(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX) == 0) { + if (KiNamedQueueNameEquals(&gNamedQueues[i], nameView)) { q = gNamedQueues[i].Queue; - ObReferenceObject(&q->Header); // one ref for the new handle + ObReferenceObject(&q->Header); break; } } @@ -1658,10 +1785,10 @@ static uint64_t SyscallOpenNamedQueue(uint64_t a0, uint64_t a1, uint64_t a2, if (!q) return STATUS_NOT_FOUND; uint32_t handle = 0; - s = KiAllocHandleForCurrent(&q->Header, &handle); + s = KiAllocHandleForCurrentWithRights(&q->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_DUPLICATE | HANDLE_RIGHT_QUERY, &handle); if (!KSUCCESS(s)) return s; - s = KiCopyHandleToUser(a1, handle); + s = KiCopyHandleToUser((uint64_t)(uintptr_t)req.HandleOut, handle); if (!KSUCCESS(s)) { PROCESS* proc = KeGetCurrentProcess(); if (proc) KeCloseHandle(proc, handle); @@ -1683,7 +1810,7 @@ static uint64_t SyscallQueueDuplicate(uint64_t a0, uint64_t a1, uint64_t a2, (void)a3; (void)a4; (void)a5; if (!a2) return STATUS_INVALID_PARAMETER; - QUEUE* q = (QUEUE*)KiHandleToObject((uint32_t)a0, ObjectTypeQueue); + QUEUE* q = (QUEUE*)KiHandleToObject((uint32_t)a0, ObjectTypeQueue, HANDLE_RIGHT_DUPLICATE); if (!q) return STATUS_NOT_FOUND; PROCESS* target = KeGetProcessById((uint32_t)a1); @@ -1693,7 +1820,7 @@ static uint64_t SyscallQueueDuplicate(uint64_t a0, uint64_t a1, uint64_t a2, } uint32_t newHandle = 0; - KSTATUS s = KeAllocateHandle(target, &q->Header, &newHandle); + KSTATUS s = KeAllocateHandleWithRights(target, &q->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_DUPLICATE | HANDLE_RIGHT_QUERY, &newHandle); if (KSUCCESS(s)) { KSTATUS cs = KiCopyHandleToUser(a2, newHandle); if (!KSUCCESS(cs)) { @@ -1716,22 +1843,26 @@ static uint64_t SyscallQueueDuplicate(uint64_t a0, uint64_t a1, uint64_t a2, // --------------------------------------------------------------------------- static uint64_t SyscallNamedQueueOwnerPid(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - if (!a1) return STATUS_INVALID_PARAMETER; + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + SYS_NAMED_QUEUE_OWNER_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); + if (!KSUCCESS(s)) return s; + if (!req.PidOut) return STATUS_INVALID_PARAMETER; char name[NAMED_QUEUE_NAME_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, name, sizeof(name)); + SYS_STRING_VIEW nameView; + s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, false); if (!KSUCCESS(s)) return s; - if (!name[0]) return STATUS_INVALID_PARAMETER; + if (nameView.Length == 0) return STATUS_INVALID_PARAMETER; uint32_t pid = 0; uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); for (int i = 0; i < NAMED_QUEUE_MAX; i++) { - if (gNamedQueues[i].Queue && - strncmp(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX) == 0) { + if (KiNamedQueueNameEquals(&gNamedQueues[i], nameView)) { pid = gNamedQueues[i].PublisherPid; KeReleaseSpinlock(&gNamedQueueLock, flags); - return KiCopyU32ToUser(a1, pid); + return KiCopyU32ToUser((uint64_t)(uintptr_t)req.PidOut, pid); } } KeReleaseSpinlock(&gNamedQueueLock, flags); @@ -1751,9 +1882,9 @@ static uint64_t SyscallKillProcess(uint64_t a0, uint64_t a1, uint64_t a2, KeExitThread(self, exitCode); while (true) ; } - KeExitProcess(proc, exitCode); + KSTATUS s = KeTerminateProcess(proc, exitCode); ObDereferenceObject(&proc->Header); - return STATUS_SUCCESS; + return s; } // --------------------------------------------------------------------------- @@ -1797,13 +1928,21 @@ static uint64_t SyscallGetProcessList(uint64_t a0, uint64_t a1, uint64_t a2, // --------------------------------------------------------------------------- static uint64_t SyscallRenameFile(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - char oldPath[SYSCALL_STRING_MAX]; - char newPath[SYSCALL_STRING_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, oldPath, sizeof(oldPath)); + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + SYS_RENAME_FILE_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; - s = KiCopyUserStringArg(a1, newPath, sizeof(newPath)); + + char oldPathBuf[SYSCALL_STRING_MAX]; + char newPathBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW oldPath; + SYS_STRING_VIEW newPath; + s = KiImportUserTextView(req.OldPath, oldPathBuf, sizeof(oldPathBuf), &oldPath, true); if (!KSUCCESS(s)) return s; + s = KiImportUserTextView(req.NewPath, newPathBuf, sizeof(newPathBuf), &newPath, true); + if (!KSUCCESS(s)) return s; + if (oldPath.Length == 0 || newPath.Length == 0) return STATUS_INVALID_PARAMETER; return IoRenameFile(oldPath, newPath); } @@ -1813,9 +1952,16 @@ static uint64_t SyscallRenameFile(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallCreateDir(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - char path[SYSCALL_STRING_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, path, sizeof(path)); + + SYS_PATH_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; + + char pathBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); + if (!KSUCCESS(s)) return s; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; return IoCreateDir(path); } @@ -1825,9 +1971,16 @@ static uint64_t SyscallCreateDir(uint64_t a0, uint64_t a1, uint64_t a2, static uint64_t SyscallRemoveDir(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - char path[SYSCALL_STRING_MAX]; - KSTATUS s = KiCopyUserStringArg(a0, path, sizeof(path)); + + SYS_PATH_REQUEST req; + KSTATUS s = KiCopyUserRequest(a0, &req, sizeof(req)); if (!KSUCCESS(s)) return s; + + char pathBuf[SYSCALL_STRING_MAX]; + SYS_STRING_VIEW path; + s = KiImportUserTextView(req.Path, pathBuf, sizeof(pathBuf), &path, true); + if (!KSUCCESS(s)) return s; + if (path.Length == 0) return STATUS_INVALID_PARAMETER; return IoRemoveDir(path); } @@ -1853,8 +2006,7 @@ typedef struct SYS_SOCKET_RECV_FROM_REQUEST_K { } SYS_SOCKET_RECV_FROM_REQUEST_K; typedef struct SYS_RESOLVE_HOST_REQUEST_K { - const char* Name; - size_t NameLength; + SYS_STRING_VIEW Name; uint32_t Family; uint32_t Flags; SYS_SOCKET_ADDRESS_IPV4_K* AddressOut; @@ -1890,12 +2042,11 @@ static uint64_t SyscallSocket(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) return s; uint32_t h = 0; - s = KiAllocHandleForCurrent(&socket->Header, &h); + s = KiAllocHandleForCurrentWithRights(&socket->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &h); if (!KSUCCESS(s)) return s; - // The process handle now owns the socket. Drop the creator reference so - // forgotten sockets are destroyed when the owning process exits. - ObDereferenceObject(&socket->Header); + // KiAllocHandleForCurrentWithRights consumed NetSocketCreate's creator + // reference; the returned process handle now owns the socket object. s = KiCopyHandleToUser(a3, h); if (!KSUCCESS(s)) { @@ -1918,7 +2069,7 @@ static uint64_t SyscallBind(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = CopyFromUser(&address, (const void*)(uintptr_t)a1, sizeof(address)); if (!KSUCCESS(s)) return s; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) return STATUS_NOT_FOUND; s = NetSocketBind(socket, &address, (size_t)a2); @@ -1933,7 +2084,7 @@ static uint64_t SyscallListen(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) return STATUS_NOT_FOUND; KSTATUS s = NetSocketListen(socket, (uint32_t)a1); @@ -1965,7 +2116,7 @@ static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(cs)) return cs; } - SOCKET_OBJECT* listener = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* listener = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!listener) return STATUS_NOT_FOUND; SOCKET_OBJECT* accepted = NULL; @@ -1975,11 +2126,11 @@ static uint64_t SyscallAccept(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) return s; uint32_t h = 0; - s = KiAllocHandleForCurrent(&accepted->Header, &h); + s = KiAllocHandleForCurrentWithRights(&accepted->Header, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY, &h); if (!KSUCCESS(s)) return s; - // The process handle owns the accepted socket from this point. - ObDereferenceObject(&accepted->Header); + // KiAllocHandleForCurrentWithRights consumed the accept creator reference; + // the returned process handle owns the accepted socket from this point. if (wantRemote) { size_t actual = sizeof(SYS_SOCKET_ADDRESS_IPV4_K); @@ -2011,7 +2162,7 @@ static uint64_t SyscallConnect(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = CopyFromUser(&address, (const void*)(uintptr_t)a1, sizeof(address)); if (!KSUCCESS(s)) return s; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) return STATUS_NOT_FOUND; s = NetSocketConnect(socket, &address, (size_t)a2); @@ -2040,7 +2191,7 @@ static uint64_t SyscallSend(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = CopyFromUser(kbuf, (const void*)(uintptr_t)a1, length); if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; } - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) { MmFreePool(kbuf); return STATUS_NOT_FOUND; } size_t sent = 0; @@ -2076,7 +2227,7 @@ static uint64_t SyscallRecv(uint64_t a0, uint64_t a1, uint64_t a2, void* kbuf = MmAllocatePool(length); if (!kbuf) return STATUS_NO_MEMORY; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) { MmFreePool(kbuf); return STATUS_NOT_FOUND; } size_t received = 0; @@ -2099,7 +2250,7 @@ static uint64_t SyscallShutdown(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { (void)a2; (void)a3; (void)a4; (void)a5; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) return STATUS_NOT_FOUND; KSTATUS s = NetSocketShutdown(socket, (uint32_t)a1); @@ -2136,7 +2287,7 @@ static uint64_t SyscallSendTo(uint64_t a0, uint64_t a1, uint64_t a2, if (!KSUCCESS(s)) { MmFreePool(kbuf); return s; } } - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) { if (kbuf) MmFreePool(kbuf); return STATUS_NOT_FOUND; } size_t sent = 0; @@ -2193,7 +2344,7 @@ static uint64_t SyscallRecvFrom(uint64_t a0, uint64_t a1, uint64_t a2, if (!kbuf) return STATUS_NO_MEMORY; } - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) { if (kbuf) MmFreePool(kbuf); return STATUS_NOT_FOUND; } SYS_SOCKET_ADDRESS_IPV4_K remote; @@ -2231,7 +2382,7 @@ static uint64_t SyscallResolveHost(uint64_t a0, uint64_t a1, uint64_t a2, KSTATUS s = CopyFromUser(&req, (const void*)(uintptr_t)a0, sizeof(req)); if (!KSUCCESS(s)) return s; - if (!req.Name || req.NameLength == 0 || req.NameLength > SYS_RESOLVE_HOST_NAME_MAX) + if (!req.Name.Data || req.Name.Length == 0 || req.Name.Length > SYS_RESOLVE_HOST_NAME_MAX) return STATUS_INVALID_PARAMETER; if (req.Family != SYS_AF_INET) return STATUS_NOT_SUPPORTED; @@ -2250,9 +2401,9 @@ static uint64_t SyscallResolveHost(uint64_t a0, uint64_t a1, uint64_t a2, } char name[SYS_RESOLVE_HOST_NAME_MAX + 1]; - s = CopyFromUser(name, req.Name, req.NameLength); + SYS_STRING_VIEW nameView; + s = KiImportUserTextView(req.Name, name, sizeof(name), &nameView, true); if (!KSUCCESS(s)) return s; - name[req.NameLength] = '\0'; SYS_SOCKET_ADDRESS_IPV4_K address; s = NetResolveHostIpv4(name, &address); @@ -2283,7 +2434,7 @@ static uint64_t SyscallSetSocketOption(uint64_t a0, uint64_t a1, uint64_t a2, s = CopyFromUser(value, req.Value, req.ValueSize); if (!KSUCCESS(s)) return s; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) return STATUS_NOT_FOUND; s = NetSocketSetOption(socket, req.Option, value, req.ValueSize); @@ -2307,7 +2458,7 @@ static uint64_t SyscallGetSocketOption(uint64_t a0, uint64_t a1, uint64_t a2, uint8_t value[sizeof(uint64_t)]; if (req.ValueSize > sizeof(value)) return STATUS_BUFFER_TOO_SMALL; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject((uint32_t)a0, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) return STATUS_NOT_FOUND; size_t actual = 0; @@ -2362,7 +2513,7 @@ static uint64_t SyscallPoll(uint64_t a0, uint64_t a1, uint64_t a2, ready = 0; for (size_t i = 0; i < count; i++) { fds[i].ReturnedEvents = 0; - SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(fds[i].Handle, ObjectTypeSocket); + SOCKET_OBJECT* socket = (SOCKET_OBJECT*)KiHandleToObject(fds[i].Handle, ObjectTypeSocket, HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE); if (!socket) { fds[i].ReturnedEvents = SYS_POLL_INVALID; ready++; @@ -2515,6 +2666,16 @@ static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { uint64_t KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { KiValidateSyscallTrapFrame(nr, a0, a1, a2, a3, a4, a5); + + THREAD* self = KeGetCurrentThread(); + PROCESS* owner = self ? self->Owner : (PROCESS*)0; + if (owner && KeIsProcessTerminating(owner) && + nr != SYS_EXIT_PROCESS && nr != SYS_EXIT_THREAD) { + int exitCode = owner->ExitCode; + KeExitThread(self, exitCode); + while (true) ; + } + if (nr >= SYS_MAX || !gSyscallTable[nr]) return STATUS_NOT_SUPPORTED; return gSyscallTable[nr](a0, a1, a2, a3, a4, a5); } diff --git a/code/kernel/ke_thread.c b/code/kernel/ke_thread.c index 187abc4..78f62f3 100644 --- a/code/kernel/ke_thread.c +++ b/code/kernel/ke_thread.c @@ -40,11 +40,20 @@ static void KiThreadDestroy(OBJECT* obj) { } if (thread->Owner) { - uintptr_t flags = KeAcquireSpinlock(&thread->Owner->HandleLock); + PROCESS* owner = thread->Owner; + uint32_t remaining = 0; + uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); ListRemove(&thread->ProcessLink); - thread->Owner->ThreadCount--; - KeReleaseSpinlock(&thread->Owner->HandleLock, flags); - ObDereferenceObject(&thread->Owner->Header); + if (owner->ThreadCount) owner->ThreadCount--; + remaining = owner->ThreadCount; + KeReleaseSpinlock(&owner->HandleLock, flags); + + if (remaining == 0 && owner->Terminating && + _InterlockedExchange(&owner->Exited, 1) == 0) { + if (owner->ExitEvent) KeSignalEvent(owner->ExitEvent); + } + + ObDereferenceObject(&owner->Header); thread->Owner = (PROCESS *)0; } @@ -104,9 +113,11 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui return STATUS_NO_MEMORY; } thread->UserStackBase = ustack; - uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE - 8; // i don't know why, i don't want to know why - // i shouldn't have to wonder why but the stack is off by 8 bytes unless we do this terribleness - // this magic number will hunt me in my dreams + // Ring-3 starts at the image entry via IRET, not CALL, so there is no + // pushed return address. The x64 ABI expects RSP % 16 == 8 at function + // entry; bias the initial stack as if a call had happened so compiler + // generated aligned SSE spills (movaps) are safe. + uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE - 8; SYS_TEB* teb = (SYS_TEB*)MmAllocateUserMemory(sizeof(SYS_TEB)); if (!teb) { diff --git a/code/kernel/ke_userenv.h b/code/kernel/ke_userenv.h index ebf8bce..b7fae85 100644 --- a/code/kernel/ke_userenv.h +++ b/code/kernel/ke_userenv.h @@ -4,25 +4,24 @@ #include #include +#include #define SYS_PEB_MAGIC 0x30424550u /* "PEB0" */ #define SYS_TEB_MAGIC 0x30424554u /* "TEB0" */ #define SYS_ENV_VERSION 1u #define SYS_CWD_MAX 520u +#ifndef SYS_USER_ENV_TYPES_DEFINED +#define SYS_USER_ENV_TYPES_DEFINED 1 + typedef struct SYS_PEB { uint32_t Magic; uint32_t Version; uint32_t ProcessId; - const char* ImagePath; - size_t ImagePathLength; - - const char* CommandLine; - size_t CommandLineLength; - - const char* CurrentDirectory; - size_t CurrentDirectoryLength; + SYS_STRING_VIEW ImagePath; + SYS_STRING_VIEW CommandLine; + SYS_STRING_VIEW CurrentDirectory; } SYS_PEB; typedef struct SYS_TEB { @@ -32,4 +31,11 @@ typedef struct SYS_TEB { struct SYS_TEB* Self; uint32_t ThreadId; uint32_t Magic; + + // Native per-thread extension pointer. The kernel leaves this NULL; + // user-mode personalities/libraries may hang their own thread-local + // state here without baking subsystem semantics into SYS_TEB. + void* SubsystemData; } SYS_TEB; + +#endif // SYS_USER_ENV_TYPES_DEFINED diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index 5c8a176..dcd1dc4 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -188,7 +188,7 @@ static void KiInitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { KSTATUS s; FILE_OBJECT* f; - s = IoOpenFile("drivers\\drivers.ini", &f); + s = IoOpenFile(SYS_SV_LITERAL("drivers\\drivers.ini"), &f); if (s != STATUS_SUCCESS) { KePanic("KiInitializeDrivers: Failed to open drivers.ini (status %d)\n", s); } @@ -222,7 +222,7 @@ static void KiInitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { memcpy(path, "drivers\\", 8); memcpy(path + 8, runtimeKeys[i], len + 1); - s = IoOpenFile(path, &f); + s = IoOpenFile(SysSvFromCstr(path), &f); if (s != STATUS_SUCCESS) { DbgPrint("KiInitializeDrivers: Failed to open runtime driver: %s (%s)\n", runtimeKeys[i], path); continue; // Skip this driver but keep going with others @@ -285,7 +285,7 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { FILE_OBJECT* f; // Read the executable file into a raw buffer. - s = IoOpenFile(filePath, &f); + s = IoOpenFile(SysSvFromCstr(filePath), &f); DbgPrint("KiLaunchUserProcess: IoOpenFile: %d\n", s); if (s != STATUS_SUCCESS) { DbgPrint("Failed to load file: \"%s\"!\n", filePath); @@ -323,7 +323,10 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { } DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, (args && args[0]) ? args : filePath); - s = KeSetupUserProcessEnvironment(proc, filePath, args, proc->CurrentDirectory); + s = KeSetupUserProcessEnvironment(proc, + SysSvFromCstr(filePath), + SysSvFromCstr(args), + SysSvFromCstr(proc->CurrentDirectory)); if (!KSUCCESS(s)) { DbgPrint("KiLaunchUserProcess: Failed to build user process environment. Status: %d\n", s); MmFreePool(mem); @@ -344,7 +347,7 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { s = LdrResolveImportsForProcess(proc, imageBase); if (!KSUCCESS(s)) { DbgPrint("KiLaunchUserProcess: Failed to resolve EXE imports for %s (status %d)\n", filePath, s); - MmFreeUserMemory((void*)imageBase); + MmFreeUserImage((void*)imageBase, LdrGetImageSize(imageBase)); ObDereferenceObject(&proc->Header); return 0; } @@ -361,7 +364,7 @@ static PROCESS* KiLaunchUserProcess(const char* filePath, const char* args) { DbgPrint("KiLaunchUserProcess: Failed to create thread. Status: %d\n", s); proc->ImageBase = 0; proc->ImageSize = 0; - MmFreeUserMemory((void*)imageBase); + MmFreeUserImage((void*)imageBase, LdrGetImageSize(imageBase)); ObDereferenceObject(&proc->Header); return 0; } diff --git a/code/kernel/ldr_image.c b/code/kernel/ldr_image.c index d824652..4761860 100644 --- a/code/kernel/ldr_image.c +++ b/code/kernel/ldr_image.c @@ -107,6 +107,10 @@ typedef struct { #pragma pack(pop) #define IMAGE_REL_BASED_DIR64 10 +#define IMAGE_SCN_CNT_CODE 0x00000020u +#define IMAGE_SCN_MEM_EXECUTE 0x20000000u +#define IMAGE_SCN_MEM_READ 0x40000000u +#define IMAGE_SCN_MEM_WRITE 0x80000000u // --------------------------------------------------------------------------- // Module registry @@ -248,6 +252,40 @@ size_t LdrGetImageSize(uintptr_t base) { return (size_t)pe->OptionalHeader.SizeOfImage; } + +static void KiFreeLoadedImage(uintptr_t imageBase, bool user) { + if (!imageBase) return; + size_t imageSize = LdrGetImageSize(imageBase); + if (user) MmFreeUserImage((void*)imageBase, imageSize); + else MmFreePool((void*)imageBase); +} + + +KSTATUS LdrProtectImage(uintptr_t imageBase, bool user) { + if (!imageBase) return STATUS_INVALID_PARAMETER; + + uint32_t peOffset = *(uint32_t *)(imageBase + 0x3C); + PE_HEADER* pe = (PE_HEADER *)(imageBase + peOffset); + if (pe->Signature != PE_SIGNATURE) return STATUS_FAILURE; + + SECTION_HEADER* sec = (SECTION_HEADER *)((uint8_t *)&pe->OptionalHeader + pe->SizeOfOptionalHeader); + for (int i = 0; i < pe->NumberOfSections; i++) { + size_t protectSize = sec[i].VirtualSize ? sec[i].VirtualSize : sec[i].SizeOfRawData; + if (!protectSize) continue; + + uint64_t prot = PAGE_PRESENT | (user ? PAGE_USER : 0); + if (sec[i].Characteristics & IMAGE_SCN_MEM_WRITE) + prot |= PAGE_WRITE; + if (!((sec[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) || + (sec[i].Characteristics & IMAGE_SCN_CNT_CODE))) + prot |= PAGE_NX; + + KSTATUS s = MmSetPageProtect(imageBase + sec[i].VirtualAddress, protectSize, prot); + if (!KSUCCESS(s)) return s; + } + return STATUS_SUCCESS; +} + // --------------------------------------------------------------------------- // Entry point lookup // --------------------------------------------------------------------------- @@ -470,13 +508,13 @@ KSTATUS LdrLoadModuleForProcess(PROCESS* process, const char* filename, uintptr_ } FILE_OBJECT* f; - KSTATUS s = IoOpenFile(filename, &f); + KSTATUS s = IoOpenFile(SysSvFromCstr(filename), &f); if (!KSUCCESS(s)) { char path[260]; memcpy(path, "0:/system/", 10); memcpy(path + 10, filename, strnlen(filename, 248) + 1); path[259] = '\0'; - s = IoOpenFile(path, &f); + s = IoOpenFile(SysSvFromCstr(path), &f); if (!KSUCCESS(s)) { DbgPrint("LdrLoadModuleForProcess: Failed to open %s (%d)\n", filename, s); return s; @@ -509,7 +547,13 @@ KSTATUS LdrLoadModuleForProcess(PROCESS* process, const char* filename, uintptr_ s = LdrResolveImportsForProcess(process, imageBase); if (!KSUCCESS(s)) { - MmFreeUserMemory((void*)imageBase); + KiFreeLoadedImage(imageBase, true); + return s; + } + + s = LdrProtectImage(imageBase, true); + if (!KSUCCESS(s)) { + KiFreeLoadedImage(imageBase, true); return s; } @@ -517,7 +561,7 @@ KSTATUS LdrLoadModuleForProcess(PROCESS* process, const char* filename, uintptr_ s = LdrRegisterProcessModule(process, filename, imageBase); if (!KSUCCESS(s)) { - MmFreeUserMemory((void*)imageBase); + KiFreeLoadedImage(imageBase, true); return s; } @@ -561,7 +605,7 @@ KSTATUS LdrLoadImage(const void* buffer, size_t size, uintptr_t* baseOut, bool u OPT_HEADER64* opt = &pe->OptionalHeader; size_t imageSize = opt->SizeOfImage; - void* mem = user ? MmAllocateUserMemory(imageSize) : MmAllocatePool(imageSize); + void* mem = user ? MmAllocateUserImage(imageSize) : MmAllocatePool(imageSize); if (!mem) return STATUS_NO_MEMORY; memset(mem, 0, imageSize); @@ -575,7 +619,9 @@ KSTATUS LdrLoadImage(const void* buffer, size_t size, uintptr_t* baseOut, bool u } uint64_t prot = PAGE_PRESENT | PAGE_WRITE | (user ? PAGE_USER : 0); - MmSetPageProtect((uintptr_t)mem + sec[i].VirtualAddress, sec[i].VirtualSize, prot); + MmSetPageProtect((uintptr_t)mem + sec[i].VirtualAddress, + sec[i].VirtualSize ? sec[i].VirtualSize : sec[i].SizeOfRawData, + prot); } uintptr_t delta = (uintptr_t)mem - opt->ImageBase; diff --git a/code/kernel/ldr_image.h b/code/kernel/ldr_image.h index 45ae5f7..34ca9f1 100644 --- a/code/kernel/ldr_image.h +++ b/code/kernel/ldr_image.h @@ -1,38 +1,41 @@ -#pragma once -// ldr_image.h - minimal PE32+ loader -// Loads an already-mapped PE image and resolves its imports. -#include -#include -#include -#include - -// Map a PE32+ image from 'buffer' into the kernel virtual address space. -// On success, *baseOut receives the virtual base address. -KSTATUS LdrLoadImage(const void* buffer, size_t size, uintptr_t* baseOut, bool user); - -// Return the SizeOfImage recorded in the PE header for an already-mapped image. -size_t LdrGetImageSize(uintptr_t base); - -// Look up a named export in a loaded module. -// 'moduleName' must match a name in BOOT_INFO.Modules (e.g., "hal.dll"). -void* LdrGetExport(const char* moduleName, const char* functionName); - -// Get the entry point address of a loaded image. -uintptr_t LdrGetEntryPoint(uintptr_t base); - -// Resolve the import table of an already-loaded image using the global registry. -KSTATUS LdrResolveImports(uintptr_t imageBase); - -// Resolve imports using a process-local module registry. -struct PROCESS; -KSTATUS LdrResolveImportsForProcess(struct PROCESS* process, uintptr_t imageBase); - -// Load a DLL for a specific process, resolve it against that process's -// loaded modules, and register it in the process-local registry. -KSTATUS LdrLoadModuleForProcess(struct PROCESS* process, const char* filename, uintptr_t* baseOut); - -// Called by KernelMain to register all modules from BOOT_INFO so that -// LdrGetExport can find them. -struct BOOT_INFO; // forward declare for the compiler to shut the fuck up -void LdrRegisterModulesFromBootInfo(const struct BOOT_INFO* bootInfo); -void LdrRegisterModule(const char* name, uintptr_t base); +#pragma once +// ldr_image.h - minimal PE32+ loader +// Loads an already-mapped PE image and resolves its imports. +#include +#include +#include +#include + +// Map a PE32+ image from 'buffer' into the kernel virtual address space. +// On success, *baseOut receives the virtual base address. +KSTATUS LdrLoadImage(const void* buffer, size_t size, uintptr_t* baseOut, bool user); + +// Return the SizeOfImage recorded in the PE header for an already-mapped image. +size_t LdrGetImageSize(uintptr_t base); + +// Look up a named export in a loaded module. +// 'moduleName' must match a name in BOOT_INFO.Modules (e.g., "hal.dll"). +void* LdrGetExport(const char* moduleName, const char* functionName); + +// Get the entry point address of a loaded image. +uintptr_t LdrGetEntryPoint(uintptr_t base); + +// Resolve the import table of an already-loaded image using the global registry. +KSTATUS LdrResolveImports(uintptr_t imageBase); + +// Resolve imports using a process-local module registry. +struct PROCESS; +KSTATUS LdrResolveImportsForProcess(struct PROCESS* process, uintptr_t imageBase); + +// Apply final page permissions for a loaded image after relocations/imports. +KSTATUS LdrProtectImage(uintptr_t imageBase, bool user); + +// Load a DLL for a specific process, resolve it against that process's +// loaded modules, and register it in the process-local registry. +KSTATUS LdrLoadModuleForProcess(struct PROCESS* process, const char* filename, uintptr_t* baseOut); + +// Called by KernelMain to register all modules from BOOT_INFO so that +// LdrGetExport can find them. +struct BOOT_INFO; // forward declare for the compiler to shut the fuck up +void LdrRegisterModulesFromBootInfo(const struct BOOT_INFO* bootInfo); +void LdrRegisterModule(const char* name, uintptr_t base); diff --git a/code/kernel/mm_virt.c b/code/kernel/mm_virt.c index 7e94d83..047143f 100644 --- a/code/kernel/mm_virt.c +++ b/code/kernel/mm_virt.c @@ -290,29 +290,6 @@ __declspec(dllexport) KSTATUS CopyToUser(void* userDst, const void* kernelSrc, s return STATUS_SUCCESS; } -__declspec(dllexport) KSTATUS CopyStringFromUser(char* kernelDst, - size_t kernelDstSize, - const char* userSrc) { - if (!kernelDst || kernelDstSize == 0 || !userSrc) - return STATUS_INVALID_PARAMETER; - - for (size_t i = 0; i < kernelDstSize; i++) { - KSTATUS s = MmProbeUserRead(userSrc + i, 1); - if (!KSUCCESS(s)) { - kernelDst[0] = '\0'; - return s; - } - - char ch = userSrc[i]; - kernelDst[i] = ch; - if (ch == '\0') - return STATUS_SUCCESS; - } - - kernelDst[kernelDstSize - 1] = '\0'; - return STATUS_BUFFER_TOO_SMALL; -} - // --------------------------------------------------------------------------- // Boundary-tag allocator (used for both pool and user heap) // --------------------------------------------------------------------------- @@ -595,6 +572,9 @@ static HEAP gPoolHeap; // User heap // --------------------------------------------------------------------------- static HEAP gUserHeap; +__declspec(dllexport) void MmFreeUserImage(void* ptr, size_t size); +static SPINLOCK gUserImageLock = SPINLOCK_INIT; +static uintptr_t gUserImageNext = USER_LIMIT; // --------------------------------------------------------------------------- // Module init @@ -621,8 +601,15 @@ void MmInitializeVirtual(PHYSICAL_ADDRESS bootPml4Phys, size_t poolSize) { gUserHeap.Start = USER_BASE; gUserHeap.Brk = USER_BASE; gUserHeap.Limit = USER_LIMIT; - gUserHeap.MapFlags = PAGE_PRESENT | PAGE_WRITE | PAGE_USER; + gUserHeap.MapFlags = PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX; gUserHeap.Lock = (SPINLOCK)SPINLOCK_INIT; + + // Executable image mappings grow downward from the top of user space. + // The regular user heap grows upward from USER_BASE. Keeping them in + // separate page-aligned regions prevents W^X section protection from + // changing heap metadata or adjacent allocations. + gUserImageLock = (SPINLOCK)SPINLOCK_INIT; + gUserImageNext = USER_LIMIT; } void MmActivate(void) { @@ -654,6 +641,60 @@ __declspec(dllexport) void* MmReallocateUserMemory(void* ptr, size_t newSize) { return KiHeapRealloc(&gUserHeap, ptr, newSize); } + +__declspec(dllexport) void* MmAllocateUserImage(size_t size) { + KeAssertIrql(PASSIVE_LEVEL); + if (!size) return (void*)0; + + size_t pagesSize = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + if (pagesSize < size) return (void*)0; + + uintptr_t flags = KeAcquireSpinlock(&gUserImageLock); + + if (pagesSize > gUserImageNext || gUserImageNext - pagesSize < gUserHeap.Brk) { + KeReleaseSpinlock(&gUserImageLock, flags); + return (void*)0; + } + + uintptr_t base = gUserImageNext - pagesSize; + gUserImageNext = base; + KeReleaseSpinlock(&gUserImageLock, flags); + + for (uintptr_t va = base; va < base + pagesSize; va += PAGE_SIZE) { + PHYSICAL_ADDRESS frame = MmAllocateFrame(); + if (!frame) { + MmFreeUserImage((void*)base, va - base); + return (void*)0; + } + KSTATUS s = MmMapPage(va, frame, PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX); + if (!KSUCCESS(s)) { + MmFreeFrame(frame); + MmFreeUserImage((void*)base, va - base); + return (void*)0; + } + } + + memset((void*)base, 0, pagesSize); + return (void*)base; +} + +__declspec(dllexport) void MmFreeUserImage(void* ptr, size_t size) { + KeAssertIrql(PASSIVE_LEVEL); + if (!ptr || !size) return; + + uintptr_t base = (uintptr_t)ptr & PAGE_MASK; + size_t pagesSize = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + if (pagesSize < size) return; + + for (uintptr_t va = base; va < base + pagesSize; va += PAGE_SIZE) { + PHYSICAL_ADDRESS phys = MmGetPhysicalAddress(va); + if (!phys) continue; + if (KSUCCESS(MmUnmapPage(va))) { + MmFreeFrame(phys); + } + } +} + size_t MmGetKernelPoolUsage(void) { // Returns bytes committed to the kernel pool return (size_t)(gPoolHeap.Brk - gPoolHeap.Start); diff --git a/code/kernel/mm_virt.h b/code/kernel/mm_virt.h index f11b16d..d930353 100644 --- a/code/kernel/mm_virt.h +++ b/code/kernel/mm_virt.h @@ -73,11 +73,6 @@ __declspec(dllexport) KSTATUS MmProbeUserWrite(void* ptr, size_t size); __declspec(dllexport) KSTATUS CopyFromUser(void* kernelDst, const void* userSrc, size_t size); __declspec(dllexport) KSTATUS CopyToUser(void* userDst, const void* kernelSrc, size_t size); -// Bounded NUL-terminated string import from user memory. Always NUL-terminates -// kernelDst when kernelDstSize is non-zero. Returns STATUS_BUFFER_TOO_SMALL if -// no NUL byte was found within kernelDstSize bytes. -__declspec(dllexport) KSTATUS CopyStringFromUser(char* kernelDst, size_t kernelDstSize, const char* userSrc); - // --------------------------------------------------------------------------- // Kernel pool - ring 0 only, never PAGE_USER. // Best-fit free-list allocator, thread-safe via spinlock. @@ -88,11 +83,17 @@ __declspec(dllexport) void* MmReallocatePool(void* ptr, size_t newSize); // --------------------------------------------------------------------------- // User heap - shared flat space, ring 3 accessible. -// All allocations are PAGE_PRESENT | PAGE_WRITE | PAGE_USER. +// Allocations are writable user pages with NX set by default; PE image +// sections opt back into execution through the loader. // --------------------------------------------------------------------------- __declspec(dllexport) void* MmAllocateUserMemory(size_t size); __declspec(dllexport) void MmFreeUserMemory(void* ptr); __declspec(dllexport) void* MmReallocateUserMemory(void* ptr, size_t newSize); +// Page-aligned user image mapping. PE section protection is page based, so +// executable images must not be allocated from the normal sub-page heap. +__declspec(dllexport) void* MmAllocateUserImage(size_t size); +__declspec(dllexport) void MmFreeUserImage(void* ptr, size_t size); + size_t MmGetKernelPoolUsage(void); size_t MmGetUserHeapUsage(void); diff --git a/code/ping/ping.c b/code/ping/ping.c index b8d5a1a..9b1e25e 100644 --- a/code/ping/ping.c +++ b/code/ping/ping.c @@ -12,11 +12,12 @@ #define PING_PACKET_BYTES (8u + PING_PAYLOAD_BYTES) static void print(const char* s) { - if (s) SysWriteConsole(s, strlen(s)); + if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } } static void print_char(char c) { - SysWriteConsole(&c, 1); + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); } static void print_u64(uint64_t v) { @@ -126,8 +127,7 @@ static uint32_t resolve_host(const char* host, uint8_t ip[4]) { SYS_RESOLVE_HOST_REQUEST req; memset(&resolved, 0, sizeof(resolved)); memset(&req, 0, sizeof(req)); - req.Name = host; - req.NameLength = strlen(host); + req.Name = SysSvFromCstr(host); req.Family = SYS_AF_INET; req.AddressOut = &resolved; req.AddressSizeInOut = &resolvedSize; @@ -218,7 +218,7 @@ int main(void) { uint32_t count = PING_DEFAULT_COUNT; strcpy(host, PING_DEFAULT_TARGET); - const char* cmd = SysGetPeb()->CommandLine; + const char* cmd = SysGetPeb()->CommandLine.Data; cmd = next_token(cmd, tok, sizeof(tok)); // program path if (cmd) { const char* rest = next_token(cmd, tok, sizeof(tok)); diff --git a/code/posix/posix_dir.c b/code/posix/posix_dir.c index 543202d..f87a67c 100644 --- a/code/posix/posix_dir.c +++ b/code/posix/posix_dir.c @@ -18,7 +18,8 @@ typedef struct { __declspec(dllexport) int chdir(const char* path) { char native[512]; PosixTranslatePath(path, native, sizeof(native)); - uint32_t s = SysSetCwd(native); + SYS_PATH_REQUEST req = { SysSvFromCstr(native) }; + uint32_t s = SysSetCwd(&req); if (s != 0) { errno = ENOENT; return -1; } return 0; } @@ -26,7 +27,10 @@ __declspec(dllexport) int chdir(const char* path) { __declspec(dllexport) char* getcwd(char* buf, size_t size) { if (!buf || size == 0) { errno = EINVAL; return (char*)0; } char native[512]; - uint32_t s = SysGetCwd(native, sizeof(native)); + size_t nativeLen = 0; + SYS_GET_CWD_REQUEST req = { native, sizeof(native) - 1, &nativeLen }; + uint32_t s = SysGetCwd(&req); + if (s == 0) native[nativeLen] = '\0'; if (s != 0) { errno = ERANGE; return (char*)0; } NativeToPosix(native, buf, size); return buf; @@ -36,7 +40,8 @@ __declspec(dllexport) DIR* opendir(const char* path) { char native[512]; PosixTranslatePath(path, native, sizeof(native)); uint32_t kh; - uint32_t s = SysOpenDir(native, &kh); + SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(native), &kh }; + uint32_t s = SysOpenDir(&req); if (s != 0) { errno = ENOENT; return (DIR*)0; } // Allocate DIR on the kernel heap (SysAlloc) so it outlives the stack. diff --git a/code/posix/posix_fd.c b/code/posix/posix_fd.c index 6f7fc4a..e5c4541 100644 --- a/code/posix/posix_fd.c +++ b/code/posix/posix_fd.c @@ -1,7 +1,7 @@ // posix_fd.c - file descriptor table +#include #include "posix.h" #include "posix_fd.h" -#include int errno = 0; diff --git a/code/posix/posix_io.c b/code/posix/posix_io.c index 77e1e54..d5487ab 100644 --- a/code/posix/posix_io.c +++ b/code/posix/posix_io.c @@ -33,11 +33,12 @@ __declspec(dllexport) int open(const char* path, int flags, ...) { } uint32_t kh = 0; + SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(p), &kh }; uint32_t s; if (flags & O_WRONLY) { - s = SysCreateFile(p, &kh); + s = SysCreateFile(&req); } else { - s = SysOpenFile(p, &kh); + s = SysOpenFile(&req); } if (s != 0) { errno = PosixStatusToErrno(s); return -1; } @@ -98,9 +99,11 @@ __declspec(dllexport) ssize_t write(int fd, const void* buf, size_t count) { if (!e) return -1; switch (e->Type) { - case FD_CONSOLE_W: - SysWriteConsole((const char*)buf, count); + case FD_CONSOLE_W: { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts((const char*)buf, count) }; + SysWriteConsole(&req); return (ssize_t)count; + } case FD_DEVNULL: case FD_DEVZERO: return (ssize_t)count; @@ -143,7 +146,8 @@ __declspec(dllexport) int unlink(const char* path) { return -1; } - uint32_t s = SysDeleteFile(p); + SYS_PATH_REQUEST req = { SysSvFromCstr(p) }; + uint32_t s = SysDeleteFile(&req); if (s != 0) { errno = PosixStatusToErrno(s); return -1; } return 0; } diff --git a/code/posix/posix_proc.c b/code/posix/posix_proc.c index 64323ef..665a7ab 100644 --- a/code/posix/posix_proc.c +++ b/code/posix/posix_proc.c @@ -15,8 +15,8 @@ static SYS_PEB* PosixPeb(void) { __declspec(dllexport) const char* posix_get_command_line(void) { SYS_PEB* peb = PosixPeb(); - if (!peb || peb->Magic != SYS_PEB_MAGIC || !peb->CommandLine) return ""; - return peb->CommandLine; + if (!peb || peb->Magic != SYS_PEB_MAGIC || !peb->CommandLine.Data) return ""; + return peb->CommandLine.Data; } // pid -> proc handle table (user-mode side, for waitpid) @@ -104,7 +104,8 @@ __declspec(dllexport) int posix_spawn(pid_t* pidOut, } uint32_t procHandle; - uint32_t s = SysCreateProcess(native, &procHandle, cmdline); + SYS_CREATE_PROCESS_REQUEST req = { SysSvFromCstr(native), SysSvFromCstr(cmdline), &procHandle }; + uint32_t s = SysCreateProcess(&req); if (s != 0) { errno = ENOENT; return -1; } uint32_t kpid = 0; diff --git a/code/posix/posix_socket.c b/code/posix/posix_socket.c index ec38f62..44f393c 100644 --- a/code/posix/posix_socket.c +++ b/code/posix/posix_socket.c @@ -392,8 +392,7 @@ static int ResolveNameIpv4(const char* name, SYS_SOCKET_ADDRESS_IPV4* out) { size_t size = sizeof(*out); SYS_RESOLVE_HOST_REQUEST req; memset(&req, 0, sizeof(req)); - req.Name = name; - req.NameLength = strlen(name); + req.Name = SysSvFromCstr(name); req.Family = SYS_AF_INET; req.AddressOut = out; req.AddressSizeInOut = &size; diff --git a/code/printf.c b/code/printf.c index 27b232f..439f203 100644 --- a/code/printf.c +++ b/code/printf.c @@ -1,920 +1,952 @@ -/////////////////////////////////////////////////////////////////////////////// -// \author (c) Marco Paland (info@paland.com) -// 2014-2019, PALANDesign Hannover, Germany -// -// \license The MIT License (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on -// embedded systems with a very limited resources. These routines are thread -// safe and reentrant! -// Use this instead of the bloated standard/newlib printf cause these use -// malloc for printf (and may not be thread safe). -// -/////////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include "printf.h" - - -// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the -// printf_config.h header file -// default: undefined -#ifdef PRINTF_INCLUDE_CONFIG_H -#include "printf_config.h" -#endif - - -// 'ntoa' conversion buffer size, this must be big enough to hold one converted -// numeric number including padded zeros (dynamically created on stack) -// default: 32 byte -#ifndef PRINTF_NTOA_BUFFER_SIZE -#define PRINTF_NTOA_BUFFER_SIZE 32U -#endif - -// 'ftoa' conversion buffer size, this must be big enough to hold one converted -// float number including padded zeros (dynamically created on stack) -// default: 32 byte -#ifndef PRINTF_FTOA_BUFFER_SIZE -#define PRINTF_FTOA_BUFFER_SIZE 32U -#endif - -// support for the floating point type (%f) -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_FLOAT -#define PRINTF_SUPPORT_FLOAT -#endif - -// support for exponential floating point notation (%e/%g) -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL -#define PRINTF_SUPPORT_EXPONENTIAL -#endif - -// define the default floating point precision -// default: 6 digits -#ifndef PRINTF_DEFAULT_FLOAT_PRECISION -#define PRINTF_DEFAULT_FLOAT_PRECISION 6U -#endif - -// define the largest float suitable to print with %f -// default: 1e9 -#ifndef PRINTF_MAX_FLOAT -#define PRINTF_MAX_FLOAT 1e9 -#endif - -// support for the long long types (%llu or %p) -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG -#define PRINTF_SUPPORT_LONG_LONG -#endif - -// support for the ptrdiff_t type (%t) -// ptrdiff_t is normally defined in as long or long long type -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T -#define PRINTF_SUPPORT_PTRDIFF_T -#endif - -/////////////////////////////////////////////////////////////////////////////// - -// internal flag definitions -#define FLAGS_ZEROPAD (1U << 0U) -#define FLAGS_LEFT (1U << 1U) -#define FLAGS_PLUS (1U << 2U) -#define FLAGS_SPACE (1U << 3U) -#define FLAGS_HASH (1U << 4U) -#define FLAGS_UPPERCASE (1U << 5U) -#define FLAGS_CHAR (1U << 6U) -#define FLAGS_SHORT (1U << 7U) -#define FLAGS_LONG (1U << 8U) -#define FLAGS_LONG_LONG (1U << 9U) -#define FLAGS_PRECISION (1U << 10U) -#define FLAGS_ADAPT_EXP (1U << 11U) - - -// import float.h for DBL_MAX -#if defined(PRINTF_SUPPORT_FLOAT) -#include -#endif - - -// output function type -typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); - - -// wrapper (used as buffer) for output function type -typedef struct { - void (*fct)(char character, void* arg); - void* arg; -} out_fct_wrap_type; - - -// internal buffer output -static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) -{ - if (idx < maxlen) { - ((char*)buffer)[idx] = character; - } -} - - -// internal null output -static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)character; (void)buffer; (void)idx; (void)maxlen; -} - -// maksw: hack around putchar, hal and kernel -static void (*g_putchar_fn)(char) = 0; - -void printf_set_putchar(void (*fn)(char)) { - g_putchar_fn = fn; -} - -// internal _putchar wrapper -static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)buffer; (void)idx; (void)maxlen; - if (character) { - if (g_putchar_fn) g_putchar_fn(character); - } -} - - -// internal output function wrapper -static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)idx; (void)maxlen; - if (character) { - // buffer is the output fct pointer - ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); - } -} - - -// internal secure strlen -// \return The length of the string (excluding the terminating 0) limited by 'maxsize' -static inline unsigned int _strnlen_s(const char* str, size_t maxsize) -{ - const char* s; - for (s = str; *s && maxsize--; ++s); - return (unsigned int)(s - str); -} - - -// internal test if char is a digit (0-9) -// \return true if char is a digit -static inline bool _is_digit(char ch) -{ - return (ch >= '0') && (ch <= '9'); -} - - -// internal ASCII string to unsigned int conversion -static unsigned int _atoi(const char** str) -{ - unsigned int i = 0U; - while (_is_digit(**str)) { - i = i * 10U + (unsigned int)(*((*str)++) - '0'); - } - return i; -} - - -// output the specified string in reverse, taking care of any zero-padding -static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags) -{ - const size_t start_idx = idx; - - // pad spaces up to given width - if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { - for (size_t i = len; i < width; i++) { - out(' ', buffer, idx++, maxlen); - } - } - - // reverse string - while (len) { - out(buf[--len], buffer, idx++, maxlen); - } - - // append pad spaces up to given width - if (flags & FLAGS_LEFT) { - while (idx - start_idx < width) { - out(' ', buffer, idx++, maxlen); - } - } - - return idx; -} - - -// internal itoa format -static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags) -{ - // pad leading zeros - if (!(flags & FLAGS_LEFT)) { - if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { - width--; - } - while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - } - - // handle hash - if (flags & FLAGS_HASH) { - if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { - len--; - if (len && (base == 16U)) { - len--; - } - } - if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = 'x'; - } - else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = 'X'; - } - else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = 'b'; - } - if (len < PRINTF_NTOA_BUFFER_SIZE) { - buf[len++] = '0'; - } - } - - if (len < PRINTF_NTOA_BUFFER_SIZE) { - if (negative) { - buf[len++] = '-'; - } - else if (flags & FLAGS_PLUS) { - buf[len++] = '+'; // ignore the space if the '+' exists - } - else if (flags & FLAGS_SPACE) { - buf[len++] = ' '; - } - } - - return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); -} - - -// internal itoa for 'long' type -static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) -{ - char buf[PRINTF_NTOA_BUFFER_SIZE]; - size_t len = 0U; - - // no hash for 0 values - if (!value) { - flags &= ~FLAGS_HASH; - } - - // write if precision != 0 and value is != 0 - if (!(flags & FLAGS_PRECISION) || value) { - do { - const char digit = (char)(value % base); - buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; - value /= base; - } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); - } - - return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); -} - - -// internal itoa for 'long long' type -#if defined(PRINTF_SUPPORT_LONG_LONG) -static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) -{ - char buf[PRINTF_NTOA_BUFFER_SIZE]; - size_t len = 0U; - - // no hash for 0 values - if (!value) { - flags &= ~FLAGS_HASH; - } - - // write if precision != 0 and value is != 0 - if (!(flags & FLAGS_PRECISION) || value) { - do { - const char digit = (char)(value % base); - buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; - value /= base; - } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); - } - - return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); -} -#endif // PRINTF_SUPPORT_LONG_LONG - - -#if defined(PRINTF_SUPPORT_FLOAT) - -#if defined(PRINTF_SUPPORT_EXPONENTIAL) -// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT -static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags); -#endif - - -// internal ftoa for fixed decimal floating point -static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) -{ - char buf[PRINTF_FTOA_BUFFER_SIZE]; - size_t len = 0U; - double diff = 0.0; - - // powers of 10 - static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; - - // test for special values - if (value != value) - return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); - if (value < -DBL_MAX) - return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); - if (value > DBL_MAX) - return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); - - // test for very large values - // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad - if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { -#if defined(PRINTF_SUPPORT_EXPONENTIAL) - return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); -#else - return 0U; -#endif - } - - // test for negative - bool negative = false; - if (value < 0) { - negative = true; - value = 0 - value; - } - - // set default precision, if not set explicitly - if (!(flags & FLAGS_PRECISION)) { - prec = PRINTF_DEFAULT_FLOAT_PRECISION; - } - // limit precision to 9, cause a prec >= 10 can lead to overflow errors - while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { - buf[len++] = '0'; - prec--; - } - - int whole = (int)value; - double tmp = (value - whole) * pow10[prec]; - unsigned long frac = (unsigned long)tmp; - diff = tmp - frac; - - if (diff > 0.5) { - ++frac; - // handle rollover, e.g. case 0.99 with prec 1 is 1.0 - if (frac >= pow10[prec]) { - frac = 0; - ++whole; - } - } - else if (diff < 0.5) { - } - else if ((frac == 0U) || (frac & 1U)) { - // if halfway, round up if odd OR if last digit is 0 - ++frac; - } - - if (prec == 0U) { - diff = value - (double)whole; - if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { - // exactly 0.5 and ODD, then round up - // 1.5 -> 2, but 2.5 -> 2 - ++whole; - } - } - else { - unsigned int count = prec; - // now do fractional part, as an unsigned number - while (len < PRINTF_FTOA_BUFFER_SIZE) { - --count; - buf[len++] = (char)(48U + (frac % 10U)); - if (!(frac /= 10U)) { - break; - } - } - // add extra 0s - while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { - buf[len++] = '0'; - } - if (len < PRINTF_FTOA_BUFFER_SIZE) { - // add decimal - buf[len++] = '.'; - } - } - - // do whole part, number is reversed - while (len < PRINTF_FTOA_BUFFER_SIZE) { - buf[len++] = (char)(48 + (whole % 10)); - if (!(whole /= 10)) { - break; - } - } - - // pad leading zeros - if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { - if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { - width--; - } - while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - } - - if (len < PRINTF_FTOA_BUFFER_SIZE) { - if (negative) { - buf[len++] = '-'; - } - else if (flags & FLAGS_PLUS) { - buf[len++] = '+'; // ignore the space if the '+' exists - } - else if (flags & FLAGS_SPACE) { - buf[len++] = ' '; - } - } - - return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); -} - - -#if defined(PRINTF_SUPPORT_EXPONENTIAL) -// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse -static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) -{ - // check for NaN and special values - if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { - return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); - } - - // determine the sign - const bool negative = value < 0; - if (negative) { - value = -value; - } - - // default precision - if (!(flags & FLAGS_PRECISION)) { - prec = PRINTF_DEFAULT_FLOAT_PRECISION; - } - - // determine the decimal exponent - // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) - union { - uint64_t U; - double F; - } conv; - - conv.F = value; - int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 - conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) - // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 - int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); - // now we want to compute 10^expval but we want to be sure it won't overflow - exp2 = (int)(expval * 3.321928094887362 + 0.5); - const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; - const double z2 = z * z; - conv.U = (uint64_t)(exp2 + 1023) << 52U; - // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex - conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); - // correct for rounding errors - if (value < conv.F) { - expval--; - conv.F /= 10; - } - - // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters - unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; - - // in "%g" mode, "prec" is the number of* significant figures* not decimals - if (flags & FLAGS_ADAPT_EXP) { - // do we want to fall-back to "%f" mode? - if ((value >= 1e-4) && (value < 1e6)) { - if ((int)prec > expval) { - prec = (unsigned)((int)prec - expval - 1); - } - else { - prec = 0; - } - flags |= FLAGS_PRECISION; // make sure _ftoa respects precision - // no characters in exponent - minwidth = 0U; - expval = 0; - } - else { - // we use one sigfig for the whole part - if ((prec > 0) && (flags & FLAGS_PRECISION)) { - --prec; - } - } - } - - // will everything fit? - unsigned int fwidth = width; - if (width > minwidth) { - // we didn't fall-back so subtract the characters required for the exponent - fwidth -= minwidth; - } else { - // not enough characters, so go back to default sizing - fwidth = 0U; - } - if ((flags & FLAGS_LEFT) && minwidth) { - // if we're padding on the right, DON'T pad the floating part - fwidth = 0U; - } - - // rescale the float value - if (expval) { - value /= conv.F; - } - - // output the floating part - const size_t start_idx = idx; - idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); - - // output the exponent part - if (minwidth) { - // output the exponential symbol - out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); - // output the exponent value - idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS); - // might need to right-pad spaces - if (flags & FLAGS_LEFT) { - while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); - } - } - return idx; -} -#endif // PRINTF_SUPPORT_EXPONENTIAL -#endif // PRINTF_SUPPORT_FLOAT - - -// internal vsnprintf -static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) -{ - unsigned int flags, width, precision, n; - size_t idx = 0U; - - if (!buffer) { - // use null output function - out = _out_null; - } - - while (*format) - { - // format specifier? %[flags][width][.precision][length] - if (*format != '%') { - // no - out(*format, buffer, idx++, maxlen); - format++; - continue; - } - else { - // yes, evaluate it - format++; - } - - // evaluate flags - flags = 0U; - do { - switch (*format) { - case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; - case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; - case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; - case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; - case '#': flags |= FLAGS_HASH; format++; n = 1U; break; - default : n = 0U; break; - } - } while (n); - - // evaluate width field - width = 0U; - if (_is_digit(*format)) { - width = _atoi(&format); - } - else if (*format == '*') { - const int w = va_arg(va, int); - if (w < 0) { - flags |= FLAGS_LEFT; // reverse padding - width = (unsigned int)-w; - } - else { - width = (unsigned int)w; - } - format++; - } - - // evaluate precision field - precision = 0U; - if (*format == '.') { - flags |= FLAGS_PRECISION; - format++; - if (_is_digit(*format)) { - precision = _atoi(&format); - } - else if (*format == '*') { - const int prec = (int)va_arg(va, int); - precision = prec > 0 ? (unsigned int)prec : 0U; - format++; - } - } - - // evaluate length field - switch (*format) { - case 'l' : - flags |= FLAGS_LONG; - format++; - if (*format == 'l') { - flags |= FLAGS_LONG_LONG; - format++; - } - break; - case 'h' : - flags |= FLAGS_SHORT; - format++; - if (*format == 'h') { - flags |= FLAGS_CHAR; - format++; - } - break; -#if defined(PRINTF_SUPPORT_PTRDIFF_T) - case 't' : - flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; -#endif - case 'j' : - flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; - case 'z' : - flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; - default : - break; - } - - // evaluate specifier - switch (*format) { - case 'd' : - case 'i' : - case 'u' : - case 'x' : - case 'X' : - case 'o' : - case 'b' : { - // set the base - unsigned int base; - if (*format == 'x' || *format == 'X') { - base = 16U; - } - else if (*format == 'o') { - base = 8U; - } - else if (*format == 'b') { - base = 2U; - } - else { - base = 10U; - flags &= ~FLAGS_HASH; // no hash for dec format - } - // uppercase - if (*format == 'X') { - flags |= FLAGS_UPPERCASE; - } - - // no plus or space flag for u, x, X, o, b - if ((*format != 'i') && (*format != 'd')) { - flags &= ~(FLAGS_PLUS | FLAGS_SPACE); - } - - // ignore '0' flag when precision is given - if (flags & FLAGS_PRECISION) { - flags &= ~FLAGS_ZEROPAD; - } - - // convert the integer - if ((*format == 'i') || (*format == 'd')) { - // signed - if (flags & FLAGS_LONG_LONG) { -#if defined(PRINTF_SUPPORT_LONG_LONG) - const long long value = va_arg(va, long long); - idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); -#endif - } - else if (flags & FLAGS_LONG) { - const long value = va_arg(va, long); - idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); - } - else { - const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int); - idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); - } - } - else { - // unsigned - if (flags & FLAGS_LONG_LONG) { -#if defined(PRINTF_SUPPORT_LONG_LONG) - idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); -#endif - } - else if (flags & FLAGS_LONG) { - idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); - } - else { - const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int); - idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); - } - } - format++; - break; - } -#if defined(PRINTF_SUPPORT_FLOAT) - case 'f' : - case 'F' : - if (*format == 'F') flags |= FLAGS_UPPERCASE; - idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); - format++; - break; -#if defined(PRINTF_SUPPORT_EXPONENTIAL) - case 'e': - case 'E': - case 'g': - case 'G': - if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; - if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; - idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); - format++; - break; -#endif // PRINTF_SUPPORT_EXPONENTIAL -#endif // PRINTF_SUPPORT_FLOAT - case 'c' : { - unsigned int l = 1U; - // pre padding - if (!(flags & FLAGS_LEFT)) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - // char output - out((char)va_arg(va, int), buffer, idx++, maxlen); - // post padding - if (flags & FLAGS_LEFT) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - format++; - break; - } - - case 's' : { - const char* p = va_arg(va, char*); - unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); - // pre padding - if (flags & FLAGS_PRECISION) { - l = (l < precision ? l : precision); - } - if (!(flags & FLAGS_LEFT)) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - // string output - while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { - out(*(p++), buffer, idx++, maxlen); - } - // post padding - if (flags & FLAGS_LEFT) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - format++; - break; - } - - case 'p' : { - width = sizeof(void*) * 2U; - flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; -#if defined(PRINTF_SUPPORT_LONG_LONG) - const bool is_ll = sizeof(uintptr_t) == sizeof(long long); - if (is_ll) { - idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags); - } - else { -#endif - idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags); -#if defined(PRINTF_SUPPORT_LONG_LONG) - } -#endif - format++; - break; - } - - case '%' : - out('%', buffer, idx++, maxlen); - format++; - break; - - default : - out(*format, buffer, idx++, maxlen); - format++; - break; - } - } - - // termination - out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); - - // return written chars without terminating \0 - return (int)idx; -} - - -/////////////////////////////////////////////////////////////////////////////// - -int printf_(const char* format, ...) -{ - va_list va; - va_start(va, format); - char buffer[1]; - const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); - va_end(va); - return ret; -} - - -int sprintf_(char* buffer, const char* format, ...) -{ - va_list va; - va_start(va, format); - const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); - va_end(va); - return ret; -} - - -int snprintf_(char* buffer, size_t count, const char* format, ...) -{ - va_list va; - va_start(va, format); - const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); - va_end(va); - return ret; -} - - -int vprintf_(const char* format, va_list va) -{ - char buffer[1]; - return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); -} - - -int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) -{ - return _vsnprintf(_out_buffer, buffer, count, format, va); -} - - -int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) -{ - va_list va; - va_start(va, format); - const out_fct_wrap_type out_fct_wrap = { out, arg }; - const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); - va_end(va); - return ret; -} +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. These routines are thread +// safe and reentrant! +// Use this instead of the bloated standard/newlib printf cause these use +// malloc for printf (and may not be thread safe). +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "printf.h" +#include "syscall/sys_string.h" + + +// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +// default: undefined +#ifdef PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_NTOA_BUFFER_SIZE +#define PRINTF_NTOA_BUFFER_SIZE 32U +#endif + +// 'ftoa' conversion buffer size, this must be big enough to hold one converted +// float number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_FTOA_BUFFER_SIZE +#define PRINTF_FTOA_BUFFER_SIZE 32U +#endif + +// support for the floating point type (%f) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_SUPPORT_FLOAT +#endif + +// support for exponential floating point notation (%e/%g) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// define the default floating point precision +// default: 6 digits +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6U +#endif + +// define the largest float suitable to print with %f +// default: 1e9 +#ifndef PRINTF_MAX_FLOAT +#define PRINTF_MAX_FLOAT 1e9 +#endif + +// support for the long long types (%llu or %p) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG +#endif + +// support for the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T +#define PRINTF_SUPPORT_PTRDIFF_T +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_ADAPT_EXP (1U << 11U) + + +// import float.h for DBL_MAX +#if defined(PRINTF_SUPPORT_FLOAT) +#include +#endif + + +// output function type +typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); + + +// wrapper (used as buffer) for output function type +typedef struct { + void (*fct)(char character, void* arg); + void* arg; +} out_fct_wrap_type; + + +// internal buffer output +static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) +{ + if (idx < maxlen) { + ((char*)buffer)[idx] = character; + } +} + + +// internal null output +static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)character; (void)buffer; (void)idx; (void)maxlen; +} + +// maksw: hack around putchar, hal and kernel +static void (*g_putchar_fn)(char) = 0; + +void printf_set_putchar(void (*fn)(char)) { + g_putchar_fn = fn; +} + +// internal _putchar wrapper +static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)buffer; (void)idx; (void)maxlen; + if (character) { + if (g_putchar_fn) g_putchar_fn(character); + } +} + + +// internal output function wrapper +static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)idx; (void)maxlen; + if (character) { + // buffer is the output fct pointer + ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); + } +} + + +// internal secure strlen +// \return The length of the string (excluding the terminating 0) limited by 'maxsize' +static inline unsigned int _strnlen_s(const char* str, size_t maxsize) +{ + const char* s; + for (s = str; *s && maxsize--; ++s); + return (unsigned int)(s - str); +} + + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + + +// internal ASCII string to unsigned int conversion +static unsigned int _atoi(const char** str) +{ + unsigned int i = 0U; + while (_is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + + +// output the specified string in reverse, taking care of any zero-padding +static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags) +{ + const size_t start_idx = idx; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + while (len) { + out(buf[--len], buffer, idx++, maxlen); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + + +static size_t _out_string_view(out_fct_type out, char* buffer, size_t idx, size_t maxlen, + const char* str, size_t len, + unsigned int precision, unsigned int width, unsigned int flags) +{ + const size_t start_idx = idx; + + if ((flags & FLAGS_PRECISION) && len > precision) { + len = precision; + } + + if (!str && len) { + str = "(null)"; + len = 6U; + if ((flags & FLAGS_PRECISION) && len > precision) { + len = precision; + } + } + + if (!(flags & FLAGS_LEFT)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + for (size_t i = 0; i < len; i++) { + out(str ? str[i] : '\0', buffer, idx++, maxlen); + } + + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + + +// internal itoa format +static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags) +{ + // pad leading zeros + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + // handle hash + if (flags & FLAGS_HASH) { + if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { + len--; + if (len && (base == 16U)) { + len--; + } + } + if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'x'; + } + else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'X'; + } + else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if (len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_NTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +// internal itoa for 'long' type +static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} + + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + + +#if defined(PRINTF_SUPPORT_FLOAT) + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT +static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags); +#endif + + +// internal ftoa for fixed decimal floating point +static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + double diff = 0.0; + + // powers of 10 + static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + + // test for special values + if (value != value) + return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); + if (value < -DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); + if (value > DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + + // test for very large values + // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad + if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); +#else + return 0U; +#endif + } + + // test for negative + bool negative = false; + if (value < 0) { + negative = true; + value = 0 - value; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { + buf[len++] = '0'; + prec--; + } + + int whole = (int)value; + double tmp = (value - whole) * pow10[prec]; + unsigned long frac = (unsigned long)tmp; + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } + else if (diff < 0.5) { + } + else if ((frac == 0U) || (frac & 1U)) { + // if halfway, round up if odd OR if last digit is 0 + ++frac; + } + + if (prec == 0U) { + diff = value - (double)whole; + if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++whole; + } + } + else { + unsigned int count = prec; + // now do fractional part, as an unsigned number + while (len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[len++] = (char)(48U + (frac % 10U)); + if (!(frac /= 10U)) { + break; + } + } + // add extra 0s + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[len++] = '0'; + } + if (len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[len++] = '.'; + } + } + + // do whole part, number is reversed + while (len < PRINTF_FTOA_BUFFER_SIZE) { + buf[len++] = (char)(48 + (whole % 10)); + if (!(whole /= 10)) { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_FTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse +static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) +{ + // check for NaN and special values + if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { + return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); + } + + // determine the sign + const bool negative = value < 0; + if (negative) { + value = -value; + } + + // default precision + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // determine the decimal exponent + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + union { + uint64_t U; + double F; + } conv; + + conv.F = value; + int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 + conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) + // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 + int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); + // now we want to compute 10^expval but we want to be sure it won't overflow + exp2 = (int)(expval * 3.321928094887362 + 0.5); + const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; + const double z2 = z * z; + conv.U = (uint64_t)(exp2 + 1023) << 52U; + // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + // correct for rounding errors + if (value < conv.F) { + expval--; + conv.F /= 10; + } + + // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters + unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; + + // in "%g" mode, "prec" is the number of* significant figures* not decimals + if (flags & FLAGS_ADAPT_EXP) { + // do we want to fall-back to "%f" mode? + if ((value >= 1e-4) && (value < 1e6)) { + if ((int)prec > expval) { + prec = (unsigned)((int)prec - expval - 1); + } + else { + prec = 0; + } + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + // no characters in exponent + minwidth = 0U; + expval = 0; + } + else { + // we use one sigfig for the whole part + if ((prec > 0) && (flags & FLAGS_PRECISION)) { + --prec; + } + } + } + + // will everything fit? + unsigned int fwidth = width; + if (width > minwidth) { + // we didn't fall-back so subtract the characters required for the exponent + fwidth -= minwidth; + } else { + // not enough characters, so go back to default sizing + fwidth = 0U; + } + if ((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 0U; + } + + // rescale the float value + if (expval) { + value /= conv.F; + } + + // output the floating part + const size_t start_idx = idx; + idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); + + // output the exponent part + if (minwidth) { + // output the exponential symbol + out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); + // output the exponent value + idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS); + // might need to right-pad spaces + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); + } + } + return idx; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + + +// internal vsnprintf +static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) +{ + unsigned int flags, width, precision, n; + size_t idx = 0U; + + if (!buffer) { + // use null output function + out = _out_null; + } + + while (*format) + { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') { + // no + out(*format, buffer, idx++, maxlen); + format++; + continue; + } + else { + // yes, evaluate it + format++; + } + + // evaluate flags + flags = 0U; + do { + switch (*format) { + case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; + case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; + case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; + case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; + case '#': flags |= FLAGS_HASH; format++; n = 1U; break; + default : n = 0U; break; + } + } while (n); + + // evaluate width field + width = 0U; + if (_is_digit(*format)) { + width = _atoi(&format); + } + else if (*format == '*') { + const int w = va_arg(va, int); + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (unsigned int)-w; + } + else { + width = (unsigned int)w; + } + format++; + } + + // evaluate precision field + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if (_is_digit(*format)) { + precision = _atoi(&format); + } + else if (*format == '*') { + const int prec = (int)va_arg(va, int); + precision = prec > 0 ? (unsigned int)prec : 0U; + format++; + } + } + + // evaluate length field + switch (*format) { + case 'l' : + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h' : + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't' : + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j' : + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z' : + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default : + break; + } + + // evaluate specifier + switch (*format) { + case 'd' : + case 'i' : + case 'u' : + case 'x' : + case 'X' : + case 'o' : + case 'b' : { + // set the base + unsigned int base; + if (*format == 'x' || *format == 'X') { + base = 16U; + } + else if (*format == 'o') { + base = 8U; + } + else if (*format == 'b') { + base = 2U; + } + else { + base = 10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + // convert the integer + if ((*format == 'i') || (*format == 'd')) { + // signed + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + const long long value = va_arg(va, long long); + idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + const long value = va_arg(va, long); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + else { + const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + } + else { + // unsigned + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); + } + else { + const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int); + idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f' : + case 'F' : + if (*format == 'F') flags |= FLAGS_UPPERCASE; + idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; + idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + case 'c' : { + unsigned int l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // char output + out((char)va_arg(va, int), buffer, idx++, maxlen); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 's' : { + const char* p = va_arg(va, const char*); + if (!p) { + p = "(null)"; + } + size_t max = (flags & FLAGS_PRECISION) ? (size_t)precision : (size_t)-1; + size_t l = _strnlen_s(p, max); + idx = _out_string_view(out, buffer, idx, maxlen, p, l, precision, width, flags); + format++; + break; + } + + case 'v' : { + SYS_STRING_VIEW sv = va_arg(va, SYS_STRING_VIEW); + idx = _out_string_view(out, buffer, idx, maxlen, sv.Data, sv.Length, precision, width, flags); + format++; + break; + } + + case 'p' : { + width = sizeof(void*) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + if (is_ll) { + idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags); + } + else { +#endif + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags); +#if defined(PRINTF_SUPPORT_LONG_LONG) + } +#endif + format++; + break; + } + + case '%' : + out('%', buffer, idx++, maxlen); + format++; + break; + + default : + out(*format, buffer, idx++, maxlen); + format++; + break; + } + } + + // termination + out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); + + // return written chars without terminating \0 + return (int)idx; +} + + +/////////////////////////////////////////////////////////////////////////////// + +int printf_(const char* format, ...) +{ + va_list va; + va_start(va, format); + char buffer[1]; + const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + + +int sprintf_(char* buffer, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + + +int snprintf_(char* buffer, size_t count, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); + va_end(va); + return ret; +} + + +int vprintf_(const char* format, va_list va) +{ + char buffer[1]; + return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); +} + + +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) +{ + return _vsnprintf(_out_buffer, buffer, count, format, va); +} + + +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) +{ + va_list va; + va_start(va, format); + const out_fct_wrap_type out_fct_wrap = { out, arg }; + const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); + va_end(va); + return ret; +} diff --git a/code/resolve/resolve.c b/code/resolve/resolve.c index 91d51a6..af4c81e 100644 --- a/code/resolve/resolve.c +++ b/code/resolve/resolve.c @@ -4,11 +4,12 @@ #include static void print(const char* s) { - if (s) SysWriteConsole(s, strlen(s)); + if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } } static void print_char(char c) { - SysWriteConsole(&c, 1); + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); } static void print_u64(uint64_t v) { @@ -43,7 +44,7 @@ static void print_ipv4(const uint8_t ip[4]) { int main(void) { char name[256]; - const char* cmd = SysGetPeb()->CommandLine; + const char* cmd = SysGetPeb()->CommandLine.Data; cmd = next_token(cmd, name, sizeof(name)); // program path (void)cmd; @@ -60,8 +61,7 @@ int main(void) { SYS_RESOLVE_HOST_REQUEST req; memset(&addr, 0, sizeof(addr)); memset(&req, 0, sizeof(req)); - req.Name = name; - req.NameLength = strlen(name); + req.Name = SysSvFromCstr(name); req.Family = SYS_AF_INET; req.AddressOut = &addr; req.AddressSizeInOut = &addrSize; diff --git a/code/shell_native/shell_native.c b/code/shell_native/shell_native.c index 20b07df..8866472 100644 --- a/code/shell_native/shell_native.c +++ b/code/shell_native/shell_native.c @@ -10,7 +10,37 @@ // --------------------------------------------------------------------------- // Output helpers // --------------------------------------------------------------------------- -static void print(const char* s) { SysWriteConsole(s, strlen(s)); } +static void print(const char* s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } + +static uint32_t open_file(const char* path, uint32_t* handleOut) { + SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; + return SysOpenFile(&req); +} + +static uint32_t create_file(const char* path, uint32_t* handleOut) { + SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; + return SysCreateFile(&req); +} + +static uint32_t open_dir(const char* path, uint32_t* handleOut) { + SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; + return SysOpenDir(&req); +} + +static uint32_t set_cwd(const char* path) { + SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; + return SysSetCwd(&req); +} + +static uint32_t delete_file(const char* path) { + SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; + return SysDeleteFile(&req); +} + +static uint32_t create_process(const char* path, uint32_t* handleOut, const char* cmdline) { + SYS_CREATE_PROCESS_REQUEST req = { SysSvFromCstr(path), SysSvFromCstr(cmdline), handleOut }; + return SysCreateProcess(&req); +} static void printUInt(uint32_t n) { char buf[12]; int i = 0; @@ -101,7 +131,7 @@ static void cmd_write(const char* args) { filename[i] = '\0'; if (args[i] == '\0' || args[i+1] == '\0') { print("write: usage: write \n"); return; } uint32_t hFile; - if (SysCreateFile(filename, &hFile) != 0) { print("write: could not create file\n"); return; } + if (create_file(filename, &hFile) != 0) { print("write: could not create file\n"); return; } size_t written; SysWriteFile(hFile, args + i + 1, strlen(args + i + 1), &written); SysCloseHandle(hFile); @@ -112,7 +142,7 @@ static void cmd_dir(const char* path) { uint32_t hDir; const char* target = (path && *path != '\0') ? path : "."; - if (SysOpenDir(target, &hDir) != 0) { + if (open_dir(target, &hDir) != 0) { print("dir: failed to open directory\n"); return; } @@ -142,7 +172,7 @@ static void cmd_dir(const char* path) { static void cmd_type(const char* filename) { uint32_t hFile; - if (SysOpenFile(filename, &hFile) != 0) { print("type: cannot open file\n"); return; } + if (open_file(filename, &hFile) != 0) { print("type: cannot open file\n"); return; } char buffer[512]; size_t bytesRead; while (SysReadFile(hFile, buffer, sizeof(buffer) - 1, &bytesRead) == 0 && bytesRead > 0) { buffer[bytesRead] = '\0'; print(buffer); @@ -183,8 +213,8 @@ static int runCommand(const char* cmd) { if (strcmp(cmd, "mem") == 0) { cmd_mem(); return 0; } if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; } if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; } - if (strncmp(cmd, "cd ", 3) == 0) { if (SysSetCwd(cmd + 3) != 0) print("cd: failed\n"); return 0; } - if (strncmp(cmd, "del ", 4) == 0) { SysDeleteFile(cmd + 4); return 0; } + if (strncmp(cmd, "cd ", 3) == 0) { if (set_cwd(cmd + 3) != 0) print("cd: failed\n"); return 0; } + if (strncmp(cmd, "del ", 4) == 0) { delete_file(cmd + 4); return 0; } if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; } if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; } @@ -192,7 +222,7 @@ static int runCommand(const char* cmd) { if (!extractProgramPath(cmd, program, sizeof(program))) return 0; uint32_t hProc; - if (SysCreateProcess(program, &hProc, cmd) == 0) { + if (create_process(program, &hProc, cmd) == 0) { SysWaitForProcess(hProc); SysCloseHandle(hProc); while (SysKbPoll()) (void)SysKbReadChar(); @@ -201,7 +231,7 @@ static int runCommand(const char* cmd) { } int main(void) { - const char* cmdline = SysGetPeb()->CommandLine; + const char* cmdline = SysGetPeb()->CommandLine.Data; char lineBuf[MAX_CMD]; print("shell: command line: "); diff --git a/code/shell_posix/shell_posix.c b/code/shell_posix/shell_posix.c index 576e807..f73a1b2 100644 --- a/code/shell_posix/shell_posix.c +++ b/code/shell_posix/shell_posix.c @@ -165,7 +165,7 @@ static int runCommand(const char* cmd) { int main(void) { PosixFdInit(); - const char* cmdline = SysGetPeb()->CommandLine; + const char* cmdline = SysGetPeb()->CommandLine.Data; char lineBuf[MAX_CMD]; print("shell: command line: "); diff --git a/code/syscall/sys_string.h b/code/syscall/sys_string.h new file mode 100644 index 0000000..d9cdf9c --- /dev/null +++ b/code/syscall/sys_string.h @@ -0,0 +1,112 @@ +#pragma once +// sys_string.h - shared native string-view ABI. +// A SYS_STRING_VIEW is a borrowed byte span. It is not NUL-terminated by +// contract and it does not own the pointed-to storage. + +#include +#include +#include + +#ifndef SYS_STRING_VIEW_DEFINED +#define SYS_STRING_VIEW_DEFINED 1 +typedef struct SYS_STRING_VIEW { + const char* Data; + size_t Length; +} SYS_STRING_VIEW; +#endif + +#define SYS_SV_LITERAL(s) ((SYS_STRING_VIEW){ (s), sizeof(s) - 1 }) +#define SYS_SV_NULL ((SYS_STRING_VIEW){ (const char*)0, 0 }) + +static inline SYS_STRING_VIEW SysSvFromParts(const char* data, size_t length) { + SYS_STRING_VIEW v; + v.Data = data; + v.Length = length; + return v; +} + +static inline SYS_STRING_VIEW SysSvFromCstr(const char* s) { + SYS_STRING_VIEW v; + v.Data = s; + v.Length = s ? strlen(s) : 0; + return v; +} + +static inline int SysSvIsEmpty(SYS_STRING_VIEW s) { + return s.Length == 0; +} + +static inline int SysSvIsValid(SYS_STRING_VIEW s) { + return s.Data != (const char*)0 || s.Length == 0; +} + +static inline int SysSvEqual(SYS_STRING_VIEW a, SYS_STRING_VIEW b) { + if (a.Length != b.Length) return 0; + if (a.Length == 0) return 1; + if (!a.Data || !b.Data) return 0; + return memcmp(a.Data, b.Data, a.Length) == 0; +} + +static inline char SysAsciiLower(char c) { + return (c >= 'A' && c <= 'Z') ? (char)(c + ('a' - 'A')) : c; +} + +static inline int SysSvEqualAsciiI(SYS_STRING_VIEW a, SYS_STRING_VIEW b) { + if (a.Length != b.Length) return 0; + if (a.Length == 0) return 1; + if (!a.Data || !b.Data) return 0; + for (size_t i = 0; i < a.Length; i++) { + if (SysAsciiLower(a.Data[i]) != SysAsciiLower(b.Data[i])) return 0; + } + return 1; +} + +static inline int SysSvEqualLit(SYS_STRING_VIEW a, const char* lit) { + return SysSvEqual(a, SysSvFromCstr(lit)); +} + +static inline int SysSvEqualLitAsciiI(SYS_STRING_VIEW a, const char* lit) { + return SysSvEqualAsciiI(a, SysSvFromCstr(lit)); +} + +static inline int SysSvContainsByte(SYS_STRING_VIEW s, char ch) { + if (!s.Data && s.Length) return 0; + for (size_t i = 0; i < s.Length; i++) { + if (s.Data[i] == ch) return 1; + } + return 0; +} + +static inline SYS_STRING_VIEW SysSvSlice(SYS_STRING_VIEW s, size_t off, size_t len) { + if (off > s.Length) off = s.Length; + if (len > s.Length - off) len = s.Length - off; + return SysSvFromParts(s.Data ? s.Data + off : (const char*)0, len); +} + +static inline SYS_STRING_VIEW SysSvTrimAscii(SYS_STRING_VIEW s) { + size_t begin = 0; + size_t end = s.Length; + while (begin < end) { + char c = s.Data[begin]; + if (c != ' ' && c != '\t' && c != '\r' && c != '\n') break; + begin++; + } + while (end > begin) { + char c = s.Data[end - 1]; + if (c != ' ' && c != '\t' && c != '\r' && c != '\n') break; + end--; + } + return SysSvSlice(s, begin, end - begin); +} + +static inline int SysSvCopyZ(char* dst, size_t dstSize, SYS_STRING_VIEW src) { + if (!dst || dstSize == 0) return 0; + if (src.Length && !src.Data) return 0; + if (src.Length + 1 > dstSize) { + dst[0] = '\0'; + return 0; + } + if (src.Length) memcpy(dst, src.Data, src.Length); + dst[src.Length] = '\0'; + return 1; +} diff --git a/code/syscall/syscall.h b/code/syscall/syscall.h index 8554625..68d79f8 100644 --- a/code/syscall/syscall.h +++ b/code/syscall/syscall.h @@ -5,6 +5,7 @@ #include #include #include +#include "sys_string.h" #define SYS_STATUS_SUCCESS 0u #define SYS_STATUS_TIMEOUT 3u @@ -25,19 +26,17 @@ #define SYS_ENV_VERSION 1u #define SYS_CWD_MAX 520u +#ifndef SYS_USER_ENV_TYPES_DEFINED +#define SYS_USER_ENV_TYPES_DEFINED 1 + typedef struct SYS_PEB { uint32_t Magic; uint32_t Version; uint32_t ProcessId; - const char* ImagePath; - size_t ImagePathLength; - - const char* CommandLine; - size_t CommandLineLength; - - const char* CurrentDirectory; - size_t CurrentDirectoryLength; + SYS_STRING_VIEW ImagePath; + SYS_STRING_VIEW CommandLine; + SYS_STRING_VIEW CurrentDirectory; } SYS_PEB; typedef struct SYS_TEB { @@ -47,8 +46,15 @@ typedef struct SYS_TEB { struct SYS_TEB* Self; uint32_t ThreadId; uint32_t Magic; + + // Native per-thread extension pointer. The kernel leaves this NULL; + // user-mode personalities/libraries may hang their own thread-local + // state here without baking subsystem semantics into SYS_TEB. + void* SubsystemData; } SYS_TEB; +#endif // SYS_USER_ENV_TYPES_DEFINED + static inline SYS_PEB* SysGetPeb(void) { return (SYS_PEB*)(uintptr_t)__readgsqword(0); } @@ -124,12 +130,64 @@ typedef struct SYS_TRAP_FRAME_INFO { uint64_t R11; } SYS_TRAP_FRAME_INFO; +// --------------------------------------------------------------------------- +// String-view based syscall request structures +// --------------------------------------------------------------------------- +typedef struct SYS_CREATE_PROCESS_REQUEST { + SYS_STRING_VIEW Path; + SYS_STRING_VIEW CommandLine; + uint32_t* HandleOut; +} SYS_CREATE_PROCESS_REQUEST; + +typedef struct SYS_PATH_HANDLE_REQUEST { + SYS_STRING_VIEW Path; + uint32_t* HandleOut; +} SYS_PATH_HANDLE_REQUEST; + +typedef struct SYS_PATH_REQUEST { + SYS_STRING_VIEW Path; +} SYS_PATH_REQUEST; + +typedef struct SYS_RENAME_FILE_REQUEST { + SYS_STRING_VIEW OldPath; + SYS_STRING_VIEW NewPath; +} SYS_RENAME_FILE_REQUEST; + +typedef struct SYS_GET_CWD_REQUEST { + char* Buffer; + size_t Capacity; + size_t* LengthOut; +} SYS_GET_CWD_REQUEST; + +typedef struct SYS_WRITE_CONSOLE_REQUEST { + SYS_STRING_VIEW Text; +} SYS_WRITE_CONSOLE_REQUEST; + +typedef struct SYS_DEBUG_PRINT_REQUEST { + SYS_STRING_VIEW Text; +} SYS_DEBUG_PRINT_REQUEST; + +typedef struct SYS_PUBLISH_QUEUE_REQUEST { + SYS_STRING_VIEW Name; + uint32_t QueueHandle; +} SYS_PUBLISH_QUEUE_REQUEST; + +typedef struct SYS_OPEN_NAMED_QUEUE_REQUEST { + SYS_STRING_VIEW Name; + uint32_t* HandleOut; +} SYS_OPEN_NAMED_QUEUE_REQUEST; + +typedef struct SYS_NAMED_QUEUE_OWNER_REQUEST { + SYS_STRING_VIEW Name; + uint32_t* PidOut; +} SYS_NAMED_QUEUE_OWNER_REQUEST; + // --------------------------------------------------------------------------- // Process & thread lifecycle (0-14) // --------------------------------------------------------------------------- void SysExitProcess(int exitCode); void SysExitThread(int exitCode); -uint32_t SysCreateProcess(const char* path, uint32_t* handleOut, const char* cmdline); +uint32_t SysCreateProcess(const SYS_CREATE_PROCESS_REQUEST* request); uint32_t SysWaitForProcess(uint32_t handle); uint32_t SysGetProcessExitCode(uint32_t handle, int* exitCodeOut); uint32_t SysKillProcess(uint32_t pid, int exitCode); @@ -178,16 +236,16 @@ uint32_t SysEventWait(uint32_t handle, uint32_t alertable); uint32_t SysQueueCreate(uint32_t* handleOut); uint32_t SysQueueSend(uint32_t handle, const void* msg, bool blocking); uint32_t SysQueueRecv(uint32_t handle, void* msg, bool blocking); -uint32_t SysPublishQueue(const char* name, uint32_t handle); -uint32_t SysOpenNamedQueue(const char* name, uint32_t* handleOut); +uint32_t SysPublishQueue(const SYS_PUBLISH_QUEUE_REQUEST* request); +uint32_t SysOpenNamedQueue(const SYS_OPEN_NAMED_QUEUE_REQUEST* request); uint32_t SysQueueDuplicate(uint32_t handle, uint32_t targetPid, uint32_t* handleOut); -uint32_t SysNamedQueueOwnerPid(const char* name, uint32_t* pidOut); +uint32_t SysNamedQueueOwnerPid(const SYS_NAMED_QUEUE_OWNER_REQUEST* request); // --------------------------------------------------------------------------- // Files, directories, and current directory (39-56) // --------------------------------------------------------------------------- -uint32_t SysOpenFile(const char* path, uint32_t* handleOut); -uint32_t SysCreateFile(const char* path, uint32_t* handleOut); +uint32_t SysOpenFile(const SYS_PATH_HANDLE_REQUEST* request); +uint32_t SysCreateFile(const SYS_PATH_HANDLE_REQUEST* request); uint32_t SysReadFile(uint32_t handle, void* buf, size_t size, size_t* bytesRead); uint32_t SysWriteFile(uint32_t handle, const void* buf, size_t size, size_t* written); uint32_t SysReadFileAsync(const SYS_ASYNC_FILE_REQUEST* request); @@ -197,20 +255,20 @@ uint32_t SysSeekFile(uint32_t handle, uint64_t offset); uint64_t SysGetFilePos(uint32_t handle); uint64_t SysGetFileSize(uint32_t handle); uint32_t SysTruncateFile(uint32_t handle, uint64_t newSize); -uint32_t SysDeleteFile(const char* path); -uint32_t SysRenameFile(const char* oldPath, const char* newPath); +uint32_t SysDeleteFile(const SYS_PATH_REQUEST* request); +uint32_t SysRenameFile(const SYS_RENAME_FILE_REQUEST* request); -uint32_t SysOpenDir(const char* path, uint32_t* handleOut); +uint32_t SysOpenDir(const SYS_PATH_HANDLE_REQUEST* request); uint32_t SysReadDir(uint32_t handle, void* dirEntryBuffer); -uint32_t SysCreateDir(const char* path); -uint32_t SysRemoveDir(const char* path); -uint32_t SysSetCwd(const char* path); -uint32_t SysGetCwd(char* buf, size_t size); +uint32_t SysCreateDir(const SYS_PATH_REQUEST* request); +uint32_t SysRemoveDir(const SYS_PATH_REQUEST* request); +uint32_t SysSetCwd(const SYS_PATH_REQUEST* request); +uint32_t SysGetCwd(const SYS_GET_CWD_REQUEST* request); // --------------------------------------------------------------------------- // Console and graphics (57-61) // --------------------------------------------------------------------------- -uint32_t SysWriteConsole(const char* buf, size_t len); +uint32_t SysWriteConsole(const SYS_WRITE_CONSOLE_REQUEST* request); uint32_t SysScreenBlit(int x, int y, int w, int h, const uint32_t* pixels); uint32_t SysScreenClear(uint32_t color); uint32_t SysScreenGetSize(int* widthOut, int* heightOut); @@ -286,8 +344,7 @@ typedef struct SYS_SOCKET_RECV_FROM_REQUEST { typedef struct SYS_RESOLVE_HOST_REQUEST { - const char* Name; - size_t NameLength; + SYS_STRING_VIEW Name; uint32_t Family; uint32_t Flags; SYS_SOCKET_ADDRESS_IPV4* AddressOut; @@ -333,7 +390,7 @@ typedef struct SYS_MEMORY_STATUS { // Time and diagnostics // --------------------------------------------------------------------------- uint64_t SysGetTick(void); -uint32_t SysDebugPrint(const char* str); +uint32_t SysDebugPrint(const SYS_DEBUG_PRINT_REQUEST* request); uint32_t SysQueryTrapFrame(SYS_TRAP_FRAME_INFO* info); uint32_t SysGetMemoryStatus(SYS_MEMORY_STATUS* statusOut); diff --git a/code/tcpserve/tcpserve.c b/code/tcpserve/tcpserve.c index 53278ca..9bbc86e 100644 --- a/code/tcpserve/tcpserve.c +++ b/code/tcpserve/tcpserve.c @@ -5,11 +5,12 @@ #include static void print(const char* s) { - if (s) SysWriteConsole(s, strlen(s)); + if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } } static void print_char(char c) { - SysWriteConsole(&c, 1); + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); } static void print_u64(uint64_t v) { @@ -63,7 +64,7 @@ static void usage(void) { int main(void) { uint16_t port = 8080; - const char* cmd = SysGetPeb()->CommandLine; + const char* cmd = SysGetPeb()->CommandLine.Data; char tok[64]; cmd = next_token(cmd, tok, sizeof(tok)); // program path if (cmd) { diff --git a/code/udp/udp.c b/code/udp/udp.c index 1def0da..cb96660 100644 --- a/code/udp/udp.c +++ b/code/udp/udp.c @@ -5,11 +5,12 @@ #include static void print(const char* s) { - if (s) SysWriteConsole(s, strlen(s)); + if (s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); } } static void print_char(char c) { - SysWriteConsole(&c, 1); + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) }; + SysWriteConsole(&req); } static void print_u64(uint64_t v) { @@ -211,7 +212,7 @@ static int run_listen(const char* cmd) { } int main(void) { - const char* cmd = SysGetPeb()->CommandLine; + const char* cmd = SysGetPeb()->CommandLine.Data; char tok[64]; cmd = next_token(cmd, tok, sizeof(tok)); // program path cmd = next_token(cmd, tok, sizeof(tok)); // verb diff --git a/code/win32/kernel32.c b/code/win32/kernel32.c index d30ac9e..5683ab9 100644 --- a/code/win32/kernel32.c +++ b/code/win32/kernel32.c @@ -11,8 +11,8 @@ // - HeapAlloc / HeapFree / HeapReAlloc ignore the HANDLE heap argument and // always use the single kernel user-heap. This is fine: we don't expose // multiple heaps, and NULL is a legal (no-op) heap argument under Win32. -// - GetLastError / SetLastError are per-thread on Windows. We keep a single -// global for now; enough for Bink and similar single-threaded guests. +// - Win32 per-thread state lives behind SYS_TEB::SubsystemData so the native +// TEB ABI stays subsystem-neutral. // - File I/O uses SYS_OPEN_FILE / SYS_READ_FILE / SYS_SEEK_FILE / // SYS_GET_FILE_SIZE / SYS_CLOSE_HANDLE (unified close path - no separate // SysCloseFile). @@ -160,14 +160,94 @@ static void TranslatePath(const char* input, char* output) { } } -static DWORD gLastError = 0; + +static uint32_t K32OpenFileNative(const char* path, uint32_t* handleOut) { + SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; + return SysOpenFile(&req); +} + +static uint32_t K32CreateFileNative(const char* path, uint32_t* handleOut) { + SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; + return SysCreateFile(&req); +} + +static uint32_t K32DeleteFileNative(const char* path) { + SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; + return SysDeleteFile(&req); +} + +static uint32_t K32OpenDirNative(const char* path, uint32_t* handleOut) { + SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut }; + return SysOpenDir(&req); +} + +static uint32_t K32SetCwdNative(const char* path) { + SYS_PATH_REQUEST req = { SysSvFromCstr(path) }; + return SysSetCwd(&req); +} + +static uint32_t K32GetCwdNative(char* buffer, size_t capacity, size_t* lengthOut) { + size_t len = 0; + size_t writable = (buffer && capacity) ? capacity - 1 : 0; + SYS_GET_CWD_REQUEST req = { buffer, writable, &len }; + uint32_t s = SysGetCwd(&req); + if (lengthOut) *lengthOut = len; + if (s == 0 && buffer && capacity) buffer[len] = '\0'; + return s; +} + +static uint32_t K32CreateProcessNative(const char* path, uint32_t* handleOut, const char* commandLine) { + SYS_CREATE_PROCESS_REQUEST req = { SysSvFromCstr(path), SysSvFromCstr(commandLine), handleOut }; + return SysCreateProcess(&req); +} + +static void K32WriteConsoleSpan(const char* data, size_t length) { + SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(data, length) }; + SysWriteConsole(&req); +} + +static void K32DebugPrintA(const char* s) { + if (!s) return; + SYS_DEBUG_PRINT_REQUEST req = { SysSvFromCstr(s) }; + SysDebugPrint(&req); +} + +#define WIN32_TLS_SLOTS 64 + +typedef struct WIN32_THREAD_ENV { + DWORD LastError; + DWORD Reserved0; + void* TlsSlots[WIN32_TLS_SLOTS]; +} WIN32_THREAD_ENV; + +static WIN32_THREAD_ENV gFallbackThreadEnv; + +static WIN32_THREAD_ENV* K32ThreadEnv(void) { + SYS_TEB* teb = SysGetTeb(); + if (!teb || teb->Magic != SYS_TEB_MAGIC) return &gFallbackThreadEnv; + + WIN32_THREAD_ENV* env = (WIN32_THREAD_ENV*)teb->SubsystemData; + if (!env) { + env = (WIN32_THREAD_ENV*)SysAlloc(sizeof(WIN32_THREAD_ENV)); + if (!env) return &gFallbackThreadEnv; + memset(env, 0, sizeof(*env)); + teb->SubsystemData = env; + } + return env; +} + +static DWORD* K32LastErrorSlot(void) { + return &K32ThreadEnv()->LastError; +} + +#define gLastError (*K32LastErrorSlot()) static BOOL KiFileExistsA(const char* path) { if (!path || !*path) return FALSE; char native[MAX_PATH]; TranslatePath(path, native); uint32_t h = 0; - if (SysOpenFile(native, &h) != 0) return FALSE; + if (K32OpenFileNative(native, &h) != 0) return FALSE; SysCloseHandle(h); return TRUE; } @@ -268,7 +348,7 @@ __declspec(dllexport) _Noreturn void ExitProcess(unsigned int code) { // Debug output // --------------------------------------------------------------------------- __declspec(dllexport) void OutputDebugStringA(const char* str) { - if (str) SysDebugPrint(str); + if (str) K32DebugPrintA(str); } // --------------------------------------------------------------------------- @@ -510,7 +590,7 @@ __declspec(dllexport) HANDLE CreateFileA(const char* name, if (disposition == OPEN_EXISTING || disposition == TRUNCATE_EXISTING) { // Open the file via the read path (it must already exist). uint32_t kh = 0; - uint32_t s = SysOpenFile(translatedName, &kh); + uint32_t s = K32OpenFileNative(translatedName, &kh); if (s != 0) { gLastError = 2; return INVALID_HANDLE_VALUE; } if (disposition == TRUNCATE_EXISTING && wantWrite) { @@ -529,10 +609,10 @@ __declspec(dllexport) HANDLE CreateFileA(const char* name, // Create-or-replace: try to create; if the file already exists, // open it and truncate it to zero instead. uint32_t kh = 0; - uint32_t s = SysCreateFile(translatedName, &kh); + uint32_t s = K32CreateFileNative(translatedName, &kh); if (s != 0) { // File may already exist - open and truncate. - s = SysOpenFile(translatedName, &kh); + s = K32OpenFileNative(translatedName, &kh); if (s != 0) { gLastError = 5; return INVALID_HANDLE_VALUE; } SysTruncateFile(kh, 0); } @@ -542,8 +622,8 @@ __declspec(dllexport) HANDLE CreateFileA(const char* name, if (disposition == OPEN_ALWAYS && wantWrite) { // Open if exists, create if not. uint32_t kh = 0; - if (SysOpenFile(translatedName, &kh) != 0) { - if (SysCreateFile(translatedName, &kh) != 0) { + if (K32OpenFileNative(translatedName, &kh) != 0) { + if (K32CreateFileNative(translatedName, &kh) != 0) { gLastError = 5; return INVALID_HANDLE_VALUE; } @@ -555,7 +635,7 @@ __declspec(dllexport) HANDLE CreateFileA(const char* name, // Fail if the file already exists (we can't check atomically, // but SysCreateFile returns NAME_COLLISION for existing files). uint32_t kh = 0; - uint32_t s = SysCreateFile(translatedName, &kh); + uint32_t s = K32CreateFileNative(translatedName, &kh); if (s != 0) { gLastError = 80; return INVALID_HANDLE_VALUE; } // ERROR_FILE_EXISTS return KH_TO_HANDLE(kh); } @@ -677,7 +757,7 @@ __declspec(dllexport) BOOL DeleteFileA(const char* lpFileName) { char translatedPath[MAX_PATH]; TranslatePath(lpFileName, translatedPath); - uint32_t s = SysDeleteFile(translatedPath); + uint32_t s = K32DeleteFileNative(translatedPath); if (s != 0) { gLastError = 2; return FALSE; } return TRUE; } @@ -747,15 +827,13 @@ __declspec(dllexport) HANDLE GetCurrentThread(void) { __declspec(dllexport) DWORD GetCurrentThreadId(void) { return (DWORD)SysGetCurrentTid(); } __declspec(dllexport) DWORD GetCurrentProcessId(void) { return (DWORD)SysGetCurrentPid(); } -// Tiny TLS table; enough for MSVCRT/TCC compatibility paths that probe TLS. -#define WIN32_TLS_SLOTS 64 -static void* gTlsSlots[WIN32_TLS_SLOTS]; +// Tiny TLS index allocator; slot values are stored per thread in WIN32_THREAD_ENV. static uint64_t gTlsBitmap = 1; // reserve slot 0 __declspec(dllexport) DWORD TlsAlloc(void) { for (DWORD i = 1; i < WIN32_TLS_SLOTS; i++) { uint64_t bit = 1ULL << i; - if (!(gTlsBitmap & bit)) { gTlsBitmap |= bit; gTlsSlots[i] = NULL; return i; } + if (!(gTlsBitmap & bit)) { gTlsBitmap |= bit; return i; } } gLastError = 8; return 0xFFFFFFFFUL; @@ -763,20 +841,23 @@ __declspec(dllexport) DWORD TlsAlloc(void) { __declspec(dllexport) BOOL TlsFree(DWORD dwTlsIndex) { if (dwTlsIndex >= WIN32_TLS_SLOTS || !(gTlsBitmap & (1ULL << dwTlsIndex))) { gLastError = 87; return FALSE; } - gTlsSlots[dwTlsIndex] = NULL; + WIN32_THREAD_ENV* env = K32ThreadEnv(); + if (env) env->TlsSlots[dwTlsIndex] = NULL; gTlsBitmap &= ~(1ULL << dwTlsIndex); return TRUE; } __declspec(dllexport) void* TlsGetValue(DWORD dwTlsIndex) { if (dwTlsIndex >= WIN32_TLS_SLOTS || !(gTlsBitmap & (1ULL << dwTlsIndex))) { gLastError = 87; return NULL; } + WIN32_THREAD_ENV* env = K32ThreadEnv(); gLastError = 0; - return gTlsSlots[dwTlsIndex]; + return env->TlsSlots[dwTlsIndex]; } __declspec(dllexport) BOOL TlsSetValue(DWORD dwTlsIndex, void* lpTlsValue) { if (dwTlsIndex >= WIN32_TLS_SLOTS || !(gTlsBitmap & (1ULL << dwTlsIndex))) { gLastError = 87; return FALSE; } - gTlsSlots[dwTlsIndex] = lpTlsValue; + WIN32_THREAD_ENV* env = K32ThreadEnv(); + env->TlsSlots[dwTlsIndex] = lpTlsValue; return TRUE; } @@ -797,7 +878,7 @@ static BOOL KiIsWin32DllNameA(const char* name) { } static void KiDbgWriteA(const char* s) { - if (s) SysWriteConsole(s, strlen(s)); + if (s) K32WriteConsoleSpan(s, strlen(s)); } __declspec(dllexport) HANDLE GetModuleHandleA(const char* lpModuleName) { @@ -856,8 +937,8 @@ __declspec(dllexport) DWORD GetModuleFileNameA(HANDLE hModule, if (!lpFilename || !nSize) return 0; SYS_PEB* peb = Win32Peb(); - const char* src = (peb && peb->Magic == SYS_PEB_MAGIC && peb->ImagePath) - ? peb->ImagePath + const char* src = (peb && peb->Magic == SYS_PEB_MAGIC && peb->ImagePath.Data) + ? peb->ImagePath.Data : ""; DWORD len = (DWORD)strlen(src); @@ -898,8 +979,8 @@ __declspec(dllexport) void GetStartupInfoA(STARTUPINFOA* lpStartupInfo) { __declspec(dllexport) char* GetCommandLineA(void) { SYS_PEB* peb = Win32Peb(); - if (!peb || peb->Magic != SYS_PEB_MAGIC || !peb->CommandLine) return ""; - return (char*)peb->CommandLine; + if (!peb || peb->Magic != SYS_PEB_MAGIC || !peb->CommandLine.Data) return ""; + return (char*)peb->CommandLine.Data; } // --------------------------------------------------------------------------- @@ -1025,11 +1106,11 @@ __declspec(dllexport) BOOL CreateProcessA( if (lpCurrentDirectory && lpCurrentDirectory[0]) { char translatedCwd[MAX_PATH]; - uint32_t gs = SysGetCwd(savedCwd, sizeof(savedCwd)); + uint32_t gs = K32GetCwdNative(savedCwd, sizeof(savedCwd), NULL); if (gs != 0) { gLastError = 3; return FALSE; } TranslatePath(lpCurrentDirectory, translatedCwd); - gs = SysSetCwd(translatedCwd); + gs = K32SetCwdNative(translatedCwd); if (gs != 0) { gLastError = 3; return FALSE; } changedCwd = true; } @@ -1038,7 +1119,7 @@ __declspec(dllexport) BOOL CreateProcessA( // Call the syscall.dll stub. The kernel snapshots the current CWD into // the child process, so the temporary lpCurrentDirectory switch can be // restored immediately after creation. - uint32_t s = SysCreateProcess(translatedPath, &kh, cmdline); + uint32_t s = K32CreateProcessNative(translatedPath, &kh, cmdline); if (changedCwd) { if (savedCwd[0]) { @@ -1048,9 +1129,9 @@ __declspec(dllexport) BOOL CreateProcessA( if (rlen + 4 > sizeof(restoreCwd)) rlen = sizeof(restoreCwd) - 4; memcpy(restoreCwd + 3, savedCwd, rlen); restoreCwd[3 + rlen] = '\0'; - SysSetCwd(restoreCwd); + K32SetCwdNative(restoreCwd); } else { - SysSetCwd("0:/"); + K32SetCwdNative("0:/"); } } @@ -1166,7 +1247,7 @@ __declspec(dllexport) BOOL WriteConsoleA(HANDLE hConsoleOutput, if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = 0; return TRUE; } - SysWriteConsole((const char*)lpBuffer, (size_t)nNumberOfCharsToWrite); + K32WriteConsoleSpan((const char*)lpBuffer, (size_t)nNumberOfCharsToWrite); if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = nNumberOfCharsToWrite; return TRUE; } @@ -1306,7 +1387,7 @@ __declspec(dllexport) HANDLE FindFirstFileA(const char* lpFileName, // Open the kernel directory iterator uint32_t kh = 0; - uint32_t s = SysOpenDir(dir[0] ? dir : NULL, &kh); + uint32_t s = K32OpenDirNative(dir[0] ? dir : NULL, &kh); if (s != 0 || kh == 0) { gLastError = 2; // ERROR_FILE_NOT_FOUND return INVALID_HANDLE_VALUE; @@ -1387,7 +1468,7 @@ __declspec(dllexport) BOOL SetCurrentDirectoryA(const char* lpPathName) { char translatedPath[MAX_PATH]; TranslatePath(lpPathName, translatedPath); - uint32_t s = SysSetCwd(translatedPath); + uint32_t s = K32SetCwdNative(translatedPath); if (s != 0) { gLastError = 2; return FALSE; } return TRUE; } @@ -1400,7 +1481,7 @@ __declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBu // Kernel returns native "N:/path". Present it as a Win32 drive letter. char raw[512]; - uint32_t s = SysGetCwd(raw, sizeof(raw)); + uint32_t s = K32GetCwdNative(raw, sizeof(raw), NULL); if (s != 0) { gLastError = 1; return 0; } char full[515]; diff --git a/code/win32/user32.c b/code/win32/user32.c index af2d4fd..61aff41 100644 --- a/code/win32/user32.c +++ b/code/win32/user32.c @@ -1,69 +1,76 @@ -// user32.c - Win32 User Interface -#include -#include -#include - -typedef void* HWND; -typedef void* HDC; -typedef long LONG; -typedef int BOOL; - -typedef struct _RECT { LONG left, top, right, bottom; } RECT; -typedef struct tagPAINTSTRUCT { - HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; char res[32]; -} PAINTSTRUCT; - -// --- Painting Functions --- -__declspec(dllexport) HDC BeginPaint(HWND hwnd, PAINTSTRUCT* lpPaint) { - (void)hwnd; - if (lpPaint) { - lpPaint->hdc = (HDC)1; - lpPaint->rcPaint.right = 1920; lpPaint->rcPaint.bottom = 1080; - } - return (HDC)1; -} - -__declspec(dllexport) BOOL EndPaint(HWND hwnd, const PAINTSTRUCT* lp) { (void)hwnd; (void)lp; return 1; } - -// --- Message Loop Stubs --- -__declspec(dllexport) int GetMessage(void* m, HWND h, uint32_t f, uint32_t l) { - (void)m; (void)h; (void)f; (void)l; SysYield(); return 0; -} -__declspec(dllexport) int PeekMessage(void* m, HWND h, uint32_t f, uint32_t l, uint32_t r) { - (void)m; (void)h; (void)f; (void)l; (void)r; - return 0; -} -__declspec(dllexport) int DispatchMessage(const void* m) { (void)m; return 0; } -__declspec(dllexport) int TranslateMessage(const void* m) { (void)m; return 0; } - -// --- Dialogs & UI --- -__declspec(dllexport) int MessageBoxA(HWND h, const char* txt, const char* cap, uint32_t type) { - (void)h; (void)cap; (void)type; - SysDebugPrint("USER32: MessageBoxA: "); - SysDebugPrint(txt); - SysDebugPrint("\n"); - return 1; // IDOK -} - -// --- Window Queries --- -__declspec(dllexport) HWND GetDesktopWindow(void) { return (HWND)0; } -__declspec(dllexport) HWND GetActiveWindow(void) { return (HWND)0; } -__declspec(dllexport) HWND GetTopWindow(HWND hwnd){ (void)hwnd; return (HWND)0; } - -__declspec(dllexport) HWND GetWindow(HWND hwnd, unsigned int cmd) { - (void)hwnd; (void)cmd; return (HWND)0; -} - -__declspec(dllexport) long GetWindowLongA(HWND hwnd, int index) { - (void)hwnd; (void)index; return 0; -} - -__declspec(dllexport) intptr_t GetWindowLongPtrA(HWND hwnd, int index) { - (void)hwnd; (void)index; return 0; -} - -__declspec(dllexport) unsigned long GetWindowThreadProcessId(HWND hwnd, unsigned long* pid) { - (void)hwnd; - if (pid) *pid = 1; - return 1; // Thread ID -} +// user32.c - Win32 User Interface +#include +#include +#include + +typedef void* HWND; +typedef void* HDC; +typedef long LONG; +typedef int BOOL; + +typedef struct _RECT { LONG left, top, right, bottom; } RECT; +typedef struct tagPAINTSTRUCT { + HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; char res[32]; +} PAINTSTRUCT; + + +static void User32DebugPrintA(const char* s) { + if (!s) return; + SYS_DEBUG_PRINT_REQUEST req = { SysSvFromCstr(s) }; + SysDebugPrint(&req); +} + +// --- Painting Functions --- +__declspec(dllexport) HDC BeginPaint(HWND hwnd, PAINTSTRUCT* lpPaint) { + (void)hwnd; + if (lpPaint) { + lpPaint->hdc = (HDC)1; + lpPaint->rcPaint.right = 1920; lpPaint->rcPaint.bottom = 1080; + } + return (HDC)1; +} + +__declspec(dllexport) BOOL EndPaint(HWND hwnd, const PAINTSTRUCT* lp) { (void)hwnd; (void)lp; return 1; } + +// --- Message Loop Stubs --- +__declspec(dllexport) int GetMessage(void* m, HWND h, uint32_t f, uint32_t l) { + (void)m; (void)h; (void)f; (void)l; SysYield(); return 0; +} +__declspec(dllexport) int PeekMessage(void* m, HWND h, uint32_t f, uint32_t l, uint32_t r) { + (void)m; (void)h; (void)f; (void)l; (void)r; + return 0; +} +__declspec(dllexport) int DispatchMessage(const void* m) { (void)m; return 0; } +__declspec(dllexport) int TranslateMessage(const void* m) { (void)m; return 0; } + +// --- Dialogs & UI --- +__declspec(dllexport) int MessageBoxA(HWND h, const char* txt, const char* cap, uint32_t type) { + (void)h; (void)cap; (void)type; + User32DebugPrintA("USER32: MessageBoxA: "); + User32DebugPrintA(txt); + User32DebugPrintA("\n"); + return 1; // IDOK +} + +// --- Window Queries --- +__declspec(dllexport) HWND GetDesktopWindow(void) { return (HWND)0; } +__declspec(dllexport) HWND GetActiveWindow(void) { return (HWND)0; } +__declspec(dllexport) HWND GetTopWindow(HWND hwnd){ (void)hwnd; return (HWND)0; } + +__declspec(dllexport) HWND GetWindow(HWND hwnd, unsigned int cmd) { + (void)hwnd; (void)cmd; return (HWND)0; +} + +__declspec(dllexport) long GetWindowLongA(HWND hwnd, int index) { + (void)hwnd; (void)index; return 0; +} + +__declspec(dllexport) intptr_t GetWindowLongPtrA(HWND hwnd, int index) { + (void)hwnd; (void)index; return 0; +} + +__declspec(dllexport) unsigned long GetWindowThreadProcessId(HWND hwnd, unsigned long* pid) { + (void)hwnd; + if (pid) *pid = 1; + return 1; // Thread ID +} diff --git a/readme.md b/readme.md index ee41dcd..20042bf 100644 --- a/readme.md +++ b/readme.md @@ -18,11 +18,13 @@ The codebase is still small enough to understand, but large enough to hurt your - higher-half x86-64 kernel - physical memory manager - virtual memory manager with kernel pool and user heap +- W^X-style user page protections - single address-space design with user/supervisor page permissions - HAL for screen, keyboard, mouse, timer, PCI, APIC, HPET, AHCI, and IDE -- preemptive priority scheduler +- preemptive priority scheduler accounting for ticks, context switches, idle selections, demotions, and aging promotions - processes and threads -- object handles +- process termination state tracking and exit signaling +- object handles with per-handle rights metadata - events, semaphores, mutexes, queues, and alertable waits - IRQL model: `PASSIVE_LEVEL`, `APC_LEVEL`, `DISPATCH_LEVEL`, and device IRQLs - interrupt-exit scheduling instead of context-switching inside hard IRQ handlers @@ -42,14 +44,14 @@ The codebase is still small enough to understand, but large enough to hurt your - lwIP network stack as a runtime driver (`lwip.sys`) - RTL8139 and RTL816x ethernet drivers - native BSD-style socket syscalls: `SysSocket`, `SysBind`, `SysConnect`, `SysSend`, `SysRecv`, `SysPoll`, `SysResolveHost`, and friends -- `SysGetMemoryStatus` syscall for querying physical/pool/heap usage +- length-tracked string-view syscall ABI with request structs for path/name/text arguments +- printf/debug formatting support for bounded string views via `%v` - INI-based configuration - PE import resolution and dynamic `.sys` driver loading - partial Win32 personality - partial (stub/microsoft-level) POSIX personality - self-tests for APCs, async disk I/O, async file I/O, trap frames, and cancellation - fully GDPR-compliant -- Doom, because priorities matter ## architecture notes @@ -59,8 +61,7 @@ Single address space for the whole system. No separate kernel/user page tables. Kernel lives in the higher half. User processes only see what they are explicitly given. Kernel pages are supervisor-only so user-mode code cannot touch them. User pages are isolated from each other through the allocator and process creation path, not by hardware address-space separation. One rogue pointer can't crash the kernel but it might corrupt other processes. But what are the chances? -Is this how a serious modern OS should isolate processes? No. -Is it very convenient while building? Absolutely. +User heap and stack mappings are non-executable, and the PE loader applies final section protections after loading. Writable sections are left writable and non-executable; executable sections are mapped executable without write access where possible. ### execution model @@ -135,7 +136,11 @@ lwIP TCP/IP network stack PE images get loaded, imports resolved, then the first thread is queued. `syscall.dll` is the shared syscall entry point, and both personalities go through it. -Scheduler is preemptive and priority-based. Process state and thread state are tracked separately, so a process can die or get reaped without taking the whole system down. `init` can restart things if needed. +String and buffer-heavy syscalls use request structs. Text arguments are passed as bounded `SYS_STRING_VIEW` values, then copied and validated by the kernel before use. + +Scheduler is preemptive and priority-based. Process state and thread state are tracked separately, so a process can die or get reaped without taking the whole system down. `init` can restart things if needed. Process objects track termination and exit state separately from individual thread lifetime. + +Handles carry rights metadata. Syscalls check both object type and required access for operations such as read, write, wait, signal, query, duplicate, and terminate. Syscalls build real trap frames. That matters because user APC delivery needs to safely redirect the return path back into user mode and then restore the interrupted context with `SysApcReturn`. @@ -203,6 +208,8 @@ Port forwarding is enabled by default in `runa.bat` — host port 8080 maps to g Volumes `0`-`7` are available. A volume only works if a filesystem is registered on it (duh). The Win32 personality translates drive letters to volume numbers, while the POSIX personality maps volumes onto a tree. +Path parsing is length-aware internally. Embedded NUL bytes are rejected for text paths and names. + ## how to build - open the VS x64 build tools as administrator @@ -260,11 +267,14 @@ todo ## faq Q: Why? -A: Learning. Fun. Spite. +A: Reasons. Q: Does it run Doom? A: `doom.exe` +Q: If I write a program now, will it run in the future? +A: Not a chance. WE DO in fact BREAK USERSPACE. + Q: Can you read and write files? A: As far as I'm aware. @@ -278,10 +288,11 @@ Q: Is the Win32 layer real? A: Depends how you frame it. Some apps will run, most will not. Q: Are processes isolated? -A: No. But IPC is free. +A: No. But IPC is free. Is this how a serious modern OS should isolate processes? No. +Is it very convenient while building? Absolutely. Q: How often does it crash? -A: Less often than before. +A: Like Windows 11. Q: Can it recover from a crash? A: Yes. Unless the kernel faulted.