From 899225f5515a104fd51c77660ef98b4e0b7d2249 Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Wed, 23 Sep 2026 09:39:42 +0200 Subject: [PATCH] move POSIX spawn, exec and child tracking into posixsrv, keep process transactions alive across reconnects and preserve stable PIDs across image replacement, flesh out spawn with ordered file actions, process-group selection and signal inheritance, implement execve with bootstrap validation and rollback before retiring the old process, add vfork children that share the native process while keeping separate POSIX descriptor and signal state, replace local spawn bookkeeping with child reaping, nonblocking and process-group waits and orphan cleanup, add signal dispositions, per-thread masks, pending delivery, process/group signalling and alarms, translate native exceptions into POSIX signals and inherit masks when creating threads, make pipe waits interruptible and raise SIGPIPE for broken pipes, pass arguments and environment into main and add getenv/environ, add direct user APC notifications that can run without an alertable wait, add current-thread capabilities and resume-and-wait, let accepted IPC endpoints open restricted peer process capabilities and preserve caller security during spawn and exec, make process preparation reusable and improve DLL lookup across current, executable and fallback directories, move keyboard repeat into RSSRV so PS/2 and USB share one repeat policy, fix video presentation, report launch failure stages and status codes, mooaaar tests. --- OVMF/OVMF_VARS_4M_d.fd | Bin 540672 -> 540672 bytes code/build/build.c | 13 +- code/docs/services.md | 2 +- code/kernel/include/kernel/ke_apc.h | 7 +- code/kernel/include/kernel/ke_port.h | 8 +- code/kernel/include/kernel/syscall_nr.h | 328 +- code/kernel/ke_apc.c | 41 +- code/kernel/ke_handle.c | 6 +- code/kernel/ke_port.c | 72 +- code/kernel/ke_syscall.c | 194 +- code/kernel/ke_trap.c | 5 +- code/public/ntlite/handle.h | 2 + code/public/ntlite/port.h | 7 +- code/public/ntlite/posix_service.h | 107 +- code/public/ntlite/posix_signal.h | 29 + code/public/ntlite/process_loader.h | 61 + code/public/ntlite/security.h | 3 + code/public/ntlite/syscall.h | 30 + code/public/posix/errno.h | 1 + code/public/posix/ntlite_posix_profile.def | 46 +- code/public/posix/signal.h | 48 +- code/public/posix/spawn.h | 55 +- code/public/posix/stdlib.h | 2 + code/public/posix/sys/wait.h | 5 +- code/public/posix/unistd.h | 7 + code/user/apps/shell_native/shell_native.c | 72 +- code/user/apps/video/video.c | 56 +- code/user/personalities/posix/posix_fd.c | 102 +- code/user/personalities/posix/posix_fd.h | 16 +- .../user/personalities/posix/posix_internal.h | 89 +- code/user/personalities/posix/posix_io.c | 23 +- code/user/personalities/posix/posix_proc.c | 780 +++- code/user/personalities/posix/posix_runtime.c | 423 +- code/user/personalities/posix/posix_signal.c | 476 +++ code/user/personalities/posix/posix_thread.c | 9 +- code/user/personalities/posix/posix_vfork.asm | 157 + code/user/personalities/posix/posix_vfork.c | 331 ++ .../personalities/posix/posix_vfork_asm.h | 37 + code/user/runtime/posix_start.c | 15 +- code/user/syscall/module_loader.c | 327 +- code/user/syscall/module_loader_internal.h | 11 +- code/user/syscall/process_loader.c | 377 +- code/user/syscall/syscall_nr.inc | 328 +- code/user/syscall/syscall_stubs.asm | 6 + code/user/system/posixsrv/posixsrv.c | 3472 +++++++++++++++-- code/user/system/rssrv/input.c | 75 +- code/user/system/rssrv/rssrv_internal.h | 2 + .../tests/apc_return_smoke/apc_return_smoke.c | 148 + .../tests/port_ipc_smoke/port_ipc_smoke.c | 71 +- .../tests/posix_fd_smoke/posix_fd_smoke.c | 37 +- .../tests/posix_pipe_smoke/posix_pipe_smoke.c | 6 + .../posix_process_smoke/posix_process_smoke.c | 555 +++ .../posix_service_smoke/posix_service_smoke.c | 223 +- .../posix_signal_smoke/posix_signal_smoke.c | 459 +++ .../process_builder_smoke.c | 228 +- .../tests/security_smoke/security_smoke.c | 36 + .../thread_name_smoke/thread_name_smoke.c | 7 +- 57 files changed, 8817 insertions(+), 1216 deletions(-) create mode 100644 code/public/ntlite/posix_signal.h create mode 100644 code/user/personalities/posix/posix_signal.c create mode 100644 code/user/personalities/posix/posix_vfork.asm create mode 100644 code/user/personalities/posix/posix_vfork.c create mode 100644 code/user/personalities/posix/posix_vfork_asm.h create mode 100644 code/user/tests/posix_process_smoke/posix_process_smoke.c create mode 100644 code/user/tests/posix_signal_smoke/posix_signal_smoke.c diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 14b4bbeaa0ead77292ee567af6bd3a11b80819f1..b8233de92a247cba309f4720809a4931cf58af7f 100644 GIT binary patch delta 144 zcmZo@P;6*W+)$y<=s!8pNPGH$pNuc2ZTg8{I?5RAYW zIKTyyj6zBJj1q=21v4-Q3$V=i-z$_g*nlnA0T1lKA>(AqqB;`)9;h@>Bz-5Q<{Rm& VtX*{(C1*i3L34uU1=STb=ntF+Vl4mw diff --git a/code/build/build.c b/code/build/build.c index 8c3f04f..5860c24 100644 --- a/code/build/build.c +++ b/code/build/build.c @@ -19,7 +19,7 @@ #define NATIVE_INIT_LIBS_LITERAL "bin\\syscall_s.lib obj\\libc.obj obj\\runtime\\native_exception.obj" #define NATIVE_SERVICE_LIBS_LITERAL "bin\\syscall_s.lib obj\\libc.obj " USER_HEAP_OBJS_LITERAL " obj\\runtime\\native_exception.obj" #define WIN32_EXE_LIBS_LITERAL "bin\\win32.lib bin\\syscall.lib obj\\libc.obj obj\\runtime\\native_start.obj" -#define POSIX_EXE_LIBS_LITERAL "bin\\posix.lib bin\\syscall.lib obj\\libc.obj obj\\runtime\\posix_start.obj obj\\runtime\\native_exception.obj" +#define POSIX_EXE_LIBS_LITERAL "bin\\posix.lib bin\\syscall.lib obj\\libc.obj obj\\runtime\\posix_start.obj" #define NATIVE_EXE_ENTRY_LITERAL "/ENTRY:NtlNativeStart" #define USER_IMAGE_FLAGS_LITERAL "/DYNAMICBASE /HIGHENTROPYVA" #define LWIP_CFLAGS_LITERAL DRIVER_CFLAGS_LITERAL " /Icode\\drivers\\net\\lwip\\include /Icode\\drivers\\net\\lwip\\third_party\\lwip\\src\\include /wd4005 /wd4100 /wd4103 /wd4127 /wd4244 /wd4267 /wd4706" @@ -80,7 +80,7 @@ void create_layout() { "obj\\tcpserve", "obj\\udp", "obj\\resolve", "obj\\ping", "obj\\doom", "obj\\video", "obj\\memtop", "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\security_smoke", "obj\\port_ipc_smoke", "obj\\resource_service_smoke", "obj\\named_queue_lifetime_smoke", "obj\\input_api_smoke", "obj\\input_model_smoke", "obj\\input_service_smoke", "obj\\console_service_smoke", "obj\\desktop_smoke", "obj\\timer_smoke", "obj\\stats_abi_smoke", "obj\\realpath_smoke", "obj\\file_lock_smoke", "obj\\process_protection_smoke", "obj\\process_exit_multithread_smoke", "obj\\thread_name_smoke", "obj\\process_builder_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\audio_stream_smoke", "obj\\winmm_waveout_smoke", "obj\\win32_condition_variable_smoke", "obj\\win32_mutex_smoke", "obj\\win32_thread_suspend_smoke", "obj\\win32_module_lookup_smoke", "obj\\cwd_userspace_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap", "obj\\pcache_invalidate", "obj\\pcache_mmap_read_reuse", "obj\\pcache_multithread", - "obj\\all_tests", "obj\\posix_service_smoke", "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_commit", "obj\\surface_cross_process_smoke", "obj\\net_irp_smoke", "obj\\module_load_smoke", "obj\\posix_module_smoke", "obj\\posix_runtime_smoke", "obj\\posix_fd_smoke", "obj\\posix_pipe_smoke", "obj\\exception_dispatcher_smoke", "obj\\native_exception_runtime_smoke", "obj\\native_raise_exception_smoke", "obj\\exception_smoke", "obj\\win32_exception_smoke", "obj\\win32_seh_smoke" + "obj\\all_tests", "obj\\posix_service_smoke", "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_commit", "obj\\surface_cross_process_smoke", "obj\\net_irp_smoke", "obj\\module_load_smoke", "obj\\posix_module_smoke", "obj\\posix_runtime_smoke", "obj\\posix_fd_smoke", "obj\\posix_process_smoke", "obj\\posix_signal_smoke", "obj\\posix_pipe_smoke", "obj\\exception_dispatcher_smoke", "obj\\native_exception_runtime_smoke", "obj\\native_raise_exception_smoke", "obj\\exception_smoke", "obj\\win32_exception_smoke", "obj\\win32_seh_smoke" }; for (size_t i = 0; i < NOB_ARRAY_LEN(dirs); i++) nob_mkdir_if_not_exists(dirs[i]); nob_mkdir_if_not_exists(PCI_BUS_DRIVER.obj_dir); @@ -121,7 +121,7 @@ bool clean_build_object_directories(void) { "obj\\tcpserve", "obj\\udp", "obj\\resolve", "obj\\ping", "obj\\doom", "obj\\video", "obj\\memtop", "obj\\file_stat_smoke", "obj\\handle_dup_smoke", "obj\\security_smoke", "obj\\port_ipc_smoke", "obj\\resource_service_smoke", "obj\\named_queue_lifetime_smoke", "obj\\input_api_smoke", "obj\\input_model_smoke", "obj\\input_service_smoke", "obj\\console_service_smoke", "obj\\desktop_smoke", "obj\\timer_smoke", "obj\\stats_abi_smoke", "obj\\realpath_smoke", "obj\\file_lock_smoke", "obj\\process_protection_smoke", "obj\\process_exit_multithread_smoke", "obj\\thread_name_smoke", "obj\\process_builder_smoke", "obj\\socket_addr_smoke", "obj\\syscall_contract_smoke", "obj\\would_block_smoke", "obj\\audio_stream_smoke", "obj\\winmm_waveout_smoke", "obj\\win32_condition_variable_smoke", "obj\\win32_mutex_smoke", "obj\\win32_thread_suspend_smoke", "obj\\win32_module_lookup_smoke", "obj\\cwd_userspace_smoke", "obj\\mmap_smoke", "obj\\pcache_smoke", "obj\\pcache_pressure", "obj\\dirty_writeback", "obj\\shared_mmap", "obj\\pcache_invalidate", "obj\\pcache_mmap_read_reuse", "obj\\pcache_multithread", - "obj\\all_tests", "obj\\posix_service_smoke", "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_commit", "obj\\surface_cross_process_smoke", "obj\\net_irp_smoke", "obj\\module_load_smoke", "obj\\posix_module_smoke", "obj\\posix_runtime_smoke", "obj\\posix_fd_smoke", "obj\\posix_pipe_smoke", "obj\\exception_dispatcher_smoke", "obj\\native_exception_runtime_smoke", "obj\\native_raise_exception_smoke", "obj\\exception_smoke", "obj\\win32_exception_smoke", "obj\\win32_seh_smoke" + "obj\\all_tests", "obj\\posix_service_smoke", "obj\\surface_smoke", "obj\\surface_invalid_rect", "obj\\surface_multithread_commit", "obj\\surface_cross_process_smoke", "obj\\net_irp_smoke", "obj\\module_load_smoke", "obj\\posix_module_smoke", "obj\\posix_runtime_smoke", "obj\\posix_fd_smoke", "obj\\posix_process_smoke", "obj\\posix_signal_smoke", "obj\\posix_pipe_smoke", "obj\\exception_dispatcher_smoke", "obj\\native_exception_runtime_smoke", "obj\\native_raise_exception_smoke", "obj\\exception_smoke", "obj\\win32_exception_smoke", "obj\\win32_seh_smoke" }; for (size_t i = 0; i < NOB_ARRAY_LEN(dirs); i++) { @@ -384,7 +384,8 @@ int main(int argc, char** argv) { "link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL " USER_IMAGE_FLAGS_LITERAL " /OUT:bin\\syscall.dll obj\\syscall\\stubs.obj obj\\syscall\\pe_loader.obj obj\\syscall\\module_loader.obj obj\\syscall\\process_loader.obj obj\\syscall\\resource.obj obj\\syscall\\input_service.obj obj\\syscall\\console_service.obj obj\\syscall\\desktop.obj obj\\syscall\\path.obj obj\\libc.obj", "lib /nologo /OUT:bin\\syscall_s.lib obj\\syscall\\stubs_s.obj obj\\syscall\\pe_loader_s.obj obj\\syscall\\module_loader_s.obj obj\\syscall\\process_loader_s.obj obj\\syscall\\resource_s.obj obj\\syscall\\input_service_s.obj obj\\syscall\\console_service_s.obj obj\\syscall\\desktop_s.obj obj\\syscall\\path_s.obj", nob_temp_sprintf("cl %s /DUSER_HEAP_EXPORTS /DNTL_EXCEPTION_EXPORTS /MP /c /Foobj\\win32\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\runtime\\native_exception.c code\\user\\personalities\\win32\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL " USER_IMAGE_FLAGS_LITERAL " /OUT:bin\\win32.dll bin\\syscall.lib obj\\libc.obj obj\\win32\\*.obj", UserCflags), - nob_temp_sprintf("cl %s /DNTL_POSIX_BUILD /DUSER_HEAP_EXPORTS /MP /c /Foobj\\posix\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\personalities\\posix\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL " USER_IMAGE_FLAGS_LITERAL " /OUT:bin\\posix.dll bin\\syscall.lib obj\\libc.obj obj\\posix\\*.obj", PosixUserCflags) + "ml64 /nologo /Icode\\user\\syscall /c /Foobj\\posix\\posix_vfork_asm.obj code\\user\\personalities\\posix\\posix_vfork.asm", + nob_temp_sprintf("cl %s /DNTL_POSIX_BUILD /DUSER_HEAP_EXPORTS /DNTL_EXCEPTION_EXPORTS /MP /c /Foobj\\posix\\ code\\user\\runtime\\heap.c code\\user\\runtime\\heap_exports.c code\\user\\runtime\\native_exception.c code\\user\\personalities\\posix\\*.c && link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL " USER_IMAGE_FLAGS_LITERAL " /OUT:bin\\posix.dll bin\\syscall.lib obj\\libc.obj obj\\posix\\*.obj", PosixUserCflags) }; for (size_t i = 0; i < NOB_ARRAY_LEN(core_tasks); i++) { @@ -496,6 +497,8 @@ int main(int argc, char** argv) { {"tests\\posix_module_smoke", POSIX_EXE_LIBS_LITERAL, "posix_module_smoke", "/ENTRY:PosixStart"}, {"tests\\posix_runtime_smoke", POSIX_EXE_LIBS_LITERAL, "posix_runtime_smoke", "/ENTRY:PosixStart"}, {"tests\\posix_fd_smoke", POSIX_EXE_LIBS_LITERAL, "posix_fd_smoke", "/ENTRY:PosixStart"}, + {"tests\\posix_process_smoke", POSIX_EXE_LIBS_LITERAL, "posix_process_smoke", "/ENTRY:PosixStart"}, + {"tests\\posix_signal_smoke", POSIX_EXE_LIBS_LITERAL, "posix_signal_smoke", "/ENTRY:PosixStart"}, {"tests\\posix_pipe_smoke", POSIX_EXE_LIBS_LITERAL, "posix_pipe_smoke", "/ENTRY:PosixStart"}, {"tests\\exception_dispatcher_smoke", NATIVE_EXE_LIBS_LITERAL, "exception_dispatcher_smoke", NATIVE_EXE_ENTRY_LITERAL}, {"tests\\native_exception_runtime_smoke", NATIVE_EXE_LIBS_LITERAL, "native_exception_runtime_smoke", NATIVE_EXE_ENTRY_LITERAL}, @@ -654,6 +657,8 @@ int main(int argc, char** argv) { "bin\\posix_module_smoke.exe", "bin\\posix_runtime_smoke.exe", "bin\\posix_fd_smoke.exe", + "bin\\posix_process_smoke.exe", + "bin\\posix_signal_smoke.exe", "bin\\posix_pipe_smoke.exe", "bin\\exception_dispatcher_smoke.exe", "bin\\native_exception_runtime_smoke.exe", diff --git a/code/docs/services.md b/code/docs/services.md index 173d624..e7c440f 100644 --- a/code/docs/services.md +++ b/code/docs/services.md @@ -51,7 +51,7 @@ The implemented RSSRV foundation uses one root `RSSRV_STATE` and passes explicit Logical input is now an RSSRV-owned facility. The kernel input manager exposes a capability-scoped stream of normalized physical key make/break and relative mouse events. RSSRV consumes that stream, owns key-down/modifier/lock state, performs the initial keyboard-layout character mapping, and owns logical pointer position and mouse-button/wheel state. Session 0 owns routing policy above that logical state: one active target, keyboard focus, ordinary pointer focus, and optional pointer capture. Keyboard and pointer dispatch snapshot the selected target before calling it, so target-specific queues do not share or race the raw logical-input queue. Session 0 owns logical pointer state and routing, while DWM mirrors that authoritative position to the visible hardware cursor through its separately delegated display-manager capability. RSSRV publishes the raw logical listener at `/Services/Input`; normal native applications and the current POSIX/Win32 personality bindings may consume that service through the `NtlInput*` client API, while the console receives a separately routed focused-input queue. The old global input syscalls remain a transitional native ABI for existing callers and contract tests, but they are no longer the canonical interactive input path and should be deleted after those callers migrate. -Typematic is not fully migrated yet. RSSRV accepts repeated physical make events as repeated logical key records, while the legacy kernel-global input path retains its existing kernel repeat timer. A later input/console phase may move repeat timing completely into RSSRV once console routing and terminal policy provide the final destination model. +RSSRV owns logical typematic timing for the Session 0 input path. Repeated physical make events are treated as idempotent there so PS/2 hardware repeat and USB HID share one logical repeat policy. The legacy kernel-global input path retains its existing kernel repeat timer only for legacy consumers that have not moved to RSSRV routing. ## Desktop window manager diff --git a/code/kernel/include/kernel/ke_apc.h b/code/kernel/include/kernel/ke_apc.h index 2ff5956..1d6b911 100644 --- a/code/kernel/include/kernel/ke_apc.h +++ b/code/kernel/include/kernel/ke_apc.h @@ -33,6 +33,9 @@ typedef void (*PKRUNDOWN_ROUTINE)(KAPC* apc); typedef struct KUSER_APC KUSER_APC; +#define KE_USER_APC_FLAG_NONE 0x00000000u +#define KE_USER_APC_FLAG_INTERRUPT 0x00000001u + typedef struct SYS_USER_APC_FRAME_K { uint64_t Magic; uint64_t ReturnToken; @@ -50,6 +53,7 @@ struct KUSER_APC { void* NormalContext; void* SystemArgument1; void* SystemArgument2; + uint32_t Flags; }; struct KAPC { @@ -92,7 +96,8 @@ __declspec(dllexport) KSTATUS KeQueueUserApc(THREAD* thread, void* routine, void* normalContext, void* systemArgument1, - void* systemArgument2); + void* systemArgument2, + uint32_t flags); __declspec(dllexport) uint64_t KeUserApcReturn(const SYS_USER_APC_FRAME_K* frame); void KiRundownApcState(THREAD* thread); diff --git a/code/kernel/include/kernel/ke_port.h b/code/kernel/include/kernel/ke_port.h index 1846c47..c8bda70 100644 --- a/code/kernel/include/kernel/ke_port.h +++ b/code/kernel/include/kernel/ke_port.h @@ -28,7 +28,7 @@ typedef struct KE_PORT_STATS { void KePortQueryStats(KE_PORT_STATS* statsOut); // Creates one waitable connection-listener Port. -KSTATUS KePortCreate(uint32_t backlog, PORT** portOut); +KSTATUS KePortCreate(uint32_t backlog, SYS_RIGHTS peerProcessRights, PORT** portOut); // Returns whether one Port object is a listener rather than a connected endpoint. bool KePortIsListener(const PORT* port); @@ -96,3 +96,9 @@ KSTATUS KePortReply(PORT* endpoint, // Disconnects one endpoint and wakes peer waiters and pending synchronous callers. KSTATUS KePortDisconnect(PORT* endpoint); + +// Opens a capability to the process bound as the peer of an authorized accepted endpoint. +KSTATUS KePortOpenPeerProcess(PORT* endpoint, + struct PROCESS* callerProcess, + SYS_RIGHTS desiredRights, + SYS_HANDLE* processHandleOut); diff --git a/code/kernel/include/kernel/syscall_nr.h b/code/kernel/include/kernel/syscall_nr.h index bcb500c..a16bbb0 100644 --- a/code/kernel/include/kernel/syscall_nr.h +++ b/code/kernel/include/kernel/syscall_nr.h @@ -20,187 +20,193 @@ #define SYS_GET_PROCESS_LIST 9 #define SYS_GET_PROCESS_PID 10 #define SYS_CREATE_THREAD 11 -#define SYS_RESUME_THREAD 12 -#define SYS_GET_THREAD_LIST 13 -#define SYS_GET_CURRENT_TID 14 -#define SYS_GET_CURRENT_PID 15 -#define SYS_SET_THREAD_PRIORITY 16 -#define SYS_SET_THREAD_NAME 17 -#define SYS_QUERY_THREAD_NAME 18 -#define SYS_YIELD 19 -#define SYS_SLEEP 20 -#define SYS_WAIT_SINGLE 21 -#define SYS_WAIT_MANY 22 -#define SYS_SET_USER_APC_DISPATCHER 23 -#define SYS_APC_RETURN 24 -#define SYS_SET_EXCEPTION_DISPATCHER 25 -#define SYS_GET_EXCEPTION_DISPATCHER 26 -#define SYS_CONTINUE_EXCEPTION 27 -#define SYS_RAISE_EXCEPTION 28 +#define SYS_OPEN_CURRENT_THREAD 12 +#define SYS_RESUME_THREAD 13 +#define SYS_RESUME_THREAD_AND_WAIT 14 +#define SYS_GET_THREAD_LIST 15 +#define SYS_GET_THREAD_TID 16 +#define SYS_GET_CURRENT_TID 17 +#define SYS_GET_CURRENT_PID 18 +#define SYS_SET_THREAD_PRIORITY 19 +#define SYS_SET_THREAD_NAME 20 +#define SYS_QUERY_THREAD_NAME 21 +#define SYS_YIELD 22 +#define SYS_SLEEP 23 +#define SYS_WAIT_SINGLE 24 +#define SYS_WAIT_MANY 25 +#define SYS_SET_USER_APC_DISPATCHER 26 +#define SYS_QUEUE_USER_APC 27 +#define SYS_APC_RETURN 28 +#define SYS_SET_EXCEPTION_DISPATCHER 29 +#define SYS_GET_EXCEPTION_DISPATCHER 30 +#define SYS_CONTINUE_EXCEPTION 31 +#define SYS_RAISE_EXCEPTION 32 // Virtual memory, mappings, and handles. -#define SYS_ALLOC_VM 29 -#define SYS_FREE_VM 30 -#define SYS_PROTECT_VM 31 -#define SYS_QUERY_VM 32 -#define SYS_CLOSE_HANDLE 33 -#define SYS_DUPLICATE_HANDLE 34 -#define SYS_DERIVE_HANDLE 35 -#define SYS_REVOKE_HANDLE 36 -#define SYS_DERIVE_SECURITY_CONTEXT 37 -#define SYS_QUERY_SECURITY_CONTEXT 38 -#define SYS_CREATE_FILE_MAPPING 39 -#define SYS_MAP_FILE 40 +#define SYS_ALLOC_VM 33 +#define SYS_FREE_VM 34 +#define SYS_PROTECT_VM 35 +#define SYS_QUERY_VM 36 +#define SYS_CLOSE_HANDLE 37 +#define SYS_DUPLICATE_HANDLE 38 +#define SYS_DERIVE_HANDLE 39 +#define SYS_REVOKE_HANDLE 40 +#define SYS_DERIVE_SECURITY_CONTEXT 41 +#define SYS_QUERY_SECURITY_CONTEXT 42 +#define SYS_OPEN_PROCESS_SECURITY_CONTEXT 43 +#define SYS_CREATE_FILE_MAPPING 44 +#define SYS_MAP_FILE 45 // Synchronization objects. -#define SYS_CREATE_MUTEX 41 -#define SYS_TRY_ACQUIRE_MUTEX 42 -#define SYS_RELEASE_MUTEX 43 -#define SYS_CREATE_SEM 44 -#define SYS_TRY_WAIT_SEM 45 -#define SYS_SIGNAL_SEM 46 -#define SYS_CREATE_EVENT 47 -#define SYS_SIGNAL_EVENT 48 -#define SYS_CLEAR_EVENT 49 +#define SYS_CREATE_MUTEX 46 +#define SYS_TRY_ACQUIRE_MUTEX 47 +#define SYS_RELEASE_MUTEX 48 +#define SYS_CREATE_SEM 49 +#define SYS_TRY_WAIT_SEM 50 +#define SYS_SIGNAL_SEM 51 +#define SYS_CREATE_EVENT 52 +#define SYS_SIGNAL_EVENT 53 +#define SYS_CLEAR_EVENT 54 // Queues and named queues. -#define SYS_CREATE_QUEUE 50 -#define SYS_SEND_QUEUE 51 -#define SYS_RECV_QUEUE 52 -#define SYS_PUBLISH_QUEUE 53 -#define SYS_OPEN_NAMED_QUEUE 54 -#define SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS 55 -#define SYS_UNPUBLISH_QUEUE 56 +#define SYS_CREATE_QUEUE 55 +#define SYS_SEND_QUEUE 56 +#define SYS_RECV_QUEUE 57 +#define SYS_PUBLISH_QUEUE 58 +#define SYS_OPEN_NAMED_QUEUE 59 +#define SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS 60 +#define SYS_UNPUBLISH_QUEUE 61 // LPC-like Ports and well-known system services. -#define SYS_CREATE_PORT 57 -#define SYS_CONNECT_PORT 58 -#define SYS_ACCEPT_PORT 59 -#define SYS_SEND_PORT 60 -#define SYS_RECEIVE_PORT 61 -#define SYS_CALL_PORT 62 -#define SYS_REPLY_PORT 63 -#define SYS_DISCONNECT_PORT 64 -#define SYS_REGISTER_SYSTEM_SERVICE 65 -#define SYS_UNREGISTER_SYSTEM_SERVICE 66 -#define SYS_CONNECT_SYSTEM_SERVICE 67 +#define SYS_CREATE_PORT 62 +#define SYS_CONNECT_PORT 63 +#define SYS_ACCEPT_PORT 64 +#define SYS_SEND_PORT 65 +#define SYS_RECEIVE_PORT 66 +#define SYS_CALL_PORT 67 +#define SYS_REPLY_PORT 68 +#define SYS_DISCONNECT_PORT 69 +#define SYS_OPEN_PORT_PEER_PROCESS 70 +#define SYS_REGISTER_SYSTEM_SERVICE 71 +#define SYS_UNREGISTER_SYSTEM_SERVICE 72 +#define SYS_CONNECT_SYSTEM_SERVICE 73 // Mounted-volume manager control plane. -#define SYS_QUERY_VOLUMES 68 -#define SYS_OPEN_VOLUME 69 -#define SYS_WAIT_VOLUME_CHANGE 70 +#define SYS_QUERY_VOLUMES 74 +#define SYS_OPEN_VOLUME 75 +#define SYS_WAIT_VOLUME_CHANGE 76 // Files and directories. -#define SYS_OPEN_FILE_AT 71 -#define SYS_OPEN_DIR_AT 72 -#define SYS_STAT_AT 73 -#define SYS_DELETE_AT 74 -#define SYS_RENAME_AT 75 -#define SYS_CREATE_DIR_AT 76 -#define SYS_REMOVE_DIR_AT 77 -#define SYS_READ_FILE 78 -#define SYS_WRITE_FILE 79 -#define SYS_READ_FILE_ASYNC 80 -#define SYS_WRITE_FILE_ASYNC 81 -#define SYS_CANCEL_IO 82 -#define SYS_SEEK_FILE 83 -#define SYS_GET_FILE_POS 84 -#define SYS_GET_FILE_SIZE 85 -#define SYS_GET_FILE_STAT 86 -#define SYS_TRUNCATE_FILE 87 -#define SYS_FLUSH_FILE 88 -#define SYS_LOCK_FILE 89 -#define SYS_UNLOCK_FILE 90 -#define SYS_QUERY_FILE_LOCK 91 -#define SYS_SET_FILE_STATUS 92 -#define SYS_CREATE_PIPE 93 -#define SYS_NOTIFY_PIPE 94 -#define SYS_READ_DIR 95 +#define SYS_OPEN_FILE_AT 77 +#define SYS_OPEN_DIR_AT 78 +#define SYS_STAT_AT 79 +#define SYS_DELETE_AT 80 +#define SYS_RENAME_AT 81 +#define SYS_CREATE_DIR_AT 82 +#define SYS_REMOVE_DIR_AT 83 +#define SYS_READ_FILE 84 +#define SYS_WRITE_FILE 85 +#define SYS_READ_FILE_ASYNC 86 +#define SYS_WRITE_FILE_ASYNC 87 +#define SYS_CANCEL_IO 88 +#define SYS_SEEK_FILE 89 +#define SYS_GET_FILE_POS 90 +#define SYS_GET_FILE_SIZE 91 +#define SYS_GET_FILE_STAT 92 +#define SYS_TRUNCATE_FILE 93 +#define SYS_FLUSH_FILE 94 +#define SYS_LOCK_FILE 95 +#define SYS_UNLOCK_FILE 96 +#define SYS_QUERY_FILE_LOCK 97 +#define SYS_SET_FILE_STATUS 98 +#define SYS_CREATE_PIPE 99 +#define SYS_NOTIFY_PIPE 100 +#define SYS_READ_DIR 101 // Console, display, and surfaces. -#define SYS_WRITE_CONSOLE 96 -#define SYS_GET_CONSOLE_INFO 97 -#define SYS_SET_CONSOLE_CURSOR 98 -#define SYS_FILL_CONSOLE_CHARACTER 99 -#define SYS_FILL_CONSOLE_ATTRIBUTE 100 -#define SYS_PUT_SCREEN_CHAR 101 -#define SYS_CLEAR_SCREEN 102 -#define SYS_GET_SCREEN_SIZE 103 -#define SYS_QUERY_DISPLAY_INFO 104 -#define SYS_QUERY_DISPLAY_MANAGER_INFO 105 -#define SYS_PRESENT_DISPLAY_MANAGER_SURFACE 106 -#define SYS_SET_DISPLAY_MANAGER_CURSOR 107 -#define SYS_WRITE_DISPLAY_MANAGER_CONSOLE 108 -#define SYS_QUERY_DISPLAY_MANAGER_CONSOLE_INFO 109 -#define SYS_SET_DISPLAY_MANAGER_CONSOLE_CURSOR 110 -#define SYS_FILL_DISPLAY_MANAGER_CONSOLE_CHARACTER 111 -#define SYS_FILL_DISPLAY_MANAGER_CONSOLE_ATTRIBUTE 112 -#define SYS_PUT_DISPLAY_MANAGER_CONSOLE_CHARACTER 113 -#define SYS_CLEAR_DISPLAY_MANAGER_CONSOLE 114 -#define SYS_OPEN_DISPLAY_CLOCK 115 -#define SYS_CREATE_SURFACE 116 -#define SYS_MAP_SURFACE 117 -#define SYS_UNMAP_SURFACE 118 -#define SYS_QUERY_SURFACE 119 -#define SYS_COMMIT_SURFACE 120 +#define SYS_WRITE_CONSOLE 102 +#define SYS_GET_CONSOLE_INFO 103 +#define SYS_SET_CONSOLE_CURSOR 104 +#define SYS_FILL_CONSOLE_CHARACTER 105 +#define SYS_FILL_CONSOLE_ATTRIBUTE 106 +#define SYS_PUT_SCREEN_CHAR 107 +#define SYS_CLEAR_SCREEN 108 +#define SYS_GET_SCREEN_SIZE 109 +#define SYS_QUERY_DISPLAY_INFO 110 +#define SYS_QUERY_DISPLAY_MANAGER_INFO 111 +#define SYS_PRESENT_DISPLAY_MANAGER_SURFACE 112 +#define SYS_SET_DISPLAY_MANAGER_CURSOR 113 +#define SYS_WRITE_DISPLAY_MANAGER_CONSOLE 114 +#define SYS_QUERY_DISPLAY_MANAGER_CONSOLE_INFO 115 +#define SYS_SET_DISPLAY_MANAGER_CONSOLE_CURSOR 116 +#define SYS_FILL_DISPLAY_MANAGER_CONSOLE_CHARACTER 117 +#define SYS_FILL_DISPLAY_MANAGER_CONSOLE_ATTRIBUTE 118 +#define SYS_PUT_DISPLAY_MANAGER_CONSOLE_CHARACTER 119 +#define SYS_CLEAR_DISPLAY_MANAGER_CONSOLE 120 +#define SYS_OPEN_DISPLAY_CLOCK 121 +#define SYS_CREATE_SURFACE 122 +#define SYS_MAP_SURFACE 123 +#define SYS_UNMAP_SURFACE 124 +#define SYS_QUERY_SURFACE 125 +#define SYS_COMMIT_SURFACE 126 // Native render audio. -#define SYS_AUDIO_ENUMERATE_ENDPOINTS 121 -#define SYS_AUDIO_OPEN_STREAM 122 -#define SYS_AUDIO_WRITE 123 -#define SYS_AUDIO_ARM_WAIT 124 -#define SYS_AUDIO_QUERY_STREAM 125 -#define SYS_AUDIO_START 126 -#define SYS_AUDIO_PAUSE 127 -#define SYS_AUDIO_RESUME 128 -#define SYS_AUDIO_DRAIN 129 -#define SYS_AUDIO_STOP 130 -#define SYS_AUDIO_RESET 131 -#define SYS_AUDIO_SET_VOLUME 132 -#define SYS_AUDIO_SET_MASTER_VOLUME 133 +#define SYS_AUDIO_ENUMERATE_ENDPOINTS 127 +#define SYS_AUDIO_OPEN_STREAM 128 +#define SYS_AUDIO_WRITE 129 +#define SYS_AUDIO_ARM_WAIT 130 +#define SYS_AUDIO_QUERY_STREAM 131 +#define SYS_AUDIO_START 132 +#define SYS_AUDIO_PAUSE 133 +#define SYS_AUDIO_RESUME 134 +#define SYS_AUDIO_DRAIN 135 +#define SYS_AUDIO_STOP 136 +#define SYS_AUDIO_RESET 137 +#define SYS_AUDIO_SET_VOLUME 138 +#define SYS_AUDIO_SET_MASTER_VOLUME 139 // Input. -#define SYS_READ_INPUT_MANAGER_EVENT 134 -#define SYS_POLL_KEYBOARD 135 -#define SYS_IS_KEY_DOWN 136 -#define SYS_READ_KEYBOARD_CHAR 137 -#define SYS_READ_KEY 138 -#define SYS_GET_KEY_MODIFIERS 139 -#define SYS_QUERY_GLOBAL_INPUT_STATE 140 -#define SYS_OPEN_INPUT_QUEUE 141 -#define SYS_READ_INPUT_EVENT 142 -#define SYS_PEEK_INPUT_EVENT 143 -#define SYS_PEEK_MOUSE 144 -#define SYS_READ_MOUSE 145 -#define SYS_GET_MOUSE_POS 146 +#define SYS_READ_INPUT_MANAGER_EVENT 140 +#define SYS_POLL_KEYBOARD 141 +#define SYS_IS_KEY_DOWN 142 +#define SYS_READ_KEYBOARD_CHAR 143 +#define SYS_READ_KEY 144 +#define SYS_GET_KEY_MODIFIERS 145 +#define SYS_QUERY_GLOBAL_INPUT_STATE 146 +#define SYS_OPEN_INPUT_QUEUE 147 +#define SYS_READ_INPUT_EVENT 148 +#define SYS_PEEK_INPUT_EVENT 149 +#define SYS_PEEK_MOUSE 150 +#define SYS_READ_MOUSE 151 +#define SYS_GET_MOUSE_POS 152 // Time, debug, and system information. -#define SYS_CREATE_TIMER 147 -#define SYS_SET_TIMER 148 -#define SYS_CANCEL_TIMER 149 -#define SYS_GET_TICK 150 -#define SYS_GET_TICK_FREQUENCY 151 -#define SYS_GET_SYSTEM_TIME 152 -#define SYS_PRINT_DEBUG 153 -#define SYS_QUERY_TRAP_FRAME 154 -#define SYS_GET_MEMORY_STATUS 155 -#define SYS_QUERY_KERNEL_STATS 156 +#define SYS_CREATE_TIMER 153 +#define SYS_SET_TIMER 154 +#define SYS_CANCEL_TIMER 155 +#define SYS_GET_TICK 156 +#define SYS_GET_TICK_FREQUENCY 157 +#define SYS_GET_SYSTEM_TIME 158 +#define SYS_PRINT_DEBUG 159 +#define SYS_QUERY_TRAP_FRAME 160 +#define SYS_GET_MEMORY_STATUS 161 +#define SYS_QUERY_KERNEL_STATS 162 // Native sockets and polling. -#define SYS_CREATE_SOCKET 157 -#define SYS_SOCKET_IO_ASYNC 158 -#define SYS_BIND_SOCKET 159 -#define SYS_LISTEN_SOCKET 160 -#define SYS_ACCEPT_SOCKET 161 -#define SYS_CONNECT_SOCKET 162 -#define SYS_SEND_SOCKET 163 -#define SYS_RECV_SOCKET 164 -#define SYS_SHUTDOWN_SOCKET 165 -#define SYS_SEND_TO_SOCKET 166 -#define SYS_RECV_FROM_SOCKET 167 -#define SYS_RESOLVE_HOST 168 -#define SYS_SET_SOCKET_OPTION 169 -#define SYS_GET_SOCKET_OPTION 170 -#define SYS_POLL 171 +#define SYS_CREATE_SOCKET 163 +#define SYS_SOCKET_IO_ASYNC 164 +#define SYS_BIND_SOCKET 165 +#define SYS_LISTEN_SOCKET 166 +#define SYS_ACCEPT_SOCKET 167 +#define SYS_CONNECT_SOCKET 168 +#define SYS_SEND_SOCKET 169 +#define SYS_RECV_SOCKET 170 +#define SYS_SHUTDOWN_SOCKET 171 +#define SYS_SEND_TO_SOCKET 172 +#define SYS_RECV_FROM_SOCKET 173 +#define SYS_RESOLVE_HOST 174 +#define SYS_SET_SOCKET_OPTION 175 +#define SYS_GET_SOCKET_OPTION 176 +#define SYS_POLL 177 -#define SYS_MAX 172 +#define SYS_MAX 178 diff --git a/code/kernel/ke_apc.c b/code/kernel/ke_apc.c index ef3668e..19cb282 100644 --- a/code/kernel/ke_apc.c +++ b/code/kernel/ke_apc.c @@ -265,8 +265,10 @@ __declspec(dllexport) KSTATUS KeQueueUserApc(THREAD* thread, void* routine, void* normalContext, void* systemArgument1, - void* systemArgument2) { - if (!thread || !routine) + void* systemArgument2, + uint32_t flags) { + if (!thread || !routine || + (flags & ~KE_USER_APC_FLAG_INTERRUPT) != 0) return NTLSTATUS_INVALID_PARAMETER; if (!KiUserAddressValid((uint64_t)(uintptr_t)routine)) return NTLSTATUS_ACCESS_DENIED; @@ -283,14 +285,15 @@ __declspec(dllexport) KSTATUS KeQueueUserApc(THREAD* thread, apc->NormalContext = normalContext; apc->SystemArgument1 = systemArgument1; apc->SystemArgument2 = systemArgument2; + apc->Flags = flags; - uintptr_t flags = KeAcquireSpinlock(&thread->UserApcLock); + uintptr_t lockFlags = KeAcquireSpinlock(&thread->UserApcLock); ListInsertTail(&thread->UserApcQueue, &apc->Link); thread->UserApcPending = 1; uint8_t wakeAlertable = (thread->State == ThreadWaiting && thread->AlertableWait && thread->UserApcDispatcher != 0); - KeReleaseSpinlock(&thread->UserApcLock, flags); + KeReleaseSpinlock(&thread->UserApcLock, lockFlags); if (wakeAlertable) KiWakeThreadForUserApc(thread); @@ -298,17 +301,23 @@ __declspec(dllexport) KSTATUS KeQueueUserApc(THREAD* thread, return NTLSTATUS_SUCCESS; } -// Implements the pop User Apc operation. -static KUSER_APC* KiPopUserApc(THREAD* thread) { +// Pops the first APC eligible for the current return-to-user transition. +static KUSER_APC* KiPopUserApc(THREAD* thread, bool alertableDelivery) { uintptr_t flags = KeAcquireSpinlock(&thread->UserApcLock); - LIST_ENTRY* node = ListPopHead(&thread->UserApcQueue); - if (LIST_IS_EMPTY(&thread->UserApcQueue)) - thread->UserApcPending = 0; + LIST_ENTRY* node = thread->UserApcQueue.Fwd; + while (node != &thread->UserApcQueue) { + KUSER_APC* apc = CONTAINING_RECORD(node, KUSER_APC, Link); + if (alertableDelivery || (apc->Flags & KE_USER_APC_FLAG_INTERRUPT) != 0) { + KeListRemoveChecked(node); + if (LIST_IS_EMPTY(&thread->UserApcQueue)) + thread->UserApcPending = 0; + KeReleaseSpinlock(&thread->UserApcLock, flags); + return apc; + } + node = node->Fwd; + } KeReleaseSpinlock(&thread->UserApcLock, flags); - - if (!node) - return (KUSER_APC*)0; - return CONTAINING_RECORD(node, KUSER_APC, Link); + return (KUSER_APC*)0; } // Implements the check For User Apc Delivery operation. @@ -320,14 +329,14 @@ void KiCheckForUserApcDelivery(KTRAP_FRAME* frame) { if (!thread) return; - if (!thread->UserApcDeliverable || thread->UserApcDeliveryActive || - !thread->UserApcPending || !thread->UserApcDispatcher) + if (thread->UserApcDeliveryActive || !thread->UserApcPending || + !thread->UserApcDispatcher) return; if (KeGetCurrentIrql() != PASSIVE_LEVEL) return; - KUSER_APC* apc = KiPopUserApc(thread); + KUSER_APC* apc = KiPopUserApc(thread, thread->UserApcDeliverable != 0); if (!apc) return; diff --git a/code/kernel/ke_handle.c b/code/kernel/ke_handle.c index e321c10..1b08475 100644 --- a/code/kernel/ke_handle.c +++ b/code/kernel/ke_handle.c @@ -234,10 +234,10 @@ SYS_RIGHTS KeGetValidHandleRights(OBJECT_TYPE type) { case ObjectTypeProcess: return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_PROCESS_RIGHT_TERMINATE | SYS_PROCESS_RIGHT_INSERT_HANDLE | - SYS_PROCESS_RIGHT_BUILD; + SYS_PROCESS_RIGHT_BUILD | SYS_PROCESS_RIGHT_SECURITY_CONTEXT; case ObjectTypeThread: return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_THREAD_RIGHT_RESUME | - SYS_THREAD_RIGHT_SET_INFORMATION; + SYS_THREAD_RIGHT_SET_INFORMATION | SYS_THREAD_RIGHT_NOTIFY; case ObjectTypeMutex: return management | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_MUTEX_RIGHT_RELEASE; case ObjectTypeSemaphore: @@ -288,7 +288,7 @@ SYS_RIGHTS KeGetValidHandleRights(OBJECT_TYPE type) { SYS_PORT_RIGHT_CONNECT | SYS_PORT_RIGHT_ACCEPT | SYS_PORT_RIGHT_SEND | SYS_PORT_RIGHT_RECEIVE | SYS_PORT_RIGHT_CALL | SYS_PORT_RIGHT_REPLY | - SYS_PORT_RIGHT_DISCONNECT; + SYS_PORT_RIGHT_DISCONNECT | SYS_PORT_RIGHT_OPEN_PEER_PROCESS; case ObjectTypeVolume: return management | SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY; case ObjectTypeVolumeManager: diff --git a/code/kernel/ke_port.c b/code/kernel/ke_port.c index 8bc7b21..f1556ac 100644 --- a/code/kernel/ke_port.c +++ b/code/kernel/ke_port.c @@ -94,6 +94,8 @@ struct PORT_CONNECTION { volatile long RefCount; SPINLOCK Lock; struct PORT* Endpoints[2]; + struct PROCESS* PeerProcesses[2]; + SYS_RIGHTS PeerProcessRights[2]; uint8_t Disconnected[2]; LIST_ENTRY Calls; }; @@ -103,6 +105,7 @@ typedef struct PORT_LISTENER_STATE { LIST_ENTRY Pending; uint32_t PendingCount; uint32_t Backlog; + SYS_RIGHTS PeerProcessRights; EVENT* WaitEvent; } PORT_LISTENER_STATE; @@ -164,11 +167,13 @@ void KePortQueryStats(KE_PORT_STATS* statsOut) { } // Returns the complete capability rights granted to a newly created endpoint handle. -static SYS_RIGHTS KiPortEndpointRights(void) { - return SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | - SYS_PORT_RIGHT_SEND | SYS_PORT_RIGHT_RECEIVE | - SYS_PORT_RIGHT_CALL | SYS_PORT_RIGHT_REPLY | - SYS_PORT_RIGHT_DISCONNECT; +static SYS_RIGHTS KiPortEndpointRights(bool openPeerProcess) { + SYS_RIGHTS rights = SYS_RIGHT_MANAGE | SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | + SYS_PORT_RIGHT_SEND | SYS_PORT_RIGHT_RECEIVE | + SYS_PORT_RIGHT_CALL | SYS_PORT_RIGHT_REPLY | + SYS_PORT_RIGHT_DISCONNECT; + if (openPeerProcess) rights |= SYS_PORT_RIGHT_OPEN_PEER_PROCESS; + return rights; } // Returns one process security identity for kernel-stamped IPC metadata. @@ -273,6 +278,12 @@ static void KiPortDereferenceConnection(PORT_CONNECTION* connection) { "port: destroying connection with pending calls connection=0x%016llX\n", (uint64_t)(uintptr_t)connection); } + for (uint32_t side = 0; side < 2u; ++side) { + if (connection->PeerProcesses[side]) { + ObDereferenceObject(&connection->PeerProcesses[side]->Header); + connection->PeerProcesses[side] = (struct PROCESS*)0; + } + } MmFreePool(connection); } @@ -713,8 +724,11 @@ static KSTATUS KiPortCreateEndpoint(PORT_CONNECTION* connection, uint32_t side, } // Allocates one paired connection and its two endpoint objects. -static KSTATUS KiPortCreateConnection(PORT** clientOut, PORT** serverOut) { - if (!clientOut || !serverOut) +static KSTATUS KiPortCreateConnection(PROCESS* clientProcess, + SYS_RIGHTS serverPeerProcessRights, + PORT** clientOut, + PORT** serverOut) { + if (!clientProcess || !clientOut || !serverOut) return NTLSTATUS_INVALID_PARAMETER; *clientOut = (PORT*)0; *serverOut = (PORT*)0; @@ -727,6 +741,15 @@ static KSTATUS KiPortCreateConnection(PORT** clientOut, PORT** serverOut) { connection->RefCount = 2; connection->Lock = (SPINLOCK)SPINLOCK_INIT; LIST_INIT(&connection->Calls); + if (serverPeerProcessRights != 0) { + if (!ObTryReferenceObject(&clientProcess->Header)) { + connection->RefCount = 1; + KiPortDereferenceConnection(connection); + return NTLSTATUS_OBJECT_DEAD; + } + connection->PeerProcesses[1] = clientProcess; + connection->PeerProcessRights[1] = serverPeerProcessRights; + } PORT* client = (PORT*)0; PORT* server = (PORT*)0; @@ -754,10 +777,12 @@ static KSTATUS KiPortCreateConnection(PORT** clientOut, PORT** serverOut) { } // Creates one bounded listener Port with a waitable pending-connection event. -KSTATUS KePortCreate(uint32_t backlog, PORT** portOut) { +KSTATUS KePortCreate(uint32_t backlog, SYS_RIGHTS peerProcessRights, PORT** portOut) { if (!portOut) return NTLSTATUS_INVALID_PARAMETER; *portOut = (PORT*)0; + if ((peerProcessRights & ~KeGetValidHandleRights(ObjectTypeProcess)) != 0) + return NTLSTATUS_INVALID_PARAMETER; if (backlog == 0) backlog = SYS_PORT_DEFAULT_BACKLOG; if (backlog > SYS_PORT_MAX_BACKLOG) @@ -770,6 +795,7 @@ KSTATUS KePortCreate(uint32_t backlog, PORT** portOut) { port->Kind = PORT_KIND_LISTENER; port->u.Listener.Lock = (SPINLOCK)SPINLOCK_INIT; port->u.Listener.Backlog = backlog; + port->u.Listener.PeerProcessRights = peerProcessRights; LIST_INIT(&port->u.Listener.Pending); KSTATUS status = KeEventCreate(EventManualReset, 0, &port->u.Listener.WaitEvent); @@ -816,14 +842,17 @@ KSTATUS KePortConnect(PORT* listener, PORT* clientEndpoint = (PORT*)0; PORT* serverEndpoint = (PORT*)0; - KSTATUS status = KiPortCreateConnection(&clientEndpoint, &serverEndpoint); + KSTATUS status = KiPortCreateConnection(clientProcess, + listener->u.Listener.PeerProcessRights, + &clientEndpoint, + &serverEndpoint); if (!KSUCCESS(status)) return status; SYS_HANDLE clientHandle = SYS_HANDLE_INVALID; status = KeCreateHandle(clientProcess, &clientEndpoint->Header, - KiPortEndpointRights(), + KiPortEndpointRights(false), &clientHandle); if (!KSUCCESS(status)) { ObDereferenceObject(&serverEndpoint->Header); @@ -899,7 +928,7 @@ KSTATUS KePortAccept(PORT* listener, SYS_HANDLE handle = SYS_HANDLE_INVALID; KSTATUS status = KeCreateHandle(serverProcess, &pending->ServerEndpoint->Header, - KiPortEndpointRights(), + KiPortEndpointRights(listener->u.Listener.PeerProcessRights != 0), &handle); if (KSUCCESS(status)) { if (infoOut) @@ -1634,3 +1663,24 @@ KSTATUS KePortDisconnect(PORT* endpoint) { KiPortMarkDisconnected(endpoint, false); return NTLSTATUS_SUCCESS; } + +// Opens one process capability through an accepted endpoint's peer-authority ceiling. +KSTATUS KePortOpenPeerProcess(PORT* endpoint, + PROCESS* callerProcess, + SYS_RIGHTS desiredRights, + SYS_HANDLE* processHandleOut) { + if (!endpoint || endpoint->Kind != PORT_KIND_ENDPOINT || !callerProcess || + desiredRights == 0 || !processHandleOut) + return NTLSTATUS_INVALID_PARAMETER; + + PORT_CONNECTION* connection = endpoint->u.Endpoint.Connection; + uint32_t side = endpoint->u.Endpoint.Side; + if (!connection || side > 1u) return NTLSTATUS_PORT_DISCONNECTED; + + PROCESS* peer = connection->PeerProcesses[side]; + SYS_RIGHTS ceiling = connection->PeerProcessRights[side]; + if (!peer || ceiling == 0 || (desiredRights & ~ceiling) != 0) + return NTLSTATUS_ACCESS_DENIED; + + return KeCreateHandle(callerProcess, &peer->Header, desiredRights, processHandleOut); +} diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index 8ddde35..2d07c03 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -416,6 +416,33 @@ static uint64_t SyscallCreateThread(const SYS_CREATE_THREAD_REQUEST* request) { return NTLSTATUS_SUCCESS; } +// Implements SYS_OPEN_CURRENT_THREAD. +static uint64_t SyscallOpenCurrentThread(SYS_RIGHTS desiredAccess, SYS_HANDLE* handleOut) { + if (!handleOut || desiredAccess == 0) return NTLSTATUS_INVALID_PARAMETER; + + SYS_RIGHTS validAccess = KeGetValidHandleRights(ObjectTypeThread); + if ((desiredAccess & ~validAccess) != 0) return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = MmProbeUserWrite(handleOut, sizeof(SYS_HANDLE)); + if (!KSUCCESS(status)) return status; + + PROCESS* process = KeGetCurrentProcess(); + THREAD* thread = KeGetCurrentThread(); + if (!process || !thread) return NTLSTATUS_FAILURE; + + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = KeCreateHandle(process, &thread->Header, desiredAccess, &handle); + if (!KSUCCESS(status)) return status; + + status = KiCopyHandleToUser(handleOut, handle); + if (!KSUCCESS(status)) { + KeCloseHandle(process, handle); + return status; + } + + return NTLSTATUS_SUCCESS; +} + // Implements SYS_RESUME_THREAD. static uint64_t SyscallResumeThread(SYS_HANDLE threadHandle, uint32_t* previousSuspendCountOut) { if (!threadHandle || !previousSuspendCountOut) return NTLSTATUS_INVALID_PARAMETER; @@ -440,6 +467,61 @@ static uint64_t SyscallResumeThread(SYS_HANDLE threadHandle, uint32_t* previousS return KiCopyU32ToUser(previousSuspendCountOut, previousSuspendCount); } +// Implements SYS_RESUME_THREAD_AND_WAIT. +// Resumes one suspended thread and blocks until that same thread exits without +// returning to userspace between the resume and wait operations. +static uint64_t SyscallResumeThreadAndWait(SYS_HANDLE threadHandle, + uint32_t* previousSuspendCountOut) { + if (!threadHandle || !previousSuspendCountOut) return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = MmProbeUserWrite(previousSuspendCountOut, sizeof(uint32_t)); + if (!KSUCCESS(status)) return status; + + PROCESS* process = KeGetCurrentProcess(); + THREAD* currentThread = KeGetCurrentThread(); + if (!process || !currentThread) return NTLSTATUS_FAILURE; + + OBJECT* object = (OBJECT*)0; + status = KeReferenceHandle(process, threadHandle, ObjectTypeThread, + SYS_THREAD_RIGHT_RESUME | SYS_RIGHT_WAIT, &object); + if (!KSUCCESS(status)) return status; + + THREAD* thread = CONTAINING_RECORD(object, THREAD, Header); + if (thread == currentThread) { + ObDereferenceObject(object); + return NTLSTATUS_INVALID_PARAMETER; + } + + uint32_t previousSuspendCount = 0; + status = KeResumeThread(thread, &previousSuspendCount); + if (KSUCCESS(status)) + status = KeWaitForEvent(thread->ExitEvent, 0); + + ObDereferenceObject(object); + if (!KSUCCESS(status)) return status; + return KiCopyU32ToUser(previousSuspendCountOut, previousSuspendCount); +} + +// Implements SYS_GET_THREAD_TID. +static uint64_t SyscallGetThreadTid(SYS_HANDLE threadHandle, uint32_t* tidOut) { + if (!threadHandle || !tidOut) return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = MmProbeUserWrite(tidOut, sizeof(uint32_t)); + if (!KSUCCESS(status)) return status; + + PROCESS* process = KeGetCurrentProcess(); + if (!process) return NTLSTATUS_FAILURE; + + OBJECT* object = (OBJECT*)0; + status = KeReferenceHandle(process, threadHandle, ObjectTypeThread, SYS_RIGHT_QUERY, &object); + if (!KSUCCESS(status)) return status; + + THREAD* thread = CONTAINING_RECORD(object, THREAD, Header); + uint32_t tid = thread->ThreadId; + ObDereferenceObject(object); + return KiCopyU32ToUser(tidOut, tid); +} + static void KiFinishAlertableUserWait(uint8_t alertable) { if (!alertable) return; @@ -740,6 +822,36 @@ static uint64_t SyscallSetUserApcDispatcher(void* dispatcher) { return KeSetUserApcDispatcher(dispatcher); } +// Implements SYS_QUEUE_USER_APC. +static uint64_t SyscallQueueUserApc(const SYS_QUEUE_USER_APC_REQUEST* userRequest) { + SYS_QUEUE_USER_APC_REQUEST request; + KSTATUS status = KiCopySizedUserRequest(userRequest, &request, sizeof(request)); + if (!KSUCCESS(status)) return status; + if (!request.ThreadHandle || !request.Routine || request.Reserved != 0 || + (request.Flags & ~SYS_QUEUE_USER_APC_FLAG_INTERRUPT) != 0) + return NTLSTATUS_INVALID_PARAMETER; + + PROCESS* process = KeGetCurrentProcess(); + if (!process) return NTLSTATUS_FAILURE; + + OBJECT* object = (OBJECT*)0; + status = KeReferenceHandle(process, request.ThreadHandle, ObjectTypeThread, + SYS_THREAD_RIGHT_NOTIFY, &object); + if (!KSUCCESS(status)) return status; + + THREAD* thread = CONTAINING_RECORD(object, THREAD, Header); + status = KeQueueUserApc(thread, + (void*)(uintptr_t)request.Routine, + request.NormalContext, + request.SystemArgument1, + request.SystemArgument2, + (request.Flags & SYS_QUEUE_USER_APC_FLAG_INTERRUPT) != 0 + ? KE_USER_APC_FLAG_INTERRUPT + : KE_USER_APC_FLAG_NONE); + ObDereferenceObject(object); + return status; +} + // Implements SYS_APC_RETURN. static uint64_t SyscallApcReturn(const SYS_USER_APC_FRAME* frame) { return KeUserApcReturn((const SYS_USER_APC_FRAME_K*)(uintptr_t)frame); @@ -1978,7 +2090,8 @@ static void SyscallUserAsyncNormalRoutine(void* normalContext, (void*)ctx->UserRoutine, ctx->UserContext, iosb, - (void*)(uintptr_t)information); + (void*)(uintptr_t)information, + KE_USER_APC_FLAG_NONE); if (!KSUCCESS(s)) { DbgPrint("USER/APC async: KeQueueUserApc failed status=%u\n", s); } @@ -3105,7 +3218,7 @@ static uint64_t SyscallOpenProcess(uint32_t pid, SYS_RIGHTS desiredAccess, SYS_H PROCESS* caller = KeGetCurrentProcess(); if (!caller) return NTLSTATUS_FAILURE; - if (pid != caller->ProcessId) { + if (pid != caller->ProcessId || (desiredAccess & SYS_PROCESS_RIGHT_SECURITY_CONTEXT) != 0) { KSTATUS privilegeStatus = KiRequirePrivilege(SYS_PRIV_PROCESS_OPEN_ANY); if (!KSUCCESS(privilegeStatus)) return privilegeStatus; } @@ -3214,13 +3327,16 @@ static uint64_t SyscallCreatePort(const SYS_CREATE_PORT_REQUEST* request) { SYS_CREATE_PORT_REQUEST req; KSTATUS status = KiCopySizedUserRequest(request, &req, sizeof(req)); if (!KSUCCESS(status)) return status; - if (req.Flags != SYS_PORT_FLAG_NONE || !req.HandleOut) + if (req.Flags != SYS_PORT_FLAG_NONE || req.Reserved != 0 || !req.HandleOut) + return NTLSTATUS_INVALID_PARAMETER; + SYS_RIGHTS validPeerRights = KeGetValidHandleRights(ObjectTypeProcess); + if ((req.PeerProcessRights & ~validPeerRights) != 0) return NTLSTATUS_INVALID_PARAMETER; status = MmProbeUserWrite(req.HandleOut, sizeof(*req.HandleOut)); if (!KSUCCESS(status)) return status; PORT* port = (PORT*)0; - status = KePortCreate(req.Backlog, &port); + status = KePortCreate(req.Backlog, req.PeerProcessRights, &port); if (!KSUCCESS(status)) return status; SYS_HANDLE handle = SYS_HANDLE_INVALID; @@ -3509,6 +3625,36 @@ static uint64_t SyscallDisconnectPort(SYS_HANDLE endpointHandle) { return status; } +// Implements SYS_OPEN_PORT_PEER_PROCESS. +static uint64_t SyscallOpenPortPeerProcess(SYS_HANDLE endpointHandle, + SYS_RIGHTS desiredRights, + SYS_HANDLE* processHandleOut) { + if (!processHandleOut || desiredRights == 0) return NTLSTATUS_INVALID_PARAMETER; + SYS_RIGHTS validRights = KeGetValidHandleRights(ObjectTypeProcess); + if ((desiredRights & ~validRights) != 0) return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = MmProbeUserWrite(processHandleOut, sizeof(*processHandleOut)); + if (!KSUCCESS(status)) return status; + + KSTATUS handleStatus = NTLSTATUS_SUCCESS; + PORT* endpoint = (PORT*)KiHandleToObject(endpointHandle, + ObjectTypePort, + SYS_PORT_RIGHT_OPEN_PEER_PROCESS, + &handleStatus); + if (!endpoint) return handleStatus; + + PROCESS* caller = KeGetCurrentProcess(); + SYS_HANDLE processHandle = SYS_HANDLE_INVALID; + status = caller ? KePortOpenPeerProcess(endpoint, caller, desiredRights, &processHandle) + : NTLSTATUS_FAILURE; + ObDereferenceObject((OBJECT*)endpoint); + if (!KSUCCESS(status)) return status; + + status = KiCopyHandleToUser(processHandleOut, processHandle); + if (!KSUCCESS(status) && caller) (void)KeCloseHandle(caller, processHandle); + return status; +} + // Implements SYS_REGISTER_SYSTEM_SERVICE. static uint64_t SyscallRegisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle) { if (serviceId == SYS_SERVICE_INVALID || serviceId > SYS_SERVICE_MAX) @@ -4060,6 +4206,40 @@ static uint64_t SyscallQuerySecurityContext(SYS_HANDLE contextHandle, SYS_SECURI return CopyToUser(infoOut, &info, sizeof(info)); } +// Implements SYS_OPEN_PROCESS_SECURITY_CONTEXT. +static uint64_t SyscallOpenProcessSecurityContext(SYS_HANDLE processHandle, + SYS_RIGHTS desiredRights, + SYS_HANDLE* contextHandleOut) { + if (!contextHandleOut || desiredRights == 0) return NTLSTATUS_INVALID_PARAMETER; + SYS_RIGHTS validRights = KeGetValidHandleRights(ObjectTypeSecurityContext); + if ((desiredRights & ~validRights) != 0) return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = MmProbeUserWrite(contextHandleOut, sizeof(*contextHandleOut)); + if (!KSUCCESS(status)) return status; + + PROCESS* target = (PROCESS*)KiHandleToObject(processHandle, + ObjectTypeProcess, + SYS_PROCESS_RIGHT_SECURITY_CONTEXT, + &status); + if (!target) return status; + SECURITY_CONTEXT* context = target->SecurityContext; + if (!context || !ObTryReferenceObject(&context->Header)) { + ObDereferenceObject(&target->Header); + return NTLSTATUS_OBJECT_DEAD; + } + ObDereferenceObject(&target->Header); + + SYS_HANDLE handle = SYS_HANDLE_INVALID; + status = KiAllocHandleForCurrentWithRights(&context->Header, desiredRights, &handle); + if (!KSUCCESS(status)) return status; + status = KiCopyHandleToUser(contextHandleOut, handle); + if (!KSUCCESS(status)) { + PROCESS* caller = KeGetCurrentProcess(); + if (caller) (void)KeCloseHandle(caller, handle); + } + return status; +} + // Implements SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS. // Opens a process handle for the process that published a named queue. @@ -6542,8 +6722,11 @@ static const SYSCALL_ENTRY gSyscallTable[SYS_MAX] = { [SYS_GET_PROCESS_LIST] = SYSCALL_ENTRY3(SyscallGetProcessList), [SYS_GET_PROCESS_PID] = SYSCALL_ENTRY2(SyscallGetProcessPid), [SYS_CREATE_THREAD] = SYSCALL_ENTRY1(SyscallCreateThread), + [SYS_OPEN_CURRENT_THREAD] = SYSCALL_ENTRY2(SyscallOpenCurrentThread), [SYS_RESUME_THREAD] = SYSCALL_ENTRY2(SyscallResumeThread), + [SYS_RESUME_THREAD_AND_WAIT] = SYSCALL_ENTRY2(SyscallResumeThreadAndWait), [SYS_GET_THREAD_LIST] = SYSCALL_ENTRY4(SyscallGetThreadList), + [SYS_GET_THREAD_TID] = SYSCALL_ENTRY2(SyscallGetThreadTid), [SYS_GET_CURRENT_TID] = SYSCALL_ENTRY0(SyscallGetCurrentTid), [SYS_GET_CURRENT_PID] = SYSCALL_ENTRY0(SyscallGetCurrentPid), [SYS_SET_THREAD_PRIORITY] = SYSCALL_ENTRY1(SyscallSetThreadPriority), @@ -6554,6 +6737,7 @@ static const SYSCALL_ENTRY gSyscallTable[SYS_MAX] = { [SYS_WAIT_SINGLE] = SYSCALL_ENTRY3(SyscallWaitSingle), [SYS_WAIT_MANY] = SYSCALL_ENTRY1(SyscallWaitMany), [SYS_SET_USER_APC_DISPATCHER] = SYSCALL_ENTRY1(SyscallSetUserApcDispatcher), + [SYS_QUEUE_USER_APC] = SYSCALL_ENTRY1(SyscallQueueUserApc), [SYS_APC_RETURN] = SYSCALL_ENTRY1(SyscallApcReturn), [SYS_SET_EXCEPTION_DISPATCHER] = SYSCALL_ENTRY1(SyscallSetExceptionDispatcher), [SYS_GET_EXCEPTION_DISPATCHER] = SYSCALL_ENTRY1(SyscallGetExceptionDispatcher), @@ -6571,6 +6755,7 @@ static const SYSCALL_ENTRY gSyscallTable[SYS_MAX] = { [SYS_REVOKE_HANDLE] = SYSCALL_ENTRY1(SyscallRevokeHandle), [SYS_DERIVE_SECURITY_CONTEXT] = SYSCALL_ENTRY1(SyscallDeriveSecurityContext), [SYS_QUERY_SECURITY_CONTEXT] = SYSCALL_ENTRY2(SyscallQuerySecurityContext), + [SYS_OPEN_PROCESS_SECURITY_CONTEXT] = SYSCALL_ENTRY3(SyscallOpenProcessSecurityContext), [SYS_CREATE_FILE_MAPPING] = SYSCALL_ENTRY1(SyscallCreateFileMapping), [SYS_MAP_FILE] = SYSCALL_ENTRY1(SyscallMapFile), @@ -6603,6 +6788,7 @@ static const SYSCALL_ENTRY gSyscallTable[SYS_MAX] = { [SYS_CALL_PORT] = SYSCALL_ENTRY1(SyscallCallPort), [SYS_REPLY_PORT] = SYSCALL_ENTRY1(SyscallReplyPort), [SYS_DISCONNECT_PORT] = SYSCALL_ENTRY1(SyscallDisconnectPort), + [SYS_OPEN_PORT_PEER_PROCESS] = SYSCALL_ENTRY3(SyscallOpenPortPeerProcess), [SYS_REGISTER_SYSTEM_SERVICE] = SYSCALL_ENTRY2(SyscallRegisterSystemService), [SYS_UNREGISTER_SYSTEM_SERVICE] = SYSCALL_ENTRY2(SyscallUnregisterSystemService), [SYS_CONNECT_SYSTEM_SERVICE] = SYSCALL_ENTRY1(SyscallConnectSystemService), diff --git a/code/kernel/ke_trap.c b/code/kernel/ke_trap.c index 8971f51..ca3e202 100644 --- a/code/kernel/ke_trap.c +++ b/code/kernel/ke_trap.c @@ -75,8 +75,9 @@ void KiExitTrapFrame(KTRAP_FRAME* frame) { if (!frame || frame->Magic != KTRAP_FRAME_MAGIC) KePanic(KE_PANIC_CODE_TRAP, "KiExitTrapFrame: invalid trap frame 0x%016llX\n", (uint64_t)frame); - // Give alertable user-mode returns a chance to redirect through the - // registered user APC dispatcher before committing IRET slots. + // Give eligible user APCs a chance to redirect through the registered + // dispatcher before committing IRET slots. Interrupt APCs are eligible on + // any user return; ordinary APCs still require an alertable wait. KiCheckForUserApcDelivery(frame); // Commit editable return state. Kernel-mode interrupts have no RSP/SS diff --git a/code/public/ntlite/handle.h b/code/public/ntlite/handle.h index 279833a..f359fd1 100644 --- a/code/public/ntlite/handle.h +++ b/code/public/ntlite/handle.h @@ -24,10 +24,12 @@ typedef uint32_t SYS_RIGHTS; #define SYS_PROCESS_RIGHT_TERMINATE 0x00000100u #define SYS_PROCESS_RIGHT_INSERT_HANDLE 0x00000200u #define SYS_PROCESS_RIGHT_BUILD 0x00000400u +#define SYS_PROCESS_RIGHT_SECURITY_CONTEXT 0x00000800u // Thread-specific rights. #define SYS_THREAD_RIGHT_RESUME 0x00000100u #define SYS_THREAD_RIGHT_SET_INFORMATION 0x00000200u +#define SYS_THREAD_RIGHT_NOTIFY 0x00000400u // Mutex-specific rights. #define SYS_MUTEX_RIGHT_RELEASE 0x00000100u diff --git a/code/public/ntlite/port.h b/code/public/ntlite/port.h index 8ff3261..26d6788 100644 --- a/code/public/ntlite/port.h +++ b/code/public/ntlite/port.h @@ -37,8 +37,9 @@ #define SYS_PORT_RIGHT_SEND 0x00000400u #define SYS_PORT_RIGHT_RECEIVE 0x00000800u #define SYS_PORT_RIGHT_CALL 0x00001000u -#define SYS_PORT_RIGHT_REPLY 0x00002000u -#define SYS_PORT_RIGHT_DISCONNECT 0x00004000u +#define SYS_PORT_RIGHT_REPLY 0x00002000u +#define SYS_PORT_RIGHT_DISCONNECT 0x00004000u +#define SYS_PORT_RIGHT_OPEN_PEER_PROCESS 0x00008000u // Describes one capability that accompanies a Port message. typedef struct SYS_PORT_HANDLE_TRANSFER { @@ -74,6 +75,8 @@ typedef struct SYS_CREATE_PORT_REQUEST { uint32_t Size; uint32_t Flags; uint32_t Backlog; + SYS_RIGHTS PeerProcessRights; + uint32_t Reserved; SYS_HANDLE* HandleOut; } SYS_CREATE_PORT_REQUEST; diff --git a/code/public/ntlite/posix_service.h b/code/public/ntlite/posix_service.h index 1e52c09..9307379 100644 --- a/code/public/ntlite/posix_service.h +++ b/code/public/ntlite/posix_service.h @@ -5,9 +5,10 @@ #include #include #include +#include #define NTL_POSIX_SERVICE_PATH "/Services/posix" -#define NTL_POSIX_PROTOCOL_VERSION 6u +#define NTL_POSIX_PROTOCOL_VERSION 15u #define NTL_POSIX_REQUEST_FLAG_NONE 0u @@ -30,15 +31,37 @@ #define NTL_POSIX_OP_CREATE_FD_INHERITANCE 17u #define NTL_POSIX_OP_IMPORT_FD_INHERITANCE 18u #define NTL_POSIX_OP_CANCEL_FD_INHERITANCE 19u -#define NTL_POSIX_OP_ARM_FD_INHERITANCE 20u +#define NTL_POSIX_OP_CONSTRUCT_SET_EXECUTABLE 20u +#define NTL_POSIX_OP_CONSTRUCT_WRITE_VECTOR 21u +#define NTL_POSIX_OP_SPAWN_ADD_FILE_ACTION 22u +#define NTL_POSIX_OP_SPAWN_SET_ATTRIBUTES 23u +#define NTL_POSIX_OP_SPAWN_COMMIT 24u +#define NTL_POSIX_OP_BOOTSTRAP_READY 25u +#define NTL_POSIX_OP_WAIT_PROCESS 26u +#define NTL_POSIX_OP_REGISTER_THREAD 27u +#define NTL_POSIX_OP_UNREGISTER_THREAD 28u +#define NTL_POSIX_OP_UPDATE_SIGNAL_MASK 29u +#define NTL_POSIX_OP_SET_SIGNAL_DISPOSITION 30u +#define NTL_POSIX_OP_GET_PENDING_SIGNALS 31u +#define NTL_POSIX_OP_SEND_SIGNAL 32u +#define NTL_POSIX_OP_SET_ALARM 33u +#define NTL_POSIX_OP_EXEC_COMMIT 34u +#define NTL_POSIX_OP_VFORK_BEGIN 35u +#define NTL_POSIX_OP_VFORK_ATTACH 36u +#define NTL_POSIX_OP_VFORK_CANCEL 37u +#define NTL_POSIX_OP_VFORK_EXIT 38u #define NTL_POSIX_TRANSACTION_NONE 0u #define NTL_POSIX_TRANSACTION_SPAWN 1u #define NTL_POSIX_TRANSACTION_EXEC 2u -#define NTL_POSIX_PROCESS_STATE_NONE 0u -#define NTL_POSIX_PROCESS_STATE_RUNNING 1u -#define NTL_POSIX_PROCESS_STATE_EXITED 2u +#define NTL_POSIX_PROCESS_STATE_NONE 0u +#define NTL_POSIX_PROCESS_STATE_CONSTRUCTING 1u +#define NTL_POSIX_PROCESS_STATE_RUNNING 2u +#define NTL_POSIX_PROCESS_STATE_EXEC_PREPARING 3u +#define NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING 4u +#define NTL_POSIX_PROCESS_STATE_EXITED 5u +#define NTL_POSIX_PROCESS_STATE_VFORK_BORROWED 6u #define NTL_POSIX_LOOKUP_NONE 0u #define NTL_POSIX_LOOKUP_BY_ID 1u @@ -68,8 +91,12 @@ #define NTL_POSIX_OPEN_STATUS_ACCMODE 0x0003u #define NTL_POSIX_OPEN_STATUS_APPEND 0x0400u #define NTL_POSIX_OPEN_STATUS_NONBLOCK 0x0800u +#define NTL_POSIX_OPEN_CREATE 0x0040u +#define NTL_POSIX_OPEN_TRUNCATE 0x0200u #define NTL_POSIX_OPEN_STATUS_VALID_MASK \ (NTL_POSIX_OPEN_STATUS_ACCMODE | NTL_POSIX_OPEN_STATUS_APPEND | NTL_POSIX_OPEN_STATUS_NONBLOCK) +#define NTL_POSIX_SPAWN_OPEN_VALID_MASK \ + (NTL_POSIX_OPEN_STATUS_VALID_MASK | NTL_POSIX_OPEN_CREATE | NTL_POSIX_OPEN_TRUNCATE) #define NTL_POSIX_SHARED_OPEN_MAGIC 0x31444F50u #define NTL_POSIX_SHARED_OPEN_VERSION 2u @@ -80,6 +107,30 @@ #define NTL_POSIX_FD_DESCRIPTOR_CLOEXEC 0x00000001u #define NTL_POSIX_FD_HANDLE_NONE UINT32_MAX +#define NTL_POSIX_CONSTRUCT_VECTOR_ARGUMENT 1u +#define NTL_POSIX_CONSTRUCT_VECTOR_ENVIRONMENT 2u +#define NTL_POSIX_CONSTRUCT_VECTOR_CHUNK_FINAL 0x00000001u +#define NTL_POSIX_CONSTRUCT_VECTOR_MAX 1024u +#define NTL_POSIX_CONSTRUCT_DATA_MAX (1024u * 1024u) + +#define NTL_POSIX_SPAWN_ACTION_CLOSE 1u +#define NTL_POSIX_SPAWN_ACTION_DUP2 2u +#define NTL_POSIX_SPAWN_ACTION_OPEN 3u +#define NTL_POSIX_SPAWN_ACTION_MAX 64u + +#define NTL_POSIX_SPAWN_ATTR_SETPGROUP 0x00000001u +#define NTL_POSIX_SPAWN_ATTR_SETSIGMASK 0x00000002u +#define NTL_POSIX_SPAWN_ATTR_SETSIGDEF 0x00000004u + +#define NTL_POSIX_WAIT_NOHANG 0x00000001u + +#define NTL_POSIX_SIGNAL_DISPOSITION_DEFAULT 0u +#define NTL_POSIX_SIGNAL_DISPOSITION_IGNORE 1u +#define NTL_POSIX_SIGNAL_DISPOSITION_CAUGHT 2u + +#define NTL_POSIX_SIGNAL_ROUTE_PROCESS 1u +#define NTL_POSIX_SIGNAL_ROUTE_THREAD 2u + typedef struct NTL_POSIX_FD_MANIFEST_ENTRY { int32_t Descriptor; uint32_t DescriptorFlags; @@ -111,11 +162,11 @@ typedef struct NTL_POSIX_SHARED_OPEN_DESCRIPTION { uint64_t SharedObjectData; } NTL_POSIX_SHARED_OPEN_DESCRIPTION; -// Future server-owned spawn/exec builders place this opaque personality payload +// Server-owned spawn/exec builders place this opaque personality payload // in the generic userspace process-loader startup data. Zero token/PID/generation // means an ordinary process is self-registering for the first time. #define NTL_POSIX_BOOTSTRAP_MAGIC 0x3258504Eu -#define NTL_POSIX_BOOTSTRAP_VERSION 2u +#define NTL_POSIX_BOOTSTRAP_VERSION 3u typedef struct NTL_POSIX_BOOTSTRAP_DATA { uint32_t Magic; @@ -126,6 +177,8 @@ typedef struct NTL_POSIX_BOOTSTRAP_DATA { uint32_t PosixPid; uint32_t Generation; uint64_t FdInheritanceToken; + uint64_t InitialSignalMask; + uint64_t IgnoredSignals; } NTL_POSIX_BOOTSTRAP_DATA; // Requests remain fixed-size through P4 open-description control. Later spawn/exec @@ -155,7 +208,31 @@ typedef struct NTL_POSIX_REQUEST { uint32_t FdInheritanceIndex; uint32_t FdInheritanceCount; uint32_t FdInheritanceFlags; - uint32_t Reserved[2]; + uint32_t ConstructionVectorKind; + uint32_t ConstructionVectorIndex; + uint32_t ConstructionVectorOffset; + uint32_t ConstructionVectorFlags; + uint32_t SpawnActionKind; + int32_t SpawnFileDescriptor; + int32_t SpawnNewFileDescriptor; + uint32_t SpawnOpenFlags; + uint32_t SpawnOpenMode; + uint32_t SpawnAttributeFlags; + uint64_t SpawnSignalMask; + uint64_t SpawnSignalDefaultMask; + int32_t WaitPid; + uint32_t WaitOptions; + int32_t SignalTargetPid; + int32_t SignalNumber; + uint32_t SignalDisposition; + uint32_t SignalReserved; + uint64_t SignalMask; + uint64_t SignalApcRoutine; + uint32_t AlarmSeconds; + uint32_t AlarmReserved; + uint64_t VforkToken; + int32_t VforkExitCode; + uint32_t VforkReserved; NTL_POSIX_FD_MANIFEST_ENTRY FdManifest[NTL_POSIX_FD_INHERIT_MAX]; char Name[NTL_POSIX_IDENTITY_NAME_MAX + 1u]; char Path[SYS_PATH_MAX + 1u]; @@ -175,10 +252,19 @@ typedef struct NTL_POSIX_REPLY { uint32_t ActiveTransactions; uint32_t TransactionCapacity; uint32_t PosixPid; + uint32_t SpawnedPosixPid; + int32_t WaitedPosixPid; + int32_t ExitCode; uint32_t NativeGeneration; uint32_t ProcessState; uint32_t RegisteredProcesses; uint32_t RegisteredOpenDescriptions; + uint32_t RegisteredThreads; + uint32_t SignalReserved; + uint64_t PendingSignals; + uint32_t AlarmRemainingSeconds; + uint32_t AlarmReserved; + uint64_t VforkToken; uint32_t OpenDescriptionType; uint32_t OpenStatusFlags; uint64_t OpenDescriptionId; @@ -217,3 +303,8 @@ typedef struct NTL_POSIX_REPLY { char Shell[NTL_POSIX_IDENTITY_SHELL_MAX + 1u]; char CurrentDirectory[SYS_PATH_MAX + 1u]; } NTL_POSIX_REPLY; + +_Static_assert(sizeof(NTL_POSIX_REQUEST) <= SYS_PORT_MAX_INLINE_BYTES, + "POSIX request must fit in one Port inline payload."); +_Static_assert(sizeof(NTL_POSIX_REPLY) <= SYS_PORT_MAX_INLINE_BYTES, + "POSIX reply must fit in one Port inline payload."); diff --git a/code/public/ntlite/posix_signal.h b/code/public/ntlite/posix_signal.h new file mode 100644 index 0000000..9c26083 --- /dev/null +++ b/code/public/ntlite/posix_signal.h @@ -0,0 +1,29 @@ +// POSIX personality signal identifiers shared with posixsrv. + +#pragma once + +#include + +#define NTL_POSIX_SIGNAL_HUP 1 +#define NTL_POSIX_SIGNAL_INT 2 +#define NTL_POSIX_SIGNAL_QUIT 3 +#define NTL_POSIX_SIGNAL_ILL 4 +#define NTL_POSIX_SIGNAL_ABRT 5 +#define NTL_POSIX_SIGNAL_FPE 6 +#define NTL_POSIX_SIGNAL_KILL 7 +#define NTL_POSIX_SIGNAL_SEGV 8 +#define NTL_POSIX_SIGNAL_PIPE 9 +#define NTL_POSIX_SIGNAL_ALRM 10 +#define NTL_POSIX_SIGNAL_TERM 11 +#define NTL_POSIX_SIGNAL_USR1 12 +#define NTL_POSIX_SIGNAL_USR2 13 +#define NTL_POSIX_SIGNAL_CHLD 14 +#define NTL_POSIX_SIGNAL_MAX 14 + +#define NTL_POSIX_SIGNAL_BIT(signalNumber) (1ull << ((uint32_t)(signalNumber) - 1u)) +#define NTL_POSIX_SIGNAL_VALID_MASK ((1ull << NTL_POSIX_SIGNAL_MAX) - 1ull) +#define NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK NTL_POSIX_SIGNAL_BIT(NTL_POSIX_SIGNAL_KILL) + +static inline int NtlPosixSignalValid(int signalNumber) { + return signalNumber >= NTL_POSIX_SIGNAL_HUP && signalNumber <= NTL_POSIX_SIGNAL_MAX; +} diff --git a/code/public/ntlite/process_loader.h b/code/public/ntlite/process_loader.h index 9aa78e6..b3c2050 100644 --- a/code/public/ntlite/process_loader.h +++ b/code/public/ntlite/process_loader.h @@ -13,6 +13,13 @@ #define NTL_PROCESS_LOAD_FLAG_NONE 0x00000000u #define NTL_PROCESS_STARTUP_DATA_MAX 1024u +// Generic userspace process vectors. The builder copies every view into the +// child loader block; callers retain ownership of the source arrays/strings. +typedef struct NTL_PROCESS_STRING_VECTOR { + const SYS_STRING_VIEW* Entries; + uint32_t Count; +} NTL_PROCESS_STRING_VECTOR; + // Describes one source capability to duplicate into a constructing child. typedef struct NTL_PROCESS_STARTUP_CAPABILITY { uint32_t Tag; @@ -21,6 +28,35 @@ typedef struct NTL_PROCESS_STARTUP_CAPABILITY { uint32_t DuplicateFlags; } NTL_PROCESS_STARTUP_CAPABILITY; +// Reusable inert-process builder used by policy services such as posixsrv. +// The storage is opaque to callers; initialize it to zero before first use. +#define NTL_PROCESS_BUILDER_OPAQUE_WORDS 160u +typedef struct NTL_PROCESS_BUILDER { + uintptr_t Opaque[NTL_PROCESS_BUILDER_OPAQUE_WORDS]; +} NTL_PROCESS_BUILDER; + +// Describes construction of an inert child. Unlike NTL_CREATE_PROCESS_AT_REQUEST, +// preparation does not start the process and therefore has no output handle field. +typedef struct NTL_PROCESS_BUILDER_REQUEST { + uint32_t Size; + uint32_t Flags; + SYS_HANDLE RootHandle; + SYS_STRING_VIEW Path; + SYS_STRING_VIEW CommandLine; + SYS_STRING_VIEW CurrentDirectory; + SYS_HANDLE SecurityContext; + SYS_HANDLE ModuleRootHandle; + SYS_STRING_VIEW ModuleDirectory; + NTL_MODULE_NAME_RESOLVER ResolveModuleName; + void* ResolveContext; + const NTL_PROCESS_STARTUP_CAPABILITY* StartupCapabilities; + uint32_t StartupCapabilityCount; + NTL_PROCESS_STRING_VECTOR Arguments; + NTL_PROCESS_STRING_VECTOR Environment; + const void* StartupData; + size_t StartupDataSize; +} NTL_PROCESS_BUILDER_REQUEST; + // Creates and starts one PE32+ process entirely through the userspace image loader. typedef struct NTL_CREATE_PROCESS_AT_REQUEST { uint32_t Size; @@ -37,6 +73,8 @@ typedef struct NTL_CREATE_PROCESS_AT_REQUEST { void* ResolveContext; const NTL_PROCESS_STARTUP_CAPABILITY* StartupCapabilities; uint32_t StartupCapabilityCount; + NTL_PROCESS_STRING_VECTOR Arguments; + NTL_PROCESS_STRING_VECTOR Environment; // Optional opaque userspace bootstrap data copied into the child's read-only loader block. const void* StartupData; size_t StartupDataSize; @@ -59,6 +97,8 @@ typedef struct NTL_CREATE_SYSTEM_PROCESS_REQUEST { void* ResolveContext; const NTL_PROCESS_STARTUP_CAPABILITY* StartupCapabilities; uint32_t StartupCapabilityCount; + NTL_PROCESS_STRING_VECTOR Arguments; + NTL_PROCESS_STRING_VECTOR Environment; // Optional opaque userspace bootstrap data copied into the child's read-only loader block. const void* StartupData; size_t StartupDataSize; @@ -73,11 +113,32 @@ typedef struct NTL_CREATE_SYSTEM_PROCESS_REQUEST { #define NTL_PROCESS_LOADER_API #endif +// Prepares an inert child and materializes its image/dependencies/bootstrap data. +// imageFile may be SYS_HANDLE_INVALID to open request->Path relative to RootHandle. +NTL_PROCESS_LOADER_API KSTATUS NtlProcessBuilderPrepare(NTL_PROCESS_BUILDER* builder, + const NTL_PROCESS_BUILDER_REQUEST* request, + SYS_HANDLE imageFile); +// Returns the prepared process capability without transferring ownership from the builder. +// The borrowed handle remains valid only until Start or Abort consumes the builder. +NTL_PROCESS_LOADER_API KSTATUS NtlProcessBuilderGetProcessHandle(NTL_PROCESS_BUILDER* builder, + SYS_HANDLE* processHandleOut); +// Starts a prepared child and returns the retained process capability to the caller. +// On success the builder is consumed and reset to the empty state. +NTL_PROCESS_LOADER_API KSTATUS NtlProcessBuilderStart(NTL_PROCESS_BUILDER* builder, + SYS_HANDLE* processHandleOut); +// Aborts a prepared child, terminating the inert native process and releasing loader state. +NTL_PROCESS_LOADER_API void NtlProcessBuilderAbort(NTL_PROCESS_BUILDER* builder); + NTL_PROCESS_LOADER_API KSTATUS NtlCreateProcessAt(const NTL_CREATE_PROCESS_AT_REQUEST* request); NTL_PROCESS_LOADER_API KSTATUS NtlCreateSystemProcess(const NTL_CREATE_SYSTEM_PROCESS_REQUEST* request); // Copies the optional opaque startup data supplied by the userspace process builder. NTL_PROCESS_LOADER_API KSTATUS NtlGetProcessStartupData(void* buffer, size_t bufferSize, size_t* sizeOut); +// Returns one immutable argument/environment entry from the current loader block. +NTL_PROCESS_LOADER_API KSTATUS NtlGetProcessArgumentCount(uint32_t* countOut); +NTL_PROCESS_LOADER_API KSTATUS NtlGetProcessArgument(uint32_t index, SYS_STRING_VIEW* argumentOut); +NTL_PROCESS_LOADER_API KSTATUS NtlGetProcessEnvironmentCount(uint32_t* countOut); +NTL_PROCESS_LOADER_API KSTATUS NtlGetProcessEnvironment(uint32_t index, SYS_STRING_VIEW* entryOut); #undef NTL_PROCESS_LOADER_API diff --git a/code/public/ntlite/security.h b/code/public/ntlite/security.h index 73ca1a9..5a1e730 100644 --- a/code/public/ntlite/security.h +++ b/code/public/ntlite/security.h @@ -64,3 +64,6 @@ typedef struct SYS_DERIVE_SECURITY_CONTEXT_REQUEST { KSTATUS SysDeriveSecurityContext(const SYS_DERIVE_SECURITY_CONTEXT_REQUEST* request); KSTATUS SysQuerySecurityContext(SYS_HANDLE contextHandle, SYS_SECURITY_CONTEXT_INFO* infoOut); +// Opens the exact immutable security context currently assigned to a process capability. +KSTATUS SysOpenProcessSecurityContext(SYS_HANDLE processHandle, SYS_RIGHTS desiredRights, + SYS_HANDLE* contextHandleOut); diff --git a/code/public/ntlite/syscall.h b/code/public/ntlite/syscall.h index 09c5ae4..6634a5e 100644 --- a/code/public/ntlite/syscall.h +++ b/code/public/ntlite/syscall.h @@ -112,6 +112,11 @@ typedef void (*SYS_USER_APC_ROUTINE)(void* normalContext, SYS_IO_STATUS_BLOCK* iosb, uint64_t information); +// Generic user APC callback shape used by capability-scoped direct notifications. +typedef void (*SYS_USER_APC_CALLBACK)(void* normalContext, + void* systemArgument1, + void* systemArgument2); + typedef struct SYS_FILE_IO_REQUEST { uint32_t Size; SYS_HANDLE FileHandle; @@ -663,6 +668,25 @@ typedef struct SYS_CREATE_THREAD_REQUEST { #define SYS_CREATE_THREAD_FLAG_NONE 0x00000000u #define SYS_CREATE_THREAD_FLAG_SUSPENDED 0x00000001u +// Describes one generic user APC queued through a thread capability. The +// routine and arguments are interpreted in the target thread's user address +// space; possession of SYS_THREAD_RIGHT_NOTIFY is the authority boundary. +// INTERRUPT permits delivery on the next user return without requiring an +// alertable wait; ordinary APCs retain alertable-wait delivery semantics. +#define SYS_QUEUE_USER_APC_FLAG_NONE 0x00000000u +#define SYS_QUEUE_USER_APC_FLAG_INTERRUPT 0x00000001u + +typedef struct SYS_QUEUE_USER_APC_REQUEST { + uint32_t Size; + SYS_HANDLE ThreadHandle; + SYS_USER_APC_CALLBACK Routine; + void* NormalContext; + void* SystemArgument1; + void* SystemArgument2; + uint32_t Flags; + uint32_t Reserved; +} SYS_QUEUE_USER_APC_REQUEST; + void SysExitProcess(int32_t exitCode); void SysExitThread(int32_t exitCode); KSTATUS SysCreateProcessObject(const SYS_CREATE_PROCESS_OBJECT_REQUEST* request); @@ -813,9 +837,12 @@ typedef struct SYS_PROCESS_INFO { KSTATUS SysGetProcessList(SYS_PROCESS_INFO* processesOut, size_t maxCount, size_t* countOut); KSTATUS SysGetProcessPid(SYS_HANDLE handle, uint32_t* pidOut); KSTATUS SysGetThreadList(uint32_t pid, SYS_THREAD_INFO* threadsOut, size_t maxCount, size_t* countOut); +KSTATUS SysGetThreadTid(SYS_HANDLE handle, uint32_t* tidOut); KSTATUS SysCreateThread(const SYS_CREATE_THREAD_REQUEST* request); +KSTATUS SysOpenCurrentThread(SYS_RIGHTS desiredAccess, SYS_HANDLE* handleOut); KSTATUS SysResumeThread(SYS_HANDLE threadHandle, uint32_t* previousSuspendCountOut); +KSTATUS SysResumeThreadAndWait(SYS_HANDLE threadHandle, uint32_t* previousSuspendCountOut); uint32_t SysGetCurrentTid(void); uint32_t SysGetCurrentPid(void); KSTATUS SysSetThreadPriority(uint32_t priority); @@ -845,6 +872,7 @@ KSTATUS SysCancelTimer(SYS_HANDLE timerHandle); // waitable timer handles. A zero-length sleep yields the processor. KSTATUS SysSleep(uint64_t ms); KSTATUS SysSetUserApcDispatcher(void* dispatcher); +KSTATUS SysQueueUserApc(const SYS_QUEUE_USER_APC_REQUEST* request); // Returns from the active user APC dispatcher. The frame pointer must identify // the kernel-issued frame for the current delivery; it is not a general resume @@ -982,6 +1010,8 @@ KSTATUS SysReceivePort(const SYS_RECEIVE_PORT_REQUEST* request); KSTATUS SysCallPort(const SYS_CALL_PORT_REQUEST* request); KSTATUS SysReplyPort(const SYS_REPLY_PORT_REQUEST* request); KSTATUS SysDisconnectPort(SYS_HANDLE endpointHandle); +KSTATUS SysOpenPortPeerProcess(SYS_HANDLE endpointHandle, SYS_RIGHTS desiredRights, + SYS_HANDLE* processHandleOut); KSTATUS SysRegisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle); KSTATUS SysUnregisterSystemService(SYS_SERVICE_ID serviceId, SYS_HANDLE listenerHandle); KSTATUS SysConnectSystemService(const SYS_CONNECT_SYSTEM_SERVICE_REQUEST* request); diff --git a/code/public/posix/errno.h b/code/public/posix/errno.h index 0edd297..bcff87a 100644 --- a/code/public/posix/errno.h +++ b/code/public/posix/errno.h @@ -44,6 +44,7 @@ #define EINPROGRESS 34 #define ETIMEDOUT 35 #define ECANCELED 36 +#define E2BIG 37 NTL_POSIX_API int* __ntl_posix_errno_location(void); diff --git a/code/public/posix/ntlite_posix_profile.def b/code/public/posix/ntlite_posix_profile.def index 63411d7..94bdf90 100644 --- a/code/public/posix/ntlite_posix_profile.def +++ b/code/public/posix/ntlite_posix_profile.def @@ -3,17 +3,17 @@ // PLANNED belongs to the active target, and UNSUPPORTED is deliberately rejected. POSIX_API(getpid, POSIX1990_REQUIRED, IMPLEMENTED) -POSIX_API(getppid, POSIX1990_DEFERRED_PROCESS_MODEL, PLANNED) -POSIX_API(wait, POSIX1990_DEFERRED_PROCESS_MODEL, PLANNED) -POSIX_API(waitpid, POSIX1990_DEFERRED_PROCESS_MODEL, PARTIAL) -POSIX_API(_exit, POSIX1990_REQUIRED, PLANNED) +POSIX_API(getppid, POSIX1990_DEFERRED_PROCESS_MODEL, IMPLEMENTED) +POSIX_API(wait, POSIX1990_DEFERRED_PROCESS_MODEL, IMPLEMENTED) +POSIX_API(waitpid, POSIX1990_DEFERRED_PROCESS_MODEL, IMPLEMENTED) +POSIX_API(_exit, POSIX1990_REQUIRED, IMPLEMENTED) POSIX_API(fork, POSIX1990_DEFERRED_PROCESS_MODEL, UNSUPPORTED) -POSIX_API(vfork, POSIX1990_DEFERRED_PROCESS_MODEL, PLANNED) +POSIX_API(vfork, POSIX1990_DEFERRED_PROCESS_MODEL, IMPLEMENTED) POSIX_API(execl, POSIX1990_DEFERRED_PROCESS_MODEL, DEFERRED) POSIX_API(execle, POSIX1990_DEFERRED_PROCESS_MODEL, DEFERRED) POSIX_API(execlp, POSIX1990_DEFERRED_PROCESS_MODEL, DEFERRED) POSIX_API(execv, POSIX1990_DEFERRED_PROCESS_MODEL, DEFERRED) -POSIX_API(execve, POSIX1990_DEFERRED_PROCESS_MODEL, PLANNED) +POSIX_API(execve, POSIX1990_DEFERRED_PROCESS_MODEL, IMPLEMENTED) POSIX_API(execvp, POSIX1990_DEFERRED_PROCESS_MODEL, DEFERRED) POSIX_API(read, POSIX1990_REQUIRED, IMPLEMENTED) @@ -65,27 +65,27 @@ POSIX_API(setsid, POSIX1990_REQUIRED, IMPLEMENTED) POSIX_API(uname, POSIX1990_REQUIRED, PLANNED) POSIX_API(time, POSIX1990_REQUIRED, PLANNED) POSIX_API(times, POSIX1990_REQUIRED, PLANNED) -POSIX_API(getenv, POSIX1990_REQUIRED, PLANNED) +POSIX_API(getenv, POSIX1990_REQUIRED, IMPLEMENTED) POSIX_API(ctermid, POSIX1990_REQUIRED, PLANNED) POSIX_API(ttyname, POSIX1990_REQUIRED, PLANNED) POSIX_API(isatty, POSIX1990_REQUIRED, PLANNED) POSIX_API(sysconf, POSIX1990_REQUIRED, PLANNED) -POSIX_API(sigemptyset, POSIX1990_REQUIRED, PLANNED) -POSIX_API(sigfillset, POSIX1990_REQUIRED, PLANNED) -POSIX_API(sigaddset, POSIX1990_REQUIRED, PLANNED) -POSIX_API(sigdelset, POSIX1990_REQUIRED, PLANNED) -POSIX_API(sigismember, POSIX1990_REQUIRED, PLANNED) -POSIX_API(sigaction, POSIX1990_REQUIRED, PLANNED) -POSIX_API(sigprocmask, POSIX1990_REQUIRED, PLANNED) -POSIX_API(sigpending, POSIX1990_REQUIRED, PLANNED) -POSIX_API(sigsuspend, POSIX1990_REQUIRED, PLANNED) -POSIX_API(kill, POSIX1990_REQUIRED, PLANNED) -POSIX_API(alarm, POSIX1990_REQUIRED, PLANNED) -POSIX_API(pause, POSIX1990_REQUIRED, PLANNED) -POSIX_API(sleep, POSIX1990_REQUIRED, PLANNED) -POSIX_API(signal, POSIX1990_REQUIRED, PLANNED) -POSIX_API(raise, POSIX1990_REQUIRED, PLANNED) +POSIX_API(sigemptyset, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(sigfillset, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(sigaddset, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(sigdelset, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(sigismember, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(sigaction, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(sigprocmask, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(sigpending, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(sigsuspend, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(kill, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(alarm, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(pause, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(sleep, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(signal, POSIX1990_REQUIRED, IMPLEMENTED) +POSIX_API(raise, POSIX1990_REQUIRED, IMPLEMENTED) POSIX_API(cfgetispeed, POSIX1990_REQUIRED, PLANNED) POSIX_API(cfgetospeed, POSIX1990_REQUIRED, PLANNED) @@ -119,7 +119,7 @@ POSIX_API(pthread_mutex_destroy, NTL_POSIX_EXTENSION, IMPLEMENTED) POSIX_API(pthread_mutex_lock, NTL_POSIX_EXTENSION, IMPLEMENTED) POSIX_API(pthread_mutex_trylock, NTL_POSIX_EXTENSION, IMPLEMENTED) POSIX_API(pthread_mutex_unlock, NTL_POSIX_EXTENSION, IMPLEMENTED) -POSIX_API(posix_spawn, NTL_POSIX_EXTENSION, PARTIAL) +POSIX_API(posix_spawn, NTL_POSIX_EXTENSION, IMPLEMENTED) POSIX_API(socket, NTL_POSIX_EXTENSION, IMPLEMENTED) POSIX_API(bind, NTL_POSIX_EXTENSION, IMPLEMENTED) POSIX_API(listen, NTL_POSIX_EXTENSION, IMPLEMENTED) diff --git a/code/public/posix/signal.h b/code/public/posix/signal.h index a79a14e..c839330 100644 --- a/code/public/posix/signal.h +++ b/code/public/posix/signal.h @@ -1,8 +1,54 @@ -// POSIX signal type declarations reserved for the next runtime phase. +// POSIX signal declarations for the nt-lite POSIX personality. #pragma once #include +#include +#include +#include typedef uint64_t sigset_t; typedef int sig_atomic_t; +typedef void (*sighandler_t)(int); + +#define SIGHUP NTL_POSIX_SIGNAL_HUP +#define SIGINT NTL_POSIX_SIGNAL_INT +#define SIGQUIT NTL_POSIX_SIGNAL_QUIT +#define SIGILL NTL_POSIX_SIGNAL_ILL +#define SIGABRT NTL_POSIX_SIGNAL_ABRT +#define SIGFPE NTL_POSIX_SIGNAL_FPE +#define SIGKILL NTL_POSIX_SIGNAL_KILL +#define SIGSEGV NTL_POSIX_SIGNAL_SEGV +#define SIGPIPE NTL_POSIX_SIGNAL_PIPE +#define SIGALRM NTL_POSIX_SIGNAL_ALRM +#define SIGTERM NTL_POSIX_SIGNAL_TERM +#define SIGUSR1 NTL_POSIX_SIGNAL_USR1 +#define SIGUSR2 NTL_POSIX_SIGNAL_USR2 +#define SIGCHLD NTL_POSIX_SIGNAL_CHLD + +#define SIG_DFL ((sighandler_t)0) +#define SIG_IGN ((sighandler_t)1) +#define SIG_ERR ((sighandler_t)-1) + +#define SIG_BLOCK 1 +#define SIG_UNBLOCK 2 +#define SIG_SETMASK 3 + +struct sigaction { + sighandler_t sa_handler; + sigset_t sa_mask; + int sa_flags; +}; + +NTL_POSIX_API int sigemptyset(sigset_t* set); +NTL_POSIX_API int sigfillset(sigset_t* set); +NTL_POSIX_API int sigaddset(sigset_t* set, int signalNumber); +NTL_POSIX_API int sigdelset(sigset_t* set, int signalNumber); +NTL_POSIX_API int sigismember(const sigset_t* set, int signalNumber); +NTL_POSIX_API int sigaction(int signalNumber, const struct sigaction* action, struct sigaction* oldAction); +NTL_POSIX_API int sigprocmask(int how, const sigset_t* set, sigset_t* oldSet); +NTL_POSIX_API int sigpending(sigset_t* set); +NTL_POSIX_API int sigsuspend(const sigset_t* set); +NTL_POSIX_API int kill(pid_t pid, int signalNumber); +NTL_POSIX_API sighandler_t signal(int signalNumber, sighandler_t handler); +NTL_POSIX_API int raise(int signalNumber); diff --git a/code/public/posix/spawn.h b/code/public/posix/spawn.h index 28408a7..3bad3ad 100644 --- a/code/public/posix/spawn.h +++ b/code/public/posix/spawn.h @@ -2,11 +2,62 @@ #pragma once +#include #include +#include #include -typedef void* posix_spawn_file_actions_t; -typedef void* posix_spawnattr_t; +#define POSIX_SPAWN_SETPGROUP 0x00000001u +#define POSIX_SPAWN_SETSIGMASK 0x00000002u +#define POSIX_SPAWN_SETSIGDEF 0x00000004u +#define POSIX_SPAWN_FILE_ACTION_CAPACITY 64u + +#define NTL_POSIX_SPAWN_FILE_ACTION_NONE 0u +#define NTL_POSIX_SPAWN_FILE_ACTION_CLOSE 1u +#define NTL_POSIX_SPAWN_FILE_ACTION_DUP2 2u +#define NTL_POSIX_SPAWN_FILE_ACTION_OPEN 3u + +typedef struct posix_spawn_file_action_entry { + uint32_t Type; + int FileDescriptor; + int NewFileDescriptor; + int OpenFlags; + mode_t Mode; + char* Path; +} posix_spawn_file_action_entry; + +typedef struct posix_spawn_file_actions_t { + uint32_t Initialized; + uint32_t Count; + posix_spawn_file_action_entry Entries[POSIX_SPAWN_FILE_ACTION_CAPACITY]; +} posix_spawn_file_actions_t; + +typedef struct posix_spawnattr_t { + uint32_t Initialized; + uint32_t Flags; + pid_t ProcessGroup; + sigset_t SignalMask; + sigset_t SignalDefault; +} posix_spawnattr_t; + +NTL_POSIX_API int posix_spawn_file_actions_init(posix_spawn_file_actions_t* actions); +NTL_POSIX_API int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t* actions); +NTL_POSIX_API int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t* actions, int fd); +NTL_POSIX_API int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t* actions, + int fd, int newFd); +NTL_POSIX_API int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t* actions, int fd, + const char* path, int openFlags, mode_t mode); + +NTL_POSIX_API int posix_spawnattr_init(posix_spawnattr_t* attributes); +NTL_POSIX_API int posix_spawnattr_destroy(posix_spawnattr_t* attributes); +NTL_POSIX_API int posix_spawnattr_getflags(const posix_spawnattr_t* attributes, short* flags); +NTL_POSIX_API int posix_spawnattr_setflags(posix_spawnattr_t* attributes, short flags); +NTL_POSIX_API int posix_spawnattr_getpgroup(const posix_spawnattr_t* attributes, pid_t* pgroup); +NTL_POSIX_API int posix_spawnattr_setpgroup(posix_spawnattr_t* attributes, pid_t pgroup); +NTL_POSIX_API int posix_spawnattr_getsigmask(const posix_spawnattr_t* attributes, sigset_t* mask); +NTL_POSIX_API int posix_spawnattr_setsigmask(posix_spawnattr_t* attributes, const sigset_t* mask); +NTL_POSIX_API int posix_spawnattr_getsigdefault(const posix_spawnattr_t* attributes, sigset_t* set); +NTL_POSIX_API int posix_spawnattr_setsigdefault(posix_spawnattr_t* attributes, const sigset_t* set); NTL_POSIX_API int posix_spawn(pid_t* pid, const char* path, const posix_spawn_file_actions_t* fileActions, diff --git a/code/public/posix/stdlib.h b/code/public/posix/stdlib.h index 33a05d5..97e4c20 100644 --- a/code/public/posix/stdlib.h +++ b/code/public/posix/stdlib.h @@ -9,4 +9,6 @@ NTL_POSIX_API void* malloc(size_t size); NTL_POSIX_API void* calloc(size_t count, size_t size); NTL_POSIX_API void* realloc(void* pointer, size_t size); NTL_POSIX_API void free(void* pointer); +NTL_POSIX_API char* getenv(const char* name); +NTL_POSIX_API extern char** environ; NTL_POSIX_API void exit(int status); diff --git a/code/public/posix/sys/wait.h b/code/public/posix/sys/wait.h index 39ec2af..a2a7587 100644 --- a/code/public/posix/sys/wait.h +++ b/code/public/posix/sys/wait.h @@ -1,10 +1,13 @@ -// POSIX wait declarations for nt-lite spawn associations. +// POSIX child wait declarations backed by posixsrv process records. #pragma once #include #include +#define WNOHANG 0x00000001 #define WEXITSTATUS(status) (((status) >> 8) & 0xff) +#define WIFEXITED(status) (((status) & 0x7f) == 0) +NTL_POSIX_API pid_t wait(int* status); NTL_POSIX_API pid_t waitpid(pid_t pid, int* status, int options); diff --git a/code/public/posix/unistd.h b/code/public/posix/unistd.h index 165c191..d5588fe 100644 --- a/code/public/posix/unistd.h +++ b/code/public/posix/unistd.h @@ -26,6 +26,13 @@ NTL_POSIX_API int chdir(const char* path); NTL_POSIX_API char* getcwd(char* buffer, size_t size); NTL_POSIX_API int unlink(const char* path); NTL_POSIX_API pid_t getpid(void); +NTL_POSIX_API pid_t getppid(void); +NTL_POSIX_API pid_t vfork(void); +NTL_POSIX_API int execve(const char* path, char* const argv[], char* const envp[]); +NTL_POSIX_API void _exit(int status); +NTL_POSIX_API unsigned int alarm(unsigned int seconds); +NTL_POSIX_API int pause(void); +NTL_POSIX_API unsigned int sleep(unsigned int seconds); NTL_POSIX_API uid_t getuid(void); NTL_POSIX_API uid_t geteuid(void); NTL_POSIX_API gid_t getgid(void); diff --git a/code/user/apps/shell_native/shell_native.c b/code/user/apps/shell_native/shell_native.c index 0f035e0..904fdd8 100644 --- a/code/user/apps/shell_native/shell_native.c +++ b/code/user/apps/shell_native/shell_native.c @@ -27,6 +27,10 @@ // Output helpers. static void print(const char* s) { (void)NtlConsoleWrite(SYS_HANDLE_INVALID, s, strlen(s), 0); } +// Numeric diagnostic helpers used before their definitions. +static void printUInt(uint32_t n); +static void printHex32(uint32_t v); + // Opens file. static uint32_t open_file(const char* path, uint32_t* handleOut) { return NtlOpenFile(SysSvFromCstr(path), SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, @@ -55,11 +59,14 @@ static uint32_t delete_file(const char* path) { return NtlDeleteFile(SysSvFromCstr(path)); } -// Creates process. -static uint32_t create_process(const char* path, uint32_t* handleOut, const char* cmdline) { +// Creates one process and reports which launch stage returned an error. +static KSTATUS create_process(const char* path, uint32_t* handleOut, const char* cmdline, + const char** failureStageOut) { + if (failureStageOut) *failureStageOut = "open-image"; + uint32_t imageFile = SYS_HANDLE_INVALID; - uint32_t status = open_file(path, &imageFile); - if (status != NTLSTATUS_SUCCESS) return status; + KSTATUS status = open_file(path, &imageFile); + if (!KSUCCESS(status)) return status; NTL_CREATE_SYSTEM_PROCESS_REQUEST req; memset(&req, 0, sizeof(req)); @@ -71,8 +78,11 @@ static uint32_t create_process(const char* path, uint32_t* handleOut, const char req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; req.ResolveModuleName = NtlResolveSystemModuleName; req.ProcessHandleOut = handleOut; + + if (failureStageOut) *failureStageOut = "process-loader"; status = NtlCreateSystemProcess(&req); (void)SysCloseHandle(imageFile); + if (KSUCCESS(status) && failureStageOut) *failureStageOut = NULL; return status; } @@ -151,6 +161,45 @@ static void printUInt64Width(uint64_t n, size_t width) { printUInt64(n); } +// Names common nt-lite status values used by shell diagnostics. +static const char* statusName(KSTATUS status) { + switch (status) { + case NTLSTATUS_SUCCESS: return "success"; + case NTLSTATUS_FAILURE: return "failure"; + case NTLSTATUS_NOT_SUPPORTED: return "not-supported"; + case NTLSTATUS_INVALID_PARAMETER: return "invalid-parameter"; + case NTLSTATUS_INVALID_HANDLE: return "invalid-handle"; + case NTLSTATUS_BUFFER_TOO_SMALL: return "buffer-too-small"; + case NTLSTATUS_CHECKSUM_MISMATCH: return "checksum-mismatch"; + case NTLSTATUS_NO_MEMORY: return "no-memory"; + case NTLSTATUS_ACCESS_DENIED: return "access-denied"; + case NTLSTATUS_NOT_FOUND: return "not-found"; + case NTLSTATUS_ALREADY_EXISTS: return "already-exists"; + case NTLSTATUS_OBJECT_NAME_COLLISION: return "object-name-collision"; + case NTLSTATUS_OBJECT_DEAD: return "object-dead"; + case NTLSTATUS_LIMIT_REACHED: return "limit-reached"; + case NTLSTATUS_INVALID_IMAGE_FORMAT: return "invalid-image-format"; + case NTLSTATUS_NAME_TOO_LONG: return "name-too-long"; + default: return NULL; + } +} + +// Prints one status with its stable numeric facility/code decomposition. +static void printStatus(KSTATUS status) { + print("0x"); + printHex32(status); + const char* name = statusName(status); + if (name) { + print(" ("); + print(name); + print(")"); + } + print(" facility="); + printUInt(KSTATUS_FACILITY(status)); + print(" code="); + printUInt(KSTATUS_CODE(status)); +} + // Names nt-lite process-abort exit statuses. static const char* processExitStatusName(uint32_t code) { switch (code) { @@ -2229,7 +2278,9 @@ static int runCommand(const char* cmd) { if (!extractProgramPath(modifiedCmd, program, sizeof(program))) return 0; uint32_t hProc; - if (create_process(program, &hProc, modifiedCmd) == 0) { + const char* launchFailureStage = NULL; + KSTATUS launchStatus = create_process(program, &hProc, modifiedCmd, &launchFailureStage); + if (KSUCCESS(launchStatus)) { if (background) { // Background mode: Clean up the handle immediately and skip waiting. print("[Launched in background, handle: "); @@ -2261,8 +2312,15 @@ static int runCommand(const char* cmd) { INPUT_KEY drainedKey; while (NtlInputPollKeyboard(&drainedKey) == NTLSTATUS_SUCCESS) (void)NtlInputReadKey(&drainedKey); } - } else { - print("shell: launch failed\n"); + } else { + print("shell: launch failed"); + if (launchFailureStage) { + print(" at "); + print(launchFailureStage); + } + print(": "); + printStatus(launchStatus); + print("\n"); } return 0; } diff --git a/code/user/apps/video/video.c b/code/user/apps/video/video.c index 8d14389..bb3039a 100644 --- a/code/user/apps/video/video.c +++ b/code/user/apps/video/video.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "bink.h" @@ -305,7 +306,52 @@ int main(void) { fail_status("SysMapSurface", status); } - print("video: native surface ready\n"); + SYS_HANDLE desktop = SYS_HANDLE_INVALID; + status = NtlDesktopConnect(&desktop); + if (!KSUCCESS(status)) { + (void)SysUnmapSurface(surfaceBase); + SysCloseHandle(surface); + BinkClose(b); + SysFreeVm(buf); + fail_status("NtlDesktopConnect", status); + } + + DISPLAY_RECT windowBounds = { 0, 0, (int32_t)width, (int32_t)height }; + NTL_WINDOW_ID window = 0; + status = NtlDesktopCreateWindow(desktop, &windowBounds, &window); + if (!KSUCCESS(status) || window == 0) { + if (KSUCCESS(status)) status = NTLSTATUS_FAILURE; + (void)NtlDesktopClose(desktop); + (void)SysUnmapSurface(surfaceBase); + SysCloseHandle(surface); + BinkClose(b); + SysFreeVm(buf); + fail_status("NtlDesktopCreateWindow", status); + } + status = NtlDesktopAttachSurface(desktop, window, surface); + if (!KSUCCESS(status)) { + (void)NtlDesktopDestroyWindow(desktop, window); + (void)NtlDesktopClose(desktop); + (void)SysUnmapSurface(surfaceBase); + SysCloseHandle(surface); + BinkClose(b); + SysFreeVm(buf); + fail_status("NtlDesktopAttachSurface", status); + } + status = NtlDesktopSetVisible(desktop, window, 1u); + if (!KSUCCESS(status)) { + (void)NtlDesktopDetachSurface(desktop, window); + (void)NtlDesktopDestroyWindow(desktop, window); + (void)NtlDesktopClose(desktop); + (void)SysUnmapSurface(surfaceBase); + SysCloseHandle(surface); + BinkClose(b); + SysFreeVm(buf); + fail_status("NtlDesktopSetVisible", status); + } + (void)NtlDesktopRaiseWindow(desktop, window); + + print("video: native surface/window ready\n"); print("video: decode loop entered\n"); DISPLAY_RECT fullFrame = { 0, 0, (int32_t)width, (int32_t)height }; @@ -331,6 +377,9 @@ int main(void) { status = SysCommitSurface(surface, &fullFrame, 1, 0); if (status != NTLSTATUS_SUCCESS) { + (void)NtlDesktopDetachSurface(desktop, window); + (void)NtlDesktopDestroyWindow(desktop, window); + (void)NtlDesktopClose(desktop); (void)SysUnmapSurface(surfaceBase); SysCloseHandle(surface); BinkClose(b); @@ -359,7 +408,10 @@ int main(void) { print(" sndBytes="); print_u64(b->SndSize); print("\n"); - print("video: closing surface\n"); + print("video: closing window/surface\n"); + (void)NtlDesktopDetachSurface(desktop, window); + (void)NtlDesktopDestroyWindow(desktop, window); + (void)NtlDesktopClose(desktop); (void)SysUnmapSurface(surfaceBase); SysCloseHandle(surface); print("video: closing Bink\n"); diff --git a/code/user/personalities/posix/posix_fd.c b/code/user/personalities/posix/posix_fd.c index a6a2cf5..37aad9f 100644 --- a/code/user/personalities/posix/posix_fd.c +++ b/code/user/personalities/posix/posix_fd.c @@ -70,8 +70,8 @@ int PosixStatusToErrno(uint32_t s) { } } -// Locks the process descriptor table. -static int PosixFdLock(POSIX_PROCESS_STATE* process) { +// Locks one logical process descriptor table. +static int PosixFdLock(POSIX_LOGICAL_PROCESS_STATE* process) { if (!process || process->FdLock == SYS_HANDLE_INVALID) { errno = EIO; return -1; @@ -84,8 +84,8 @@ static int PosixFdLock(POSIX_PROCESS_STATE* process) { return 0; } -// Unlocks the process descriptor table. -static void PosixFdUnlock(POSIX_PROCESS_STATE* process) { +// Unlocks one logical process descriptor table. +static void PosixFdUnlock(POSIX_LOGICAL_PROCESS_STATE* process) { uint32_t status = SysReleaseMutex(process->FdLock); if (status != NTLSTATUS_SUCCESS) errno = PosixStatusToErrno(status); @@ -134,12 +134,12 @@ void PosixFdReleaseServerDescription(uint64_t descriptionId) { (void)PosixRuntimeCallServer(&request, &reply); } -// Captures process-local descriptor numbers/flags while posixsrv pins unique descriptions. +// Captures logical-process descriptor numbers/flags while posixsrv pins unique descriptions. KSTATUS PosixFdCreateInheritanceSnapshot(uint32_t flags, uint64_t* tokenOut) { if (!tokenOut || (flags & ~POSIX_FD_INHERIT_SKIP_CLOEXEC) != 0) return NTLSTATUS_INVALID_PARAMETER; *tokenOut = 0; - POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + POSIX_LOGICAL_PROCESS_STATE* process = PosixRuntimeLogicalProcessState(); if (!process) return NTLSTATUS_NOT_FOUND; if (PosixFdLock(process) != 0) return NTLSTATUS_FAILURE; @@ -177,30 +177,6 @@ KSTATUS PosixFdCreateInheritanceSnapshot(uint32_t flags, uint64_t* tokenOut) { return NTLSTATUS_SUCCESS; } -// Arms one snapshot to a concrete native child lifetime before the parent returns from spawn. -KSTATUS PosixFdArmInheritanceSnapshot(uint64_t token, SYS_HANDLE processHandle) { - if (token == 0 || processHandle == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_PARAMETER; - - NTL_POSIX_REQUEST request; - memset(&request, 0, sizeof(request)); - request.Size = sizeof(request); - request.Version = NTL_POSIX_PROTOCOL_VERSION; - request.Operation = NTL_POSIX_OP_ARM_FD_INHERITANCE; - request.Flags = NTL_POSIX_REQUEST_FLAG_NONE; - request.FdInheritanceToken = token; - - SYS_PORT_HANDLE_TRANSFER transfer; - memset(&transfer, 0, sizeof(transfer)); - transfer.Handle = processHandle; - transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT; - transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; - - NTL_POSIX_REPLY reply; - KSTATUS status = PosixRuntimeCallServerWithHandles(&request, &transfer, 1u, &reply); - if (!KSUCCESS(status)) return status; - return reply.Status; -} - // Cancels one snapshot when native process construction failed before a child could claim it. void PosixFdCancelInheritanceSnapshot(uint64_t token) { if (token == 0) return; @@ -327,13 +303,20 @@ int PosixFdSetStatusFlags(POSIX_OPEN_DESCRIPTION* description, int statusFlags) return 0; } +// Releases one stable local wrapper without changing server holder ownership. +static void PosixFdReleaseLocal(POSIX_OPEN_DESCRIPTION* description) { + if (!description) return; + if (_InterlockedDecrement(&description->RefCount) != 0) return; + if (PosixOpenDescriptionNeedsHandle(description->Type) && + description->KHandle != SYS_HANDLE_INVALID) + (void)SysCloseHandle(description->KHandle); + free(description); +} + // Releases one stable local wrapper and its server holder at the final reference. void PosixFdRelease(POSIX_OPEN_DESCRIPTION* description) { - if (!description) - return; - if (_InterlockedDecrement(&description->RefCount) != 0) - return; - + if (!description) return; + if (_InterlockedDecrement(&description->RefCount) != 0) return; if (PosixOpenDescriptionNeedsHandle(description->Type) && description->KHandle != SYS_HANDLE_INVALID) (void)SysCloseHandle(description->KHandle); @@ -342,7 +325,7 @@ void PosixFdRelease(POSIX_OPEN_DESCRIPTION* description) { } // Installs a description at the lowest free descriptor at or above minFd. -static int PosixFdInstall(POSIX_PROCESS_STATE* process, +static int PosixFdInstall(POSIX_LOGICAL_PROCESS_STATE* process, POSIX_OPEN_DESCRIPTION* description, int minFd, int descriptorFlags) { @@ -371,7 +354,7 @@ static int PosixFdInstall(POSIX_PROCESS_STATE* process, // Creates and installs one new open description. int PosixFdCreate(POSIX_FD_TYPE type, uint32_t khandle, int statusFlags) { - POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + POSIX_LOGICAL_PROCESS_STATE* process = PosixRuntimeLogicalProcessState(); if (!process) { errno = EIO; return -1; @@ -427,7 +410,7 @@ static POSIX_OPEN_DESCRIPTION* PosixOpenDescriptionImport(POSIX_FD_TYPE type, // Installs one process-local wrapper for a server-created shared open description. int PosixFdImport(POSIX_FD_TYPE type, uint32_t khandle, uint64_t descriptionId, NTL_POSIX_SHARED_OPEN_DESCRIPTION* shared, int descriptorFlags) { - POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + POSIX_LOGICAL_PROCESS_STATE* process = PosixRuntimeLogicalProcessState(); if (!process) { errno = EIO; return -1; } POSIX_OPEN_DESCRIPTION* description = PosixOpenDescriptionImport(type, khandle, descriptionId, shared); @@ -439,7 +422,7 @@ int PosixFdImport(POSIX_FD_TYPE type, uint32_t khandle, uint64_t descriptionId, // Acquires a stable reference to the open description backing fd. POSIX_OPEN_DESCRIPTION* PosixFdAcquire(int fd) { - POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + POSIX_LOGICAL_PROCESS_STATE* process = PosixRuntimeLogicalProcessState(); if (!process || fd < 0 || fd >= POSIX_FD_MAX) { errno = EBADF; return (POSIX_OPEN_DESCRIPTION*)0; @@ -459,7 +442,7 @@ POSIX_OPEN_DESCRIPTION* PosixFdAcquire(int fd) { // Removes one descriptor and releases its table-owned reference. int PosixFdClose(int fd) { - POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + POSIX_LOGICAL_PROCESS_STATE* process = PosixRuntimeLogicalProcessState(); if (!process || fd < 0 || fd >= POSIX_FD_MAX) { errno = EBADF; return -1; @@ -481,7 +464,7 @@ int PosixFdClose(int fd) { } // Installs one fixed standard descriptor during process initialization. -static uint32_t PosixFdInstallStandard(POSIX_PROCESS_STATE* process, +static uint32_t PosixFdInstallStandard(POSIX_LOGICAL_PROCESS_STATE* process, int fd, POSIX_FD_TYPE type, int statusFlags) { @@ -498,7 +481,7 @@ static uint32_t PosixFdInstallStandard(POSIX_PROCESS_STATE* process, } // Reconstructs one exact descriptor table from a server-owned inheritance snapshot. -static uint32_t PosixFdImportInheritance(POSIX_PROCESS_STATE* process, uint64_t token) { +static uint32_t PosixFdImportInheritance(POSIX_LOGICAL_PROCESS_STATE* process, uint64_t token) { if (!process || token == 0) return NTLSTATUS_INVALID_PARAMETER; NTL_POSIX_FD_MANIFEST_ENTRY manifest[NTL_POSIX_FD_INHERIT_MAX]; @@ -650,8 +633,8 @@ static uint32_t PosixFdImportInheritance(POSIX_PROCESS_STATE* process, uint64_t return result; } -// Initializes the process descriptor table and standard descriptors. -uint32_t PosixFdInitializeProcess(POSIX_PROCESS_STATE* process, uint64_t inheritanceToken) { +// Initializes one logical process descriptor table and standard descriptors. +uint32_t PosixFdInitializeLogicalProcess(POSIX_LOGICAL_PROCESS_STATE* process, uint64_t inheritanceToken) { if (!process) return NTLSTATUS_INVALID_PARAMETER; @@ -664,7 +647,7 @@ uint32_t PosixFdInitializeProcess(POSIX_PROCESS_STATE* process, uint64_t inherit if (inheritanceToken != 0) { status = PosixFdImportInheritance(process, inheritanceToken); - if (status != NTLSTATUS_SUCCESS) PosixFdDestroyProcess(process); + if (status != NTLSTATUS_SUCCESS) PosixFdDestroyLogicalProcess(process); return status; } @@ -674,15 +657,13 @@ uint32_t PosixFdInitializeProcess(POSIX_PROCESS_STATE* process, uint64_t inherit if (status == NTLSTATUS_SUCCESS) status = PosixFdInstallStandard(process, 2, PosixFdConsoleWrite, O_WRONLY); if (status != NTLSTATUS_SUCCESS) - PosixFdDestroyProcess(process); + PosixFdDestroyLogicalProcess(process); return status; } // Releases descriptor-table resources during failed process startup. -void PosixFdDestroyProcess(POSIX_PROCESS_STATE* process) { - if (!process) - return; - +void PosixFdDestroyLogicalProcess(POSIX_LOGICAL_PROCESS_STATE* process) { + if (!process) return; for (int fd = 0; fd < POSIX_FD_MAX; fd++) { POSIX_OPEN_DESCRIPTION* description = process->FdTable[fd].Description; process->FdTable[fd].Description = (POSIX_OPEN_DESCRIPTION*)0; @@ -695,6 +676,21 @@ void PosixFdDestroyProcess(POSIX_PROCESS_STATE* process) { } } +// Drops inherited local aliases after borrowed exec without releasing committed server holders. +void PosixFdDestroyLogicalProcessLocal(POSIX_LOGICAL_PROCESS_STATE* process) { + if (!process) return; + for (int fd = 0; fd < POSIX_FD_MAX; fd++) { + POSIX_OPEN_DESCRIPTION* description = process->FdTable[fd].Description; + process->FdTable[fd].Description = (POSIX_OPEN_DESCRIPTION*)0; + process->FdTable[fd].DescriptorFlags = 0; + PosixFdReleaseLocal(description); + } + if (process->FdLock != SYS_HANDLE_INVALID) { + (void)SysCloseHandle(process->FdLock); + process->FdLock = SYS_HANDLE_INVALID; + } +} + // Duplicates oldfd into the lowest free descriptor at or above minFd. static int PosixFdDuplicateFrom(int oldfd, int minFd) { if (minFd < 0 || minFd >= POSIX_FD_MAX) { @@ -704,7 +700,7 @@ static int PosixFdDuplicateFrom(int oldfd, int minFd) { POSIX_OPEN_DESCRIPTION* description = PosixFdAcquire(oldfd); if (!description) return -1; - int result = PosixFdInstall(PosixRuntimeProcessState(), description, minFd, 0); + int result = PosixFdInstall(PosixRuntimeLogicalProcessState(), description, minFd, 0); PosixFdRelease(description); return result; } @@ -729,7 +725,7 @@ NTL_POSIX_API int dup2(int oldfd, int newfd) { return newfd; } - POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + POSIX_LOGICAL_PROCESS_STATE* process = PosixRuntimeLogicalProcessState(); if (PosixFdLock(process) != 0) { PosixFdRelease(description); return -1; @@ -903,7 +899,7 @@ static int PosixFdControlLock(POSIX_OPEN_DESCRIPTION* description, int command, // Implements descriptor and open-description file-control operations. NTL_POSIX_API int fcntl(int fd, int command, ...) { - POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + POSIX_LOGICAL_PROCESS_STATE* process = PosixRuntimeLogicalProcessState(); if (!process || fd < 0 || fd >= POSIX_FD_MAX) { errno = EBADF; return -1; diff --git a/code/user/personalities/posix/posix_fd.h b/code/user/personalities/posix/posix_fd.h index 0f90f2b..092b36a 100644 --- a/code/user/personalities/posix/posix_fd.h +++ b/code/user/personalities/posix/posix_fd.h @@ -30,28 +30,28 @@ typedef struct POSIX_OPEN_DESCRIPTION { NTL_POSIX_SHARED_OPEN_DESCRIPTION* Shared; } POSIX_OPEN_DESCRIPTION; -// One process-local descriptor entry with descriptor-local flags. +// One logical-process descriptor entry with descriptor-local flags. typedef struct POSIX_FD { POSIX_OPEN_DESCRIPTION* Description; int DescriptorFlags; } POSIX_FD; -struct POSIX_PROCESS_STATE; +struct POSIX_LOGICAL_PROCESS_STATE; -// Initializes the process descriptor table from inherited state or fresh standard descriptors. -uint32_t PosixFdInitializeProcess(struct POSIX_PROCESS_STATE* process, uint64_t inheritanceToken); +// Initializes one logical process descriptor table from inherited state or fresh standard descriptors. +uint32_t PosixFdInitializeLogicalProcess(struct POSIX_LOGICAL_PROCESS_STATE* process, uint64_t inheritanceToken); // Captures the current descriptor table in one server-owned inheritance snapshot. KSTATUS PosixFdCreateInheritanceSnapshot(uint32_t flags, uint64_t* tokenOut); -// Arms one inheritance snapshot to the lifetime of the native child process. -KSTATUS PosixFdArmInheritanceSnapshot(uint64_t token, SYS_HANDLE processHandle); - // Cancels one unclaimed inheritance snapshot owned by the current process. void PosixFdCancelInheritanceSnapshot(uint64_t token); // Releases descriptor-table resources during failed process startup. -void PosixFdDestroyProcess(struct POSIX_PROCESS_STATE* process); +void PosixFdDestroyLogicalProcess(struct POSIX_LOGICAL_PROCESS_STATE* process); + +// Releases only process-local wrappers after server-side exec holder ownership has committed. +void PosixFdDestroyLogicalProcessLocal(struct POSIX_LOGICAL_PROCESS_STATE* process); // Creates and installs one new open description at the lowest free descriptor >= 3. int PosixFdCreate(POSIX_FD_TYPE type, uint32_t khandle, int statusFlags); diff --git a/code/user/personalities/posix/posix_internal.h b/code/user/personalities/posix/posix_internal.h index e41b03e..a26ef50 100644 --- a/code/user/personalities/posix/posix_internal.h +++ b/code/user/personalities/posix/posix_internal.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -31,25 +32,27 @@ #define POSIX_PROCESS_STATE_MAGIC 0x50505243u #define POSIX_THREAD_STATE_MAGIC 0x50544852u #define POSIX_DLERROR_MAX 160u -#define POSIX_SPAWN_TABLE_SIZE 32u #define POSIX_PTHREAD_TABLE_SIZE 32u -typedef struct POSIX_SPAWN_ENTRY { - pid_t Pid; - SYS_HANDLE Handle; - uint64_t FdInheritanceToken; - bool Used; -} POSIX_SPAWN_ENTRY; - typedef struct POSIX_PTHREAD_SLOT { uint32_t Tid; void* RetVal; bool Used; } POSIX_PTHREAD_SLOT; +typedef struct POSIX_SIGNAL_ACTION_STATE { + volatile uintptr_t Handler; + volatile uint64_t Mask; + volatile uint32_t Flags; + uint32_t Reserved; +} POSIX_SIGNAL_ACTION_STATE; + typedef struct POSIX_LOGICAL_PROCESS_STATE { uint32_t PosixPid; uint32_t Generation; + SYS_HANDLE FdLock; + POSIX_FD FdTable[POSIX_FD_MAX]; + POSIX_SIGNAL_ACTION_STATE SignalActions[NTL_POSIX_SIGNAL_MAX + 1u]; } POSIX_LOGICAL_PROCESS_STATE; typedef struct POSIX_PROCESS_STATE { @@ -57,20 +60,40 @@ typedef struct POSIX_PROCESS_STATE { SYS_PEB* Peb; SYS_HANDLE ServerEndpoint; POSIX_LOGICAL_PROCESS_STATE DefaultIdentity; - SYS_HANDLE FdLock; - POSIX_FD FdTable[POSIX_FD_MAX]; - POSIX_SPAWN_ENTRY SpawnTable[POSIX_SPAWN_TABLE_SIZE]; + uint64_t BootstrapToken; + uint32_t BootstrapPosixPid; + uint32_t BootstrapGeneration; + void* ExceptionHandlerHandle; + int ArgumentCount; + char** Arguments; + char** Environment; + char* CommandLine; POSIX_PTHREAD_SLOT PthreadTable[POSIX_PTHREAD_TABLE_SIZE]; } POSIX_PROCESS_STATE; +typedef struct POSIX_VFORK_CHILD_STATE { + POSIX_LOGICAL_PROCESS_STATE LogicalProcess; + uint64_t ServerToken; + uint64_t FdInheritanceToken; + uint64_t InitialSignalMask; +} POSIX_VFORK_CHILD_STATE; + typedef struct POSIX_THREAD_STATE { uint32_t Magic; int ErrnoValue; + uint32_t NativeThreadId; uint8_t DlErrorPending; - uint8_t Reserved[3]; + uint8_t ServerRegistered; + uint8_t Reserved[2]; + volatile uint64_t SignalBlocked; + volatile uint64_t SignalHandlerBlocked; + volatile uint64_t SignalPending; + volatile uint64_t SignalDeliveryCount; + SYS_HANDLE SignalWaitEvent; char DlError[POSIX_DLERROR_MAX]; POSIX_PROCESS_STATE* NativeProcess; POSIX_LOGICAL_PROCESS_STATE* LogicalProcess; + POSIX_VFORK_CHILD_STATE* BorrowedChild; struct passwd PasswordResult; char PasswordName[NTL_POSIX_IDENTITY_NAME_MAX + 1u]; char PasswordHome[NTL_POSIX_IDENTITY_HOME_MAX + 1u]; @@ -90,9 +113,42 @@ POSIX_PROCESS_STATE* PosixRuntimeProcessState(void); // Returns the current thread state, attaching lazily when necessary. POSIX_THREAD_STATE* PosixRuntimeThreadState(void); +// Returns the logical POSIX process selected by the current native thread. +POSIX_LOGICAL_PROCESS_STATE* PosixRuntimeLogicalProcessState(void); + // Attaches POSIX state to the current native TEB. uint32_t PosixRuntimeAttachThread(void); +// Attaches a thread while seeding its inherited persistent signal mask. +uint32_t PosixRuntimeAttachThreadWithSignalMask(uint64_t signalMask); + +// Allocates one borrowed logical child and pins its inherited descriptor snapshot. +KSTATUS PosixVforkBeginBorrowedChild(POSIX_VFORK_CHILD_STATE** childOut); + +// Attaches the calling native thread to one prepared borrowed logical child. +KSTATUS PosixVforkAttachBorrowedChild(POSIX_VFORK_CHILD_STATE* child); + +// Tears down the calling borrowed thread's local wrappers before its native thread exits. +void PosixVforkDetachBorrowedChildLocal(POSIX_VFORK_CHILD_STATE* child, bool releaseServerHolders); + +// Cancels one borrowed child after setup failure or after its native thread has exited. +KSTATUS PosixVforkCancelBorrowedChild(POSIX_VFORK_CHILD_STATE* child); + +// Records a genuine borrowed-child exit before its native thread terminates. +KSTATUS PosixVforkRecordExit(POSIX_VFORK_CHILD_STATE* child, int status); + +// Returns whether the calling POSIX thread is one borrowed vfork child. +POSIX_VFORK_CHILD_STATE* PosixVforkCurrentBorrowedChild(void); + +// Delivers one POSIX signal from the generic user-APC substrate. +void PosixSignalApc(void* signalValue, void* routeValue, void* reserved); + +// Dispatches thread-pending signals made deliverable by a mask change. +void PosixSignalDispatchLocalPending(POSIX_THREAD_STATE* thread); + +// Installs the POSIX exception-to-signal personality adapter. +KSTATUS PosixSignalInitializeExceptionTranslation(POSIX_PROCESS_STATE* process); + // Releases POSIX state owned by the current native TEB. void PosixRuntimeDetachThread(void); @@ -108,6 +164,15 @@ char* PosixRuntimeTakeDlError(void); // Calls posixsrv over the registered process endpoint and validates one fixed-size reply. KSTATUS PosixRuntimeCallServer(const NTL_POSIX_REQUEST* request, NTL_POSIX_REPLY* replyOut); +// Calls posixsrv through a temporary registered connection for operations that may block. +KSTATUS PosixRuntimeCallServerBlocking(const NTL_POSIX_REQUEST* request, NTL_POSIX_REPLY* replyOut); + +// Calls posixsrv through a temporary registered connection while transferring capabilities. +KSTATUS PosixRuntimeCallServerBlockingWithHandles(const NTL_POSIX_REQUEST* request, + const SYS_PORT_HANDLE_TRANSFER* handles, + uint32_t handleCount, + NTL_POSIX_REPLY* replyOut); + // Calls posixsrv while transferring a bounded set of native capabilities. KSTATUS PosixRuntimeCallServerWithHandles(const NTL_POSIX_REQUEST* request, const SYS_PORT_HANDLE_TRANSFER* handles, diff --git a/code/user/personalities/posix/posix_io.c b/code/user/personalities/posix/posix_io.c index 16c024e..641293b 100644 --- a/code/user/personalities/posix/posix_io.c +++ b/code/user/personalities/posix/posix_io.c @@ -122,7 +122,16 @@ static ssize_t PosixPipeRead(POSIX_OPEN_DESCRIPTION* description, void* buffer, } PosixPipeUnlock(&shared->ReadLock); - uint32_t status = SysWait(description->KHandle, SYS_WAIT_FLAG_NONE); + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + uint64_t deliveryCount = thread ? thread->SignalDeliveryCount : 0; + uint32_t status = SysWait(description->KHandle, SYS_WAIT_FLAG_ALERTABLE); + if (status == NTLSTATUS_USER_APC || status == NTLSTATUS_KERNEL_APC || + status == NTLSTATUS_ALERTED) { + if (!thread || thread->SignalDeliveryCount == deliveryCount) + continue; + errno = EINTR; + return -1; + } if (status != NTLSTATUS_SUCCESS) { errno = PosixStatusToErrno(status); return -1; @@ -139,6 +148,7 @@ static ssize_t PosixPipeWrite(POSIX_OPEN_DESCRIPTION* description, const void* b PosixPipeLock(&shared->WriteLock); if (shared->ReaderOpen == 0) { PosixPipeUnlock(&shared->WriteLock); + (void)raise(SIGPIPE); errno = EPIPE; return -1; } @@ -177,7 +187,16 @@ static ssize_t PosixPipeWrite(POSIX_OPEN_DESCRIPTION* description, const void* b } PosixPipeUnlock(&shared->WriteLock); - uint32_t status = SysWait(description->KHandle, SYS_WAIT_FLAG_NONE); + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + uint64_t deliveryCount = thread ? thread->SignalDeliveryCount : 0; + uint32_t status = SysWait(description->KHandle, SYS_WAIT_FLAG_ALERTABLE); + if (status == NTLSTATUS_USER_APC || status == NTLSTATUS_KERNEL_APC || + status == NTLSTATUS_ALERTED) { + if (!thread || thread->SignalDeliveryCount == deliveryCount) + continue; + errno = EINTR; + return -1; + } if (status != NTLSTATUS_SUCCESS) { errno = PosixStatusToErrno(status); return -1; diff --git a/code/user/personalities/posix/posix_proc.c b/code/user/personalities/posix/posix_proc.c index ee56003..ca47028 100644 --- a/code/user/personalities/posix/posix_proc.c +++ b/code/user/personalities/posix/posix_proc.c @@ -1,8 +1,10 @@ // POSIX compatibility support for posix proc. #include +#include #include #include +#include #include #include #include "posix_internal.h" @@ -14,80 +16,16 @@ static SYS_PEB* PosixPeb(void) { return process ? process->Peb : (SYS_PEB*)0; } -// Implements the posix get command line operation. +// Returns the POSIX argv-derived command-line compatibility view when available. __declspec(dllexport) const char* posix_get_command_line(void) { + POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + if (process && process->CommandLine) return process->CommandLine; SYS_PEB* peb = PosixPeb(); if (!peb || peb->Magic != SYS_PEB_MAGIC || !peb->CommandLine.Data) return ""; return peb->CommandLine.Data; } -// Records one spawn association in process-local POSIX state. -static void ProcTableInsert(pid_t pid, SYS_HANDLE handle, uint64_t inheritanceToken) { - POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); - if (!process) { - SysCloseHandle(handle); - return; - } - - for (uint32_t i = 0; i < POSIX_SPAWN_TABLE_SIZE; i++) { - if (!process->SpawnTable[i].Used) { - process->SpawnTable[i] = (POSIX_SPAWN_ENTRY){ pid, handle, inheritanceToken, true }; - return; - } - } - SysCloseHandle(handle); - if (inheritanceToken != 0) PosixFdCancelInheritanceSnapshot(inheritanceToken); -} - -// Finds a spawned process handle retained by the current POSIX process. -static SYS_HANDLE ProcTableFind(pid_t pid) { - POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); - if (!process) return SYS_HANDLE_INVALID; - - for (uint32_t i = 0; i < POSIX_SPAWN_TABLE_SIZE; i++) { - if (process->SpawnTable[i].Used && process->SpawnTable[i].Pid == pid) - return process->SpawnTable[i].Handle; - } - return SYS_HANDLE_INVALID; -} - -// Removes one spawn association from process-local POSIX state. -static void ProcTableRemove(pid_t pid) { - POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); - if (!process) return; - - for (uint32_t i = 0; i < POSIX_SPAWN_TABLE_SIZE; i++) { - if (process->SpawnTable[i].Used && process->SpawnTable[i].Pid == pid) { - uint64_t inheritanceToken = process->SpawnTable[i].FdInheritanceToken; - memset(&process->SpawnTable[i], 0, sizeof(process->SpawnTable[i])); - if (inheritanceToken != 0) PosixFdCancelInheritanceSnapshot(inheritanceToken); - return; - } - } -} - -// Build a flat Windows-style cmdline from a NULL-terminated argv array. -// Each token that contains a space is wrapped in double-quotes. -// argv[0] is included (it becomes the executable token visible to _start). -static void BuildCmdline(char* const argv[], char* out, size_t outSize) { - size_t pos = 0; - for (int i = 0; argv && argv[i]; i++) { - if (i > 0 && pos < outSize - 1) out[pos++] = ' '; - const char* tok = argv[i]; - bool needQuote = false; - for (const char* c = tok; *c; c++) if (*c == ' ' || *c == '\t') { needQuote = true; break; } - if (needQuote && pos < outSize - 1) out[pos++] = '"'; - for (; *tok && pos < outSize - 2; tok++) { - if (*tok == '"') { out[pos++] = '\\'; } - out[pos++] = *tok; - } - if (needQuote && pos < outSize - 1) out[pos++] = '"'; - } - if (pos < outSize) out[pos] = '\0'; -} - -// POSIX API. - +// Returns the stable POSIX PID selected for the calling logical thread. __declspec(dllexport) pid_t getpid(void) { POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); if (!thread || !thread->LogicalProcess || thread->LogicalProcess->PosixPid == 0) @@ -95,110 +33,622 @@ __declspec(dllexport) pid_t getpid(void) { return (pid_t)thread->LogicalProcess->PosixPid; } -__declspec(dllexport) int posix_spawn(pid_t* pidOut, - const char* path, - const posix_spawn_file_actions_t* file_actions, // ignored v1 - const posix_spawnattr_t* attrp, // ignored v1 - char* const argv[], - char* const envp[]) // ignored v1 -{ - (void)file_actions; (void)attrp; (void)envp; +#define POSIX_SPAWN_ACTIONS_MAGIC 0x53464131u +#define POSIX_SPAWN_ATTRIBUTES_MAGIC 0x53415431u - char native[512]; - uint32_t pathStatus = PosixProcessPath(path, native, sizeof(native)); - if (pathStatus != NTLSTATUS_SUCCESS) { errno = PosixStatusToErrno(pathStatus); return -1; } - - SYS_HANDLE imageFile = SYS_HANDLE_INVALID; - pathStatus = NtlOpenFile(SysSvFromCstr(native), SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &imageFile); - if (pathStatus != NTLSTATUS_SUCCESS) { errno = PosixStatusToErrno(pathStatus); return -1; } - - char cmdline[512]; - BuildCmdline(argv, cmdline, sizeof(cmdline)); - // If argv was NULL or empty, fall back to just the path so _start still - // gets a meaningful argv[0]. - if (cmdline[0] == '\0') { - size_t len = strlen(path); - if (len >= sizeof(cmdline)) len = sizeof(cmdline) - 1; - memcpy(cmdline, path, len); - cmdline[len] = '\0'; - } - - uint64_t inheritanceToken = 0; - uint32_t snapshotStatus = - PosixFdCreateInheritanceSnapshot(POSIX_FD_INHERIT_SKIP_CLOEXEC, &inheritanceToken); - if (snapshotStatus != NTLSTATUS_SUCCESS) { - (void)SysCloseHandle(imageFile); - errno = PosixStatusToErrno(snapshotStatus); - return -1; - } - - NTL_POSIX_BOOTSTRAP_DATA bootstrap; - memset(&bootstrap, 0, sizeof(bootstrap)); - bootstrap.Magic = NTL_POSIX_BOOTSTRAP_MAGIC; - bootstrap.Version = NTL_POSIX_BOOTSTRAP_VERSION; - bootstrap.Size = sizeof(bootstrap); - bootstrap.FdInheritanceToken = inheritanceToken; - - uint32_t procHandle = 0; - NTL_CREATE_SYSTEM_PROCESS_REQUEST req; - memset(&req, 0, sizeof(req)); - req.Size = sizeof(req); - req.Flags = NTL_PROCESS_LOAD_FLAG_NONE; - req.ImageFile = imageFile; - req.Path = SysSvFromCstr(native); - req.CommandLine = SysSvFromCstr(cmdline); - req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; - req.ResolveModuleName = PosixResolveModuleName; - req.StartupData = &bootstrap; - req.StartupDataSize = sizeof(bootstrap); - req.ProcessHandleOut = &procHandle; - uint32_t s = NtlCreateSystemProcess(&req); - if (imageFile != SYS_HANDLE_INVALID) (void)SysCloseHandle(imageFile); - if (s != NTLSTATUS_SUCCESS) { - PosixFdCancelInheritanceSnapshot(inheritanceToken); - errno = PosixStatusToErrno(s); - return -1; - } - - KSTATUS armStatus = PosixFdArmInheritanceSnapshot(inheritanceToken, procHandle); - if (!KSUCCESS(armStatus) && armStatus != NTLSTATUS_NOT_FOUND) { - (void)SysKillProcess(procHandle, (int32_t)NTLSTATUS_PROCESS_KILLED); - (void)SysCloseHandle(procHandle); - PosixFdCancelInheritanceSnapshot(inheritanceToken); - errno = PosixStatusToErrno(armStatus); - return -1; - } - - uint32_t kpid = 0; - SysGetProcessPid(procHandle, &kpid); - ProcTableInsert((pid_t)kpid, procHandle, inheritanceToken); - if (pidOut) *pidOut = (pid_t)kpid; +// Converts one posixsrv transport/result pair into a POSIX construction error. +static int PosixConstructionCall(NTL_POSIX_REQUEST* request, NTL_POSIX_REPLY* reply) { + KSTATUS status = PosixRuntimeCallServer(request, reply); + if (!KSUCCESS(status)) return PosixStatusToErrno(status); + if (!KSUCCESS(reply->Status)) return PosixStatusToErrno(reply->Status); return 0; } -// options: WNOHANG not supported in v1 (always blocks). -__declspec(dllexport) pid_t waitpid(pid_t pid, int* status, int options) { - (void)options; - - SYS_HANDLE handle = ProcTableFind(pid); - if (handle == SYS_HANDLE_INVALID) { errno = ESRCH; return -1; } - - SysWait(handle, SYS_WAIT_FLAG_NONE); - - int32_t exitCode = 0; - SysGetProcessExitCode(handle, &exitCode); - if (status) *status = exitCode << 8; // WEXITSTATUS convention - - SysCloseHandle(handle); - ProcTableRemove(pid); - return pid; +// Cancels one process-owned construction transaction after a client-side failure. +static void PosixConstructionCancelTransaction(uint32_t transactionId) { + if (!transactionId) return; + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_CANCEL_TRANSACTION; + request.TransactionId = transactionId; + NTL_POSIX_REPLY reply; + (void)PosixRuntimeCallServer(&request, &reply); } -// Implements the exit operation. +// Begins one server-owned process-construction transaction. +static int PosixConstructionBegin(uint32_t kind, uint32_t* transactionIdOut) { + if (!transactionIdOut || + (kind != NTL_POSIX_TRANSACTION_SPAWN && kind != NTL_POSIX_TRANSACTION_EXEC)) + return EINVAL; + *transactionIdOut = 0; + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_BEGIN_TRANSACTION; + request.TransactionKind = kind; + NTL_POSIX_REPLY reply; + int error = PosixConstructionCall(&request, &reply); + if (error) return error; + if (!reply.TransactionId) return EIO; + *transactionIdOut = reply.TransactionId; + return 0; +} + +// Adds one bounded string length to the aggregate construction payload size. +static int PosixConstructionMeasureString(const char* text, uint32_t* totalBytes) { + if (!text || !totalBytes) return EINVAL; + size_t length = strlen(text); + if (length > NTL_POSIX_CONSTRUCT_DATA_MAX || + (uint32_t)length > NTL_POSIX_CONSTRUCT_DATA_MAX - *totalBytes) + return E2BIG; + *totalBytes += (uint32_t)length; + return 0; +} + +// Validates one argv/envp vector against the bounded server transaction format. +static int PosixConstructionMeasureVector(char* const vector[], uint32_t* totalBytes) { + if (!vector) return 0; + for (uint32_t index = 0; vector[index]; ++index) { + if (index >= NTL_POSIX_CONSTRUCT_VECTOR_MAX) return E2BIG; + int error = PosixConstructionMeasureString(vector[index], totalBytes); + if (error) return error; + } + return 0; +} + +// Validates all bounded spawn text before allocating a server transaction. +static int PosixSpawnValidateInput(const char* path, + const posix_spawn_file_actions_t* actions, + char* const argv[], + char* const envp[]) { + if (!path || !path[0]) return EINVAL; + size_t pathLength = strlen(path); + if (pathLength > SYS_PATH_MAX) return ENAMETOOLONG; + + uint32_t totalBytes = 0; + int error = PosixConstructionMeasureString(path, &totalBytes); + if (error) return error; + error = PosixConstructionMeasureVector(argv, &totalBytes); + if (error) return error; + error = PosixConstructionMeasureVector(envp, &totalBytes); + if (error) return error; + + if (!actions) return 0; + if (actions->Count > POSIX_SPAWN_FILE_ACTION_CAPACITY || + actions->Count > NTL_POSIX_SPAWN_ACTION_MAX) + return EINVAL; + for (uint32_t i = 0; i < actions->Count; ++i) { + const posix_spawn_file_action_entry* action = &actions->Entries[i]; + if (action->Type != NTL_POSIX_SPAWN_FILE_ACTION_OPEN) continue; + if (!action->Path) return EINVAL; + size_t length = strlen(action->Path); + if (length > SYS_PATH_MAX) return ENAMETOOLONG; + error = PosixConstructionMeasureString(action->Path, &totalBytes); + if (error) return error; + } + return 0; +} + +// Sends one construction executable pathname to posixsrv. +static int PosixConstructionSendExecutable(uint32_t transactionId, const char* path) { + if (!path || !path[0]) return EINVAL; + size_t length = strlen(path); + if (length > SYS_PATH_MAX) return ENAMETOOLONG; + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_CONSTRUCT_SET_EXECUTABLE; + request.TransactionId = transactionId; + request.PathLength = (uint32_t)length; + memcpy(request.Path, path, length + 1u); + NTL_POSIX_REPLY reply; + return PosixConstructionCall(&request, &reply); +} + +// Streams one argv/envp vector through bounded Port messages. +static int PosixConstructionSendVector(uint32_t transactionId, uint32_t kind, char* const vector[]) { + if (!vector) return 0; + for (uint32_t index = 0; vector[index]; ++index) { + if (index >= NTL_POSIX_CONSTRUCT_VECTOR_MAX) return E2BIG; + size_t length = strlen(vector[index]); + if (length > UINT32_MAX) return E2BIG; + uint32_t offset = 0; + do { + uint32_t remaining = (uint32_t)length - offset; + uint32_t chunk = remaining > SYS_PATH_MAX ? SYS_PATH_MAX : remaining; + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_CONSTRUCT_WRITE_VECTOR; + request.TransactionId = transactionId; + request.ConstructionVectorKind = kind; + request.ConstructionVectorIndex = index; + request.ConstructionVectorOffset = offset; + request.PathLength = chunk; + if (chunk) memcpy(request.Path, vector[index] + offset, chunk); + request.Path[chunk] = '\0'; + if (offset + chunk == (uint32_t)length) + request.ConstructionVectorFlags = NTL_POSIX_CONSTRUCT_VECTOR_CHUNK_FINAL; + NTL_POSIX_REPLY reply; + int error = PosixConstructionCall(&request, &reply); + if (error) return error; + offset += chunk; + } while (offset < (uint32_t)length || (length == 0 && offset == 0)); + } + return 0; +} + +// Sends ordered file actions to the server-owned logical descriptor manifest. +static int PosixSpawnSendFileActions(uint32_t transactionId, + const posix_spawn_file_actions_t* actions) { + if (!actions) return 0; + for (uint32_t i = 0; i < actions->Count; ++i) { + const posix_spawn_file_action_entry* action = &actions->Entries[i]; + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_SPAWN_ADD_FILE_ACTION; + request.TransactionId = transactionId; + request.SpawnActionKind = action->Type; + request.SpawnFileDescriptor = action->FileDescriptor; + request.SpawnNewFileDescriptor = action->NewFileDescriptor; + request.SpawnOpenFlags = (uint32_t)action->OpenFlags; + request.SpawnOpenMode = (uint32_t)action->Mode; + if (action->Type == NTL_POSIX_SPAWN_FILE_ACTION_OPEN) { + if (!action->Path) return EINVAL; + size_t length = strlen(action->Path); + if (length > SYS_PATH_MAX) return ENAMETOOLONG; + request.PathLength = (uint32_t)length; + memcpy(request.Path, action->Path, length + 1u); + } + NTL_POSIX_REPLY reply; + int error = PosixConstructionCall(&request, &reply); + if (error) return error; + } + return 0; +} + +// Sends process-group and signal inheritance policy for one server-owned spawn. +static int PosixSpawnSendAttributes(uint32_t transactionId, const posix_spawnattr_t* attributes) { + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + if (!thread) return EIO; + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_SPAWN_SET_ATTRIBUTES; + request.TransactionId = transactionId; + request.SpawnSignalMask = thread->SignalBlocked; + if (attributes) { + request.SpawnAttributeFlags = attributes->Flags; + request.ProcessGroupId = (uint32_t)attributes->ProcessGroup; + if (attributes->Flags & POSIX_SPAWN_SETSIGMASK) + request.SpawnSignalMask = attributes->SignalMask; + if (attributes->Flags & POSIX_SPAWN_SETSIGDEF) + request.SpawnSignalDefaultMask = attributes->SignalDefault; + } + NTL_POSIX_REPLY reply; + return PosixConstructionCall(&request, &reply); +} + +// Delegates the caller's native security authority so posixsrv does not spawn as itself. +static KSTATUS PosixProcessDeriveSecurityContext(SYS_HANDLE* contextOut) { + if (!contextOut) return NTLSTATUS_INVALID_PARAMETER; + *contextOut = SYS_HANDLE_INVALID; + SYS_SECURITY_CONTEXT_INFO info; + memset(&info, 0, sizeof(info)); + info.Size = sizeof(info); + KSTATUS status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &info); + if (!KSUCCESS(status)) return status; + if ((info.EffectivePrivileges & ~info.DelegablePrivileges) != 0 || + (info.ServiceClaims & ~info.DelegableServiceClaims) != 0) + return NTLSTATUS_ACCESS_DENIED; + + SYS_DERIVE_SECURITY_CONTEXT_REQUEST derive; + memset(&derive, 0, sizeof(derive)); + derive.Size = sizeof(derive); + derive.SourceContext = SYS_SECURITY_CONTEXT_CURRENT; + derive.EffectivePrivileges = info.EffectivePrivileges; + derive.DelegablePrivileges = info.DelegablePrivileges; + derive.ServiceClaims = info.ServiceClaims; + derive.DelegableServiceClaims = info.DelegableServiceClaims; + derive.Flags = SYS_SECURITY_CONTEXT_FLAG_NONE; + derive.ContextHandleOut = contextOut; + return SysDeriveSecurityContext(&derive); +} + +// Initializes an empty ordered spawn file-action set. +__declspec(dllexport) int posix_spawn_file_actions_init(posix_spawn_file_actions_t* actions) { + if (!actions) return EINVAL; + memset(actions, 0, sizeof(*actions)); + actions->Initialized = POSIX_SPAWN_ACTIONS_MAGIC; + return 0; +} + +// Releases pathname storage owned by one file-action set. +__declspec(dllexport) int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t* actions) { + if (!actions || actions->Initialized != POSIX_SPAWN_ACTIONS_MAGIC) return EINVAL; + for (uint32_t i = 0; i < actions->Count; ++i) free(actions->Entries[i].Path); + memset(actions, 0, sizeof(*actions)); + return 0; +} + +// Appends one descriptor close action. +__declspec(dllexport) int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t* actions, + int fd) { + if (!actions || actions->Initialized != POSIX_SPAWN_ACTIONS_MAGIC || fd < 0) return EBADF; + if (actions->Count >= POSIX_SPAWN_FILE_ACTION_CAPACITY) return ENOMEM; + posix_spawn_file_action_entry* action = &actions->Entries[actions->Count++]; + memset(action, 0, sizeof(*action)); + action->Type = NTL_POSIX_SPAWN_FILE_ACTION_CLOSE; + action->FileDescriptor = fd; + return 0; +} + +// Appends one dup2 action; the destination loses FD_CLOEXEC during server application. +__declspec(dllexport) int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t* actions, + int fd, int newFd) { + if (!actions || actions->Initialized != POSIX_SPAWN_ACTIONS_MAGIC || fd < 0 || newFd < 0) + return EBADF; + if (actions->Count >= POSIX_SPAWN_FILE_ACTION_CAPACITY) return ENOMEM; + posix_spawn_file_action_entry* action = &actions->Entries[actions->Count++]; + memset(action, 0, sizeof(*action)); + action->Type = NTL_POSIX_SPAWN_FILE_ACTION_DUP2; + action->FileDescriptor = fd; + action->NewFileDescriptor = newFd; + return 0; +} + +// Appends one open-at-descriptor action and owns a private copy of its pathname. +__declspec(dllexport) int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t* actions, + int fd, const char* path, + int openFlags, mode_t mode) { + if (!actions || actions->Initialized != POSIX_SPAWN_ACTIONS_MAGIC || fd < 0 || !path) + return EINVAL; + if (actions->Count >= POSIX_SPAWN_FILE_ACTION_CAPACITY) return ENOMEM; + size_t length = strlen(path); + char* copy = (char*)malloc(length + 1u); + if (!copy) return ENOMEM; + memcpy(copy, path, length + 1u); + posix_spawn_file_action_entry* action = &actions->Entries[actions->Count++]; + memset(action, 0, sizeof(*action)); + action->Type = NTL_POSIX_SPAWN_FILE_ACTION_OPEN; + action->FileDescriptor = fd; + action->OpenFlags = openFlags; + action->Mode = mode; + action->Path = copy; + return 0; +} + +// Initializes one POSIX spawn attribute object. +__declspec(dllexport) int posix_spawnattr_init(posix_spawnattr_t* attributes) { + if (!attributes) return EINVAL; + memset(attributes, 0, sizeof(*attributes)); + attributes->Initialized = POSIX_SPAWN_ATTRIBUTES_MAGIC; + return 0; +} + +// Invalidates one spawn attribute object. +__declspec(dllexport) int posix_spawnattr_destroy(posix_spawnattr_t* attributes) { + if (!attributes || attributes->Initialized != POSIX_SPAWN_ATTRIBUTES_MAGIC) return EINVAL; + memset(attributes, 0, sizeof(*attributes)); + return 0; +} + +// Returns active spawn attribute flags. +__declspec(dllexport) int posix_spawnattr_getflags(const posix_spawnattr_t* attributes, + short* flags) { + if (!attributes || attributes->Initialized != POSIX_SPAWN_ATTRIBUTES_MAGIC || !flags) + return EINVAL; + *flags = (short)attributes->Flags; + return 0; +} + +// Selects supported process-group and signal spawn attributes. +__declspec(dllexport) int posix_spawnattr_setflags(posix_spawnattr_t* attributes, short flags) { + const uint32_t valid = POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | + POSIX_SPAWN_SETSIGDEF; + if (!attributes || attributes->Initialized != POSIX_SPAWN_ATTRIBUTES_MAGIC || + ((uint32_t)(unsigned short)flags & ~valid) != 0) + return EINVAL; + attributes->Flags = (uint32_t)(unsigned short)flags; + return 0; +} + +// Returns the requested process group for POSIX_SPAWN_SETPGROUP. +__declspec(dllexport) int posix_spawnattr_getpgroup(const posix_spawnattr_t* attributes, + pid_t* pgroup) { + if (!attributes || attributes->Initialized != POSIX_SPAWN_ATTRIBUTES_MAGIC || !pgroup) + return EINVAL; + *pgroup = attributes->ProcessGroup; + return 0; +} + +// Sets the requested child process group; zero means the child's own stable POSIX PID. +__declspec(dllexport) int posix_spawnattr_setpgroup(posix_spawnattr_t* attributes, pid_t pgroup) { + if (!attributes || attributes->Initialized != POSIX_SPAWN_ATTRIBUTES_MAGIC || pgroup < 0) + return EINVAL; + attributes->ProcessGroup = pgroup; + return 0; +} + +// Returns the explicit child signal mask stored in one spawn attribute object. +__declspec(dllexport) int posix_spawnattr_getsigmask(const posix_spawnattr_t* attributes, + sigset_t* mask) { + if (!attributes || attributes->Initialized != POSIX_SPAWN_ATTRIBUTES_MAGIC || !mask) + return EINVAL; + *mask = attributes->SignalMask; + return 0; +} + +// Stores the child signal mask used with POSIX_SPAWN_SETSIGMASK. +__declspec(dllexport) int posix_spawnattr_setsigmask(posix_spawnattr_t* attributes, + const sigset_t* mask) { + if (!attributes || attributes->Initialized != POSIX_SPAWN_ATTRIBUTES_MAGIC || !mask || + (*mask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) + return EINVAL; + attributes->SignalMask = *mask & ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + return 0; +} + +// Returns the set forced to default disposition in the spawned image. +__declspec(dllexport) int posix_spawnattr_getsigdefault(const posix_spawnattr_t* attributes, + sigset_t* set) { + if (!attributes || attributes->Initialized != POSIX_SPAWN_ATTRIBUTES_MAGIC || !set) + return EINVAL; + *set = attributes->SignalDefault; + return 0; +} + +// Stores the signals forced to default disposition in the spawned image. +__declspec(dllexport) int posix_spawnattr_setsigdefault(posix_spawnattr_t* attributes, + const sigset_t* set) { + if (!attributes || attributes->Initialized != POSIX_SPAWN_ATTRIBUTES_MAGIC || !set || + (*set & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) + return EINVAL; + attributes->SignalDefault = *set; + return 0; +} + +// Constructs one POSIX child through the authoritative posixsrv transaction. +__declspec(dllexport) int posix_spawn(pid_t* pidOut, + const char* path, + const posix_spawn_file_actions_t* fileActions, + const posix_spawnattr_t* attributes, + char* const argv[], + char* const envp[]) { + if (!pidOut || !path || !path[0]) return EINVAL; + if (fileActions && fileActions->Initialized != POSIX_SPAWN_ACTIONS_MAGIC) return EINVAL; + if (attributes && attributes->Initialized != POSIX_SPAWN_ATTRIBUTES_MAGIC) return EINVAL; + char* const* environment = envp ? envp : environ; + int error = PosixSpawnValidateInput(path, fileActions, argv, (char* const*)environment); + if (error) return error; + *pidOut = (pid_t)-1; + + uint32_t transactionId = 0; + error = PosixConstructionBegin(NTL_POSIX_TRANSACTION_SPAWN, &transactionId); + if (error) return error; + + error = PosixConstructionSendExecutable(transactionId, path); + if (!error) error = PosixConstructionSendVector(transactionId, NTL_POSIX_CONSTRUCT_VECTOR_ARGUMENT, argv); + if (!error) error = PosixConstructionSendVector(transactionId, NTL_POSIX_CONSTRUCT_VECTOR_ENVIRONMENT, + (char* const*)environment); + if (!error) error = PosixSpawnSendFileActions(transactionId, fileActions); + if (!error) error = PosixSpawnSendAttributes(transactionId, attributes); + if (error) { + PosixConstructionCancelTransaction(transactionId); + return error; + } + + uint64_t inheritanceToken = 0; + KSTATUS status = PosixFdCreateInheritanceSnapshot(0, &inheritanceToken); + if (!KSUCCESS(status)) { + PosixConstructionCancelTransaction(transactionId); + return PosixStatusToErrno(status); + } + + SYS_HANDLE securityContext = SYS_HANDLE_INVALID; + status = PosixProcessDeriveSecurityContext(&securityContext); + if (!KSUCCESS(status)) { + PosixFdCancelInheritanceSnapshot(inheritanceToken); + PosixConstructionCancelTransaction(transactionId); + return PosixStatusToErrno(status); + } + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_SPAWN_COMMIT; + request.TransactionId = transactionId; + request.FdInheritanceToken = inheritanceToken; + SYS_PORT_HANDLE_TRANSFER transfer; + memset(&transfer, 0, sizeof(transfer)); + transfer.Handle = securityContext; + transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_SECURITY_CONTEXT_RIGHT_ASSIGN; + transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; + NTL_POSIX_REPLY reply; + status = PosixRuntimeCallServerWithHandles(&request, &transfer, 1u, &reply); + (void)SysCloseHandle(securityContext); + if (!KSUCCESS(status)) { + PosixFdCancelInheritanceSnapshot(inheritanceToken); + PosixConstructionCancelTransaction(transactionId); + return PosixStatusToErrno(status); + } + if (!KSUCCESS(reply.Status)) { + PosixFdCancelInheritanceSnapshot(inheritanceToken); + PosixConstructionCancelTransaction(transactionId); + return PosixStatusToErrno(reply.Status); + } + if (reply.SpawnedPosixPid == 0 || reply.SpawnedPosixPid > INT32_MAX) return EIO; + *pidOut = (pid_t)reply.SpawnedPosixPid; + return 0; +} + +// Validates one exec image and its replacement argv/envp payload. +static int PosixExecValidateInput(const char* path, char* const argv[], char* const envp[]) { + if (!path || !path[0]) return EINVAL; + size_t pathLength = strlen(path); + if (pathLength > SYS_PATH_MAX) return ENAMETOOLONG; + uint32_t totalBytes = 0; + int error = PosixConstructionMeasureString(path, &totalBytes); + if (!error) error = PosixConstructionMeasureVector(argv, &totalBytes); + if (!error) error = PosixConstructionMeasureVector(envp, &totalBytes); + return error; +} + +// Replaces the current native generation while preserving its stable POSIX process identity. +__declspec(dllexport) int execve(const char* path, char* const argv[], char* const envp[]) { + int error = PosixExecValidateInput(path, argv, envp); + if (error) { + errno = error; + return -1; + } + + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + if (!thread || !thread->LogicalProcess) { + errno = EIO; + return -1; + } + POSIX_VFORK_CHILD_STATE* borrowedChild = PosixVforkCurrentBorrowedChild(); + + uint32_t transactionId = 0; + error = PosixConstructionBegin(NTL_POSIX_TRANSACTION_EXEC, &transactionId); + if (error) { + errno = error; + return -1; + } + + error = PosixConstructionSendExecutable(transactionId, path); + if (!error) + error = PosixConstructionSendVector(transactionId, NTL_POSIX_CONSTRUCT_VECTOR_ARGUMENT, + argv); + if (!error) + error = PosixConstructionSendVector(transactionId, NTL_POSIX_CONSTRUCT_VECTOR_ENVIRONMENT, + envp); + if (error) { + PosixConstructionCancelTransaction(transactionId); + errno = error; + return -1; + } + + uint64_t inheritanceToken = 0; + KSTATUS status = PosixFdCreateInheritanceSnapshot(POSIX_FD_INHERIT_SKIP_CLOEXEC, + &inheritanceToken); + if (!KSUCCESS(status)) { + PosixConstructionCancelTransaction(transactionId); + errno = PosixStatusToErrno(status); + return -1; + } + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_EXEC_COMMIT; + request.TransactionId = transactionId; + request.FdInheritanceToken = inheritanceToken; + request.SignalMask = (thread->SignalBlocked | thread->SignalHandlerBlocked) & + ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + + NTL_POSIX_REPLY reply; + status = PosixRuntimeCallServerBlockingWithHandles( + &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply); + if (!KSUCCESS(status)) { + PosixFdCancelInheritanceSnapshot(inheritanceToken); + PosixConstructionCancelTransaction(transactionId); + errno = PosixStatusToErrno(status); + return -1; + } + if (!KSUCCESS(reply.Status)) { + PosixFdCancelInheritanceSnapshot(inheritanceToken); + PosixConstructionCancelTransaction(transactionId); + errno = PosixStatusToErrno(reply.Status); + return -1; + } + + if (borrowedChild) { + // The server has a READY replacement but will release it only after this thread is dead. + PosixVforkDetachBorrowedChildLocal(borrowedChild, false); + for (;;) SysExitThread(0); + } + + // Ordinary successful exec has no reply path; reaching here violates that contract. + errno = EIO; + return -1; +} + +// Returns the server-owned parent launch association for the current POSIX process. +__declspec(dllexport) pid_t getppid(void) { + NTL_POSIX_REPLY reply; + KSTATUS status = PosixRuntimeGetProcessAttributes(&reply); + if (!KSUCCESS(status)) { + errno = PosixStatusToErrno(status); + return (pid_t)-1; + } + return (pid_t)reply.ParentPosixPid; +} + +// Waits for one matching POSIX child record and collects it exactly once. +__declspec(dllexport) pid_t waitpid(pid_t pid, int* statusOut, int options) { + if ((options & ~WNOHANG) != 0) { + errno = EINVAL; + return (pid_t)-1; + } + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_WAIT_PROCESS; + request.WaitPid = (int32_t)pid; + request.WaitOptions = (options & WNOHANG) ? NTL_POSIX_WAIT_NOHANG : 0u; + + NTL_POSIX_REPLY reply; + KSTATUS status = PosixRuntimeCallServerBlocking(&request, &reply); + if (!KSUCCESS(status)) { + errno = PosixStatusToErrno(status); + return (pid_t)-1; + } + if (!KSUCCESS(reply.Status)) { + errno = PosixStatusToErrno(reply.Status); + return (pid_t)-1; + } + if (reply.WaitedPosixPid == 0) return 0; + if (reply.WaitedPosixPid < 0) { + errno = EIO; + return (pid_t)-1; + } + if (statusOut) *statusOut = (reply.ExitCode & 0xff) << 8; + return (pid_t)reply.WaitedPosixPid; +} + +// Waits for any POSIX child. +__declspec(dllexport) pid_t wait(int* status) { + return waitpid((pid_t)-1, status, 0); +} + +// Terminates either an ordinary POSIX native generation or one borrowed vfork child. +__declspec(dllexport) void _exit(int status) { + POSIX_VFORK_CHILD_STATE* borrowedChild = PosixVforkCurrentBorrowedChild(); + if (borrowedChild) { + (void)PosixVforkRecordExit(borrowedChild, status); + PosixVforkDetachBorrowedChildLocal(borrowedChild, true); + for (;;) SysExitThread(status); + } + for (;;) SysExitProcess(status); +} + +// Terminates the process; stdio/atexit policy can layer above this when those facilities exist. __declspec(dllexport) void exit(int status) { - SysExitProcess(status); - while (true) {} + _exit(status); } // Initializes one ordinary shared-attribute request. diff --git a/code/user/personalities/posix/posix_runtime.c b/code/user/personalities/posix/posix_runtime.c index 2af94b4..8f933d0 100644 --- a/code/user/personalities/posix/posix_runtime.c +++ b/code/user/personalities/posix/posix_runtime.c @@ -12,6 +12,159 @@ static POSIX_PROCESS_STATE* gPosixProcessState; static int gEmergencyErrno; +NTL_POSIX_API char** environ; + +// Reports one startup failure without depending on the POSIX descriptor layer. +static void PosixRuntimeReportStartupFailure(const char* stage, KSTATUS status) { + static const char hex[] = "0123456789ABCDEF"; + char buffer[128]; + const char* prefix = "posix startup: "; + size_t position = 0; + while (*prefix && position + 1u < sizeof(buffer)) buffer[position++] = *prefix++; + while (stage && *stage && position + 1u < sizeof(buffer)) buffer[position++] = *stage++; + const char* middle = " failed status=0x"; + while (*middle && position + 1u < sizeof(buffer)) buffer[position++] = *middle++; + for (int shift = 28; shift >= 0 && position + 1u < sizeof(buffer); shift -= 4) + buffer[position++] = hex[(status >> shift) & 0xFu]; + if (position + 1u < sizeof(buffer)) buffer[position++] = '\n'; + buffer[position] = '\0'; + SYS_WRITE_CONSOLE_REQUEST write; + memset(&write, 0, sizeof(write)); + write.Size = sizeof(write); + write.Text = SysSvFromParts(buffer, position); + (void)SysWriteConsole(&write); +} + +static void PosixRuntimeDestroyVectors(POSIX_PROCESS_STATE* process); + +// Copies one immutable loader vector into mutable process-local POSIX strings. +static KSTATUS PosixRuntimeCopyVector(int environment, char*** vectorOut, int* countOut) { + if (!vectorOut || !countOut) return NTLSTATUS_INVALID_PARAMETER; + *vectorOut = (char**)0; + *countOut = 0; + + uint32_t count = 0; + KSTATUS status = environment ? NtlGetProcessEnvironmentCount(&count) + : NtlGetProcessArgumentCount(&count); + if (!KSUCCESS(status)) return status; + if (count > INT32_MAX) return NTLSTATUS_LIMIT_REACHED; + + char** vector = (char**)calloc((size_t)count + 1u, sizeof(char*)); + if (!vector) return NTLSTATUS_NO_MEMORY; + for (uint32_t i = 0; i < count; ++i) { + SYS_STRING_VIEW view = SYS_SV_NULL; + status = environment ? NtlGetProcessEnvironment(i, &view) + : NtlGetProcessArgument(i, &view); + if (!KSUCCESS(status)) { + for (uint32_t j = 0; j < i; ++j) free(vector[j]); + free(vector); + return status; + } + vector[i] = (char*)malloc(view.Length + 1u); + if (!vector[i]) { + for (uint32_t j = 0; j < i; ++j) free(vector[j]); + free(vector); + return NTLSTATUS_NO_MEMORY; + } + if (view.Length) memcpy(vector[i], view.Data, view.Length); + vector[i][view.Length] = '\0'; + } + *vectorOut = vector; + *countOut = (int)count; + return NTLSTATUS_SUCCESS; +} + +// Builds the compatibility command-line view from the loader-owned argv vector. +static KSTATUS PosixRuntimeBuildCommandLine(POSIX_PROCESS_STATE* process) { + if (!process) return NTLSTATUS_INVALID_PARAMETER; + if (process->ArgumentCount <= 0 || !process->Arguments) return NTLSTATUS_SUCCESS; + + size_t required = 1u; + for (int i = 0; i < process->ArgumentCount; ++i) { + const char* argument = process->Arguments[i] ? process->Arguments[i] : ""; + int quote = 0; + for (const char* cursor = argument; *cursor; ++cursor) { + if (*cursor == ' ' || *cursor == '\t') quote = 1; + if (*cursor == '"') { + if (required == (size_t)-1) return NTLSTATUS_LIMIT_REACHED; + ++required; + } + if (required == (size_t)-1) return NTLSTATUS_LIMIT_REACHED; + ++required; + } + if (quote) { + if (required > (size_t)-1 - 2u) return NTLSTATUS_LIMIT_REACHED; + required += 2u; + } + if (i != 0) { + if (required == (size_t)-1) return NTLSTATUS_LIMIT_REACHED; + ++required; + } + } + + char* commandLine = (char*)malloc(required); + if (!commandLine) return NTLSTATUS_NO_MEMORY; + size_t position = 0; + for (int i = 0; i < process->ArgumentCount; ++i) { + const char* argument = process->Arguments[i] ? process->Arguments[i] : ""; + int quote = 0; + for (const char* cursor = argument; *cursor; ++cursor) { + if (*cursor == ' ' || *cursor == '\t') { quote = 1; break; } + } + if (i != 0) commandLine[position++] = ' '; + if (quote) commandLine[position++] = '"'; + for (const char* cursor = argument; *cursor; ++cursor) { + if (*cursor == '"') commandLine[position++] = '\\'; + commandLine[position++] = *cursor; + } + if (quote) commandLine[position++] = '"'; + } + commandLine[position] = '\0'; + process->CommandLine = commandLine; + return NTLSTATUS_SUCCESS; +} + +// Materializes argv and environ before application entry executes. +static KSTATUS PosixRuntimeInitializeVectors(POSIX_PROCESS_STATE* process) { + if (!process) return NTLSTATUS_INVALID_PARAMETER; + int environmentCount = 0; + KSTATUS status = PosixRuntimeCopyVector(0, &process->Arguments, &process->ArgumentCount); + if (!KSUCCESS(status)) return status; + status = PosixRuntimeCopyVector(1, &process->Environment, &environmentCount); + if (!KSUCCESS(status)) { + for (int i = 0; i < process->ArgumentCount; ++i) free(process->Arguments[i]); + free(process->Arguments); + process->Arguments = (char**)0; + process->ArgumentCount = 0; + return status; + } + environ = process->Environment; + status = PosixRuntimeBuildCommandLine(process); + if (!KSUCCESS(status)) { + PosixRuntimeDestroyVectors(process); + return status; + } + return NTLSTATUS_SUCCESS; +} + +// Releases process-local argv/environ storage during initialization rollback. +static void PosixRuntimeDestroyVectors(POSIX_PROCESS_STATE* process) { + if (!process) return; + if (process->Arguments) { + for (int i = 0; i < process->ArgumentCount; ++i) free(process->Arguments[i]); + free(process->Arguments); + } + if (process->Environment) { + for (char** entry = process->Environment; *entry; ++entry) free(*entry); + free(process->Environment); + } + free(process->CommandLine); + process->Arguments = (char**)0; + process->Environment = (char**)0; + process->CommandLine = (char*)0; + process->ArgumentCount = 0; + environ = (char**)0; +} // Resolves the published POSIX service and opens one private client endpoint. static KSTATUS PosixRuntimeConnectServer(SYS_HANDLE* endpointOut) { @@ -154,6 +307,65 @@ KSTATUS PosixRuntimeCallServer(const NTL_POSIX_REQUEST* request, NTL_POSIX_REPLY replyOut); } +// Registers a temporary endpoint to the current native/POSIX process identity. +static KSTATUS PosixRuntimeRegisterAuxiliaryEndpoint(SYS_HANDLE endpoint) { + POSIX_PROCESS_STATE* process = gPosixProcessState; + if (!process || process->Magic != POSIX_PROCESS_STATE_MAGIC || endpoint == SYS_HANDLE_INVALID) + return NTLSTATUS_NOT_FOUND; + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_REGISTER_PROCESS; + + size_t currentDirectorySize = 0; + KSTATUS status = NtlGetCurrentDirectory(request.Path, sizeof(request.Path), ¤tDirectorySize); + if (!KSUCCESS(status) || currentDirectorySize == 0 || currentDirectorySize > sizeof(request.Path)) + return KSUCCESS(status) ? NTLSTATUS_FAILURE : status; + request.PathLength = (uint32_t)strlen(request.Path); + + NTL_POSIX_REPLY reply; + status = PosixRuntimeCallEndpoint(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, + (SYS_HANDLE*)0, 0, 0, (uint32_t*)0, &reply); + if (!KSUCCESS(status)) return status; + if (!KSUCCESS(reply.Status)) return reply.Status; + if (reply.PosixPid != process->DefaultIdentity.PosixPid || + reply.NativeGeneration != process->DefaultIdentity.Generation || + (reply.ProcessState != NTL_POSIX_PROCESS_STATE_RUNNING && + reply.ProcessState != NTL_POSIX_PROCESS_STATE_EXEC_PREPARING)) + return NTLSTATUS_ACCESS_DENIED; + return NTLSTATUS_SUCCESS; +} + +// Uses an auxiliary registered connection so one blocking request cannot stall ordinary POSIX calls. +KSTATUS PosixRuntimeCallServerBlockingWithHandles(const NTL_POSIX_REQUEST* request, + const SYS_PORT_HANDLE_TRANSFER* handles, + uint32_t handleCount, + NTL_POSIX_REPLY* replyOut) { + if (!request || !replyOut || (handleCount != 0 && !handles) || + handleCount > SYS_PORT_MAX_TRANSFER_HANDLES) + return NTLSTATUS_INVALID_PARAMETER; + + SYS_HANDLE endpoint = SYS_HANDLE_INVALID; + KSTATUS status = PosixRuntimeConnectServer(&endpoint); + if (!KSUCCESS(status)) return status; + + status = PosixRuntimeRegisterAuxiliaryEndpoint(endpoint); + if (KSUCCESS(status)) + status = PosixRuntimeCallEndpoint(endpoint, request, handles, handleCount, + (SYS_HANDLE*)0, 0, 0, (uint32_t*)0, replyOut); + (void)SysDisconnectPort(endpoint); + (void)SysCloseHandle(endpoint); + return status; +} + +// Uses an auxiliary registered connection without transferring capabilities. +KSTATUS PosixRuntimeCallServerBlocking(const NTL_POSIX_REQUEST* request, NTL_POSIX_REPLY* replyOut) { + return PosixRuntimeCallServerBlockingWithHandles( + request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, replyOut); +} + // Queries the current server-owned POSIX process attributes. KSTATUS PosixRuntimeGetProcessAttributes(NTL_POSIX_REPLY* replyOut) { if (!replyOut) return NTLSTATUS_INVALID_PARAMETER; @@ -180,8 +392,12 @@ static KSTATUS PosixRuntimeReadBootstrap(NTL_POSIX_BOOTSTRAP_DATA* bootstrapOut) if (!KSUCCESS(status)) return status; if (size != sizeof(*bootstrapOut) || bootstrapOut->Magic != NTL_POSIX_BOOTSTRAP_MAGIC || bootstrapOut->Version != NTL_POSIX_BOOTSTRAP_VERSION || - bootstrapOut->Size != sizeof(*bootstrapOut) || bootstrapOut->Reserved != 0) + bootstrapOut->Size != sizeof(*bootstrapOut) || bootstrapOut->Reserved != 0 || + (bootstrapOut->InitialSignalMask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0 || + (bootstrapOut->IgnoredSignals & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) return NTLSTATUS_INVALID_PARAMETER; + bootstrapOut->InitialSignalMask &= ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + bootstrapOut->IgnoredSignals &= ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; return NTLSTATUS_SUCCESS; } @@ -195,16 +411,6 @@ static KSTATUS PosixRuntimeRegisterProcess(POSIX_PROCESS_STATE* process, status = PosixRuntimeConnectServer(&endpoint); if (!KSUCCESS(status)) return status; - SYS_HANDLE selfProcess = SYS_HANDLE_INVALID; - status = SysOpenProcess(SysGetCurrentPid(), - SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_RIGHT_TRANSFER, - &selfProcess); - if (!KSUCCESS(status)) { - (void)SysDisconnectPort(endpoint); - (void)SysCloseHandle(endpoint); - return status; - } - NTL_POSIX_REQUEST request; memset(&request, 0, sizeof(request)); request.Size = sizeof(request); @@ -221,21 +427,13 @@ static KSTATUS PosixRuntimeRegisterProcess(POSIX_PROCESS_STATE* process, if (!KSUCCESS(status) || currentDirectorySize == 0 || currentDirectorySize > sizeof(request.Path)) { (void)SysDisconnectPort(endpoint); (void)SysCloseHandle(endpoint); - (void)SysCloseHandle(selfProcess); return KSUCCESS(status) ? NTLSTATUS_FAILURE : status; } request.PathLength = (uint32_t)strlen(request.Path); - SYS_PORT_HANDLE_TRANSFER transfer; - memset(&transfer, 0, sizeof(transfer)); - transfer.Handle = selfProcess; - transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT; - transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; - NTL_POSIX_REPLY reply; - status = PosixRuntimeCallEndpoint(endpoint, &request, &transfer, 1u, + status = PosixRuntimeCallEndpoint(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, (SYS_HANDLE*)0, 0, 0, (uint32_t*)0, &reply); - (void)SysCloseHandle(selfProcess); if (!KSUCCESS(status)) { (void)SysDisconnectPort(endpoint); (void)SysCloseHandle(endpoint); @@ -249,7 +447,10 @@ static KSTATUS PosixRuntimeRegisterProcess(POSIX_PROCESS_STATE* process, } if (reply.ClientProcessId != SysGetCurrentPid() || reply.PosixPid == 0 || reply.PosixPid > 0x7FFFFFFFu || reply.NativeGeneration == 0 || - reply.ProcessState != NTL_POSIX_PROCESS_STATE_RUNNING) { + (reply.ProcessState != NTL_POSIX_PROCESS_STATE_RUNNING && + reply.ProcessState != NTL_POSIX_PROCESS_STATE_CONSTRUCTING && + reply.ProcessState != NTL_POSIX_PROCESS_STATE_EXEC_PREPARING && + reply.ProcessState != NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING)) { (void)SysDisconnectPort(endpoint); (void)SysCloseHandle(endpoint); return NTLSTATUS_FAILURE; @@ -277,7 +478,7 @@ uint32_t PosixRuntimeInitializeProcess(void) { process->Magic = POSIX_PROCESS_STATE_MAGIC; process->Peb = peb; process->ServerEndpoint = SYS_HANDLE_INVALID; - process->FdLock = SYS_HANDLE_INVALID; + process->DefaultIdentity.FdLock = SYS_HANDLE_INVALID; gPosixProcessState = process; NTL_POSIX_BOOTSTRAP_DATA bootstrap; @@ -290,13 +491,18 @@ uint32_t PosixRuntimeInitializeProcess(void) { status = PosixRuntimeRegisterProcess(process, &bootstrap); if (status != NTLSTATUS_SUCCESS) { + PosixRuntimeReportStartupFailure("register-process", status); gPosixProcessState = (POSIX_PROCESS_STATE*)0; free(process); return status; } + process->BootstrapToken = bootstrap.Token; + process->BootstrapPosixPid = bootstrap.PosixPid; + process->BootstrapGeneration = bootstrap.Generation; - status = PosixFdInitializeProcess(process, bootstrap.FdInheritanceToken); + status = PosixRuntimeInitializeVectors(process); if (status != NTLSTATUS_SUCCESS) { + PosixRuntimeReportStartupFailure("vectors", status); (void)SysDisconnectPort(process->ServerEndpoint); (void)SysCloseHandle(process->ServerEndpoint); process->ServerEndpoint = SYS_HANDLE_INVALID; @@ -305,9 +511,30 @@ uint32_t PosixRuntimeInitializeProcess(void) { return status; } - status = PosixRuntimeAttachThread(); + for (int signalNumber = NTL_POSIX_SIGNAL_HUP; + signalNumber <= NTL_POSIX_SIGNAL_MAX; ++signalNumber) { + uint64_t bit = NTL_POSIX_SIGNAL_BIT(signalNumber); + if (bootstrap.IgnoredSignals & bit) + process->DefaultIdentity.SignalActions[signalNumber].Handler = (uintptr_t)SIG_IGN; + } + + status = PosixFdInitializeLogicalProcess(&process->DefaultIdentity, bootstrap.FdInheritanceToken); if (status != NTLSTATUS_SUCCESS) { - PosixFdDestroyProcess(process); + PosixRuntimeReportStartupFailure("fd-init", status); + PosixRuntimeDestroyVectors(process); + (void)SysDisconnectPort(process->ServerEndpoint); + (void)SysCloseHandle(process->ServerEndpoint); + process->ServerEndpoint = SYS_HANDLE_INVALID; + gPosixProcessState = (POSIX_PROCESS_STATE*)0; + free(process); + return status; + } + + status = PosixRuntimeAttachThreadWithSignalMask(bootstrap.InitialSignalMask); + if (status != NTLSTATUS_SUCCESS) { + PosixRuntimeReportStartupFailure("thread-attach", status); + PosixFdDestroyLogicalProcess(&process->DefaultIdentity); + PosixRuntimeDestroyVectors(process); (void)SysDisconnectPort(process->ServerEndpoint); (void)SysCloseHandle(process->ServerEndpoint); process->ServerEndpoint = SYS_HANDLE_INVALID; @@ -327,8 +554,61 @@ POSIX_PROCESS_STATE* PosixRuntimeProcessState(void) { return process; } -// Attaches the native thread to the process's default logical POSIX identity. -uint32_t PosixRuntimeAttachThread(void) { +// Registers the current native thread capability with posixsrv. +static KSTATUS PosixRuntimeRegisterThread(POSIX_PROCESS_STATE* process, POSIX_THREAD_STATE* thread) { + if (!process || process->Magic != POSIX_PROCESS_STATE_MAGIC || !thread) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = SysSetUserApcDispatcher((void*)SysUserApcDispatcher); + if (!KSUCCESS(status)) return status; + + SYS_HANDLE selfThread = SYS_HANDLE_INVALID; + status = SysOpenCurrentThread(SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | SYS_THREAD_RIGHT_NOTIFY, + &selfThread); + if (!KSUCCESS(status)) return status; + + SYS_PORT_HANDLE_TRANSFER transfer; + memset(&transfer, 0, sizeof(transfer)); + transfer.Handle = selfThread; + transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_THREAD_RIGHT_NOTIFY; + transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_REGISTER_THREAD; + request.Flags = NTL_POSIX_REQUEST_FLAG_NONE; + request.SignalMask = thread->SignalBlocked; + request.SignalApcRoutine = (uint64_t)(uintptr_t)PosixSignalApc; + + NTL_POSIX_REPLY reply; + status = PosixRuntimeCallServerWithHandles(&request, &transfer, 1u, &reply); + (void)SysCloseHandle(selfThread); + if (!KSUCCESS(status)) return status; + return reply.Status; +} + +// Removes the current native thread from posixsrv's target registry. +static void PosixRuntimeUnregisterThread(POSIX_THREAD_STATE* thread) { + if (!thread || !thread->ServerRegistered || + !thread->NativeProcess || thread->NativeProcess->Magic != POSIX_PROCESS_STATE_MAGIC) + return; + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_UNREGISTER_THREAD; + request.Flags = NTL_POSIX_REQUEST_FLAG_NONE; + + NTL_POSIX_REPLY reply; + (void)PosixRuntimeCallServer(&request, &reply); + thread->ServerRegistered = 0; +} + +// Attaches the native thread with one inherited persistent POSIX signal mask. +uint32_t PosixRuntimeAttachThreadWithSignalMask(uint64_t signalMask) { SYS_TEB* teb = SysGetTeb(); POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); if (!teb || teb->Magic != SYS_TEB_MAGIC || !process) @@ -349,10 +629,30 @@ uint32_t PosixRuntimeAttachThread(void) { thread->Magic = POSIX_THREAD_STATE_MAGIC; thread->NativeProcess = process; thread->LogicalProcess = &process->DefaultIdentity; + thread->NativeThreadId = SysGetCurrentTid(); + thread->SignalBlocked = signalMask & NTL_POSIX_SIGNAL_VALID_MASK & + ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + if (thread->NativeThreadId == 0) { + free(thread); + return NTLSTATUS_FAILURE; + } + + KSTATUS status = PosixRuntimeRegisterThread(process, thread); + if (!KSUCCESS(status)) { + thread->Magic = 0; + free(thread); + return status; + } + thread->ServerRegistered = 1u; teb->SubsystemData = thread; return NTLSTATUS_SUCCESS; } +// Attaches a native thread with the default empty signal mask. +uint32_t PosixRuntimeAttachThread(void) { + return PosixRuntimeAttachThreadWithSignalMask(0); +} + // Returns the current thread state, attaching lazily when necessary. POSIX_THREAD_STATE* PosixRuntimeThreadState(void) { if (!PosixRuntimeProcessState()) { @@ -374,6 +674,13 @@ POSIX_THREAD_STATE* PosixRuntimeThreadState(void) { return thread; } + +// Returns the logical POSIX process selected by the current native thread. +POSIX_LOGICAL_PROCESS_STATE* PosixRuntimeLogicalProcessState(void) { + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + return thread ? thread->LogicalProcess : (POSIX_LOGICAL_PROCESS_STATE*)0; +} + // Releases POSIX state owned by the current native TEB. void PosixRuntimeDetachThread(void) { SYS_TEB* teb = SysGetTeb(); @@ -385,6 +692,7 @@ void PosixRuntimeDetachThread(void) { thread->NativeProcess != PosixRuntimeProcessState()) return; + PosixRuntimeUnregisterThread(thread); teb->SubsystemData = (void*)0; thread->Magic = 0; free(thread); @@ -428,11 +736,72 @@ char* PosixRuntimeTakeDlError(void) { return thread->DlError; } +// Commits a server-built child only after every current personality bootstrap step succeeds. +static KSTATUS PosixRuntimeReportBootstrapReady(POSIX_PROCESS_STATE* process) { + if (!process || process->Magic != POSIX_PROCESS_STATE_MAGIC) + return NTLSTATUS_INVALID_PARAMETER; + if (process->BootstrapToken == 0) return NTLSTATUS_SUCCESS; + + NTL_POSIX_REQUEST ready; + memset(&ready, 0, sizeof(ready)); + ready.Size = sizeof(ready); + ready.Version = NTL_POSIX_PROTOCOL_VERSION; + ready.Operation = NTL_POSIX_OP_BOOTSTRAP_READY; + ready.BootstrapToken = process->BootstrapToken; + ready.BootstrapPosixPid = process->BootstrapPosixPid; + ready.BootstrapGeneration = process->BootstrapGeneration; + NTL_POSIX_REPLY reply; + KSTATUS status = PosixRuntimeCallServer(&ready, &reply); + if (!KSUCCESS(status)) return status; + if (!KSUCCESS(reply.Status)) return reply.Status; + + process->BootstrapToken = 0; + process->BootstrapPosixPid = 0; + process->BootstrapGeneration = 0; + return NTLSTATUS_SUCCESS; +} + // Initializes all POSIX process state required before application main. NTL_POSIX_API uint32_t __ntl_posix_process_start(void) { uint32_t status = PosixRuntimeInitializeProcess(); if (status != NTLSTATUS_SUCCESS) return status; + POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + status = PosixSignalInitializeExceptionTranslation(process); + if (status != NTLSTATUS_SUCCESS) { + PosixRuntimeReportStartupFailure("signal-init", status); + return status; + } + status = PosixRuntimeReportBootstrapReady(process); + if (status != NTLSTATUS_SUCCESS) { + PosixRuntimeReportStartupFailure("bootstrap-ready", status); + return status; + } return NTLSTATUS_SUCCESS; } + +// Returns process-local vectors to the POSIX startup shim after initialization. +NTL_POSIX_API uint32_t __ntl_posix_process_vectors(int* argcOut, + char*** argvOut, + char*** envpOut) { + POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + if (!process || !argcOut || !argvOut || !envpOut) return NTLSTATUS_INVALID_PARAMETER; + *argcOut = process->ArgumentCount; + *argvOut = process->Arguments; + *envpOut = process->Environment; + return NTLSTATUS_SUCCESS; +} + +// Finds one process-local environment entry by exact variable name. +NTL_POSIX_API char* getenv(const char* name) { + if (!name || !name[0] || strchr(name, '=')) return (char*)0; + POSIX_PROCESS_STATE* process = PosixRuntimeProcessState(); + if (!process || !process->Environment) return (char*)0; + size_t nameLength = strlen(name); + for (char** entry = process->Environment; *entry; ++entry) { + if (strncmp(*entry, name, nameLength) == 0 && (*entry)[nameLength] == '=') + return *entry + nameLength + 1u; + } + return (char*)0; +} diff --git a/code/user/personalities/posix/posix_signal.c b/code/user/personalities/posix/posix_signal.c new file mode 100644 index 0000000..983afb0 --- /dev/null +++ b/code/user/personalities/posix/posix_signal.c @@ -0,0 +1,476 @@ +// POSIX signal policy and user-mode dispatch over generic thread APC delivery. + +#include +#include +#include +#include +#include +#include +#include "posix_internal.h" + +// Returns one signal bit after validating its public signal number. +static uint64_t PosixSignalBit(int signalNumber) { + return NtlPosixSignalValid(signalNumber) ? NTL_POSIX_SIGNAL_BIT(signalNumber) : 0; +} + +// Converts the process-local action snapshot into the server's routing view. +static uint32_t PosixSignalDisposition(const POSIX_SIGNAL_ACTION_STATE* action) { + uintptr_t handler = action ? action->Handler : 0; + if (handler == (uintptr_t)SIG_IGN) return NTL_POSIX_SIGNAL_DISPOSITION_IGNORE; + if (handler == (uintptr_t)SIG_DFL) return NTL_POSIX_SIGNAL_DISPOSITION_DEFAULT; + return NTL_POSIX_SIGNAL_DISPOSITION_CAUGHT; +} + +// Sends one signal-control request through the authoritative POSIX server. +static KSTATUS PosixSignalCallServer(NTL_POSIX_REQUEST* request, NTL_POSIX_REPLY* reply) { + if (!request || !reply) return NTLSTATUS_INVALID_PARAMETER; + request->Size = sizeof(*request); + request->Version = NTL_POSIX_PROTOCOL_VERSION; + request->Flags = NTL_POSIX_REQUEST_FLAG_NONE; + KSTATUS status = PosixRuntimeCallServer(request, reply); + if (!KSUCCESS(status)) return status; + return reply->Status; +} + +// Applies one caught/default/ignored signal on the current POSIX thread. +static int PosixSignalDeliver(POSIX_THREAD_STATE* thread, int signalNumber) { + if (!thread || !thread->NativeProcess || !NtlPosixSignalValid(signalNumber)) return 0; + uint64_t bit = PosixSignalBit(signalNumber); + uint64_t effectiveMask = thread->SignalBlocked | thread->SignalHandlerBlocked; + if (signalNumber != SIGKILL && (effectiveMask & bit) != 0) { + thread->SignalPending |= bit; + return 0; + } + + thread->SignalPending &= ~bit; + POSIX_SIGNAL_ACTION_STATE* state = &thread->LogicalProcess->SignalActions[signalNumber]; + uintptr_t handlerValue = state->Handler; + if (signalNumber == SIGKILL) handlerValue = (uintptr_t)SIG_DFL; + + if (handlerValue == (uintptr_t)SIG_IGN || + (handlerValue == (uintptr_t)SIG_DFL && signalNumber == SIGCHLD)) + return 0; + if (handlerValue == (uintptr_t)SIG_DFL) { + _exit(128 + signalNumber); + return 1; + } + + uint64_t previousHandlerMask = thread->SignalHandlerBlocked; + thread->SignalHandlerBlocked = (previousHandlerMask | state->Mask | bit) & + NTL_POSIX_SIGNAL_VALID_MASK & + ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + ++thread->SignalDeliveryCount; + if (thread->SignalWaitEvent != SYS_HANDLE_INVALID) + (void)SysSignalEvent(thread->SignalWaitEvent); + ((sighandler_t)handlerValue)(signalNumber); + thread->SignalHandlerBlocked = previousHandlerMask; + PosixSignalDispatchLocalPending(thread); + return 1; +} + +// Maps one personality-neutral native exception class to a POSIX signal. +static int PosixSignalForNativeException(uint32_t code) { + switch (code) { + case SYS_EXCEPTION_CODE_ACCESS_VIOLATION: + case SYS_EXCEPTION_CODE_DATATYPE_MISALIGNMENT: + case SYS_EXCEPTION_CODE_STACK_OVERFLOW: + case SYS_EXCEPTION_CODE_GUARD_PAGE_VIOLATION: + return SIGSEGV; + case SYS_EXCEPTION_CODE_ILLEGAL_INSTRUCTION: + case SYS_EXCEPTION_CODE_PRIVILEGED_INSTRUCTION: + return SIGILL; + case SYS_EXCEPTION_CODE_INT_DIVIDE_BY_ZERO: + case SYS_EXCEPTION_CODE_INT_OVERFLOW: + case SYS_EXCEPTION_CODE_FLT_DENORMAL_OPERAND: + case SYS_EXCEPTION_CODE_FLT_DIVIDE_BY_ZERO: + case SYS_EXCEPTION_CODE_FLT_INEXACT_RESULT: + case SYS_EXCEPTION_CODE_FLT_INVALID_OPERATION: + case SYS_EXCEPTION_CODE_FLT_OVERFLOW: + case SYS_EXCEPTION_CODE_FLT_STACK_CHECK: + case SYS_EXCEPTION_CODE_FLT_UNDERFLOW: + return SIGFPE; + default: + return 0; + } +} + +// Translates selected native faults at the POSIX personality boundary. +static int PosixSignalNativeException(SYS_EXCEPTION_RECORD* record, + SYS_USER_CONTEXT* context, + void* userContext) { + (void)context; + (void)userContext; + if (!record) return NTL_EXCEPTION_CONTINUE_SEARCH; + int signalNumber = PosixSignalForNativeException(record->Code); + if (!signalNumber) return NTL_EXCEPTION_CONTINUE_SEARCH; + + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + if (!thread) return NTL_EXCEPTION_CONTINUE_SEARCH; + uint64_t bit = PosixSignalBit(signalNumber); + if ((thread->SignalBlocked | thread->SignalHandlerBlocked) & bit) + return NTL_EXCEPTION_CONTINUE_SEARCH; + + POSIX_SIGNAL_ACTION_STATE* state = &thread->LogicalProcess->SignalActions[signalNumber]; + if (state->Handler == (uintptr_t)SIG_IGN) + return NTL_EXCEPTION_CONTINUE_SEARCH; + return PosixSignalDeliver(thread, signalNumber) + ? NTL_EXCEPTION_CONTINUE_EXECUTION + : NTL_EXCEPTION_CONTINUE_SEARCH; +} + +// Installs the native-exception adapter exactly once for one POSIX image. +KSTATUS PosixSignalInitializeExceptionTranslation(POSIX_PROCESS_STATE* process) { + if (!process) return NTLSTATUS_INVALID_PARAMETER; + if (process->ExceptionHandlerHandle) return NTLSTATUS_SUCCESS; + void* handle = NtlAddExceptionHandler(1, PosixSignalNativeException, (void*)0); + if (!handle) return NTLSTATUS_LIMIT_REACHED; + process->ExceptionHandlerHandle = handle; + return NTLSTATUS_SUCCESS; +} + +// Delivers one POSIX signal from the generic user-APC substrate. +void PosixSignalApc(void* signalValue, void* routeValue, void* reserved) { + (void)routeValue; + (void)reserved; + int signalNumber = (int)(uintptr_t)signalValue; + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + if (!thread || !NtlPosixSignalValid(signalNumber)) return; + PosixSignalDeliver(thread, signalNumber); +} + +// Dispatches thread-pending signals made deliverable by a mask change. +void PosixSignalDispatchLocalPending(POSIX_THREAD_STATE* thread) { + if (!thread) return; + for (;;) { + uint64_t deliverable = thread->SignalPending & + ~(thread->SignalBlocked | thread->SignalHandlerBlocked) & + NTL_POSIX_SIGNAL_VALID_MASK; + deliverable |= thread->SignalPending & NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + if (deliverable == 0) return; + int signalNumber = NTL_POSIX_SIGNAL_HUP; + while (signalNumber <= NTL_POSIX_SIGNAL_MAX && + (deliverable & PosixSignalBit(signalNumber)) == 0) + ++signalNumber; + if (signalNumber > NTL_POSIX_SIGNAL_MAX) return; + PosixSignalDeliver(thread, signalNumber); + } +} + +// Initializes an empty signal set. +__declspec(dllexport) int sigemptyset(sigset_t* set) { + if (!set) { errno = EINVAL; return -1; } + *set = 0; + return 0; +} + +// Initializes a signal set containing every supported signal. +__declspec(dllexport) int sigfillset(sigset_t* set) { + if (!set) { errno = EINVAL; return -1; } + *set = NTL_POSIX_SIGNAL_VALID_MASK; + return 0; +} + +// Adds one signal to a signal set. +__declspec(dllexport) int sigaddset(sigset_t* set, int signalNumber) { + uint64_t bit = PosixSignalBit(signalNumber); + if (!set || bit == 0) { errno = EINVAL; return -1; } + *set |= bit; + return 0; +} + +// Removes one signal from a signal set. +__declspec(dllexport) int sigdelset(sigset_t* set, int signalNumber) { + uint64_t bit = PosixSignalBit(signalNumber); + if (!set || bit == 0) { errno = EINVAL; return -1; } + *set &= ~bit; + return 0; +} + +// Tests whether one signal belongs to a signal set. +__declspec(dllexport) int sigismember(const sigset_t* set, int signalNumber) { + uint64_t bit = PosixSignalBit(signalNumber); + if (!set || bit == 0) { errno = EINVAL; return -1; } + return (*set & bit) != 0 ? 1 : 0; +} + +// Installs or queries one logical-process-wide POSIX signal action. +__declspec(dllexport) int sigaction(int signalNumber, + const struct sigaction* action, + struct sigaction* oldAction) { + if (!NtlPosixSignalValid(signalNumber)) { errno = EINVAL; return -1; } + POSIX_LOGICAL_PROCESS_STATE* process = PosixRuntimeLogicalProcessState(); + if (!process) { errno = EIO; return -1; } + POSIX_SIGNAL_ACTION_STATE* state = &process->SignalActions[signalNumber]; + + if (oldAction) { + oldAction->sa_handler = (sighandler_t)state->Handler; + oldAction->sa_mask = state->Mask; + oldAction->sa_flags = (int)state->Flags; + } + if (!action) return 0; + if (action->sa_flags != 0 || (action->sa_mask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0 || + action->sa_handler == SIG_ERR || + (signalNumber == SIGKILL && action->sa_handler != SIG_DFL)) { + errno = EINVAL; + return -1; + } + + POSIX_SIGNAL_ACTION_STATE previous = *state; + state->Handler = (uintptr_t)action->sa_handler; + state->Mask = action->sa_mask & ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + state->Flags = (uint32_t)action->sa_flags; + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Operation = NTL_POSIX_OP_SET_SIGNAL_DISPOSITION; + request.SignalNumber = signalNumber; + request.SignalDisposition = PosixSignalDisposition(state); + NTL_POSIX_REPLY reply; + KSTATUS status = PosixSignalCallServer(&request, &reply); + if (!KSUCCESS(status)) { + *state = previous; + errno = PosixStatusToErrno(status); + return -1; + } + return 0; +} + +// Changes the calling thread's persistent POSIX signal mask. +__declspec(dllexport) int sigprocmask(int how, const sigset_t* set, sigset_t* oldSet) { + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + if (!thread) { errno = EIO; return -1; } + uint64_t previous = thread->SignalBlocked; + if (oldSet) *oldSet = previous; + if (!set) return 0; + if ((*set & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0 || + (how != SIG_BLOCK && how != SIG_UNBLOCK && how != SIG_SETMASK)) { + errno = EINVAL; + return -1; + } + + uint64_t next = previous; + if (how == SIG_BLOCK) next |= *set; + else if (how == SIG_UNBLOCK) next &= ~*set; + else next = *set; + next &= NTL_POSIX_SIGNAL_VALID_MASK & ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + thread->SignalBlocked = next; + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Operation = NTL_POSIX_OP_UPDATE_SIGNAL_MASK; + request.SignalMask = next; + NTL_POSIX_REPLY reply; + KSTATUS status = PosixSignalCallServer(&request, &reply); + if (!KSUCCESS(status)) { + thread->SignalBlocked = previous; + errno = PosixStatusToErrno(status); + return -1; + } + PosixSignalDispatchLocalPending(thread); + return 0; +} + +// Returns the union of calling-thread and process-pending signals. +__declspec(dllexport) int sigpending(sigset_t* set) { + if (!set) { errno = EINVAL; return -1; } + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + if (!thread) { errno = EIO; return -1; } + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Operation = NTL_POSIX_OP_GET_PENDING_SIGNALS; + NTL_POSIX_REPLY reply; + KSTATUS status = PosixSignalCallServer(&request, &reply); + if (!KSUCCESS(status)) { errno = PosixStatusToErrno(status); return -1; } + *set = (thread->SignalPending | reply.PendingSignals) & NTL_POSIX_SIGNAL_VALID_MASK; + return 0; +} + +// Atomically substitutes a temporary mask for the duration of one signal wait. +__declspec(dllexport) int sigsuspend(const sigset_t* set) { + if (!set || (*set & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) { + errno = EINVAL; + return -1; + } + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + if (!thread) { errno = EIO; return -1; } + + SYS_HANDLE event = SYS_HANDLE_INVALID; + KSTATUS status = SysCreateEvent(0u, 0u, &event); + if (!KSUCCESS(status)) { errno = PosixStatusToErrno(status); return -1; } + + SYS_HANDLE previousEvent = thread->SignalWaitEvent; + uint64_t deliveryCount = thread->SignalDeliveryCount; + thread->SignalWaitEvent = event; + sigset_t previousMask = 0; + if (sigprocmask(SIG_SETMASK, set, &previousMask) != 0) { + thread->SignalWaitEvent = previousEvent; + (void)SysCloseHandle(event); + return -1; + } + + for (;;) { + if (thread->SignalDeliveryCount != deliveryCount) { + status = NTLSTATUS_SUCCESS; + break; + } + status = SysWait(event, SYS_WAIT_FLAG_ALERTABLE); + if (status == NTLSTATUS_SUCCESS && thread->SignalDeliveryCount != deliveryCount) + break; + if (status == NTLSTATUS_USER_APC || status == NTLSTATUS_KERNEL_APC || + status == NTLSTATUS_ALERTED) + continue; + if (!KSUCCESS(status)) break; + } + + thread->SignalWaitEvent = previousEvent; + int restore = sigprocmask(SIG_SETMASK, &previousMask, (sigset_t*)0); + (void)SysCloseHandle(event); + if (restore != 0) return -1; + if (!KSUCCESS(status)) { + errno = PosixStatusToErrno(status); + return -1; + } + errno = EINTR; + return -1; +} + +// Sends one signal through stable POSIX process or process-group identity. +__declspec(dllexport) int kill(pid_t pid, int signalNumber) { + if (signalNumber < 0 || signalNumber > NTL_POSIX_SIGNAL_MAX) { + errno = EINVAL; + return -1; + } + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Operation = NTL_POSIX_OP_SEND_SIGNAL; + request.SignalTargetPid = (int32_t)pid; + request.SignalNumber = signalNumber; + NTL_POSIX_REPLY reply; + KSTATUS status = PosixSignalCallServer(&request, &reply); + if (!KSUCCESS(status)) { + if (status == NTLSTATUS_NOT_FOUND) errno = ESRCH; + else if (status == NTLSTATUS_ACCESS_DENIED) errno = EPERM; + else errno = PosixStatusToErrno(status); + return -1; + } + return 0; +} + +// Replaces the process alarm and returns the rounded-up previous remaining interval. +__declspec(dllexport) unsigned int alarm(unsigned int seconds) { + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Operation = NTL_POSIX_OP_SET_ALARM; + request.AlarmSeconds = seconds; + NTL_POSIX_REPLY reply; + KSTATUS status = PosixSignalCallServer(&request, &reply); + if (!KSUCCESS(status)) { + errno = PosixStatusToErrno(status); + return 0; + } + return reply.AlarmRemainingSeconds; +} + +// Waits until one caught signal runs. +__declspec(dllexport) int pause(void) { + sigset_t mask = 0; + if (sigprocmask(SIG_SETMASK, (const sigset_t*)0, &mask) != 0) return -1; + return sigsuspend(&mask); +} + +// Sleeps on a native waitable timer and reports whole seconds left after a caught signal. +__declspec(dllexport) unsigned int sleep(unsigned int seconds) { + if (seconds == 0) { + (void)SysYield(); + return 0; + } + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + if (!thread) return seconds; + + SYS_HANDLE timer = SYS_HANDLE_INVALID; + SYS_HANDLE signalEvent = SYS_HANDLE_INVALID; + KSTATUS status = SysCreateTimer(&timer); + if (!KSUCCESS(status)) return seconds; + status = SysCreateEvent(0u, 0u, &signalEvent); + if (!KSUCCESS(status)) { (void)SysCloseHandle(timer); return seconds; } + + uint64_t frequency = 0; + status = SysGetTickFrequency(&frequency); + if (!KSUCCESS(status) || frequency == 0) { + (void)SysCloseHandle(signalEvent); + (void)SysCloseHandle(timer); + return seconds; + } + uint64_t start = SysGetTick(); + + SYS_SET_TIMER_REQUEST setTimer; + memset(&setTimer, 0, sizeof(setTimer)); + setTimer.Size = sizeof(setTimer); + setTimer.TimerHandle = timer; + setTimer.Flags = SYS_TIMER_FLAG_NONE; + setTimer.DueTimeMilliseconds = (uint64_t)seconds * 1000u; + status = SysSetTimer(&setTimer); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(signalEvent); + (void)SysCloseHandle(timer); + return seconds; + } + + SYS_HANDLE previousEvent = thread->SignalWaitEvent; + uint64_t deliveryCount = thread->SignalDeliveryCount; + thread->SignalWaitEvent = signalEvent; + SYS_HANDLE waits[2] = { timer, signalEvent }; + uint32_t selected = UINT32_MAX; + SYS_WAIT_MANY_REQUEST wait; + memset(&wait, 0, sizeof(wait)); + wait.Size = sizeof(wait); + wait.Flags = SYS_WAIT_FLAG_ALERTABLE; + wait.Handles = waits; + wait.Count = 2u; + wait.SignaledIndexOut = &selected; + wait.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE; + for (;;) { + selected = UINT32_MAX; + status = SysWaitMany(&wait); + if (status == NTLSTATUS_SUCCESS && selected == 0u) break; + if (thread->SignalDeliveryCount != deliveryCount) break; + if (status == NTLSTATUS_SUCCESS || status == NTLSTATUS_USER_APC || + status == NTLSTATUS_KERNEL_APC || status == NTLSTATUS_ALERTED) + continue; + break; + } + thread->SignalWaitEvent = previousEvent; + + uint64_t elapsedTicks = SysGetTick() - start; + uint64_t elapsedMs = SysTicksToMilliseconds(elapsedTicks, frequency); + uint64_t totalMs = (uint64_t)seconds * 1000u; + uint64_t remainingMs = elapsedMs < totalMs ? totalMs - elapsedMs : 0; + unsigned int remaining = (unsigned int)((remainingMs + 999u) / 1000u); + + (void)SysCancelTimer(timer); + (void)SysCloseHandle(signalEvent); + (void)SysCloseHandle(timer); + if (status == NTLSTATUS_SUCCESS && selected == 0u) return 0; + if (thread->SignalDeliveryCount != deliveryCount) return remaining; + return remaining ? remaining : seconds; +} + +// Installs the historical one-argument signal disposition API. +__declspec(dllexport) sighandler_t signal(int signalNumber, sighandler_t handler) { + struct sigaction action; + struct sigaction previous; + action.sa_handler = handler; + action.sa_mask = 0; + action.sa_flags = 0; + if (sigaction(signalNumber, &action, &previous) != 0) return SIG_ERR; + return previous.sa_handler; +} + +// Raises one signal on the calling POSIX thread. +__declspec(dllexport) int raise(int signalNumber) { + if (!NtlPosixSignalValid(signalNumber)) { errno = EINVAL; return -1; } + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + if (!thread) { errno = EIO; return -1; } + PosixSignalDeliver(thread, signalNumber); + return 0; +} diff --git a/code/user/personalities/posix/posix_thread.c b/code/user/personalities/posix/posix_thread.c index f37c4d4..7104d87 100644 --- a/code/user/personalities/posix/posix_thread.c +++ b/code/user/personalities/posix/posix_thread.c @@ -35,6 +35,7 @@ static POSIX_PTHREAD_SLOT* PthreadSlotFind(uint32_t tid) { typedef struct { void* (*Fn)(void*); void* Arg; + uint64_t SignalMask; } PTHREAD_TRAMP; // Implements the pthread Trampoline operation. @@ -44,7 +45,7 @@ static void PthreadTrampoline(uintptr_t raw) { void* arg = tr->Arg; uint32_t tid = SysGetCurrentTid(); - if (PosixRuntimeAttachThread() != NTLSTATUS_SUCCESS) { + if (PosixRuntimeAttachThreadWithSignalMask(tr->SignalMask) != NTLSTATUS_SUCCESS) { free(tr); SysExitThread(ENOMEM); } @@ -76,6 +77,12 @@ __declspec(dllexport) int pthread_create(pthread_t* t, const pthread_attr_t* att if (!tr) return ENOMEM; tr->Fn = fn; tr->Arg = arg; + POSIX_THREAD_STATE* parentThread = PosixRuntimeThreadState(); + if (!parentThread) { + free(tr); + return EIO; + } + tr->SignalMask = parentThread->SignalBlocked; uint32_t tid = 0; uint32_t handle = 0; diff --git a/code/user/personalities/posix/posix_vfork.asm b/code/user/personalities/posix/posix_vfork.asm new file mode 100644 index 0000000..be62656 --- /dev/null +++ b/code/user/personalities/posix/posix_vfork.asm @@ -0,0 +1,157 @@ +; x64 POSIX vfork continuation. The parent enters the resume/wait syscall without +; a nested userspace return frame while the child owns the caller's stack. + +INCLUDE syscall_nr.inc + +VF_ReturnAddress EQU 0 +VF_ParentRsp EQU 8 +VF_Rbx EQU 16 +VF_Rbp EQU 24 +VF_Rsi EQU 32 +VF_Rdi EQU 40 +VF_R12 EQU 48 +VF_R13 EQU 56 +VF_R14 EQU 64 +VF_R15 EQU 72 +VF_Xmm6 EQU 80 +VF_Xmm7 EQU 96 +VF_Xmm8 EQU 112 +VF_Xmm9 EQU 128 +VF_Xmm10 EQU 144 +VF_Xmm11 EQU 160 +VF_Xmm12 EQU 176 +VF_Xmm13 EQU 192 +VF_Xmm14 EQU 208 +VF_Xmm15 EQU 224 +VF_Mxcsr EQU 240 +VF_X87Control EQU 244 +VF_ThreadHandle EQU 256 +VF_PreviousSuspendCount EQU 264 +VF_ContextSize EQU 280 +VF_LocalSize EQU 312 + +EXTERN PosixVforkPrepare:PROC +EXTERN PosixVforkParentFinish:PROC + +.CODE + +PUBLIC vfork +vfork PROC EXPORT + sub rsp, VF_LocalSize + lea r10, [rsp + 32] + + mov rax, [rsp + VF_LocalSize] + mov [r10 + VF_ReturnAddress], rax + lea rax, [rsp + VF_LocalSize + 8] + mov [r10 + VF_ParentRsp], rax + mov [r10 + VF_Rbx], rbx + mov [r10 + VF_Rbp], rbp + mov [r10 + VF_Rsi], rsi + mov [r10 + VF_Rdi], rdi + mov [r10 + VF_R12], r12 + mov [r10 + VF_R13], r13 + mov [r10 + VF_R14], r14 + mov [r10 + VF_R15], r15 + movdqu xmmword ptr [r10 + VF_Xmm6], xmm6 + movdqu xmmword ptr [r10 + VF_Xmm7], xmm7 + movdqu xmmword ptr [r10 + VF_Xmm8], xmm8 + movdqu xmmword ptr [r10 + VF_Xmm9], xmm9 + movdqu xmmword ptr [r10 + VF_Xmm10], xmm10 + movdqu xmmword ptr [r10 + VF_Xmm11], xmm11 + movdqu xmmword ptr [r10 + VF_Xmm12], xmm12 + movdqu xmmword ptr [r10 + VF_Xmm13], xmm13 + movdqu xmmword ptr [r10 + VF_Xmm14], xmm14 + movdqu xmmword ptr [r10 + VF_Xmm15], xmm15 + stmxcsr dword ptr [r10 + VF_Mxcsr] + fnstcw word ptr [r10 + VF_X87Control] + + mov rcx, r10 + call PosixVforkPrepare + test rax, rax + jnz vfork_wait + add rsp, VF_LocalSize + mov eax, -1 + ret + +vfork_wait: + ; R12 deliberately holds the heap context across the kernel wait. The child + ; restores the caller's original R12 in its independent native context. + mov r12, rax + mov ecx, dword ptr [r12 + VF_ThreadHandle] + lea rdx, [r12 + VF_PreviousSuspendCount] + mov eax, SYS_RESUME_THREAD_AND_WAIT + int 40h + mov r9d, eax + + ; The child may have overwritten every byte of the old vfork/helper frames. + ; Rebuild only a synthetic return frame from the heap continuation. + mov r10, r12 + mov r11, [r10 + VF_ParentRsp] + mov rax, [r10 + VF_ReturnAddress] + mov rsp, r11 + sub rsp, 8 + mov [rsp], rax + sub rsp, 40 + + ldmxcsr dword ptr [r10 + VF_Mxcsr] + fldcw word ptr [r10 + VF_X87Control] + movdqu xmm6, xmmword ptr [r10 + VF_Xmm6] + movdqu xmm7, xmmword ptr [r10 + VF_Xmm7] + movdqu xmm8, xmmword ptr [r10 + VF_Xmm8] + movdqu xmm9, xmmword ptr [r10 + VF_Xmm9] + movdqu xmm10, xmmword ptr [r10 + VF_Xmm10] + movdqu xmm11, xmmword ptr [r10 + VF_Xmm11] + movdqu xmm12, xmmword ptr [r10 + VF_Xmm12] + movdqu xmm13, xmmword ptr [r10 + VF_Xmm13] + movdqu xmm14, xmmword ptr [r10 + VF_Xmm14] + movdqu xmm15, xmmword ptr [r10 + VF_Xmm15] + mov rbx, [r10 + VF_Rbx] + mov rbp, [r10 + VF_Rbp] + mov rsi, [r10 + VF_Rsi] + mov rdi, [r10 + VF_Rdi] + mov r12, [r10 + VF_R12] + mov r13, [r10 + VF_R13] + mov r14, [r10 + VF_R14] + mov r15, [r10 + VF_R15] + + mov rcx, r10 + mov edx, r9d + call PosixVforkParentFinish + add rsp, 40 + ret +vfork ENDP + +PUBLIC PosixVforkEnterChild +PosixVforkEnterChild PROC + mov r10, rcx + mov r11, [r10 + VF_ParentRsp] + mov rax, [r10 + VF_ReturnAddress] + mov rsp, r11 + sub rsp, 8 + mov [rsp], rax + + ldmxcsr dword ptr [r10 + VF_Mxcsr] + fldcw word ptr [r10 + VF_X87Control] + movdqu xmm6, xmmword ptr [r10 + VF_Xmm6] + movdqu xmm7, xmmword ptr [r10 + VF_Xmm7] + movdqu xmm8, xmmword ptr [r10 + VF_Xmm8] + movdqu xmm9, xmmword ptr [r10 + VF_Xmm9] + movdqu xmm10, xmmword ptr [r10 + VF_Xmm10] + movdqu xmm11, xmmword ptr [r10 + VF_Xmm11] + movdqu xmm12, xmmword ptr [r10 + VF_Xmm12] + movdqu xmm13, xmmword ptr [r10 + VF_Xmm13] + movdqu xmm14, xmmword ptr [r10 + VF_Xmm14] + movdqu xmm15, xmmword ptr [r10 + VF_Xmm15] + mov rbx, [r10 + VF_Rbx] + mov rbp, [r10 + VF_Rbp] + mov rsi, [r10 + VF_Rsi] + mov rdi, [r10 + VF_Rdi] + mov r12, [r10 + VF_R12] + mov r13, [r10 + VF_R13] + mov r14, [r10 + VF_R14] + mov r15, [r10 + VF_R15] + xor eax, eax + ret +PosixVforkEnterChild ENDP + +END diff --git a/code/user/personalities/posix/posix_vfork.c b/code/user/personalities/posix/posix_vfork.c new file mode 100644 index 0000000..787926a --- /dev/null +++ b/code/user/personalities/posix/posix_vfork.c @@ -0,0 +1,331 @@ +// POSIX borrowed-child substrate used by the later public vfork continuation layer. + +#include +#include +#include +#include +#include "posix_internal.h" +#include "posix_fd.h" + +// Allocates one borrowed logical child and pins its inherited descriptor snapshot. +KSTATUS PosixVforkBeginBorrowedChild(POSIX_VFORK_CHILD_STATE** childOut) { + if (!childOut) return NTLSTATUS_INVALID_PARAMETER; + *childOut = (POSIX_VFORK_CHILD_STATE*)0; + + POSIX_THREAD_STATE* parentThread = PosixRuntimeThreadState(); + POSIX_PROCESS_STATE* nativeProcess = PosixRuntimeProcessState(); + if (!parentThread || !nativeProcess || + parentThread->LogicalProcess != &nativeProcess->DefaultIdentity) + return NTLSTATUS_NOT_SUPPORTED; + + POSIX_VFORK_CHILD_STATE* child = + (POSIX_VFORK_CHILD_STATE*)calloc(1u, sizeof(*child)); + if (!child) return NTLSTATUS_NO_MEMORY; + child->LogicalProcess.FdLock = SYS_HANDLE_INVALID; + child->InitialSignalMask = parentThread->SignalBlocked & + NTL_POSIX_SIGNAL_VALID_MASK & + ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + memcpy(child->LogicalProcess.SignalActions, + parentThread->LogicalProcess->SignalActions, + sizeof(child->LogicalProcess.SignalActions)); + + KSTATUS status = PosixFdCreateInheritanceSnapshot(0u, &child->FdInheritanceToken); + if (!KSUCCESS(status)) { + free(child); + return status; + } + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_VFORK_BEGIN; + request.FdInheritanceToken = child->FdInheritanceToken; + + NTL_POSIX_REPLY reply; + status = PosixRuntimeCallServer(&request, &reply); + if (!KSUCCESS(status) || !KSUCCESS(reply.Status)) { + KSTATUS failure = KSUCCESS(status) ? reply.Status : status; + PosixFdCancelInheritanceSnapshot(child->FdInheritanceToken); + free(child); + return failure; + } + if (reply.SpawnedPosixPid == 0 || reply.VforkToken == 0 || + reply.SpawnedPosixPid > 0x7FFFFFFFu) { + if (reply.SpawnedPosixPid != 0 && reply.VforkToken != 0) { + child->LogicalProcess.PosixPid = reply.SpawnedPosixPid; + child->LogicalProcess.Generation = 1u; + child->ServerToken = reply.VforkToken; + if (KSUCCESS(PosixVforkCancelBorrowedChild(child))) + return NTLSTATUS_FAILURE; + } + free(child); + return NTLSTATUS_FAILURE; + } + + child->LogicalProcess.PosixPid = reply.SpawnedPosixPid; + child->LogicalProcess.Generation = 1u; + child->ServerToken = reply.VforkToken; + *childOut = child; + return NTLSTATUS_SUCCESS; +} + +// Attaches the calling native thread to one prepared borrowed logical child. +KSTATUS PosixVforkAttachBorrowedChild(POSIX_VFORK_CHILD_STATE* child) { + if (!child || child->LogicalProcess.PosixPid == 0 || child->ServerToken == 0 || + child->FdInheritanceToken == 0) + return NTLSTATUS_INVALID_PARAMETER; + + SYS_TEB* teb = SysGetTeb(); + POSIX_PROCESS_STATE* nativeProcess = PosixRuntimeProcessState(); + if (!teb || teb->Magic != SYS_TEB_MAGIC || !nativeProcess || teb->SubsystemData) + return NTLSTATUS_ACCESS_DENIED; + + POSIX_THREAD_STATE* thread = (POSIX_THREAD_STATE*)calloc(1u, sizeof(*thread)); + if (!thread) return NTLSTATUS_NO_MEMORY; + thread->Magic = POSIX_THREAD_STATE_MAGIC; + thread->NativeProcess = nativeProcess; + thread->LogicalProcess = &child->LogicalProcess; + thread->BorrowedChild = child; + thread->NativeThreadId = SysGetCurrentTid(); + thread->SignalBlocked = child->InitialSignalMask; + if (thread->NativeThreadId == 0) { + free(thread); + return NTLSTATUS_FAILURE; + } + + KSTATUS status = SysSetUserApcDispatcher((void*)SysUserApcDispatcher); + if (!KSUCCESS(status)) { + free(thread); + return status; + } + + SYS_HANDLE selfThread = SYS_HANDLE_INVALID; + status = SysOpenCurrentThread(SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_RIGHT_DERIVE | + SYS_RIGHT_TRANSFER | SYS_THREAD_RIGHT_NOTIFY, &selfThread); + if (!KSUCCESS(status)) { + free(thread); + return status; + } + + SYS_PORT_HANDLE_TRANSFER transfer; + memset(&transfer, 0, sizeof(transfer)); + transfer.Handle = selfThread; + transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_RIGHT_DERIVE | + SYS_THREAD_RIGHT_NOTIFY; + transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_VFORK_ATTACH; + request.TargetPosixPid = child->LogicalProcess.PosixPid; + request.VforkToken = child->ServerToken; + request.SignalMask = thread->SignalBlocked; + request.SignalApcRoutine = (uint64_t)(uintptr_t)PosixSignalApc; + + NTL_POSIX_REPLY reply; + status = PosixRuntimeCallServerWithHandles(&request, &transfer, 1u, &reply); + (void)SysCloseHandle(selfThread); + if (!KSUCCESS(status) || !KSUCCESS(reply.Status)) { + KSTATUS failure = KSUCCESS(status) ? reply.Status : status; + thread->Magic = 0; + free(thread); + return failure; + } + if (reply.PosixPid != child->LogicalProcess.PosixPid || + reply.NativeGeneration != child->LogicalProcess.Generation || + reply.ProcessState != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED || + reply.VforkToken != child->ServerToken) { + thread->Magic = 0; + free(thread); + return NTLSTATUS_FAILURE; + } + + thread->ServerRegistered = 1u; + teb->SubsystemData = thread; + status = PosixFdInitializeLogicalProcess(&child->LogicalProcess, + child->FdInheritanceToken); + if (!KSUCCESS(status)) { + teb->SubsystemData = (void*)0; + thread->ServerRegistered = 0; + thread->BorrowedChild = (POSIX_VFORK_CHILD_STATE*)0; + thread->Magic = 0; + free(thread); + return status; + } + child->FdInheritanceToken = 0; + return NTLSTATUS_SUCCESS; +} + +// Tears down the calling borrowed thread's local wrappers before its native thread exits. +void PosixVforkDetachBorrowedChildLocal(POSIX_VFORK_CHILD_STATE* child, bool releaseServerHolders) { + if (!child) return; + SYS_TEB* teb = SysGetTeb(); + if (!teb || teb->Magic != SYS_TEB_MAGIC || !teb->SubsystemData) return; + POSIX_THREAD_STATE* thread = (POSIX_THREAD_STATE*)teb->SubsystemData; + if (thread->Magic != POSIX_THREAD_STATE_MAGIC || + thread->LogicalProcess != &child->LogicalProcess) + return; + + if (releaseServerHolders) PosixFdDestroyLogicalProcess(&child->LogicalProcess); + else PosixFdDestroyLogicalProcessLocal(&child->LogicalProcess); + teb->SubsystemData = (void*)0; + thread->ServerRegistered = 0; + thread->BorrowedChild = (POSIX_VFORK_CHILD_STATE*)0; + thread->Magic = 0; + free(thread); +} + +// Cancels one borrowed child after setup failure or after its native thread has exited. +KSTATUS PosixVforkCancelBorrowedChild(POSIX_VFORK_CHILD_STATE* child) { + if (!child || child->LogicalProcess.PosixPid == 0 || child->ServerToken == 0) + return NTLSTATUS_INVALID_PARAMETER; + + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_VFORK_CANCEL; + request.TargetPosixPid = child->LogicalProcess.PosixPid; + request.VforkToken = child->ServerToken; + + NTL_POSIX_REPLY reply; + KSTATUS status = PosixRuntimeCallServer(&request, &reply); + if (!KSUCCESS(status)) return status; + if (!KSUCCESS(reply.Status)) return reply.Status; + free(child); + return NTLSTATUS_SUCCESS; +} + +// Returns the borrowed-child state attached to the calling thread, if any. +POSIX_VFORK_CHILD_STATE* PosixVforkCurrentBorrowedChild(void) { + POSIX_THREAD_STATE* thread = PosixRuntimeThreadState(); + return thread ? thread->BorrowedChild : (POSIX_VFORK_CHILD_STATE*)0; +} + +// Records a genuine POSIX exit before the borrowed native thread self-terminates. +KSTATUS PosixVforkRecordExit(POSIX_VFORK_CHILD_STATE* child, int statusCode) { + if (!child || child->LogicalProcess.PosixPid == 0 || child->ServerToken == 0) + return NTLSTATUS_INVALID_PARAMETER; + NTL_POSIX_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Version = NTL_POSIX_PROTOCOL_VERSION; + request.Operation = NTL_POSIX_OP_VFORK_EXIT; + request.TargetPosixPid = child->LogicalProcess.PosixPid; + request.VforkToken = child->ServerToken; + request.VforkExitCode = statusCode; + NTL_POSIX_REPLY reply; + KSTATUS status = PosixRuntimeCallServer(&request, &reply); + if (!KSUCCESS(status)) return status; + return reply.Status; +} + +#include "posix_vfork_asm.h" + +_Static_assert(offsetof(POSIX_VFORK_CONTEXT, ReturnAddress) == 0, "vfork return offset"); +_Static_assert(offsetof(POSIX_VFORK_CONTEXT, ParentRsp) == 8, "vfork rsp offset"); +_Static_assert(offsetof(POSIX_VFORK_CONTEXT, Xmm) == 80, "vfork xmm offset"); +_Static_assert(offsetof(POSIX_VFORK_CONTEXT, Mxcsr) == 240, "vfork mxcsr offset"); +_Static_assert(offsetof(POSIX_VFORK_CONTEXT, Child) == 248, "vfork child offset"); +_Static_assert(offsetof(POSIX_VFORK_CONTEXT, ThreadHandle) == 256, "vfork handle offset"); +_Static_assert(offsetof(POSIX_VFORK_CONTEXT, PreviousSuspendCount) == 264, "vfork suspend offset"); +_Static_assert(offsetof(POSIX_VFORK_CONTEXT, ChildAttached) == 268, "vfork attached offset"); +_Static_assert(offsetof(POSIX_VFORK_CONTEXT, ChildAttachStatus) == 272, "vfork status offset"); +_Static_assert(sizeof(POSIX_VFORK_CONTEXT) == 280, "vfork context size"); + +// Allocates the persistent continuation and creates its borrowed native thread suspended. +POSIX_VFORK_CONTEXT* PosixVforkPrepare(const POSIX_VFORK_CONTEXT* captured) { + if (!captured) { + errno = EINVAL; + return (POSIX_VFORK_CONTEXT*)0; + } + POSIX_VFORK_CONTEXT* context = (POSIX_VFORK_CONTEXT*)calloc(1u, sizeof(*context)); + if (!context) { + errno = ENOMEM; + return (POSIX_VFORK_CONTEXT*)0; + } + memcpy(context, captured, offsetof(POSIX_VFORK_CONTEXT, Child)); + + KSTATUS status = PosixVforkBeginBorrowedChild(&context->Child); + if (!KSUCCESS(status)) { + free(context); + errno = PosixStatusToErrno(status); + return (POSIX_VFORK_CONTEXT*)0; + } + context->ChildPid = context->Child->LogicalProcess.PosixPid; + context->ThreadHandle = SYS_HANDLE_INVALID; + context->ChildAttachStatus = NTLSTATUS_FAILURE; + + SYS_CREATE_THREAD_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)PosixVforkChildBootstrap; + create.Argument = (uintptr_t)context; + create.Priority = 1u; + create.Flags = SYS_CREATE_THREAD_FLAG_SUSPENDED; + create.ThreadHandleOut = &context->ThreadHandle; + status = SysCreateThread(&create); + if (!KSUCCESS(status)) { + (void)PosixVforkCancelBorrowedChild(context->Child); + context->Child = (POSIX_VFORK_CHILD_STATE*)0; + free(context); + errno = PosixStatusToErrno(status); + return (POSIX_VFORK_CONTEXT*)0; + } + return context; +} + +// Attaches on the private initial stack before entering the borrowed parent continuation. +void PosixVforkChildBootstrap(uintptr_t argument) { + POSIX_VFORK_CONTEXT* context = (POSIX_VFORK_CONTEXT*)argument; + if (!context || !context->Child) { + SysExitThread((int32_t)NTLSTATUS_INVALID_PARAMETER); + return; + } + KSTATUS status = PosixVforkAttachBorrowedChild(context->Child); + context->ChildAttachStatus = status; + if (!KSUCCESS(status)) { + SysExitThread((int32_t)status); + return; + } + _ReadWriteBarrier(); + context->ChildAttached = 1u; + _ReadWriteBarrier(); + PosixVforkEnterChild(context); + SysExitThread((int32_t)NTLSTATUS_FAILURE); +} + +// Closes parent-side construction state after the atomic resume/wait has made the stack safe. +int PosixVforkParentFinish(POSIX_VFORK_CONTEXT* context, KSTATUS waitStatus) { + if (!context) { + errno = EIO; + return -1; + } + if (context->ThreadHandle != SYS_HANDLE_INVALID) { + (void)SysCloseHandle(context->ThreadHandle); + context->ThreadHandle = SYS_HANDLE_INVALID; + } + + int result = -1; + if (!KSUCCESS(waitStatus)) { + errno = PosixStatusToErrno(waitStatus); + } else if (context->PreviousSuspendCount != 1u) { + errno = EIO; + } else if (!context->ChildAttached) { + KSTATUS attachStatus = context->ChildAttachStatus; + if (context->Child) { + KSTATUS cancelStatus = PosixVforkCancelBorrowedChild(context->Child); + if (KSUCCESS(cancelStatus)) context->Child = (POSIX_VFORK_CHILD_STATE*)0; + } + errno = PosixStatusToErrno(KSUCCESS(attachStatus) ? NTLSTATUS_FAILURE : attachStatus); + } else { + result = (int)context->ChildPid; + } + + if (context->Child) free(context->Child); + free(context); + return result; +} diff --git a/code/user/personalities/posix/posix_vfork_asm.h b/code/user/personalities/posix/posix_vfork_asm.h new file mode 100644 index 0000000..12ad791 --- /dev/null +++ b/code/user/personalities/posix/posix_vfork_asm.h @@ -0,0 +1,37 @@ +// Shared layout for the x64 vfork continuation context. + +#pragma once + +#include +#include +#include +#include "posix_internal.h" + +typedef struct POSIX_VFORK_CONTEXT { + uint64_t ReturnAddress; + uint64_t ParentRsp; + uint64_t Rbx; + uint64_t Rbp; + uint64_t Rsi; + uint64_t Rdi; + uint64_t R12; + uint64_t R13; + uint64_t R14; + uint64_t R15; + uint8_t Xmm[10][16]; + uint32_t Mxcsr; + uint16_t X87Control; + uint16_t Reserved; + POSIX_VFORK_CHILD_STATE* Child; + SYS_HANDLE ThreadHandle; + uint32_t ChildPid; + uint32_t PreviousSuspendCount; + volatile uint32_t ChildAttached; + volatile KSTATUS ChildAttachStatus; + uint32_t Reserved2; +} POSIX_VFORK_CONTEXT; + +POSIX_VFORK_CONTEXT* PosixVforkPrepare(const POSIX_VFORK_CONTEXT* captured); +void PosixVforkChildBootstrap(uintptr_t argument); +int PosixVforkParentFinish(POSIX_VFORK_CONTEXT* context, KSTATUS waitStatus); +void PosixVforkEnterChild(POSIX_VFORK_CONTEXT* context); diff --git a/code/user/runtime/posix_start.c b/code/user/runtime/posix_start.c index a69d16b..6ba067e 100644 --- a/code/user/runtime/posix_start.c +++ b/code/user/runtime/posix_start.c @@ -5,8 +5,11 @@ #include #include -extern int main(void); +extern int main(int argc, char** argv, char** envp); __declspec(dllimport) uint32_t __ntl_posix_process_start(void); +__declspec(dllimport) uint32_t __ntl_posix_process_vectors(int* argcOut, + char*** argvOut, + char*** envpOut); // Initializes POSIX process state and exits with main's return value. void PosixStart(void) { @@ -20,7 +23,15 @@ void PosixStart(void) { while (true) SysExitProcess((int32_t)status); } - int exitCode = main(); + int argc = 0; + char** argv = (char**)0; + char** envp = (char**)0; + status = __ntl_posix_process_vectors(&argc, &argv, &envp); + if (status != NTLSTATUS_SUCCESS) { + while (true) SysExitProcess((int32_t)status); + } + + int exitCode = main(argc, argv, envp); exit(exitCode); while (true) {} } diff --git a/code/user/syscall/module_loader.c b/code/user/syscall/module_loader.c index 7402c31..45856e2 100644 --- a/code/user/syscall/module_loader.c +++ b/code/user/syscall/module_loader.c @@ -13,7 +13,7 @@ #define NTL_MODULE_CONTEXT_MAGIC 0x4D4F444Cu #define NTL_MODULE_PAGE_SIZE 4096u #define NTL_LOADER_DATA_MAGIC 0x30444C4Eu -#define NTL_LOADER_DATA_VERSION 3u +#define NTL_LOADER_DATA_VERSION 4u typedef struct NTL_LOADER_IMAGE_RECORD { uintptr_t Base; @@ -22,6 +22,11 @@ typedef struct NTL_LOADER_IMAGE_RECORD { char Name[NTL_MODULE_NAME_MAX]; } NTL_LOADER_IMAGE_RECORD; +typedef struct NTL_LOADER_STRING_RECORD { + uint32_t Offset; + uint32_t Length; +} NTL_LOADER_STRING_RECORD; + typedef struct NTL_LOADER_DATA { uint32_t Magic; uint32_t Version; @@ -31,6 +36,10 @@ typedef struct NTL_LOADER_DATA { uint32_t CurrentDirectoryLength; uint32_t StartupDataOffset; uint32_t StartupDataLength; + uint32_t ArgumentVectorOffset; + uint32_t ArgumentCount; + uint32_t EnvironmentVectorOffset; + uint32_t EnvironmentCount; NTL_LOADER_IMAGE_RECORD Images[]; } NTL_LOADER_DATA; @@ -48,8 +57,12 @@ typedef struct NTL_MODULE_ENTRY { struct NTL_MODULE_CONTEXT { uint32_t Magic; SYS_HANDLE ProcessHandle; - SYS_HANDLE ModuleRootHandle; - SYS_STRING_VIEW ModuleDirectory; + SYS_HANDLE PrimaryModuleRootHandle; + SYS_STRING_VIEW PrimaryModuleDirectory; + SYS_HANDLE SecondaryModuleRootHandle; + SYS_STRING_VIEW SecondaryModuleDirectory; + SYS_HANDLE FallbackModuleRootHandle; + SYS_STRING_VIEW FallbackModuleDirectory; NTL_MODULE_NAME_RESOLVER Resolver; void* ResolverContext; NTL_MODULE_ENTRY* Head; @@ -202,27 +215,6 @@ static KSTATUS NtlpCanonicalName(NTL_MODULE_CONTEXT* context, return NtlpCopyName(*canonicalOut, scratch); } -// Builds the capability-relative dependency path beneath the configured module directory. -static KSTATUS NtlpBuildModulePath(NTL_MODULE_CONTEXT* context, - SYS_STRING_VIEW canonical, - char out[SYS_PATH_MAX]) { - if (!context || !out) return NTLSTATUS_INVALID_PARAMETER; - char name[NTL_MODULE_NAME_MAX]; - KSTATUS status = NtlpCopyName(canonical, name); - if (!KSUCCESS(status)) return status; - size_t dirLength = context->ModuleDirectory.Length; - if (!SysSvIsValid(context->ModuleDirectory) || SysSvContainsByte(context->ModuleDirectory, '\0')) - return NTLSTATUS_INVALID_PARAMETER; - size_t total = dirLength + (dirLength ? 1u : 0u) + canonical.Length; - if (total + 1u > SYS_PATH_MAX) return NTLSTATUS_BUFFER_TOO_SMALL; - if (dirLength) memcpy(out, context->ModuleDirectory.Data, dirLength); - size_t pos = dirLength; - if (dirLength) out[pos++] = '/'; - memcpy(out + pos, canonical.Data, canonical.Length); - out[total] = '\0'; - return NTLSTATUS_SUCCESS; -} - // Replaces the final component of an explicit module path with its canonical policy name. static KSTATUS NtlpBuildCanonicalPath(SYS_STRING_VIEW path, SYS_STRING_VIEW canonical, @@ -246,23 +238,54 @@ static KSTATUS NtlpBuildCanonicalPath(SYS_STRING_VIEW path, return NTLSTATUS_SUCCESS; } -// Opens one canonical dependency through the caller-supplied module-root capability. +// Opens one canonical dependency from a single capability-relative search location. +static KSTATUS NtlpOpenDependencyAt(SYS_HANDLE rootHandle, + SYS_STRING_VIEW directory, + SYS_STRING_VIEW canonical, + SYS_HANDLE* fileOut) { + if (rootHandle == SYS_HANDLE_INVALID || !fileOut || !SysSvIsValid(directory) || + SysSvContainsByte(directory, '\0')) + return NTLSTATUS_INVALID_PARAMETER; + *fileOut = SYS_HANDLE_INVALID; + + char path[SYS_PATH_MAX]; + char name[NTL_MODULE_NAME_MAX]; + KSTATUS status = NtlpCopyName(canonical, name); + if (!KSUCCESS(status)) return status; + size_t dirLength = directory.Length; + size_t total = dirLength + (dirLength ? 1u : 0u) + canonical.Length; + if (total + 1u > sizeof(path)) return NTLSTATUS_BUFFER_TOO_SMALL; + if (dirLength) memcpy(path, directory.Data, dirLength); + size_t pos = dirLength; + if (dirLength) path[pos++] = '/'; + memcpy(path + pos, canonical.Data, canonical.Length); + path[total] = '\0'; + + SYS_OPEN_FILE_AT_REQUEST open = SysMakeOpenFileAtRequest(rootHandle, SysSvFromCstr(path), + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, fileOut); + return SysOpenFileAt(&open); +} + +// Searches the available current-directory root, then image-private and configured fallback roots. static KSTATUS NtlpOpenDependency(NTL_MODULE_CONTEXT* context, SYS_STRING_VIEW canonical, SYS_HANDLE* fileOut) { - if (!context || context->ModuleRootHandle == SYS_HANDLE_INVALID || !fileOut) - return NTLSTATUS_INVALID_PARAMETER; - *fileOut = SYS_HANDLE_INVALID; - char path[SYS_PATH_MAX]; - KSTATUS status = NtlpBuildModulePath(context, canonical, path); - if (!KSUCCESS(status)) return status; - SYS_OPEN_FILE_AT_REQUEST open = SysMakeOpenFileAtRequest(context->ModuleRootHandle, - SysSvFromCstr(path), - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - fileOut); - return SysOpenFileAt(&open); + if (!context || !fileOut) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = NtlpOpenDependencyAt(context->PrimaryModuleRootHandle, + context->PrimaryModuleDirectory, canonical, fileOut); + if (status != NTLSTATUS_NOT_FOUND) return status; + + if (context->SecondaryModuleRootHandle != SYS_HANDLE_INVALID) { + status = NtlpOpenDependencyAt(context->SecondaryModuleRootHandle, + context->SecondaryModuleDirectory, canonical, fileOut); + if (status != NTLSTATUS_NOT_FOUND) return status; + } + + if (context->FallbackModuleRootHandle == SYS_HANDLE_INVALID) return NTLSTATUS_NOT_FOUND; + return NtlpOpenDependencyAt(context->FallbackModuleRootHandle, + context->FallbackModuleDirectory, canonical, fileOut); } // Publishes a mapped image before import binding so cyclic dependencies can resolve it. @@ -356,14 +379,22 @@ static KSTATUS NtlpResolveImport(void* opaque, // Creates loader state for a child process or seeds the current process from PEB loader data. KSTATUS NtlpCreateModuleContext(SYS_HANDLE processHandle, - SYS_HANDLE moduleRootHandle, - SYS_STRING_VIEW moduleDirectory, + SYS_HANDLE primaryModuleRootHandle, + SYS_STRING_VIEW primaryModuleDirectory, + SYS_HANDLE secondaryModuleRootHandle, + SYS_STRING_VIEW secondaryModuleDirectory, + SYS_HANDLE fallbackModuleRootHandle, + SYS_STRING_VIEW fallbackModuleDirectory, NTL_MODULE_NAME_RESOLVER resolver, void* resolverContext, int seedCurrentProcess, NTL_MODULE_CONTEXT** contextOut) { - if (!contextOut || moduleRootHandle == SYS_HANDLE_INVALID || !SysSvIsValid(moduleDirectory) || - SysSvContainsByte(moduleDirectory, '\0')) + if (!contextOut || primaryModuleRootHandle == SYS_HANDLE_INVALID || + !SysSvIsValid(primaryModuleDirectory) || SysSvContainsByte(primaryModuleDirectory, '\0') || + (secondaryModuleRootHandle != SYS_HANDLE_INVALID && + (!SysSvIsValid(secondaryModuleDirectory) || SysSvContainsByte(secondaryModuleDirectory, '\0'))) || + (fallbackModuleRootHandle != SYS_HANDLE_INVALID && + (!SysSvIsValid(fallbackModuleDirectory) || SysSvContainsByte(fallbackModuleDirectory, '\0')))) return NTLSTATUS_INVALID_PARAMETER; *contextOut = 0; @@ -372,8 +403,12 @@ KSTATUS NtlpCreateModuleContext(SYS_HANDLE processHandle, if (!KSUCCESS(status)) return status; context->Magic = NTL_MODULE_CONTEXT_MAGIC; context->ProcessHandle = processHandle; - context->ModuleRootHandle = moduleRootHandle; - context->ModuleDirectory = moduleDirectory; + context->PrimaryModuleRootHandle = primaryModuleRootHandle; + context->PrimaryModuleDirectory = primaryModuleDirectory; + context->SecondaryModuleRootHandle = secondaryModuleRootHandle; + context->SecondaryModuleDirectory = secondaryModuleDirectory; + context->FallbackModuleRootHandle = fallbackModuleRootHandle; + context->FallbackModuleDirectory = fallbackModuleDirectory; context->Resolver = resolver; context->ResolverContext = resolverContext; context->OwnsStorage = 1u; @@ -495,9 +530,26 @@ KSTATUS NtlpLoadMainImage(NTL_MODULE_CONTEXT* context, return NtlLoadPeImage(&load); } +// Validates one caller-owned process string vector and returns its serialized string bytes. +static KSTATUS NtlpMeasureProcessVector(NTL_PROCESS_STRING_VECTOR vector, size_t* bytesOut) { + if (!bytesOut || (vector.Count && !vector.Entries)) return NTLSTATUS_INVALID_PARAMETER; + size_t bytes = 0; + for (uint32_t i = 0; i < vector.Count; ++i) { + SYS_STRING_VIEW entry = vector.Entries[i]; + if (!SysSvIsValid(entry) || SysSvContainsByte(entry, '\0') || entry.Length > UINT32_MAX) + return NTLSTATUS_INVALID_PARAMETER; + if (entry.Length > SIZE_MAX - bytes - 1u) return NTLSTATUS_NO_MEMORY; + bytes += entry.Length + 1u; + } + *bytesOut = bytes; + return NTLSTATUS_SUCCESS; +} + // Serializes startup dependency metadata into a read-only child-owned VAD. KSTATUS NtlpBuildLoaderData(NTL_MODULE_CONTEXT* context, SYS_STRING_VIEW currentDirectory, + NTL_PROCESS_STRING_VECTOR arguments, + NTL_PROCESS_STRING_VECTOR environment, const void* startupData, size_t startupDataSize, void** loaderDataOut) { @@ -510,10 +562,28 @@ KSTATUS NtlpBuildLoaderData(NTL_MODULE_CONTEXT* context, return NTLSTATUS_INVALID_PARAMETER; *loaderDataOut = 0; + size_t argumentStringBytes = 0; + size_t environmentStringBytes = 0; + KSTATUS status = NtlpMeasureProcessVector(arguments, &argumentStringBytes); + if (!KSUCCESS(status)) return status; + status = NtlpMeasureProcessVector(environment, &environmentStringBytes); + if (!KSUCCESS(status)) return status; + size_t recordsBytes = (size_t)context->Count * sizeof(NTL_LOADER_IMAGE_RECORD); if (context->Count && recordsBytes / sizeof(NTL_LOADER_IMAGE_RECORD) != context->Count) return NTLSTATUS_NO_MEMORY; size_t bytes = sizeof(NTL_LOADER_DATA) + recordsBytes; + size_t argumentRecordsBytes = (size_t)arguments.Count * sizeof(NTL_LOADER_STRING_RECORD); + size_t environmentRecordsBytes = (size_t)environment.Count * sizeof(NTL_LOADER_STRING_RECORD); + if ((arguments.Count && argumentRecordsBytes / sizeof(NTL_LOADER_STRING_RECORD) != arguments.Count) || + (environment.Count && environmentRecordsBytes / sizeof(NTL_LOADER_STRING_RECORD) != environment.Count) || + argumentRecordsBytes > SIZE_MAX - bytes) + return NTLSTATUS_NO_MEMORY; + size_t argumentVectorOffset = arguments.Count ? bytes : 0u; + bytes += argumentRecordsBytes; + if (environmentRecordsBytes > SIZE_MAX - bytes) return NTLSTATUS_NO_MEMORY; + size_t environmentVectorOffset = environment.Count ? bytes : 0u; + bytes += environmentRecordsBytes; if (bytes < sizeof(NTL_LOADER_DATA) || currentDirectory.Length > SIZE_MAX - bytes - 1u) return NTLSTATUS_NO_MEMORY; size_t currentDirectoryOffset = bytes; @@ -524,6 +594,12 @@ KSTATUS NtlpBuildLoaderData(NTL_MODULE_CONTEXT* context, startupDataOffset = bytes; bytes += startupDataSize; } + if (argumentStringBytes > SIZE_MAX - bytes) return NTLSTATUS_NO_MEMORY; + size_t argumentStringsOffset = bytes; + bytes += argumentStringBytes; + if (environmentStringBytes > SIZE_MAX - bytes) return NTLSTATUS_NO_MEMORY; + size_t environmentStringsOffset = bytes; + bytes += environmentStringBytes; if (bytes > UINT32_MAX) return NTLSTATUS_NO_MEMORY; if (bytes > SIZE_MAX - (NTL_MODULE_PAGE_SIZE - 1u)) return NTLSTATUS_NO_MEMORY; size_t pages = (bytes + NTL_MODULE_PAGE_SIZE - 1u) / NTL_MODULE_PAGE_SIZE; @@ -537,7 +613,7 @@ KSTATUS NtlpBuildLoaderData(NTL_MODULE_CONTEXT* context, alloc.Flags = SYS_VM_FLAG_NONE; alloc.PageCount = pages; alloc.BaseOut = &base; - KSTATUS status = SysAllocProcessVm(&alloc); + status = SysAllocProcessVm(&alloc); if (!KSUCCESS(status)) return status; NTL_LOADER_DATA* data = (NTL_LOADER_DATA*)base; @@ -550,6 +626,10 @@ KSTATUS NtlpBuildLoaderData(NTL_MODULE_CONTEXT* context, data->CurrentDirectoryLength = (uint32_t)currentDirectory.Length; data->StartupDataOffset = startupDataSize ? (uint32_t)startupDataOffset : 0u; data->StartupDataLength = (uint32_t)startupDataSize; + data->ArgumentVectorOffset = arguments.Count ? (uint32_t)argumentVectorOffset : 0u; + data->ArgumentCount = arguments.Count; + data->EnvironmentVectorOffset = environment.Count ? (uint32_t)environmentVectorOffset : 0u; + data->EnvironmentCount = environment.Count; uint32_t i = 0; for (NTL_MODULE_ENTRY* entry = context->Head; entry; entry = entry->Next) { NTL_LOADER_IMAGE_RECORD* image = &data->Images[i++]; @@ -564,16 +644,40 @@ KSTATUS NtlpBuildLoaderData(NTL_MODULE_CONTEXT* context, if (startupDataSize) memcpy((char*)data + startupDataOffset, startupData, startupDataSize); + NTL_LOADER_STRING_RECORD* argumentRecords = arguments.Count + ? (NTL_LOADER_STRING_RECORD*)((char*)data + argumentVectorOffset) : 0; + size_t stringOffset = argumentStringsOffset; + for (uint32_t argumentIndex = 0; argumentIndex < arguments.Count; ++argumentIndex) { + SYS_STRING_VIEW entry = arguments.Entries[argumentIndex]; + argumentRecords[argumentIndex].Offset = (uint32_t)stringOffset; + argumentRecords[argumentIndex].Length = (uint32_t)entry.Length; + if (entry.Length) memcpy((char*)data + stringOffset, entry.Data, entry.Length); + ((char*)data)[stringOffset + entry.Length] = '\0'; + stringOffset += entry.Length + 1u; + } + + NTL_LOADER_STRING_RECORD* environmentRecords = environment.Count + ? (NTL_LOADER_STRING_RECORD*)((char*)data + environmentVectorOffset) : 0; + stringOffset = environmentStringsOffset; + for (uint32_t environmentIndex = 0; environmentIndex < environment.Count; ++environmentIndex) { + SYS_STRING_VIEW entry = environment.Entries[environmentIndex]; + environmentRecords[environmentIndex].Offset = (uint32_t)stringOffset; + environmentRecords[environmentIndex].Length = (uint32_t)entry.Length; + if (entry.Length) memcpy((char*)data + stringOffset, entry.Data, entry.Length); + ((char*)data)[stringOffset + entry.Length] = '\0'; + stringOffset += entry.Length + 1u; + } + status = SysProtectProcessVm(context->ProcessHandle, base, pages, SYS_VM_READ); if (!KSUCCESS(status)) return status; *loaderDataOut = base; return NTLSTATUS_SUCCESS; } -// Copies the opaque userspace bootstrap payload from the current process loader block. -KSTATUS NtlGetProcessStartupData(void* buffer, size_t bufferSize, size_t* sizeOut) { - if (!sizeOut || (bufferSize && !buffer)) return NTLSTATUS_INVALID_PARAMETER; - *sizeOut = 0; +// Validates and returns the immutable loader block for the current process. +static KSTATUS NtlpGetCurrentLoaderData(const NTL_LOADER_DATA** dataOut) { + if (!dataOut) return NTLSTATUS_INVALID_PARAMETER; + *dataOut = 0; SYS_PEB* peb = SysGetPeb(); if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION || !peb->LoaderData) @@ -595,8 +699,21 @@ KSTATUS NtlGetProcessStartupData(void* buffer, size_t bufferSize, size_t* sizeOu const NTL_LOADER_DATA* data = (const NTL_LOADER_DATA*)peb->LoaderData; if (data->Magic != NTL_LOADER_DATA_MAGIC || data->Version != NTL_LOADER_DATA_VERSION || - data->Size < sizeof(*data) || data->Size > regionBytes - (dataBase - regionBase) || - data->StartupDataLength > NTL_PROCESS_STARTUP_DATA_MAX || + data->Size < sizeof(*data) || data->Size > regionBytes - (dataBase - regionBase)) + return NTLSTATUS_FAILURE; + *dataOut = data; + return NTLSTATUS_SUCCESS; +} + +// Copies the opaque userspace bootstrap payload from the current process loader block. +KSTATUS NtlGetProcessStartupData(void* buffer, size_t bufferSize, size_t* sizeOut) { + if (!sizeOut || (bufferSize && !buffer)) return NTLSTATUS_INVALID_PARAMETER; + *sizeOut = 0; + + const NTL_LOADER_DATA* data = 0; + KSTATUS status = NtlpGetCurrentLoaderData(&data); + if (!KSUCCESS(status)) return status; + if (data->StartupDataLength > NTL_PROCESS_STARTUP_DATA_MAX || (data->StartupDataLength == 0 && data->StartupDataOffset != 0) || (data->StartupDataLength != 0 && (data->StartupDataOffset < sizeof(*data) || data->StartupDataOffset > data->Size || @@ -612,33 +729,85 @@ KSTATUS NtlGetProcessStartupData(void* buffer, size_t bufferSize, size_t* sizeOu return NTLSTATUS_SUCCESS; } +// Returns one loader string vector count after validating its descriptor table. +static KSTATUS NtlpGetProcessVectorCount(int environment, uint32_t* countOut) { + if (!countOut) return NTLSTATUS_INVALID_PARAMETER; + *countOut = 0; + const NTL_LOADER_DATA* data = 0; + KSTATUS status = NtlpGetCurrentLoaderData(&data); + if (!KSUCCESS(status)) return status; + + uint32_t count = environment ? data->EnvironmentCount : data->ArgumentCount; + uint32_t offset = environment ? data->EnvironmentVectorOffset : data->ArgumentVectorOffset; + if ((count == 0 && offset != 0) || (count != 0 && offset == 0)) return NTLSTATUS_FAILURE; + size_t recordsBytes = (size_t)count * sizeof(NTL_LOADER_STRING_RECORD); + if (count && recordsBytes / sizeof(NTL_LOADER_STRING_RECORD) != count) + return NTLSTATUS_FAILURE; + if (offset > data->Size || recordsBytes > data->Size - offset) + return NTLSTATUS_FAILURE; + *countOut = count; + return NTLSTATUS_SUCCESS; +} + +// Returns one immutable loader string vector entry. +static KSTATUS NtlpGetProcessVectorEntry(int environment, uint32_t index, SYS_STRING_VIEW* entryOut) { + if (!entryOut) return NTLSTATUS_INVALID_PARAMETER; + *entryOut = SYS_SV_NULL; + const NTL_LOADER_DATA* data = 0; + KSTATUS status = NtlpGetCurrentLoaderData(&data); + if (!KSUCCESS(status)) return status; + + uint32_t count = environment ? data->EnvironmentCount : data->ArgumentCount; + uint32_t offset = environment ? data->EnvironmentVectorOffset : data->ArgumentVectorOffset; + if (index >= count || !count || !offset) return NTLSTATUS_NOT_FOUND; + size_t recordsBytes = (size_t)count * sizeof(NTL_LOADER_STRING_RECORD); + if (recordsBytes / sizeof(NTL_LOADER_STRING_RECORD) != count || + offset > data->Size || recordsBytes > data->Size - offset) + return NTLSTATUS_FAILURE; + + const NTL_LOADER_STRING_RECORD* records = + (const NTL_LOADER_STRING_RECORD*)((const char*)data + offset); + NTL_LOADER_STRING_RECORD record = records[index]; + if (record.Offset >= data->Size || record.Length > data->Size - record.Offset || + record.Length == data->Size - record.Offset) + return NTLSTATUS_FAILURE; + const char* string = (const char*)data + record.Offset; + SYS_STRING_VIEW view = SysSvFromParts(string, record.Length); + if (string[record.Length] != '\0' || SysSvContainsByte(view, '\0')) + return NTLSTATUS_FAILURE; + *entryOut = view; + return NTLSTATUS_SUCCESS; +} + +// Returns the number of serialized process arguments. +KSTATUS NtlGetProcessArgumentCount(uint32_t* countOut) { + return NtlpGetProcessVectorCount(0, countOut); +} + +// Returns one serialized process argument. +KSTATUS NtlGetProcessArgument(uint32_t index, SYS_STRING_VIEW* argumentOut) { + return NtlpGetProcessVectorEntry(0, index, argumentOut); +} + +// Returns the number of serialized environment entries. +KSTATUS NtlGetProcessEnvironmentCount(uint32_t* countOut) { + return NtlpGetProcessVectorCount(1, countOut); +} + +// Returns one serialized environment entry. +KSTATUS NtlGetProcessEnvironment(uint32_t index, SYS_STRING_VIEW* entryOut) { + return NtlpGetProcessVectorEntry(1, index, entryOut); +} + // Returns the canonical inherited resource CWD stored in this process's loader data. KSTATUS NtlpGetLoaderCurrentDirectory(SYS_STRING_VIEW* currentDirectoryOut) { if (!currentDirectoryOut) return NTLSTATUS_INVALID_PARAMETER; - *currentDirectoryOut = SysSvFromParts((const char*)0, 0); + *currentDirectoryOut = SYS_SV_NULL; - SYS_PEB* peb = SysGetPeb(); - if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION || !peb->LoaderData) - return NTLSTATUS_NOT_FOUND; - - SYS_VM_REGION_INFO region; - memset(®ion, 0, sizeof(region)); - KSTATUS status = SysQueryVm((void*)peb->LoaderData, ®ion); - if (!KSUCCESS(status) || !region.Base || !region.PageCount || - region.PageCount > SIZE_MAX / NTL_MODULE_PAGE_SIZE) - return NTLSTATUS_FAILURE; - - size_t regionBytes = region.PageCount * NTL_MODULE_PAGE_SIZE; - uintptr_t regionBase = (uintptr_t)region.Base; - uintptr_t dataBase = (uintptr_t)peb->LoaderData; - if (dataBase < regionBase || dataBase - regionBase > regionBytes || - regionBytes - (dataBase - regionBase) < sizeof(NTL_LOADER_DATA)) - return NTLSTATUS_FAILURE; - - const NTL_LOADER_DATA* data = (const NTL_LOADER_DATA*)peb->LoaderData; - if (data->Magic != NTL_LOADER_DATA_MAGIC || data->Version != NTL_LOADER_DATA_VERSION || - data->Size < sizeof(*data) || data->Size > regionBytes - (dataBase - regionBase) || - data->CurrentDirectoryOffset < sizeof(*data) || + const NTL_LOADER_DATA* data = 0; + KSTATUS status = NtlpGetCurrentLoaderData(&data); + if (!KSUCCESS(status)) return status; + if (data->CurrentDirectoryOffset < sizeof(*data) || data->CurrentDirectoryLength == 0 || data->CurrentDirectoryOffset > data->Size || data->CurrentDirectoryLength > data->Size - data->CurrentDirectoryOffset || @@ -664,6 +833,8 @@ static KSTATUS NtlpEnsureCurrentContext(SYS_HANDLE moduleRoot, if (_InterlockedCompareExchange(&gCurrentContextState, 1, 0) == 0) { NTL_MODULE_CONTEXT* temporary = 0; KSTATUS status = NtlpCreateModuleContext(SYS_HANDLE_INVALID, moduleRoot, moduleDirectory, + SYS_HANDLE_INVALID, SysSvFromParts((const char*)0, 0), + SYS_HANDLE_INVALID, SysSvFromParts((const char*)0, 0), resolver, resolverContext, 1, &temporary); if (!KSUCCESS(status)) { _InterlockedExchange(&gCurrentContextState, 0); @@ -679,8 +850,12 @@ static KSTATUS NtlpEnsureCurrentContext(SYS_HANDLE moduleRoot, while (_InterlockedCompareExchange(&gCurrentContextState, 2, 2) == 1) SysYield(); if (_InterlockedCompareExchange(&gCurrentContextState, 2, 2) != 2) return NTLSTATUS_FAILURE; - gCurrentContext.ModuleRootHandle = moduleRoot; - gCurrentContext.ModuleDirectory = moduleDirectory; + gCurrentContext.PrimaryModuleRootHandle = moduleRoot; + gCurrentContext.PrimaryModuleDirectory = moduleDirectory; + gCurrentContext.SecondaryModuleRootHandle = SYS_HANDLE_INVALID; + gCurrentContext.SecondaryModuleDirectory = SysSvFromParts((const char*)0, 0); + gCurrentContext.FallbackModuleRootHandle = SYS_HANDLE_INVALID; + gCurrentContext.FallbackModuleDirectory = SysSvFromParts((const char*)0, 0); gCurrentContext.Resolver = resolver; gCurrentContext.ResolverContext = resolverContext; } diff --git a/code/user/syscall/module_loader_internal.h b/code/user/syscall/module_loader_internal.h index 126945a..3f4d5b3 100644 --- a/code/user/syscall/module_loader_internal.h +++ b/code/user/syscall/module_loader_internal.h @@ -4,14 +4,19 @@ #include #include +#include #include typedef struct NTL_MODULE_CONTEXT NTL_MODULE_CONTEXT; // Creates dependency-loader state for one constructing child or the current process. KSTATUS NtlpCreateModuleContext(SYS_HANDLE processHandle, - SYS_HANDLE moduleRootHandle, - SYS_STRING_VIEW moduleDirectory, + SYS_HANDLE primaryModuleRootHandle, + SYS_STRING_VIEW primaryModuleDirectory, + SYS_HANDLE secondaryModuleRootHandle, + SYS_STRING_VIEW secondaryModuleDirectory, + SYS_HANDLE fallbackModuleRootHandle, + SYS_STRING_VIEW fallbackModuleDirectory, NTL_MODULE_NAME_RESOLVER resolver, void* resolverContext, int seedCurrentProcess, @@ -25,6 +30,8 @@ KSTATUS NtlpLoadMainImage(NTL_MODULE_CONTEXT* context, // Builds the opaque read-only loader-data block consumed later by userspace base services. KSTATUS NtlpBuildLoaderData(NTL_MODULE_CONTEXT* context, SYS_STRING_VIEW currentDirectory, + NTL_PROCESS_STRING_VECTOR arguments, + NTL_PROCESS_STRING_VECTOR environment, const void* startupData, size_t startupDataSize, void** loaderDataOut); diff --git a/code/user/syscall/process_loader.c b/code/user/syscall/process_loader.c index db7e1e7..3c44dd4 100644 --- a/code/user/syscall/process_loader.c +++ b/code/user/syscall/process_loader.c @@ -15,12 +15,116 @@ static void NtlDiscardChild(SYS_HANDLE processHandle) { (void)SysCloseHandle(processHandle); } -// Creates and starts one child from an already-open PE file capability. -static KSTATUS NtlCreateProcessFile(const NTL_CREATE_PROCESS_AT_REQUEST* request, SYS_HANDLE fileHandle) { - if (!request || fileHandle == SYS_HANDLE_INVALID) - return NTLSTATUS_INVALID_PARAMETER; +#define NTL_PROCESS_BUILDER_MAGIC 0x3142504Eu + +typedef struct NTL_PROCESS_BUILDER_INTERNAL { + uint32_t Magic; + uint32_t StartupCapabilityCount; + SYS_HANDLE ProcessHandle; + NTL_MODULE_CONTEXT* Modules; + NTL_PE_IMAGE Image; + void* LoaderData; + SYS_STARTUP_CAPABILITY ChildCapabilities[SYS_STARTUP_CAPABILITY_MAX]; +} NTL_PROCESS_BUILDER_INTERNAL; + +typedef char NTL_PROCESS_BUILDER_SIZE_CHECK[ + sizeof(NTL_PROCESS_BUILDER_INTERNAL) <= sizeof(NTL_PROCESS_BUILDER) ? 1 : -1]; + +static NTL_PROCESS_BUILDER_INTERNAL* NtlBuilderInternal(NTL_PROCESS_BUILDER* builder) { + return (NTL_PROCESS_BUILDER_INTERNAL*)(void*)builder; +} + +static int NtlBuilderRequestValid(const NTL_PROCESS_BUILDER_REQUEST* request) { + return request && request->Size == sizeof(*request) && + request->Flags == NTL_PROCESS_LOAD_FLAG_NONE && + request->RootHandle != SYS_HANDLE_INVALID && + request->ModuleRootHandle != SYS_HANDLE_INVALID && + request->Path.Data && request->Path.Length != 0 && + SysSvIsValid(request->ModuleDirectory) && + request->StartupCapabilityCount <= SYS_STARTUP_CAPABILITY_MAX && + (!request->StartupCapabilityCount || request->StartupCapabilities) && + (!request->Arguments.Count || request->Arguments.Entries) && + (!request->Environment.Count || request->Environment.Entries) && + request->StartupDataSize <= NTL_PROCESS_STARTUP_DATA_MAX && + (!request->StartupDataSize || request->StartupData); +} + +static void NtlBuilderReset(NTL_PROCESS_BUILDER* builder) { + if (builder) memset(builder, 0, sizeof(*builder)); +} + +// Aborts one prepared inert child. This is deliberately safe on an empty builder. +void NtlProcessBuilderAbort(NTL_PROCESS_BUILDER* builder) { + if (!builder) return; + NTL_PROCESS_BUILDER_INTERNAL* state = NtlBuilderInternal(builder); + if (state->Magic != NTL_PROCESS_BUILDER_MAGIC) { + NtlBuilderReset(builder); + return; + } + if (state->Modules) NtlpDestroyModuleContext(state->Modules); + if (state->ProcessHandle != SYS_HANDLE_INVALID) NtlDiscardChild(state->ProcessHandle); + NtlBuilderReset(builder); +} + +// Opens the executable's containing directory as the private dependency-search root. +static KSTATUS NtlOpenImageDirectory(const NTL_PROCESS_BUILDER_REQUEST* request, + SYS_HANDLE* directoryOut, int* borrowedRootOut) { + if (!request || !directoryOut || !borrowedRootOut || !SysSvIsValid(request->Path) || + request->Path.Length == 0 || SysSvContainsByte(request->Path, '\0')) + return NTLSTATUS_INVALID_PARAMETER; + *directoryOut = SYS_HANDLE_INVALID; + *borrowedRootOut = 0; + + size_t slash = SIZE_MAX; + for (size_t i = 0; i < request->Path.Length; ++i) { + if (request->Path.Data[i] == '/') slash = i; + else if (request->Path.Data[i] == '\\') return NTLSTATUS_INVALID_PARAMETER; + } + if (slash == SIZE_MAX) { + *directoryOut = request->RootHandle; + *borrowedRootOut = 1; + return NTLSTATUS_SUCCESS; + } + + SYS_STRING_VIEW directoryPath; + if (slash == 0) directoryPath = SYS_SV_LITERAL("/"); + else directoryPath = SysSvFromParts(request->Path.Data, slash); + + if (request->Path.Data[0] == '/') + return NtlOpenDir(directoryPath, SYS_DIR_ACCESS_OPEN, SYS_DIR_FLAG_NONE, directoryOut); + + SYS_OPEN_DIR_AT_REQUEST open = SysMakeOpenDirAtRequest(request->RootHandle, directoryPath, + SYS_DIR_ACCESS_OPEN, SYS_DIR_FLAG_NONE, + directoryOut); + return SysOpenDirAt(&open); +} + +// Prepares an inert child: process object, image/dependencies, inherited startup +// capabilities, CWD, and loader/bootstrap block are all complete before return. +KSTATUS NtlProcessBuilderPrepare(NTL_PROCESS_BUILDER* builder, + const NTL_PROCESS_BUILDER_REQUEST* request, + SYS_HANDLE imageFile) { + if (!builder || !NtlBuilderRequestValid(request)) return NTLSTATUS_INVALID_PARAMETER; + NtlBuilderReset(builder); + NTL_PROCESS_BUILDER_INTERNAL* state = NtlBuilderInternal(builder); + state->ProcessHandle = SYS_HANDLE_INVALID; + + SYS_HANDLE openedImage = SYS_HANDLE_INVALID; + if (imageFile == SYS_HANDLE_INVALID) { + SYS_OPEN_FILE_AT_REQUEST open; + memset(&open, 0, sizeof(open)); + open.Size = sizeof(open); + open.RootHandle = request->RootHandle; + open.Path = request->Path; + open.Access = SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY; + open.Disposition = SYS_FILE_OPEN_EXISTING; + open.Flags = SYS_FILE_FLAG_NONE; + open.HandleOut = &openedImage; + KSTATUS openStatus = SysOpenFileAt(&open); + if (!KSUCCESS(openStatus)) return openStatus; + imageFile = openedImage; + } - SYS_HANDLE processHandle = SYS_HANDLE_INVALID; SYS_CREATE_PROCESS_OBJECT_REQUEST create; memset(&create, 0, sizeof(create)); create.Size = sizeof(create); @@ -28,133 +132,200 @@ static KSTATUS NtlCreateProcessFile(const NTL_CREATE_PROCESS_AT_REQUEST* request create.ImagePath = request->Path; create.CommandLine = request->CommandLine; create.SecurityContext = request->SecurityContext; - create.ProcessHandleOut = &processHandle; + create.ProcessHandleOut = &state->ProcessHandle; KSTATUS status = SysCreateProcessObject(&create); - if (!KSUCCESS(status)) - return status; + if (!KSUCCESS(status)) goto done; - NTL_MODULE_CONTEXT* modules = 0; - status = NtlpCreateModuleContext(processHandle, - request->ModuleRootHandle, - request->ModuleDirectory, - request->ResolveModuleName, - request->ResolveContext, - 0, - &modules); + char currentDirectoryBuffer[SYS_PATH_MAX + 1u]; + SYS_STRING_VIEW currentDirectory; + size_t currentDirectorySize = 0; + if (request->CurrentDirectory.Length != 0) { + status = NtlRealPath(request->CurrentDirectory, currentDirectoryBuffer, + sizeof(currentDirectoryBuffer), ¤tDirectorySize); + } else { + status = NtlGetCurrentDirectory(currentDirectoryBuffer, sizeof(currentDirectoryBuffer), + ¤tDirectorySize); + } + if (!KSUCCESS(status)) goto fail; + currentDirectory = SysSvFromParts(currentDirectoryBuffer, currentDirectorySize - 1u); + + SYS_HANDLE currentDirectoryHandle = SYS_HANDLE_INVALID; + KSTATUS currentDirectoryStatus = NtlOpenDir(currentDirectory, SYS_DIR_ACCESS_OPEN, + SYS_DIR_FLAG_NONE, ¤tDirectoryHandle); + + SYS_HANDLE imageDirectory = SYS_HANDLE_INVALID; + int imageDirectoryBorrowed = 0; + status = NtlOpenImageDirectory(request, &imageDirectory, &imageDirectoryBorrowed); if (!KSUCCESS(status)) { - NtlDiscardChild(processHandle); - return status; + if (currentDirectoryHandle != SYS_HANDLE_INVALID) + (void)SysCloseHandle(currentDirectoryHandle); + goto fail; } - NTL_PE_IMAGE image; - memset(&image, 0, sizeof(image)); - status = NtlpLoadMainImage(modules, fileHandle, &image); - if (!KSUCCESS(status)) { - NtlpDestroyModuleContext(modules); - NtlDiscardChild(processHandle); - return status; + SYS_HANDLE primaryModuleRoot = imageDirectory; + SYS_STRING_VIEW primaryModuleDirectory = SysSvFromParts((const char*)0, 0); + SYS_HANDLE secondaryModuleRoot = request->ModuleRootHandle; + SYS_STRING_VIEW secondaryModuleDirectory = request->ModuleDirectory; + SYS_HANDLE fallbackModuleRoot = SYS_HANDLE_INVALID; + SYS_STRING_VIEW fallbackModuleDirectory = SysSvFromParts((const char*)0, 0); + + // Current-directory lookup is a search-path optimization, not a bootstrap dependency. + if (KSUCCESS(currentDirectoryStatus)) { + primaryModuleRoot = currentDirectoryHandle; + secondaryModuleRoot = imageDirectory; + secondaryModuleDirectory = SysSvFromParts((const char*)0, 0); + fallbackModuleRoot = request->ModuleRootHandle; + fallbackModuleDirectory = request->ModuleDirectory; } - SYS_STARTUP_CAPABILITY childCapabilities[SYS_STARTUP_CAPABILITY_MAX]; - memset(childCapabilities, 0, sizeof(childCapabilities)); + status = NtlpCreateModuleContext(state->ProcessHandle, primaryModuleRoot, + primaryModuleDirectory, secondaryModuleRoot, + secondaryModuleDirectory, fallbackModuleRoot, + fallbackModuleDirectory, request->ResolveModuleName, + request->ResolveContext, 0, &state->Modules); + if (!KSUCCESS(status)) { + if (!imageDirectoryBorrowed) (void)SysCloseHandle(imageDirectory); + if (currentDirectoryHandle != SYS_HANDLE_INVALID) + (void)SysCloseHandle(currentDirectoryHandle); + goto fail; + } + + memset(&state->Image, 0, sizeof(state->Image)); + status = NtlpLoadMainImage(state->Modules, imageFile, &state->Image); + if (!imageDirectoryBorrowed) (void)SysCloseHandle(imageDirectory); + if (currentDirectoryHandle != SYS_HANDLE_INVALID) + (void)SysCloseHandle(currentDirectoryHandle); + if (!KSUCCESS(status)) goto fail; + + memset(state->ChildCapabilities, 0, sizeof(state->ChildCapabilities)); for (uint32_t i = 0; i < request->StartupCapabilityCount; ++i) { const NTL_PROCESS_STARTUP_CAPABILITY* source = &request->StartupCapabilities[i]; if (!source->Tag || source->SourceHandle == SYS_HANDLE_INVALID) { - NtlpDestroyModuleContext(modules); - NtlDiscardChild(processHandle); - return NTLSTATUS_INVALID_PARAMETER; + status = NTLSTATUS_INVALID_PARAMETER; + goto fail; } for (uint32_t j = 0; j < i; ++j) { - if (childCapabilities[j].Tag == source->Tag) { - NtlpDestroyModuleContext(modules); - NtlDiscardChild(processHandle); - return NTLSTATUS_INVALID_PARAMETER; + if (state->ChildCapabilities[j].Tag == source->Tag) { + status = NTLSTATUS_INVALID_PARAMETER; + goto fail; } } - SYS_HANDLE childHandle = SYS_HANDLE_INVALID; SYS_DUPLICATE_HANDLE_REQUEST duplicate; memset(&duplicate, 0, sizeof(duplicate)); duplicate.Size = sizeof(duplicate); duplicate.SourceHandle = source->SourceHandle; - duplicate.TargetProcessHandle = processHandle; + duplicate.TargetProcessHandle = state->ProcessHandle; duplicate.DesiredRights = source->DesiredRights; duplicate.Flags = source->DuplicateFlags; duplicate.TargetHandleOut = &childHandle; status = SysDuplicateHandle(&duplicate); - if (!KSUCCESS(status)) { - NtlpDestroyModuleContext(modules); - NtlDiscardChild(processHandle); - return status; - } - childCapabilities[i].Tag = source->Tag; - childCapabilities[i].Handle = childHandle; + if (!KSUCCESS(status)) goto fail; + state->ChildCapabilities[i].Tag = source->Tag; + state->ChildCapabilities[i].Handle = childHandle; } + state->StartupCapabilityCount = request->StartupCapabilityCount; - char currentDirectoryBuffer[SYS_PATH_MAX + 1u]; - SYS_STRING_VIEW currentDirectory; - if (request->CurrentDirectory.Length != 0) { - size_t required = 0; - status = NtlRealPath(request->CurrentDirectory, currentDirectoryBuffer, - sizeof(currentDirectoryBuffer), &required); - if (!KSUCCESS(status)) { - NtlpDestroyModuleContext(modules); - NtlDiscardChild(processHandle); - return status; - } - currentDirectory = SysSvFromParts(currentDirectoryBuffer, required - 1u); + status = NtlpBuildLoaderData(state->Modules, currentDirectory, request->Arguments, + request->Environment, request->StartupData, + request->StartupDataSize, &state->LoaderData); + if (!KSUCCESS(status)) goto fail; - SYS_HANDLE cwdHandle = SYS_HANDLE_INVALID; - status = NtlOpenDir(currentDirectory, SYS_DIR_ACCESS_ENUMERATE | SYS_DIR_ACCESS_OPEN, - SYS_DIR_FLAG_NONE, &cwdHandle); - if (!KSUCCESS(status)) { - NtlpDestroyModuleContext(modules); - NtlDiscardChild(processHandle); - return status; - } - (void)SysCloseHandle(cwdHandle); - } else { - size_t required = 0; - status = NtlGetCurrentDirectory(currentDirectoryBuffer, sizeof(currentDirectoryBuffer), &required); - if (!KSUCCESS(status)) { - NtlpDestroyModuleContext(modules); - NtlDiscardChild(processHandle); - return status; - } - currentDirectory = SysSvFromParts(currentDirectoryBuffer, required - 1u); - } + state->Magic = NTL_PROCESS_BUILDER_MAGIC; + status = NTLSTATUS_SUCCESS; + goto done; - void* loaderData = 0; - status = NtlpBuildLoaderData(modules, currentDirectory, request->StartupData, - request->StartupDataSize, &loaderData); - if (!KSUCCESS(status)) { - NtlpDestroyModuleContext(modules); - NtlDiscardChild(processHandle); - return status; - } +fail: + if (state->Modules) NtlpDestroyModuleContext(state->Modules); + if (state->ProcessHandle != SYS_HANDLE_INVALID) NtlDiscardChild(state->ProcessHandle); + NtlBuilderReset(builder); +done: + if (openedImage != SYS_HANDLE_INVALID) (void)SysCloseHandle(openedImage); + return status; +} + +// Returns the process capability owned by one prepared builder without transferring it. +KSTATUS NtlProcessBuilderGetProcessHandle(NTL_PROCESS_BUILDER* builder, + SYS_HANDLE* processHandleOut) { + if (!builder || !processHandleOut) return NTLSTATUS_INVALID_PARAMETER; + *processHandleOut = SYS_HANDLE_INVALID; + NTL_PROCESS_BUILDER_INTERNAL* state = NtlBuilderInternal(builder); + if (state->Magic != NTL_PROCESS_BUILDER_MAGIC || + state->ProcessHandle == SYS_HANDLE_INVALID || !state->Modules || !state->LoaderData) + return NTLSTATUS_INVALID_PARAMETER; + *processHandleOut = state->ProcessHandle; + return NTLSTATUS_SUCCESS; +} + +// Starts one prepared child. On success ownership of the retained native process +// capability moves to the caller; on failure the child is destroyed. +KSTATUS NtlProcessBuilderStart(NTL_PROCESS_BUILDER* builder, SYS_HANDLE* processHandleOut) { + if (!builder || !processHandleOut) return NTLSTATUS_INVALID_PARAMETER; + *processHandleOut = SYS_HANDLE_INVALID; + NTL_PROCESS_BUILDER_INTERNAL* state = NtlBuilderInternal(builder); + if (state->Magic != NTL_PROCESS_BUILDER_MAGIC || + state->ProcessHandle == SYS_HANDLE_INVALID || !state->Modules || !state->LoaderData) + return NTLSTATUS_INVALID_PARAMETER; SYS_START_PROCESS_REQUEST startRequest; memset(&startRequest, 0, sizeof(startRequest)); startRequest.Size = sizeof(startRequest); - startRequest.ProcessHandle = processHandle; + startRequest.ProcessHandle = state->ProcessHandle; startRequest.Flags = SYS_START_PROCESS_FLAG_NONE; - startRequest.MainImageBase = image.Base; - startRequest.MainImageSize = image.Size; - startRequest.EntryPoint = image.EntryPoint; - startRequest.StartupCapabilities = childCapabilities; - startRequest.StartupCapabilityCount = request->StartupCapabilityCount; - startRequest.LoaderData = loaderData; - status = SysStartProcess(&startRequest); - NtlpDestroyModuleContext(modules); + startRequest.MainImageBase = state->Image.Base; + startRequest.MainImageSize = state->Image.Size; + startRequest.EntryPoint = state->Image.EntryPoint; + startRequest.StartupCapabilities = state->ChildCapabilities; + startRequest.StartupCapabilityCount = state->StartupCapabilityCount; + startRequest.LoaderData = state->LoaderData; + KSTATUS status = SysStartProcess(&startRequest); + NtlpDestroyModuleContext(state->Modules); + state->Modules = 0; if (!KSUCCESS(status)) { - NtlDiscardChild(processHandle); + NtlDiscardChild(state->ProcessHandle); + NtlBuilderReset(builder); return status; } - *request->ProcessHandleOut = processHandle; + *processHandleOut = state->ProcessHandle; + NtlBuilderReset(builder); return NTLSTATUS_SUCCESS; } +static void NtlBuilderRequestFromCreate(const NTL_CREATE_PROCESS_AT_REQUEST* create, + NTL_PROCESS_BUILDER_REQUEST* builder) { + memset(builder, 0, sizeof(*builder)); + builder->Size = sizeof(*builder); + builder->Flags = create->Flags; + builder->RootHandle = create->RootHandle; + builder->Path = create->Path; + builder->CommandLine = create->CommandLine; + builder->CurrentDirectory = create->CurrentDirectory; + builder->SecurityContext = create->SecurityContext; + builder->ModuleRootHandle = create->ModuleRootHandle; + builder->ModuleDirectory = create->ModuleDirectory; + builder->ResolveModuleName = create->ResolveModuleName; + builder->ResolveContext = create->ResolveContext; + builder->StartupCapabilities = create->StartupCapabilities; + builder->StartupCapabilityCount = create->StartupCapabilityCount; + builder->Arguments = create->Arguments; + builder->Environment = create->Environment; + builder->StartupData = create->StartupData; + builder->StartupDataSize = create->StartupDataSize; +} + +static KSTATUS NtlCreateProcessFile(const NTL_CREATE_PROCESS_AT_REQUEST* request, SYS_HANDLE fileHandle) { + NTL_PROCESS_BUILDER_REQUEST builderRequest; + NtlBuilderRequestFromCreate(request, &builderRequest); + NTL_PROCESS_BUILDER builder; + memset(&builder, 0, sizeof(builder)); + KSTATUS status = NtlProcessBuilderPrepare(&builder, &builderRequest, fileHandle); + if (!KSUCCESS(status)) return status; + status = NtlProcessBuilderStart(&builder, request->ProcessHandleOut); + if (!KSUCCESS(status)) NtlProcessBuilderAbort(&builder); + return status; +} + // Creates and starts one child from a PE file beneath a capability-relative root. KSTATUS NtlCreateProcessAt(const NTL_CREATE_PROCESS_AT_REQUEST* request) { if (!request || request->Size != sizeof(*request) || @@ -165,6 +336,8 @@ KSTATUS NtlCreateProcessAt(const NTL_CREATE_PROCESS_AT_REQUEST* request) { !SysSvIsValid(request->ModuleDirectory) || request->StartupCapabilityCount > SYS_STARTUP_CAPABILITY_MAX || (request->StartupCapabilityCount && !request->StartupCapabilities) || + (request->Arguments.Count && !request->Arguments.Entries) || + (request->Environment.Count && !request->Environment.Entries) || request->StartupDataSize > NTL_PROCESS_STARTUP_DATA_MAX || (request->StartupDataSize && !request->StartupData) || !request->ProcessHandleOut) @@ -195,11 +368,23 @@ KSTATUS NtlCreateSystemProcess(const NTL_CREATE_SYSTEM_PROCESS_REQUEST* request) if (!request || request->Size != sizeof(*request) || request->Flags != NTL_PROCESS_LOAD_FLAG_NONE || !request->Path.Data || request->Path.Length == 0 || request->StartupDataSize > NTL_PROCESS_STARTUP_DATA_MAX || + (request->Arguments.Count && !request->Arguments.Entries) || + (request->Environment.Count && !request->Environment.Entries) || (request->StartupDataSize && !request->StartupData) || !request->ProcessHandleOut) return NTLSTATUS_INVALID_PARAMETER; *request->ProcessHandleOut = SYS_HANDLE_INVALID; + char canonicalPath[SYS_PATH_MAX + 1u]; + size_t canonicalSize = 0; + KSTATUS canonicalStatus = NtlRealPath(request->Path, canonicalPath, sizeof(canonicalPath), + &canonicalSize); + SYS_STRING_VIEW imagePath = request->Path; + if (KSUCCESS(canonicalStatus)) + imagePath = SysSvFromParts(canonicalPath, canonicalSize - 1u); + else if (request->ImageFile == SYS_HANDLE_INVALID) + return canonicalStatus; + SYS_HANDLE systemRoot = SYS_HANDLE_INVALID; KSTATUS status = NtlResourceOpenSystemRoot(SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &systemRoot); if (!KSUCCESS(status)) return status; @@ -209,7 +394,7 @@ KSTATUS NtlCreateSystemProcess(const NTL_CREATE_SYSTEM_PROCESS_REQUEST* request) create.Size = sizeof(create); create.Flags = request->Flags; create.RootHandle = systemRoot; - create.Path = request->Path; + create.Path = imagePath; create.CommandLine = request->CommandLine; create.CurrentDirectory = request->CurrentDirectory; create.SecurityContext = request->SecurityContext; @@ -219,6 +404,8 @@ KSTATUS NtlCreateSystemProcess(const NTL_CREATE_SYSTEM_PROCESS_REQUEST* request) create.ResolveContext = request->ResolveContext; create.StartupCapabilities = request->StartupCapabilities; create.StartupCapabilityCount = request->StartupCapabilityCount; + create.Arguments = request->Arguments; + create.Environment = request->Environment; create.StartupData = request->StartupData; create.StartupDataSize = request->StartupDataSize; create.ProcessHandleOut = request->ProcessHandleOut; @@ -226,7 +413,7 @@ KSTATUS NtlCreateSystemProcess(const NTL_CREATE_SYSTEM_PROCESS_REQUEST* request) status = NtlCreateProcessFile(&create, request->ImageFile); } else { SYS_HANDLE imageFile = SYS_HANDLE_INVALID; - status = NtlOpenFile(request->Path, SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + status = NtlOpenFile(create.Path, SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &imageFile); if (KSUCCESS(status)) { status = NtlCreateProcessFile(&create, imageFile); diff --git a/code/user/syscall/syscall_nr.inc b/code/user/syscall/syscall_nr.inc index 12b96cf..a671226 100644 --- a/code/user/syscall/syscall_nr.inc +++ b/code/user/syscall/syscall_nr.inc @@ -13,187 +13,193 @@ SYS_KILL_PROCESS EQU 8 SYS_GET_PROCESS_LIST EQU 9 SYS_GET_PROCESS_PID EQU 10 SYS_CREATE_THREAD EQU 11 -SYS_RESUME_THREAD EQU 12 -SYS_GET_THREAD_LIST EQU 13 -SYS_GET_CURRENT_TID EQU 14 -SYS_GET_CURRENT_PID EQU 15 -SYS_SET_THREAD_PRIORITY EQU 16 -SYS_SET_THREAD_NAME EQU 17 -SYS_QUERY_THREAD_NAME EQU 18 -SYS_YIELD EQU 19 -SYS_SLEEP EQU 20 -SYS_WAIT_SINGLE EQU 21 -SYS_WAIT_MANY EQU 22 -SYS_SET_USER_APC_DISPATCHER EQU 23 -SYS_APC_RETURN EQU 24 -SYS_SET_EXCEPTION_DISPATCHER EQU 25 -SYS_GET_EXCEPTION_DISPATCHER EQU 26 -SYS_CONTINUE_EXCEPTION EQU 27 -SYS_RAISE_EXCEPTION EQU 28 +SYS_OPEN_CURRENT_THREAD EQU 12 +SYS_RESUME_THREAD EQU 13 +SYS_RESUME_THREAD_AND_WAIT EQU 14 +SYS_GET_THREAD_LIST EQU 15 +SYS_GET_THREAD_TID EQU 16 +SYS_GET_CURRENT_TID EQU 17 +SYS_GET_CURRENT_PID EQU 18 +SYS_SET_THREAD_PRIORITY EQU 19 +SYS_SET_THREAD_NAME EQU 20 +SYS_QUERY_THREAD_NAME EQU 21 +SYS_YIELD EQU 22 +SYS_SLEEP EQU 23 +SYS_WAIT_SINGLE EQU 24 +SYS_WAIT_MANY EQU 25 +SYS_SET_USER_APC_DISPATCHER EQU 26 +SYS_QUEUE_USER_APC EQU 27 +SYS_APC_RETURN EQU 28 +SYS_SET_EXCEPTION_DISPATCHER EQU 29 +SYS_GET_EXCEPTION_DISPATCHER EQU 30 +SYS_CONTINUE_EXCEPTION EQU 31 +SYS_RAISE_EXCEPTION EQU 32 ; Virtual memory, mappings, and handles. -SYS_ALLOC_VM EQU 29 -SYS_FREE_VM EQU 30 -SYS_PROTECT_VM EQU 31 -SYS_QUERY_VM EQU 32 -SYS_CLOSE_HANDLE EQU 33 -SYS_DUPLICATE_HANDLE EQU 34 -SYS_DERIVE_HANDLE EQU 35 -SYS_REVOKE_HANDLE EQU 36 -SYS_DERIVE_SECURITY_CONTEXT EQU 37 -SYS_QUERY_SECURITY_CONTEXT EQU 38 -SYS_CREATE_FILE_MAPPING EQU 39 -SYS_MAP_FILE EQU 40 +SYS_ALLOC_VM EQU 33 +SYS_FREE_VM EQU 34 +SYS_PROTECT_VM EQU 35 +SYS_QUERY_VM EQU 36 +SYS_CLOSE_HANDLE EQU 37 +SYS_DUPLICATE_HANDLE EQU 38 +SYS_DERIVE_HANDLE EQU 39 +SYS_REVOKE_HANDLE EQU 40 +SYS_DERIVE_SECURITY_CONTEXT EQU 41 +SYS_QUERY_SECURITY_CONTEXT EQU 42 +SYS_OPEN_PROCESS_SECURITY_CONTEXT EQU 43 +SYS_CREATE_FILE_MAPPING EQU 44 +SYS_MAP_FILE EQU 45 ; Synchronization objects. -SYS_CREATE_MUTEX EQU 41 -SYS_TRY_ACQUIRE_MUTEX EQU 42 -SYS_RELEASE_MUTEX EQU 43 -SYS_CREATE_SEM EQU 44 -SYS_TRY_WAIT_SEM EQU 45 -SYS_SIGNAL_SEM EQU 46 -SYS_CREATE_EVENT EQU 47 -SYS_SIGNAL_EVENT EQU 48 -SYS_CLEAR_EVENT EQU 49 +SYS_CREATE_MUTEX EQU 46 +SYS_TRY_ACQUIRE_MUTEX EQU 47 +SYS_RELEASE_MUTEX EQU 48 +SYS_CREATE_SEM EQU 49 +SYS_TRY_WAIT_SEM EQU 50 +SYS_SIGNAL_SEM EQU 51 +SYS_CREATE_EVENT EQU 52 +SYS_SIGNAL_EVENT EQU 53 +SYS_CLEAR_EVENT EQU 54 ; Queues and named queues. -SYS_CREATE_QUEUE EQU 50 -SYS_SEND_QUEUE EQU 51 -SYS_RECV_QUEUE EQU 52 -SYS_PUBLISH_QUEUE EQU 53 -SYS_OPEN_NAMED_QUEUE EQU 54 -SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS EQU 55 -SYS_UNPUBLISH_QUEUE EQU 56 +SYS_CREATE_QUEUE EQU 55 +SYS_SEND_QUEUE EQU 56 +SYS_RECV_QUEUE EQU 57 +SYS_PUBLISH_QUEUE EQU 58 +SYS_OPEN_NAMED_QUEUE EQU 59 +SYS_OPEN_NAMED_QUEUE_OWNER_PROCESS EQU 60 +SYS_UNPUBLISH_QUEUE EQU 61 ; LPC-like Ports and well-known system services. -SYS_CREATE_PORT EQU 57 -SYS_CONNECT_PORT EQU 58 -SYS_ACCEPT_PORT EQU 59 -SYS_SEND_PORT EQU 60 -SYS_RECEIVE_PORT EQU 61 -SYS_CALL_PORT EQU 62 -SYS_REPLY_PORT EQU 63 -SYS_DISCONNECT_PORT EQU 64 -SYS_REGISTER_SYSTEM_SERVICE EQU 65 -SYS_UNREGISTER_SYSTEM_SERVICE EQU 66 -SYS_CONNECT_SYSTEM_SERVICE EQU 67 +SYS_CREATE_PORT EQU 62 +SYS_CONNECT_PORT EQU 63 +SYS_ACCEPT_PORT EQU 64 +SYS_SEND_PORT EQU 65 +SYS_RECEIVE_PORT EQU 66 +SYS_CALL_PORT EQU 67 +SYS_REPLY_PORT EQU 68 +SYS_DISCONNECT_PORT EQU 69 +SYS_OPEN_PORT_PEER_PROCESS EQU 70 +SYS_REGISTER_SYSTEM_SERVICE EQU 71 +SYS_UNREGISTER_SYSTEM_SERVICE EQU 72 +SYS_CONNECT_SYSTEM_SERVICE EQU 73 ; Mounted-volume manager control plane. -SYS_QUERY_VOLUMES EQU 68 -SYS_OPEN_VOLUME EQU 69 -SYS_WAIT_VOLUME_CHANGE EQU 70 +SYS_QUERY_VOLUMES EQU 74 +SYS_OPEN_VOLUME EQU 75 +SYS_WAIT_VOLUME_CHANGE EQU 76 ; Files and directories. -SYS_OPEN_FILE_AT EQU 71 -SYS_OPEN_DIR_AT EQU 72 -SYS_STAT_AT EQU 73 -SYS_DELETE_AT EQU 74 -SYS_RENAME_AT EQU 75 -SYS_CREATE_DIR_AT EQU 76 -SYS_REMOVE_DIR_AT EQU 77 -SYS_READ_FILE EQU 78 -SYS_WRITE_FILE EQU 79 -SYS_READ_FILE_ASYNC EQU 80 -SYS_WRITE_FILE_ASYNC EQU 81 -SYS_CANCEL_IO EQU 82 -SYS_SEEK_FILE EQU 83 -SYS_GET_FILE_POS EQU 84 -SYS_GET_FILE_SIZE EQU 85 -SYS_GET_FILE_STAT EQU 86 -SYS_TRUNCATE_FILE EQU 87 -SYS_FLUSH_FILE EQU 88 -SYS_LOCK_FILE EQU 89 -SYS_UNLOCK_FILE EQU 90 -SYS_QUERY_FILE_LOCK EQU 91 -SYS_SET_FILE_STATUS EQU 92 -SYS_CREATE_PIPE EQU 93 -SYS_NOTIFY_PIPE EQU 94 -SYS_READ_DIR EQU 95 +SYS_OPEN_FILE_AT EQU 77 +SYS_OPEN_DIR_AT EQU 78 +SYS_STAT_AT EQU 79 +SYS_DELETE_AT EQU 80 +SYS_RENAME_AT EQU 81 +SYS_CREATE_DIR_AT EQU 82 +SYS_REMOVE_DIR_AT EQU 83 +SYS_READ_FILE EQU 84 +SYS_WRITE_FILE EQU 85 +SYS_READ_FILE_ASYNC EQU 86 +SYS_WRITE_FILE_ASYNC EQU 87 +SYS_CANCEL_IO EQU 88 +SYS_SEEK_FILE EQU 89 +SYS_GET_FILE_POS EQU 90 +SYS_GET_FILE_SIZE EQU 91 +SYS_GET_FILE_STAT EQU 92 +SYS_TRUNCATE_FILE EQU 93 +SYS_FLUSH_FILE EQU 94 +SYS_LOCK_FILE EQU 95 +SYS_UNLOCK_FILE EQU 96 +SYS_QUERY_FILE_LOCK EQU 97 +SYS_SET_FILE_STATUS EQU 98 +SYS_CREATE_PIPE EQU 99 +SYS_NOTIFY_PIPE EQU 100 +SYS_READ_DIR EQU 101 ; Console, display, and surfaces. -SYS_WRITE_CONSOLE EQU 96 -SYS_GET_CONSOLE_INFO EQU 97 -SYS_SET_CONSOLE_CURSOR EQU 98 -SYS_FILL_CONSOLE_CHARACTER EQU 99 -SYS_FILL_CONSOLE_ATTRIBUTE EQU 100 -SYS_PUT_SCREEN_CHAR EQU 101 -SYS_CLEAR_SCREEN EQU 102 -SYS_GET_SCREEN_SIZE EQU 103 -SYS_QUERY_DISPLAY_INFO EQU 104 -SYS_QUERY_DISPLAY_MANAGER_INFO EQU 105 -SYS_PRESENT_DISPLAY_MANAGER_SURFACE EQU 106 -SYS_SET_DISPLAY_MANAGER_CURSOR EQU 107 -SYS_WRITE_DISPLAY_MANAGER_CONSOLE EQU 108 -SYS_QUERY_DISPLAY_MANAGER_CONSOLE_INFO EQU 109 -SYS_SET_DISPLAY_MANAGER_CONSOLE_CURSOR EQU 110 -SYS_FILL_DISPLAY_MANAGER_CONSOLE_CHARACTER EQU 111 -SYS_FILL_DISPLAY_MANAGER_CONSOLE_ATTRIBUTE EQU 112 -SYS_PUT_DISPLAY_MANAGER_CONSOLE_CHARACTER EQU 113 -SYS_CLEAR_DISPLAY_MANAGER_CONSOLE EQU 114 -SYS_OPEN_DISPLAY_CLOCK EQU 115 -SYS_CREATE_SURFACE EQU 116 -SYS_MAP_SURFACE EQU 117 -SYS_UNMAP_SURFACE EQU 118 -SYS_QUERY_SURFACE EQU 119 -SYS_COMMIT_SURFACE EQU 120 +SYS_WRITE_CONSOLE EQU 102 +SYS_GET_CONSOLE_INFO EQU 103 +SYS_SET_CONSOLE_CURSOR EQU 104 +SYS_FILL_CONSOLE_CHARACTER EQU 105 +SYS_FILL_CONSOLE_ATTRIBUTE EQU 106 +SYS_PUT_SCREEN_CHAR EQU 107 +SYS_CLEAR_SCREEN EQU 108 +SYS_GET_SCREEN_SIZE EQU 109 +SYS_QUERY_DISPLAY_INFO EQU 110 +SYS_QUERY_DISPLAY_MANAGER_INFO EQU 111 +SYS_PRESENT_DISPLAY_MANAGER_SURFACE EQU 112 +SYS_SET_DISPLAY_MANAGER_CURSOR EQU 113 +SYS_WRITE_DISPLAY_MANAGER_CONSOLE EQU 114 +SYS_QUERY_DISPLAY_MANAGER_CONSOLE_INFO EQU 115 +SYS_SET_DISPLAY_MANAGER_CONSOLE_CURSOR EQU 116 +SYS_FILL_DISPLAY_MANAGER_CONSOLE_CHARACTER EQU 117 +SYS_FILL_DISPLAY_MANAGER_CONSOLE_ATTRIBUTE EQU 118 +SYS_PUT_DISPLAY_MANAGER_CONSOLE_CHARACTER EQU 119 +SYS_CLEAR_DISPLAY_MANAGER_CONSOLE EQU 120 +SYS_OPEN_DISPLAY_CLOCK EQU 121 +SYS_CREATE_SURFACE EQU 122 +SYS_MAP_SURFACE EQU 123 +SYS_UNMAP_SURFACE EQU 124 +SYS_QUERY_SURFACE EQU 125 +SYS_COMMIT_SURFACE EQU 126 ; Native render audio. -SYS_AUDIO_ENUMERATE_ENDPOINTS EQU 121 -SYS_AUDIO_OPEN_STREAM EQU 122 -SYS_AUDIO_WRITE EQU 123 -SYS_AUDIO_ARM_WAIT EQU 124 -SYS_AUDIO_QUERY_STREAM EQU 125 -SYS_AUDIO_START EQU 126 -SYS_AUDIO_PAUSE EQU 127 -SYS_AUDIO_RESUME EQU 128 -SYS_AUDIO_DRAIN EQU 129 -SYS_AUDIO_STOP EQU 130 -SYS_AUDIO_RESET EQU 131 -SYS_AUDIO_SET_VOLUME EQU 132 -SYS_AUDIO_SET_MASTER_VOLUME EQU 133 +SYS_AUDIO_ENUMERATE_ENDPOINTS EQU 127 +SYS_AUDIO_OPEN_STREAM EQU 128 +SYS_AUDIO_WRITE EQU 129 +SYS_AUDIO_ARM_WAIT EQU 130 +SYS_AUDIO_QUERY_STREAM EQU 131 +SYS_AUDIO_START EQU 132 +SYS_AUDIO_PAUSE EQU 133 +SYS_AUDIO_RESUME EQU 134 +SYS_AUDIO_DRAIN EQU 135 +SYS_AUDIO_STOP EQU 136 +SYS_AUDIO_RESET EQU 137 +SYS_AUDIO_SET_VOLUME EQU 138 +SYS_AUDIO_SET_MASTER_VOLUME EQU 139 ; Input. -SYS_READ_INPUT_MANAGER_EVENT EQU 134 -SYS_POLL_KEYBOARD EQU 135 -SYS_IS_KEY_DOWN EQU 136 -SYS_READ_KEYBOARD_CHAR EQU 137 -SYS_READ_KEY EQU 138 -SYS_GET_KEY_MODIFIERS EQU 139 -SYS_QUERY_GLOBAL_INPUT_STATE EQU 140 -SYS_OPEN_INPUT_QUEUE EQU 141 -SYS_READ_INPUT_EVENT EQU 142 -SYS_PEEK_INPUT_EVENT EQU 143 -SYS_PEEK_MOUSE EQU 144 -SYS_READ_MOUSE EQU 145 -SYS_GET_MOUSE_POS EQU 146 +SYS_READ_INPUT_MANAGER_EVENT EQU 140 +SYS_POLL_KEYBOARD EQU 141 +SYS_IS_KEY_DOWN EQU 142 +SYS_READ_KEYBOARD_CHAR EQU 143 +SYS_READ_KEY EQU 144 +SYS_GET_KEY_MODIFIERS EQU 145 +SYS_QUERY_GLOBAL_INPUT_STATE EQU 146 +SYS_OPEN_INPUT_QUEUE EQU 147 +SYS_READ_INPUT_EVENT EQU 148 +SYS_PEEK_INPUT_EVENT EQU 149 +SYS_PEEK_MOUSE EQU 150 +SYS_READ_MOUSE EQU 151 +SYS_GET_MOUSE_POS EQU 152 ; Time, debug, and system information. -SYS_CREATE_TIMER EQU 147 -SYS_SET_TIMER EQU 148 -SYS_CANCEL_TIMER EQU 149 -SYS_GET_TICK EQU 150 -SYS_GET_TICK_FREQUENCY EQU 151 -SYS_GET_SYSTEM_TIME EQU 152 -SYS_PRINT_DEBUG EQU 153 -SYS_QUERY_TRAP_FRAME EQU 154 -SYS_GET_MEMORY_STATUS EQU 155 -SYS_QUERY_KERNEL_STATS EQU 156 +SYS_CREATE_TIMER EQU 153 +SYS_SET_TIMER EQU 154 +SYS_CANCEL_TIMER EQU 155 +SYS_GET_TICK EQU 156 +SYS_GET_TICK_FREQUENCY EQU 157 +SYS_GET_SYSTEM_TIME EQU 158 +SYS_PRINT_DEBUG EQU 159 +SYS_QUERY_TRAP_FRAME EQU 160 +SYS_GET_MEMORY_STATUS EQU 161 +SYS_QUERY_KERNEL_STATS EQU 162 ; Native sockets and polling. -SYS_CREATE_SOCKET EQU 157 -SYS_SOCKET_IO_ASYNC EQU 158 -SYS_BIND_SOCKET EQU 159 -SYS_LISTEN_SOCKET EQU 160 -SYS_ACCEPT_SOCKET EQU 161 -SYS_CONNECT_SOCKET EQU 162 -SYS_SEND_SOCKET EQU 163 -SYS_RECV_SOCKET EQU 164 -SYS_SHUTDOWN_SOCKET EQU 165 -SYS_SEND_TO_SOCKET EQU 166 -SYS_RECV_FROM_SOCKET EQU 167 -SYS_RESOLVE_HOST EQU 168 -SYS_SET_SOCKET_OPTION EQU 169 -SYS_GET_SOCKET_OPTION EQU 170 -SYS_POLL EQU 171 +SYS_CREATE_SOCKET EQU 163 +SYS_SOCKET_IO_ASYNC EQU 164 +SYS_BIND_SOCKET EQU 165 +SYS_LISTEN_SOCKET EQU 166 +SYS_ACCEPT_SOCKET EQU 167 +SYS_CONNECT_SOCKET EQU 168 +SYS_SEND_SOCKET EQU 169 +SYS_RECV_SOCKET EQU 170 +SYS_SHUTDOWN_SOCKET EQU 171 +SYS_SEND_TO_SOCKET EQU 172 +SYS_RECV_FROM_SOCKET EQU 173 +SYS_RESOLVE_HOST EQU 174 +SYS_SET_SOCKET_OPTION EQU 175 +SYS_GET_SOCKET_OPTION EQU 176 +SYS_POLL EQU 177 -SYS_MAX EQU 172 +SYS_MAX EQU 178 diff --git a/code/user/syscall/syscall_stubs.asm b/code/user/syscall/syscall_stubs.asm index f407b1f..cb06d61 100644 --- a/code/user/syscall/syscall_stubs.asm +++ b/code/user/syscall/syscall_stubs.asm @@ -53,9 +53,12 @@ SYSCALL_STUB KillProcess, SYS_KILL_PROCESS SYSCALL_STUB GetProcessList, SYS_GET_PROCESS_LIST SYSCALL_STUB GetProcessPid, SYS_GET_PROCESS_PID SYSCALL_STUB GetThreadList, SYS_GET_THREAD_LIST +SYSCALL_STUB GetThreadTid, SYS_GET_THREAD_TID SYSCALL_STUB CreateThread, SYS_CREATE_THREAD +SYSCALL_STUB OpenCurrentThread, SYS_OPEN_CURRENT_THREAD SYSCALL_STUB ResumeThread, SYS_RESUME_THREAD +SYSCALL_STUB ResumeThreadAndWait, SYS_RESUME_THREAD_AND_WAIT SYSCALL_STUB GetCurrentTid, SYS_GET_CURRENT_TID SYSCALL_STUB GetCurrentPid, SYS_GET_CURRENT_PID SYSCALL_STUB SetThreadPriority, SYS_SET_THREAD_PRIORITY @@ -64,6 +67,7 @@ SYSCALL_STUB QueryThreadName, SYS_QUERY_THREAD_NAME SYSCALL_STUB Yield, SYS_YIELD SYSCALL_STUB Sleep, SYS_SLEEP SYSCALL_STUB SetUserApcDispatcher, SYS_SET_USER_APC_DISPATCHER +SYSCALL_STUB QueueUserApc, SYS_QUEUE_USER_APC SYSCALL_STUB ApcReturn, SYS_APC_RETURN SYSCALL_STUB SetExceptionDispatcher, SYS_SET_EXCEPTION_DISPATCHER SYSCALL_STUB GetExceptionDispatcher, SYS_GET_EXCEPTION_DISPATCHER @@ -116,6 +120,7 @@ SYSCALL_STUB DeriveHandle, SYS_DERIVE_HANDLE SYSCALL_STUB RevokeHandle, SYS_REVOKE_HANDLE SYSCALL_STUB DeriveSecurityContext, SYS_DERIVE_SECURITY_CONTEXT SYSCALL_STUB QuerySecurityContext, SYS_QUERY_SECURITY_CONTEXT +SYSCALL_STUB OpenProcessSecurityContext, SYS_OPEN_PROCESS_SECURITY_CONTEXT ; --------------------------------------------------------------------------- ; Synchronization objects @@ -155,6 +160,7 @@ SYSCALL_STUB ReceivePort, SYS_RECEIVE_PORT SYSCALL_STUB CallPort, SYS_CALL_PORT SYSCALL_STUB ReplyPort, SYS_REPLY_PORT SYSCALL_STUB DisconnectPort, SYS_DISCONNECT_PORT +SYSCALL_STUB OpenPortPeerProcess, SYS_OPEN_PORT_PEER_PROCESS SYSCALL_STUB RegisterSystemService, SYS_REGISTER_SYSTEM_SERVICE SYSCALL_STUB UnregisterSystemService, SYS_UNREGISTER_SYSTEM_SERVICE SYSCALL_STUB ConnectSystemService, SYS_CONNECT_SYSTEM_SERVICE diff --git a/code/user/system/posixsrv/posixsrv.c b/code/user/system/posixsrv/posixsrv.c index 446af75..adbf349 100644 --- a/code/user/system/posixsrv/posixsrv.c +++ b/code/user/system/posixsrv/posixsrv.c @@ -5,7 +5,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -13,14 +16,63 @@ #define POSIXSRV_FIRST_PID 0x00010000u #define POSIXSRV_LAST_PID 0x7FFFFFFFu +typedef struct POSIXSRV_CONSTRUCTION_STRING { + char* Data; + uint32_t Length; + uint32_t Capacity; + uint32_t Complete; +} POSIXSRV_CONSTRUCTION_STRING; + +typedef struct POSIXSRV_SPAWN_ACTION { + uint32_t Kind; + int32_t FileDescriptor; + int32_t NewFileDescriptor; + uint32_t OpenFlags; + uint32_t OpenMode; + char* Path; +} POSIXSRV_SPAWN_ACTION; + +typedef struct POSIXSRV_CONSTRUCTION { + char* Executable; + POSIXSRV_CONSTRUCTION_STRING* Arguments; + uint32_t ArgumentCount; + uint32_t ArgumentCapacity; + POSIXSRV_CONSTRUCTION_STRING* Environment; + uint32_t EnvironmentCount; + uint32_t EnvironmentCapacity; + uint32_t TotalDataBytes; +} POSIXSRV_CONSTRUCTION; + +typedef struct POSIXSRV_SPAWN_POLICY { + POSIXSRV_SPAWN_ACTION Actions[NTL_POSIX_SPAWN_ACTION_MAX]; + uint32_t ActionCount; + uint32_t AttributeFlags; + uint32_t ProcessGroupId; + uint64_t SignalMask; + uint64_t SignalDefaultMask; +} POSIXSRV_SPAWN_POLICY; + typedef struct POSIXSRV_TRANSACTION { uint32_t Id; uint32_t Kind; + POSIXSRV_CONSTRUCTION* Construction; + POSIXSRV_SPAWN_POLICY* Spawn; } POSIXSRV_TRANSACTION; typedef struct POSIXSRV_OPEN_DESCRIPTION POSIXSRV_OPEN_DESCRIPTION; typedef struct POSIXSRV_PROCESS_RECORD POSIXSRV_PROCESS_RECORD; typedef struct POSIXSRV_FD_INHERITANCE POSIXSRV_FD_INHERITANCE; +typedef struct POSIXSRV_THREAD_RECORD POSIXSRV_THREAD_RECORD; +typedef struct POSIXSRV_PROCESS_MONITOR_CONTEXT POSIXSRV_PROCESS_MONITOR_CONTEXT; +typedef struct POSIXSRV_BORROWED_MONITOR_CONTEXT POSIXSRV_BORROWED_MONITOR_CONTEXT; + +typedef struct POSIXSRV_WAITER { + struct POSIXSRV_WAITER* Next; + uint32_t ParentPosixPid; + uint32_t ParentProcessGroupId; + int32_t WaitPid; + SYS_HANDLE Event; +} POSIXSRV_WAITER; typedef struct POSIXSRV_PIPE_BACKING { SYS_PIPE_SHARED_STATE* Shared; @@ -32,31 +84,42 @@ typedef struct POSIXSRV_OPEN_HOLDER { POSIXSRV_OPEN_DESCRIPTION* Description; } POSIXSRV_OPEN_HOLDER; +typedef struct POSIXSRV_EXEC_HOLDER_PLAN { + POSIXSRV_OPEN_HOLDER* Holders[NTL_POSIX_FD_INHERIT_MAX]; + uint32_t Count; +} POSIXSRV_EXEC_HOLDER_PLAN; + +#define POSIXSRV_VFORK_TERMINAL_NONE 0u +#define POSIXSRV_VFORK_TERMINAL_EXIT 1u +#define POSIXSRV_VFORK_TERMINAL_EXEC 2u + typedef struct POSIXSRV_FD_INHERITANCE_ENTRY { NTL_POSIX_FD_MANIFEST_ENTRY Manifest; POSIXSRV_OPEN_DESCRIPTION* Description; } POSIXSRV_FD_INHERITANCE_ENTRY; -typedef struct POSIXSRV_FD_INHERITANCE_MONITOR { - uint64_t Token; - uint32_t NativeProcessId; - SYS_HANDLE ProcessHandle; -} POSIXSRV_FD_INHERITANCE_MONITOR; - struct POSIXSRV_FD_INHERITANCE { POSIXSRV_FD_INHERITANCE* Next; uint64_t Token; POSIXSRV_PROCESS_RECORD* SourceProcess; POSIXSRV_PROCESS_RECORD* ClaimProcess; uint32_t SecurityId; - uint32_t Armed; - uint32_t TargetNativeProcessId; uint32_t EntryCount; uint32_t DescriptionCount; POSIXSRV_FD_INHERITANCE_ENTRY Entries[NTL_POSIX_FD_INHERIT_MAX]; POSIXSRV_OPEN_DESCRIPTION* Descriptions[NTL_POSIX_FD_INHERIT_MAX]; }; + +struct POSIXSRV_THREAD_RECORD { + POSIXSRV_THREAD_RECORD* Next; + uint32_t NativeThreadId; + uint32_t Generation; + SYS_HANDLE ThreadHandle; + uint64_t BlockedSignals; + uint64_t SignalApcRoutine; +}; + struct POSIXSRV_OPEN_DESCRIPTION { POSIXSRV_OPEN_DESCRIPTION* Next; uint64_t DescriptionId; @@ -78,6 +141,25 @@ struct POSIXSRV_PROCESS_RECORD { uint32_t State; uint32_t ConnectionCount; uint32_t MonitorActive; + POSIXSRV_PROCESS_RECORD* BorrowedHostProcess; + uint32_t BorrowedHostGeneration; + uint32_t BorrowedNativeThreadId; + uint64_t BorrowedToken; + uint32_t BorrowedMonitorActive; + uint32_t BorrowedTerminalKind; + int32_t BorrowedExitCode; + POSIXSRV_EXEC_HOLDER_PLAN* PendingExecHolderPlan; + uint32_t AlarmMonitorActive; + uint64_t BootstrapToken; + SYS_HANDLE BootstrapReadyEvent; + uint32_t PendingNativeProcessId; + uint32_t PendingGeneration; + uint64_t PendingBootstrapToken; + SYS_HANDLE PendingBootstrapReadyEvent; + SYS_HANDLE PendingBootstrapReleaseEvent; + uint32_t PendingBootstrapReadyObserved; + SYS_HANDLE PendingProcessHandle; + uint64_t PendingFdInheritanceToken; uint32_t UserId; uint32_t EffectiveUserId; uint32_t GroupId; @@ -89,8 +171,19 @@ struct POSIXSRV_PROCESS_RECORD { char LoginName[NTL_POSIX_IDENTITY_NAME_MAX + 1u]; char CurrentDirectory[SYS_PATH_MAX + 1u]; int32_t ExitCode; + uint64_t ProcessPendingSignals; + uint64_t IgnoredSignals; + uint64_t ExplicitIgnoredSignals; + SYS_HANDLE AlarmTimer; + SYS_HANDLE AlarmControlEvent; + uint64_t AlarmGeneration; + uint64_t AlarmDeadlineTicks; SYS_HANDLE ProcessHandle; POSIXSRV_OPEN_HOLDER* OpenDescriptions; + POSIXSRV_THREAD_RECORD* Threads; + uint32_t NextTransactionId; + uint32_t ActiveTransactions; + POSIXSRV_TRANSACTION Transactions[NTL_POSIX_TRANSACTION_CAPACITY]; }; typedef struct POSIXSRV_USER_RECORD { @@ -110,20 +203,85 @@ typedef struct POSIXSRV_CLIENT { SYS_HANDLE Endpoint; SYS_PORT_CONNECTION_INFO Connection; POSIXSRV_PROCESS_RECORD* Process; - uint32_t NextTransactionId; - uint32_t ActiveTransactions; - POSIXSRV_TRANSACTION Transactions[NTL_POSIX_TRANSACTION_CAPACITY]; + uint32_t NativeGeneration; } POSIXSRV_CLIENT; +struct POSIXSRV_PROCESS_MONITOR_CONTEXT { + POSIXSRV_PROCESS_RECORD* Process; + SYS_HANDLE ProcessHandle; + uint32_t Generation; +}; + +struct POSIXSRV_BORROWED_MONITOR_CONTEXT { + POSIXSRV_PROCESS_RECORD* Process; + SYS_HANDLE ThreadHandle; + uint64_t BorrowedToken; +}; + static SYS_HANDLE gProcessRegistryMutex = SYS_HANDLE_INVALID; static POSIXSRV_PROCESS_RECORD* gProcessRegistry; static POSIXSRV_OPEN_DESCRIPTION* gOpenDescriptionRegistry; static POSIXSRV_FD_INHERITANCE* gFdInheritanceRegistry; +static POSIXSRV_WAITER* gWaiters; static uint32_t gNextPosixPid = POSIXSRV_FIRST_PID; static uint64_t gNextOpenDescriptionId = 1u; static uint64_t gNextFdInheritanceToken = 1u; +static uint64_t gNextBootstrapToken = 1u; +static uint64_t gNextVforkToken = 1u; static uint32_t gRegisteredProcesses; static uint32_t gRegisteredOpenDescriptions; +static uint32_t gRegisteredThreads; + +static void PosixsrvDestroyTransaction(POSIXSRV_TRANSACTION* transaction); +static void PosixsrvFreeProcessRecord(POSIXSRV_PROCESS_RECORD* record); + +// Returns whether the stable POSIX process still has a committed native generation. +static int PosixsrvProcessHasCommittedGeneration(const POSIXSRV_PROCESS_RECORD* process) { + return process && (process->State == NTL_POSIX_PROCESS_STATE_RUNNING || + process->State == NTL_POSIX_PROCESS_STATE_EXEC_PREPARING || + process->State == NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING); +} + +// Returns whether ordinary operations may still run in the committed old generation. +static int PosixsrvCommittedGenerationOperational(const POSIXSRV_CLIENT* client) { + return client && client->Process && client->NativeGeneration == client->Process->Generation && + (client->Process->State == NTL_POSIX_PROCESS_STATE_RUNNING || + client->Process->State == NTL_POSIX_PROCESS_STATE_EXEC_PREPARING); +} + +// Returns whether this connection belongs to the not-yet-committed replacement generation. +static int PosixsrvClientIsPendingExecGeneration(const POSIXSRV_CLIENT* client) { + return client && client->Process && client->Process->PendingGeneration != 0 && + client->NativeGeneration == client->Process->PendingGeneration && + (client->Process->State == NTL_POSIX_PROCESS_STATE_EXEC_PREPARING || + client->Process->State == NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING); +} + +// Returns whether this routed client represents one borrowed vfork logical process. +static int PosixsrvClientIsBorrowedProcess(const POSIXSRV_CLIENT* client) { + return client && client->Process && + client->Process->State == NTL_POSIX_PROCESS_STATE_VFORK_BORROWED && + client->NativeGeneration == client->Process->Generation; +} + +// Finds the borrowed logical child bound to one native sender thread. +static POSIXSRV_PROCESS_RECORD* PosixsrvFindBorrowedChildLocked( + POSIXSRV_PROCESS_RECORD* host, uint32_t hostGeneration, uint32_t nativeThreadId) { + if (!host || hostGeneration == 0 || nativeThreadId == 0) + return (POSIXSRV_PROCESS_RECORD*)0; + for (POSIXSRV_PROCESS_RECORD* record = gProcessRegistry; record; record = record->Next) { + if (record->State != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED || + record->BorrowedHostProcess != host || + record->BorrowedHostGeneration != hostGeneration || + record->BorrowedNativeThreadId != nativeThreadId || !record->Threads || + record->Threads->NativeThreadId != nativeThreadId || + record->Threads->ThreadHandle == SYS_HANDLE_INVALID) + continue; + if (SysWaitSingle(record->Threads->ThreadHandle, SYS_WAIT_FLAG_NONE, 0u) == NTLSTATUS_TIMEOUT) + return record; + } + return (POSIXSRV_PROCESS_RECORD*)0; +} static const POSIXSRV_USER_RECORD gUsers[] = { { "root", 0u, 0u, "/", "/system/shell_posix.exe" }, @@ -162,10 +320,13 @@ static void PosixsrvUnlockRegistry(void) { (void)SysReleaseMutex(gProcessRegistryMutex); } -// Finds one live or retained record by kernel-stamped native identity. -static POSIXSRV_PROCESS_RECORD* PosixsrvFindProcessLocked(uint32_t nativePid, uint32_t securityId) { +// Finds the committed process generation represented by one native identity. +static POSIXSRV_PROCESS_RECORD* PosixsrvFindCommittedProcessLocked(uint32_t nativePid, + uint32_t securityId) { for (POSIXSRV_PROCESS_RECORD* record = gProcessRegistry; record; record = record->Next) { - if (record->NativeProcessId == nativePid && record->SecurityId == securityId) + if (record->NativeProcessId != nativePid || record->SecurityId != securityId) continue; + if (record->State == NTL_POSIX_PROCESS_STATE_RUNNING || + record->State == NTL_POSIX_PROCESS_STATE_EXEC_PREPARING) return record; } return (POSIXSRV_PROCESS_RECORD*)0; @@ -184,7 +345,7 @@ static POSIXSRV_PROCESS_RECORD* PosixsrvFindPosixProcessLocked(uint32_t posixPid static int PosixsrvProcessGroupExistsLocked(uint32_t sessionId, uint32_t processGroupId) { if (processGroupId == 0) return 0; for (POSIXSRV_PROCESS_RECORD* record = gProcessRegistry; record; record = record->Next) { - if (record->State == NTL_POSIX_PROCESS_STATE_RUNNING && record->SessionId == sessionId && + if (PosixsrvProcessHasCommittedGeneration(record) && record->SessionId == sessionId && record->ProcessGroupId == processGroupId) return 1; } @@ -307,7 +468,7 @@ static void PosixsrvReleaseFdInheritanceForProcessLocked(POSIXSRV_PROCESS_RECORD while (snapshot) { POSIXSRV_FD_INHERITANCE* next = snapshot->Next; if (snapshot->ClaimProcess == process || - (snapshot->SourceProcess == process && !snapshot->ClaimProcess && !snapshot->Armed)) { + (snapshot->SourceProcess == process && !snapshot->ClaimProcess)) { PosixsrvDestroyFdInheritanceLocked(snapshot); } else if (snapshot->SourceProcess == process) { snapshot->SourceProcess = (POSIXSRV_PROCESS_RECORD*)0; @@ -316,108 +477,6 @@ static void PosixsrvReleaseFdInheritanceForProcessLocked(POSIXSRV_PROCESS_RECORD } } -// Reclaims one armed snapshot if its designated native child dies before import completes. -static void PosixsrvFdInheritanceMonitorThread(uintptr_t argument) { - POSIXSRV_FD_INHERITANCE_MONITOR* monitor = - (POSIXSRV_FD_INHERITANCE_MONITOR*)argument; - if (!monitor) { - SysExitThread(1); - return; - } - PosixsrvSetCurrentThreadName("posixsrv.fd-inherit"); - - KSTATUS waitStatus = SysWait(monitor->ProcessHandle, SYS_WAIT_FLAG_NONE); - if (KSUCCESS(PosixsrvLockRegistry())) { - POSIXSRV_FD_INHERITANCE* snapshot = PosixsrvFindFdInheritanceLocked(monitor->Token); - if (snapshot && snapshot->Armed && - snapshot->TargetNativeProcessId == monitor->NativeProcessId) - PosixsrvDestroyFdInheritanceLocked(snapshot); - PosixsrvUnlockRegistry(); - } - - (void)SysCloseHandle(monitor->ProcessHandle); - free(monitor); - SysExitThread(KSUCCESS(waitStatus) ? 0 : 1); -} - -// Pins one inheritance snapshot to a concrete native child until import or child exit. -static KSTATUS PosixsrvArmFdInheritance(POSIXSRV_CLIENT* client, uint64_t token, - SYS_HANDLE* targetProcessHandle) { - if (!client || !client->Process || token == 0 || !targetProcessHandle || - *targetProcessHandle == SYS_HANDLE_INVALID) - return NTLSTATUS_INVALID_PARAMETER; - - uint32_t nativePid = 0; - KSTATUS status = SysGetProcessPid(*targetProcessHandle, &nativePid); - if (!KSUCCESS(status)) return status; - if (nativePid == 0 || nativePid == client->Process->NativeProcessId) - return NTLSTATUS_INVALID_PARAMETER; - - POSIXSRV_FD_INHERITANCE_MONITOR* monitor = - (POSIXSRV_FD_INHERITANCE_MONITOR*)calloc(1, sizeof(*monitor)); - if (!monitor) return NTLSTATUS_NO_MEMORY; - monitor->Token = token; - monitor->NativeProcessId = nativePid; - monitor->ProcessHandle = *targetProcessHandle; - - status = PosixsrvLockRegistry(); - if (!KSUCCESS(status)) { - free(monitor); - return status; - } - - POSIXSRV_FD_INHERITANCE* snapshot = PosixsrvFindFdInheritanceLocked(token); - if (!snapshot) { - PosixsrvUnlockRegistry(); - free(monitor); - return NTLSTATUS_NOT_FOUND; - } - if (snapshot->SecurityId != client->Process->SecurityId || - snapshot->SourceProcess != client->Process) { - PosixsrvUnlockRegistry(); - free(monitor); - return NTLSTATUS_ACCESS_DENIED; - } - if (snapshot->ClaimProcess) { - status = snapshot->ClaimProcess->NativeProcessId == nativePid - ? NTLSTATUS_SUCCESS - : NTLSTATUS_ACCESS_DENIED; - PosixsrvUnlockRegistry(); - free(monitor); - return status; - } - if (snapshot->Armed) { - status = snapshot->TargetNativeProcessId == nativePid - ? NTLSTATUS_SUCCESS - : NTLSTATUS_ACCESS_DENIED; - PosixsrvUnlockRegistry(); - free(monitor); - return status; - } - - snapshot->Armed = 1u; - snapshot->TargetNativeProcessId = nativePid; - SYS_CREATE_THREAD_REQUEST create; - memset(&create, 0, sizeof(create)); - create.Size = sizeof(create); - create.Entry = (uintptr_t)PosixsrvFdInheritanceMonitorThread; - create.Argument = (uintptr_t)monitor; - create.Priority = 1; - create.Flags = SYS_CREATE_THREAD_FLAG_NONE; - status = SysCreateThread(&create); - if (!KSUCCESS(status)) { - snapshot->Armed = 0; - snapshot->TargetNativeProcessId = 0; - PosixsrvUnlockRegistry(); - free(monitor); - return status; - } - - *targetProcessHandle = SYS_HANDLE_INVALID; - PosixsrvUnlockRegistry(); - return NTLSTATUS_SUCCESS; -} - // Returns the process-local rights needed by one imported native capability. static SYS_RIGHTS PosixsrvOpenDescriptionClientRights(POSIXSRV_OPEN_DESCRIPTION* description) { if (!description || !description->Shared) return 0; @@ -462,7 +521,7 @@ static KSTATUS PosixsrvCreateFdInheritance(POSIXSRV_CLIENT* client, KSTATUS status = PosixsrvLockRegistry(); if (!KSUCCESS(status)) { free(snapshot); return status; } - if (client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING) { + if (!PosixsrvCommittedGenerationOperational(client) && !PosixsrvClientIsBorrowedProcess(client)) { PosixsrvUnlockRegistry(); free(snapshot); return NTLSTATUS_OBJECT_DEAD; } if (gNextFdInheritanceToken == 0) { @@ -540,8 +599,6 @@ static KSTATUS PosixsrvCancelFdInheritance(POSIXSRV_CLIENT* client, uint64_t tok static KSTATUS PosixsrvClaimFdInheritanceLocked(POSIXSRV_FD_INHERITANCE* snapshot, POSIXSRV_PROCESS_RECORD* process) { if (!snapshot || !process) return NTLSTATUS_INVALID_PARAMETER; - if (snapshot->Armed && snapshot->TargetNativeProcessId != process->NativeProcessId) - return NTLSTATUS_ACCESS_DENIED; if (snapshot->ClaimProcess) return snapshot->ClaimProcess == process ? NTLSTATUS_SUCCESS : NTLSTATUS_ACCESS_DENIED; if (process->OpenDescriptions) @@ -567,13 +624,79 @@ static KSTATUS PosixsrvClaimFdInheritanceLocked(POSIXSRV_FD_INHERITANCE* snapsho return NTLSTATUS_SUCCESS; } +// Preallocates every holder node that exec may need after the point of no return. +static KSTATUS PosixsrvPrepareExecHolderPlanLocked(POSIXSRV_PROCESS_RECORD* process, + POSIXSRV_FD_INHERITANCE* snapshot, + POSIXSRV_EXEC_HOLDER_PLAN* plan) { + if (!process || !snapshot || !plan || snapshot->SourceProcess != process || snapshot->ClaimProcess) + return NTLSTATUS_INVALID_PARAMETER; + memset(plan, 0, sizeof(*plan)); + for (uint32_t i = 0; i < snapshot->DescriptionCount; ++i) { + POSIXSRV_OPEN_DESCRIPTION* description = snapshot->Descriptions[i]; + if (!description) return NTLSTATUS_OBJECT_DEAD; + if (PosixsrvFindOpenHolderLocked(process, description->DescriptionId)) continue; + POSIXSRV_OPEN_HOLDER* holder = (POSIXSRV_OPEN_HOLDER*)calloc(1, sizeof(*holder)); + if (!holder) { + for (uint32_t j = 0; j < plan->Count; ++j) free(plan->Holders[j]); + memset(plan, 0, sizeof(*plan)); + return NTLSTATUS_NO_MEMORY; + } + holder->Description = description; + plan->Holders[plan->Count++] = holder; + } + return NTLSTATUS_SUCCESS; +} + +// Releases an uncommitted exec holder plan. +static void PosixsrvDestroyExecHolderPlan(POSIXSRV_EXEC_HOLDER_PLAN* plan) { + if (!plan) return; + for (uint32_t i = 0; i < plan->Count; ++i) free(plan->Holders[i]); + memset(plan, 0, sizeof(*plan)); +} + +// Applies a preallocated holder plan and drops every description excluded by the exec snapshot. +static void PosixsrvApplyExecHolderPlanLocked(POSIXSRV_PROCESS_RECORD* process, + POSIXSRV_FD_INHERITANCE* snapshot, + POSIXSRV_EXEC_HOLDER_PLAN* plan) { + if (!process || !snapshot || !plan) return; + for (uint32_t i = 0; i < plan->Count; ++i) { + POSIXSRV_OPEN_HOLDER* holder = plan->Holders[i]; + if (!holder || !holder->Description) continue; + holder->Next = process->OpenDescriptions; + process->OpenDescriptions = holder; + ++holder->Description->HolderCount; + plan->Holders[i] = (POSIXSRV_OPEN_HOLDER*)0; + } + plan->Count = 0; + + POSIXSRV_OPEN_HOLDER** link = &process->OpenDescriptions; + while (*link) { + POSIXSRV_OPEN_HOLDER* holder = *link; + int retained = 0; + for (uint32_t i = 0; i < snapshot->DescriptionCount; ++i) { + if (snapshot->Descriptions[i] == holder->Description) { retained = 1; break; } + } + if (retained) { + link = &holder->Next; + continue; + } + POSIXSRV_OPEN_DESCRIPTION* description = holder->Description; + *link = holder->Next; + free(holder); + if (description && description->HolderCount != 0) --description->HolderCount; + PosixsrvDestroyOpenDescriptionLocked(description); + } +} + // Releases one holder owned by the calling POSIX process. static KSTATUS PosixsrvReleaseOpenDescription(POSIXSRV_CLIENT* client, uint64_t descriptionId) { if (!client || !client->Process || descriptionId == 0) return NTLSTATUS_INVALID_PARAMETER; KSTATUS status = PosixsrvLockRegistry(); if (!KSUCCESS(status)) return status; - if (client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING) + if (PosixsrvClientIsPendingExecGeneration(client)) + status = NTLSTATUS_SUCCESS; + else if (!PosixsrvCommittedGenerationOperational(client) && !PosixsrvClientIsBorrowedProcess(client)) status = NTLSTATUS_OBJECT_DEAD; else status = PosixsrvReleaseOpenDescriptionLocked(client->Process, descriptionId); @@ -622,7 +745,7 @@ static KSTATUS PosixsrvCreateOpenDescription(POSIXSRV_CLIENT* client, free(shared); return status; } - if (client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING) { + if (!PosixsrvCommittedGenerationOperational(client)) { PosixsrvUnlockRegistry(); free(description); free(holder); @@ -738,7 +861,7 @@ static KSTATUS PosixsrvCreatePipe(POSIXSRV_CLIENT* client, (void)SysFreeVm(pipeShared); return status; } - if (client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING) { + if (!PosixsrvCommittedGenerationOperational(client)) { PosixsrvUnlockRegistry(); free(backing); free(readDescription); @@ -826,6 +949,10 @@ static KSTATUS PosixsrvSetOpenStatus(POSIXSRV_CLIENT* client, KSTATUS status = PosixsrvLockRegistry(); if (!KSUCCESS(status)) return status; + if (!PosixsrvCommittedGenerationOperational(client)) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_OBJECT_DEAD; + } POSIXSRV_OPEN_HOLDER* holder = PosixsrvFindOpenHolderLocked(client->Process, descriptionId); if (!holder || !holder->Description || !holder->Description->Shared) { @@ -912,6 +1039,330 @@ static int PosixsrvValidateLookupName(const NTL_POSIX_REQUEST* request) { return strlen(request->Name) == request->NameLength; } +// Retries process-pending delivery after thread registration or mask changes. +static void PosixsrvDrainPendingSignalsLocked(POSIXSRV_PROCESS_RECORD* process); + +// Finds one registered native thread while the process registry lock is held. +static POSIXSRV_THREAD_RECORD* PosixsrvFindThreadLocked(POSIXSRV_PROCESS_RECORD* process, + uint32_t nativeThreadId, + uint32_t generation) { + if (!process || nativeThreadId == 0 || generation == 0) return (POSIXSRV_THREAD_RECORD*)0; + for (POSIXSRV_THREAD_RECORD* thread = process->Threads; thread; thread = thread->Next) { + if (thread->NativeThreadId == nativeThreadId && thread->Generation == generation) + return thread; + } + return (POSIXSRV_THREAD_RECORD*)0; +} + +// Releases every retained native thread capability owned by one POSIX process. +static void PosixsrvReleaseThreadsLocked(POSIXSRV_PROCESS_RECORD* process) { + if (!process) return; + while (process->Threads) { + POSIXSRV_THREAD_RECORD* thread = process->Threads; + process->Threads = thread->Next; + if (thread->ThreadHandle != SYS_HANDLE_INVALID) + (void)SysCloseHandle(thread->ThreadHandle); + if (gRegisteredThreads != 0) --gRegisteredThreads; + free(thread); + } +} + +// Releases retained native thread capabilities for one retired native generation. +static void PosixsrvReleaseThreadsForGenerationLocked(POSIXSRV_PROCESS_RECORD* process, + uint32_t generation) { + if (!process || generation == 0) return; + POSIXSRV_THREAD_RECORD** link = &process->Threads; + while (*link) { + POSIXSRV_THREAD_RECORD* thread = *link; + if (thread->Generation != generation) { + link = &thread->Next; + continue; + } + *link = thread->Next; + if (thread->ThreadHandle != SYS_HANDLE_INVALID) + (void)SysCloseHandle(thread->ThreadHandle); + if (gRegisteredThreads != 0) --gRegisteredThreads; + free(thread); + } +} + +// Registers one thread capability and verifies it against the kernel-stamped sender TID. +static KSTATUS PosixsrvRegisterThread(POSIXSRV_CLIENT* client, + const NTL_POSIX_REQUEST* request, + const SYS_PORT_MESSAGE_INFO* info, + SYS_HANDLE* transferredThreadHandle) { + if (!client || !client->Process || !request || !info || !transferredThreadHandle || + *transferredThreadHandle == SYS_HANDLE_INVALID) + return NTLSTATUS_INVALID_PARAMETER; + if (client->NativeGeneration == 0 || + (client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING && + client->Process->State != NTL_POSIX_PROCESS_STATE_CONSTRUCTING && + client->Process->State != NTL_POSIX_PROCESS_STATE_EXEC_PREPARING && + client->Process->State != NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING)) + return NTLSTATUS_OBJECT_DEAD; + + uint32_t nativeThreadId = 0; + KSTATUS status = SysGetThreadTid(*transferredThreadHandle, &nativeThreadId); + if (!KSUCCESS(status)) return status; + if (nativeThreadId == 0 || nativeThreadId != info->SenderThreadId) + return NTLSTATUS_ACCESS_DENIED; + + POSIXSRV_THREAD_RECORD* candidate = + (POSIXSRV_THREAD_RECORD*)calloc(1u, sizeof(*candidate)); + if (!candidate) return NTLSTATUS_NO_MEMORY; + candidate->NativeThreadId = nativeThreadId; + candidate->Generation = client->NativeGeneration; + candidate->ThreadHandle = *transferredThreadHandle; + candidate->BlockedSignals = request->SignalMask & NTL_POSIX_SIGNAL_VALID_MASK & + ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + candidate->SignalApcRoutine = request->SignalApcRoutine; + + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { + free(candidate); + return status; + } + + POSIXSRV_THREAD_RECORD* existing = PosixsrvFindThreadLocked(client->Process, nativeThreadId, client->NativeGeneration); + if (existing) { + existing->BlockedSignals = candidate->BlockedSignals; + existing->SignalApcRoutine = candidate->SignalApcRoutine; + PosixsrvDrainPendingSignalsLocked(client->Process); + PosixsrvUnlockRegistry(); + free(candidate); + (void)SysCloseHandle(*transferredThreadHandle); + *transferredThreadHandle = SYS_HANDLE_INVALID; + return NTLSTATUS_SUCCESS; + } + + candidate->Next = client->Process->Threads; + client->Process->Threads = candidate; + ++gRegisteredThreads; + *transferredThreadHandle = SYS_HANDLE_INVALID; + PosixsrvDrainPendingSignalsLocked(client->Process); + PosixsrvUnlockRegistry(); + return NTLSTATUS_SUCCESS; +} + +// Unregisters the calling native thread using the kernel-stamped Port sender TID. +static KSTATUS PosixsrvUnregisterThread(POSIXSRV_CLIENT* client, + const SYS_PORT_MESSAGE_INFO* info) { + if (!client || !client->Process || !info || info->SenderThreadId == 0) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + + POSIXSRV_THREAD_RECORD** link = &client->Process->Threads; + while (*link && ((*link)->NativeThreadId != info->SenderThreadId || + (*link)->Generation != client->NativeGeneration)) + link = &(*link)->Next; + if (!*link) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_NOT_FOUND; + } + + POSIXSRV_THREAD_RECORD* thread = *link; + *link = thread->Next; + thread->Next = (POSIXSRV_THREAD_RECORD*)0; + if (gRegisteredThreads != 0) --gRegisteredThreads; + PosixsrvUnlockRegistry(); + + if (thread->ThreadHandle != SYS_HANDLE_INVALID) + (void)SysCloseHandle(thread->ThreadHandle); + free(thread); + return NTLSTATUS_SUCCESS; +} + + +// Returns whether the server should discard this signal for one process. +static int PosixsrvSignalIgnoredLocked(const POSIXSRV_PROCESS_RECORD* process, int signalNumber) { + if (!process || !NtlPosixSignalValid(signalNumber)) return 0; + return (process->IgnoredSignals & NTL_POSIX_SIGNAL_BIT(signalNumber)) != 0; +} + +// Queues one POSIX signal notification through a retained native thread capability. +static KSTATUS PosixsrvQueueSignalToThreadLocked(POSIXSRV_THREAD_RECORD* thread, + int signalNumber, + uint32_t route) { + if (!thread || thread->ThreadHandle == SYS_HANDLE_INVALID || thread->SignalApcRoutine == 0 || + !NtlPosixSignalValid(signalNumber)) + return NTLSTATUS_INVALID_PARAMETER; + + SYS_QUEUE_USER_APC_REQUEST queue; + memset(&queue, 0, sizeof(queue)); + queue.Size = sizeof(queue); + queue.ThreadHandle = thread->ThreadHandle; + queue.Routine = (SYS_USER_APC_CALLBACK)(uintptr_t)thread->SignalApcRoutine; + queue.NormalContext = (void*)(uintptr_t)(uint32_t)signalNumber; + queue.SystemArgument1 = (void*)(uintptr_t)route; + queue.Flags = SYS_QUEUE_USER_APC_FLAG_INTERRUPT; + return SysQueueUserApc(&queue); +} + +// Selects one unblocked registered thread for a process-directed signal. +static POSIXSRV_THREAD_RECORD* PosixsrvSelectSignalThreadLocked(POSIXSRV_PROCESS_RECORD* process, + int signalNumber) { + if (!process || !NtlPosixSignalValid(signalNumber)) return (POSIXSRV_THREAD_RECORD*)0; + uint64_t bit = NTL_POSIX_SIGNAL_BIT(signalNumber); + for (POSIXSRV_THREAD_RECORD* thread = process->Threads; thread; thread = thread->Next) { + if (thread->Generation != process->Generation || thread->SignalApcRoutine == 0) continue; + if (signalNumber == NTL_POSIX_SIGNAL_KILL || (thread->BlockedSignals & bit) == 0) + return thread; + } + return (POSIXSRV_THREAD_RECORD*)0; +} + +// Delivers one process-directed signal or leaves it pending when every thread blocks it. +static KSTATUS PosixsrvRouteSignalLocked(POSIXSRV_PROCESS_RECORD* process, int signalNumber) { + if (!process || !PosixsrvProcessHasCommittedGeneration(process) || + !NtlPosixSignalValid(signalNumber)) + return NTLSTATUS_INVALID_PARAMETER; + uint64_t bit = NTL_POSIX_SIGNAL_BIT(signalNumber); + if (PosixsrvSignalIgnoredLocked(process, signalNumber)) { + process->ProcessPendingSignals &= ~bit; + return NTLSTATUS_SUCCESS; + } + if (process->State == NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING) { + process->ProcessPendingSignals |= bit; + return NTLSTATUS_SUCCESS; + } + + POSIXSRV_THREAD_RECORD* thread = PosixsrvSelectSignalThreadLocked(process, signalNumber); + if (!thread) { + process->ProcessPendingSignals |= bit; + return NTLSTATUS_SUCCESS; + } + + KSTATUS status = PosixsrvQueueSignalToThreadLocked(thread, signalNumber, + NTL_POSIX_SIGNAL_ROUTE_PROCESS); + if (KSUCCESS(status)) process->ProcessPendingSignals &= ~bit; + else process->ProcessPendingSignals |= bit; + return NTLSTATUS_SUCCESS; +} + +// Attempts every process-pending signal that has become deliverable. +static void PosixsrvDrainPendingSignalsLocked(POSIXSRV_PROCESS_RECORD* process) { + if (!process || (process->State != NTL_POSIX_PROCESS_STATE_RUNNING && + process->State != NTL_POSIX_PROCESS_STATE_EXEC_PREPARING)) + return; + for (int signalNumber = NTL_POSIX_SIGNAL_HUP; + signalNumber <= NTL_POSIX_SIGNAL_MAX; ++signalNumber) { + uint64_t bit = NTL_POSIX_SIGNAL_BIT(signalNumber); + if ((process->ProcessPendingSignals & bit) == 0) continue; + if (PosixsrvSignalIgnoredLocked(process, signalNumber)) { + process->ProcessPendingSignals &= ~bit; + continue; + } + POSIXSRV_THREAD_RECORD* thread = PosixsrvSelectSignalThreadLocked(process, signalNumber); + if (!thread) continue; + if (KSUCCESS(PosixsrvQueueSignalToThreadLocked(thread, signalNumber, + NTL_POSIX_SIGNAL_ROUTE_PROCESS))) + process->ProcessPendingSignals &= ~bit; + } +} + +// Updates the calling native thread's persistent POSIX signal mask. +static KSTATUS PosixsrvUpdateSignalMask(POSIXSRV_CLIENT* client, + const SYS_PORT_MESSAGE_INFO* info, + uint64_t signalMask) { + if (!client || !client->Process || !info || + (signalMask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) + return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_THREAD_RECORD* thread = PosixsrvFindThreadLocked(client->Process, info->SenderThreadId, client->NativeGeneration); + if (!thread) status = NTLSTATUS_NOT_FOUND; + else { + thread->BlockedSignals = signalMask & ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + PosixsrvDrainPendingSignalsLocked(client->Process); + status = NTLSTATUS_SUCCESS; + } + PosixsrvUnlockRegistry(); + return status; +} + +// Changes the server-side routing view of one process signal disposition. +static KSTATUS PosixsrvSetSignalDisposition(POSIXSRV_CLIENT* client, + int signalNumber, + uint32_t disposition) { + if (!client || !client->Process || !NtlPosixSignalValid(signalNumber) || + disposition > NTL_POSIX_SIGNAL_DISPOSITION_CAUGHT || + (signalNumber == NTL_POSIX_SIGNAL_KILL && disposition != NTL_POSIX_SIGNAL_DISPOSITION_DEFAULT)) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + if (!PosixsrvCommittedGenerationOperational(client) || + client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_WOULD_BLOCK; + } + uint64_t bit = NTL_POSIX_SIGNAL_BIT(signalNumber); + int explicitlyIgnored = disposition == NTL_POSIX_SIGNAL_DISPOSITION_IGNORE; + int ignored = explicitlyIgnored || + (disposition == NTL_POSIX_SIGNAL_DISPOSITION_DEFAULT && + signalNumber == NTL_POSIX_SIGNAL_CHLD); + if (explicitlyIgnored) client->Process->ExplicitIgnoredSignals |= bit; + else client->Process->ExplicitIgnoredSignals &= ~bit; + if (ignored) { + client->Process->IgnoredSignals |= bit; + client->Process->ProcessPendingSignals &= ~bit; + } else { + client->Process->IgnoredSignals &= ~bit; + PosixsrvDrainPendingSignalsLocked(client->Process); + } + PosixsrvUnlockRegistry(); + return NTLSTATUS_SUCCESS; +} + +// Returns whether a sender is permitted to signal one synthetic POSIX identity. +static int PosixsrvCanSignalLocked(const POSIXSRV_PROCESS_RECORD* sender, + const POSIXSRV_PROCESS_RECORD* target) { + if (!sender || !target) return 0; + if (sender->EffectiveUserId == 0) return 1; + return sender->UserId == target->UserId || sender->UserId == target->EffectiveUserId || + sender->EffectiveUserId == target->UserId || + sender->EffectiveUserId == target->EffectiveUserId; +} + +// Routes kill-style process or process-group selection through stable POSIX identity. +static KSTATUS PosixsrvSendSignal(POSIXSRV_CLIENT* client, int32_t targetPid, int signalNumber) { + if (!client || !client->Process || signalNumber < 0 || signalNumber > NTL_POSIX_SIGNAL_MAX || + targetPid == INT32_MIN) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_PROCESS_RECORD* sender = client->Process; + uint32_t matched = 0; + uint32_t permitted = 0; + KSTATUS deliveryStatus = NTLSTATUS_SUCCESS; + + for (POSIXSRV_PROCESS_RECORD* target = gProcessRegistry; target; target = target->Next) { + if (!PosixsrvProcessHasCommittedGeneration(target)) continue; + int selected = 0; + if (targetPid > 0) selected = target->PosixPid == (uint32_t)targetPid; + else if (targetPid == 0) selected = target->ProcessGroupId == sender->ProcessGroupId; + else if (targetPid == -1) selected = 1; + else selected = target->ProcessGroupId == (uint32_t)(-targetPid); + if (!selected) continue; + ++matched; + if (!PosixsrvCanSignalLocked(sender, target)) continue; + ++permitted; + if (signalNumber != 0) { + KSTATUS one = PosixsrvRouteSignalLocked(target, signalNumber); + if (!KSUCCESS(one) && KSUCCESS(deliveryStatus)) deliveryStatus = one; + } + } + + if (matched == 0) status = NTLSTATUS_NOT_FOUND; + else if (permitted == 0) status = NTLSTATUS_ACCESS_DENIED; + else status = deliveryStatus; + PosixsrvUnlockRegistry(); + return status; +} + // Removes one process record from the registry while the registry mutex is held. static void PosixsrvUnlinkProcessLocked(POSIXSRV_PROCESS_RECORD* record) { if (!record) return; @@ -927,11 +1378,293 @@ static void PosixsrvUnlinkProcessLocked(POSIXSRV_PROCESS_RECORD* record) { // Releases one detached process record and its retained native capability. static void PosixsrvFreeProcessRecord(POSIXSRV_PROCESS_RECORD* record) { if (!record) return; + for (uint32_t i = 0; i < NTL_POSIX_TRANSACTION_CAPACITY; ++i) + PosixsrvDestroyTransaction(&record->Transactions[i]); + record->ActiveTransactions = 0; + while (record->Threads) { + POSIXSRV_THREAD_RECORD* thread = record->Threads; + record->Threads = thread->Next; + if (thread->ThreadHandle != SYS_HANDLE_INVALID) + (void)SysCloseHandle(thread->ThreadHandle); + if (gRegisteredThreads != 0) --gRegisteredThreads; + free(thread); + } if (record->ProcessHandle != SYS_HANDLE_INVALID) (void)SysCloseHandle(record->ProcessHandle); + if (record->BootstrapReadyEvent != SYS_HANDLE_INVALID) + (void)SysCloseHandle(record->BootstrapReadyEvent); + if (record->PendingProcessHandle != SYS_HANDLE_INVALID) + (void)SysCloseHandle(record->PendingProcessHandle); + if (record->PendingBootstrapReadyEvent != SYS_HANDLE_INVALID) + (void)SysCloseHandle(record->PendingBootstrapReadyEvent); + if (record->PendingBootstrapReleaseEvent != SYS_HANDLE_INVALID) + (void)SysCloseHandle(record->PendingBootstrapReleaseEvent); + if (record->AlarmTimer != SYS_HANDLE_INVALID) + (void)SysCloseHandle(record->AlarmTimer); + if (record->AlarmControlEvent != SYS_HANDLE_INVALID) + (void)SysCloseHandle(record->AlarmControlEvent); + if (record->PendingExecHolderPlan) { + PosixsrvDestroyExecHolderPlan(record->PendingExecHolderPlan); + free(record->PendingExecHolderPlan); + record->PendingExecHolderPlan = (POSIXSRV_EXEC_HOLDER_PLAN*)0; + } free(record); } +// Removes borrowed logical children when their shared host native process dies. +static void PosixsrvReleaseBorrowedChildrenForHostLocked(POSIXSRV_PROCESS_RECORD* host) { + if (!host) return; + POSIXSRV_PROCESS_RECORD** link = &gProcessRegistry; + while (*link) { + POSIXSRV_PROCESS_RECORD* child = *link; + if (child->State != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED || + child->BorrowedHostProcess != host || child->BorrowedMonitorActive) { + link = &child->Next; + continue; + } + *link = child->Next; + child->Next = (POSIXSRV_PROCESS_RECORD*)0; + if (gRegisteredProcesses != 0) --gRegisteredProcesses; + PosixsrvReleaseFdInheritanceForProcessLocked(child); + PosixsrvReleaseAllOpenDescriptionsLocked(child); + PosixsrvReleaseThreadsLocked(child); + PosixsrvFreeProcessRecord(child); + } +} + +// Returns whether one exited process record has no remaining owner that requires retention. +static int PosixsrvCanFreeProcessLocked(const POSIXSRV_PROCESS_RECORD* record) { + return record && record->State == NTL_POSIX_PROCESS_STATE_EXITED && + record->ParentPosixPid == 0 && record->ConnectionCount == 0 && + !record->MonitorActive && !record->AlarmMonitorActive && !record->BorrowedMonitorActive; +} + +// Converts one monotonic alarm deadline into POSIX whole seconds remaining. +static uint32_t PosixsrvAlarmRemainingSeconds(uint64_t deadlineTicks, + uint64_t nowTicks, + uint64_t frequency) { + if (deadlineTicks == 0 || deadlineTicks <= nowTicks || frequency == 0) return 0; + uint64_t remainingTicks = deadlineTicks - nowTicks; + uint64_t seconds = remainingTicks / frequency; + if (remainingTicks % frequency) ++seconds; + return seconds > UINT32_MAX ? UINT32_MAX : (uint32_t)seconds; +} + +// Waits on one process alarm timer until process teardown ends its ownership. +static void PosixsrvAlarmMonitorThread(uintptr_t argument) { + POSIXSRV_PROCESS_RECORD* record = (POSIXSRV_PROCESS_RECORD*)argument; + if (!record) { + SysExitThread(1); + return; + } + PosixsrvSetCurrentThreadName("posixsrv.alarm"); + + for (;;) { + SYS_HANDLE timer = SYS_HANDLE_INVALID; + SYS_HANDLE control = SYS_HANDLE_INVALID; + uint64_t generation = 0; + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) break; + if (!PosixsrvProcessHasCommittedGeneration(record) || + record->AlarmTimer == SYS_HANDLE_INVALID || + record->AlarmControlEvent == SYS_HANDLE_INVALID) { + record->AlarmMonitorActive = 0; + POSIXSRV_PROCESS_RECORD* freeRecord = (POSIXSRV_PROCESS_RECORD*)0; + if (PosixsrvCanFreeProcessLocked(record)) { + PosixsrvUnlinkProcessLocked(record); + freeRecord = record; + } + PosixsrvUnlockRegistry(); + if (freeRecord) PosixsrvFreeProcessRecord(freeRecord); + SysExitThread(0); + return; + } + timer = record->AlarmTimer; + control = record->AlarmControlEvent; + generation = record->AlarmGeneration; + PosixsrvUnlockRegistry(); + + SYS_HANDLE handles[2] = { timer, control }; + uint32_t selected = UINT32_MAX; + SYS_WAIT_MANY_REQUEST wait; + memset(&wait, 0, sizeof(wait)); + wait.Size = sizeof(wait); + wait.Flags = SYS_WAIT_FLAG_NONE; + wait.Handles = handles; + wait.Count = 2u; + wait.SignaledIndexOut = &selected; + wait.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE; + status = SysWaitMany(&wait); + + POSIXSRV_PROCESS_RECORD* freeRecord = (POSIXSRV_PROCESS_RECORD*)0; + KSTATUS lockStatus = PosixsrvLockRegistry(); + if (!KSUCCESS(lockStatus)) break; + if (!KSUCCESS(status)) { + record->AlarmMonitorActive = 0; + } else if (!PosixsrvProcessHasCommittedGeneration(record)) { + record->AlarmMonitorActive = 0; + } else if (selected == 0u && generation == record->AlarmGeneration && + record->AlarmDeadlineTicks != 0) { + record->AlarmDeadlineTicks = 0; + ++record->AlarmGeneration; + (void)PosixsrvRouteSignalLocked(record, NTL_POSIX_SIGNAL_ALRM); + } + int stop = !record->AlarmMonitorActive; + if (stop && PosixsrvCanFreeProcessLocked(record)) { + PosixsrvUnlinkProcessLocked(record); + freeRecord = record; + } + PosixsrvUnlockRegistry(); + if (freeRecord) PosixsrvFreeProcessRecord(freeRecord); + if (stop) { + SysExitThread(KSUCCESS(status) ? 0 : 1); + return; + } + } + + if (KSUCCESS(PosixsrvLockRegistry())) { + record->AlarmMonitorActive = 0; + PosixsrvUnlockRegistry(); + } + SysExitThread(1); +} + +// Ensures one running process has the server-owned alarm timer and monitor. +static KSTATUS PosixsrvEnsureAlarmMonitorLocked(POSIXSRV_PROCESS_RECORD* process) { + if (!process || !PosixsrvProcessHasCommittedGeneration(process)) + return NTLSTATUS_OBJECT_DEAD; + if (process->AlarmTimer == SYS_HANDLE_INVALID) { + KSTATUS status = SysCreateTimer(&process->AlarmTimer); + if (!KSUCCESS(status)) return status; + } + if (process->AlarmControlEvent == SYS_HANDLE_INVALID) { + KSTATUS status = SysCreateEvent(0u, 0u, &process->AlarmControlEvent); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(process->AlarmTimer); + process->AlarmTimer = SYS_HANDLE_INVALID; + return status; + } + } + if (process->AlarmMonitorActive) return NTLSTATUS_SUCCESS; + + process->AlarmMonitorActive = 1u; + SYS_CREATE_THREAD_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)PosixsrvAlarmMonitorThread; + create.Argument = (uintptr_t)process; + create.Priority = 1; + create.Flags = SYS_CREATE_THREAD_FLAG_NONE; + KSTATUS status = SysCreateThread(&create); + if (!KSUCCESS(status)) process->AlarmMonitorActive = 0; + return status; +} + +// Replaces one process alarm and reports the previous interval rounded up. +static KSTATUS PosixsrvSetAlarm(POSIXSRV_CLIENT* client, + uint32_t seconds, + uint32_t* previousSecondsOut) { + if (!client || !client->Process || !previousSecondsOut) + return NTLSTATUS_INVALID_PARAMETER; + *previousSecondsOut = 0; + + uint64_t frequency = 0; + KSTATUS status = SysGetTickFrequency(&frequency); + if (!KSUCCESS(status) || frequency == 0) + return KSUCCESS(status) ? NTLSTATUS_DEVICE_ERROR : status; + uint64_t now = SysGetTick(); + + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_PROCESS_RECORD* process = client->Process; + if (!PosixsrvCommittedGenerationOperational(client)) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_OBJECT_DEAD; + } + *previousSecondsOut = PosixsrvAlarmRemainingSeconds(process->AlarmDeadlineTicks, + now, frequency); + + if (seconds == 0) { + process->AlarmDeadlineTicks = 0; + ++process->AlarmGeneration; + if (process->AlarmTimer != SYS_HANDLE_INVALID) + (void)SysCancelTimer(process->AlarmTimer); + if (process->AlarmControlEvent != SYS_HANDLE_INVALID) + (void)SysSignalEvent(process->AlarmControlEvent); + PosixsrvUnlockRegistry(); + return NTLSTATUS_SUCCESS; + } + + status = PosixsrvEnsureAlarmMonitorLocked(process); + if (!KSUCCESS(status)) { + PosixsrvUnlockRegistry(); + return status; + } + + uint64_t intervalTicks = (uint64_t)seconds * frequency; + process->AlarmDeadlineTicks = intervalTicks > UINT64_MAX - now ? UINT64_MAX : now + intervalTicks; + ++process->AlarmGeneration; + + SYS_SET_TIMER_REQUEST setTimer; + memset(&setTimer, 0, sizeof(setTimer)); + setTimer.Size = sizeof(setTimer); + setTimer.TimerHandle = process->AlarmTimer; + setTimer.Flags = SYS_TIMER_FLAG_NONE; + setTimer.DueTimeMilliseconds = (uint64_t)seconds * 1000u; + status = SysSetTimer(&setTimer); + if (!KSUCCESS(status)) { + process->AlarmDeadlineTicks = 0; + ++process->AlarmGeneration; + } + (void)SysSignalEvent(process->AlarmControlEvent); + PosixsrvUnlockRegistry(); + return status; +} + +// Tests whether one waiter selection includes the supplied direct child. +static int PosixsrvWaiterMatchesChild(const POSIXSRV_WAITER* waiter, + const POSIXSRV_PROCESS_RECORD* child) { + if (!waiter || !child || child->ParentPosixPid != waiter->ParentPosixPid) return 0; + if (waiter->WaitPid > 0) return child->PosixPid == (uint32_t)waiter->WaitPid; + if (waiter->WaitPid == -1) return 1; + if (waiter->WaitPid == 0) return child->ProcessGroupId == waiter->ParentProcessGroupId; + uint64_t group = (uint64_t)(-(int64_t)waiter->WaitPid); + return group <= UINT32_MAX && child->ProcessGroupId == (uint32_t)group; +} + +// Wakes every blocked wait request whose selection includes one child transition. +static void PosixsrvSignalWaitersForChildLocked(const POSIXSRV_PROCESS_RECORD* child) { + for (POSIXSRV_WAITER* waiter = gWaiters; waiter; waiter = waiter->Next) { + if (PosixsrvWaiterMatchesChild(waiter, child)) + (void)SysSignalEvent(waiter->Event); + } +} + +// Wakes waits issued by a process that has itself terminated. +static void PosixsrvSignalWaitersForParentLocked(uint32_t parentPid) { + for (POSIXSRV_WAITER* waiter = gWaiters; waiter; waiter = waiter->Next) { + if (waiter->ParentPosixPid == parentPid) + (void)SysSignalEvent(waiter->Event); + } +} + +// Drops parent associations when a POSIX parent dies and reclaims already-exited orphans. +static void PosixsrvOrphanChildrenLocked(uint32_t parentPid) { + POSIXSRV_PROCESS_RECORD* record = gProcessRegistry; + while (record) { + POSIXSRV_PROCESS_RECORD* next = record->Next; + if (record->ParentPosixPid == parentPid) { + record->ParentPosixPid = 0; + if (PosixsrvCanFreeProcessLocked(record)) { + PosixsrvUnlinkProcessLocked(record); + PosixsrvFreeProcessRecord(record); + } + } + record = next; + } + PosixsrvSignalWaitersForParentLocked(parentPid); +} + // Allocates one stable positive POSIX PID from the server-owned namespace. static KSTATUS PosixsrvAllocatePidLocked(uint32_t* pidOut) { if (!pidOut) return NTLSTATUS_INVALID_PARAMETER; @@ -977,6 +1710,8 @@ static KSTATUS PosixsrvCreateListener(SYS_HANDLE* listenerOut) { create.Size = sizeof(create); create.Flags = SYS_PORT_FLAG_NONE; create.Backlog = SYS_PORT_DEFAULT_BACKLOG; + create.PeerProcessRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_RIGHT_DERIVE | + SYS_PROCESS_RIGHT_TERMINATE | SYS_PROCESS_RIGHT_SECURITY_CONTEXT; create.HandleOut = listenerOut; return SysCreatePort(&create); } @@ -998,7 +1733,7 @@ static void PosixsrvSnapshotReplyIdentity(POSIXSRV_CLIENT* client, NTL_POSIX_REP if (client->Process) { POSIXSRV_PROCESS_RECORD* record = client->Process; reply->PosixPid = record->PosixPid; - reply->NativeGeneration = record->Generation; + reply->NativeGeneration = client->NativeGeneration ? client->NativeGeneration : record->Generation; reply->ProcessState = record->State; reply->ParentPosixPid = record->ParentPosixPid; reply->UserId = record->UserId; @@ -1007,6 +1742,7 @@ static void PosixsrvSnapshotReplyIdentity(POSIXSRV_CLIENT* client, NTL_POSIX_REP reply->EffectiveGroupId = record->EffectiveGroupId; reply->ProcessGroupId = record->ProcessGroupId; reply->SessionId = record->SessionId; + reply->ActiveTransactions = record->ActiveTransactions; reply->SupplementaryGroupCount = record->SupplementaryGroupCount; if (reply->SupplementaryGroupCount > NTL_POSIX_SUPPLEMENTARY_GROUP_MAX) reply->SupplementaryGroupCount = NTL_POSIX_SUPPLEMENTARY_GROUP_MAX; @@ -1019,6 +1755,7 @@ static void PosixsrvSnapshotReplyIdentity(POSIXSRV_CLIENT* client, NTL_POSIX_REP } reply->RegisteredProcesses = gRegisteredProcesses; reply->RegisteredOpenDescriptions = gRegisteredOpenDescriptions; + reply->RegisteredThreads = gRegisteredThreads; PosixsrvUnlockRegistry(); } @@ -1037,7 +1774,6 @@ static void PosixsrvInitializeReply(POSIXSRV_CLIENT* client, reply->TransactionId = transactionId; reply->ClientProcessId = client->Connection.ClientProcessId; reply->ClientSecurityId = client->Connection.ClientSecurityId; - reply->ActiveTransactions = client->ActiveTransactions; reply->TransactionCapacity = NTL_POSIX_TRANSACTION_CAPACITY; PosixsrvSnapshotReplyIdentity(client, reply); } @@ -1184,8 +1920,16 @@ static KSTATUS PosixsrvReplyFdInheritance(POSIXSRV_CLIENT* client, if (!KSUCCESS(status)) return status; POSIXSRV_FD_INHERITANCE* snapshot = PosixsrvFindFdInheritanceLocked(token); - if (!snapshot || snapshot->SecurityId != client->Process->SecurityId || - client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING) { + int execImport = snapshot && PosixsrvClientIsPendingExecGeneration(client) && + client->Process->PendingFdInheritanceToken == token && + snapshot->SourceProcess == client->Process && !snapshot->ClaimProcess; + int processStateValid = client->Process->State == NTL_POSIX_PROCESS_STATE_RUNNING || + client->Process->State == NTL_POSIX_PROCESS_STATE_CONSTRUCTING || + client->Process->State == NTL_POSIX_PROCESS_STATE_VFORK_BORROWED || + execImport; + int ownershipValid = snapshot && (execImport || snapshot->ClaimProcess == client->Process || + (!snapshot->ClaimProcess && snapshot->SecurityId == client->Process->SecurityId)); + if (!snapshot || !ownershipValid || !processStateValid) { PosixsrvUnlockRegistry(); return PosixsrvReply(client, messageId, NTL_POSIX_OP_IMPORT_FD_INHERITANCE, snapshot ? NTLSTATUS_ACCESS_DENIED : NTLSTATUS_NOT_FOUND, 0); @@ -1197,10 +1941,12 @@ static KSTATUS PosixsrvReplyFdInheritance(POSIXSRV_CLIENT* client, NTLSTATUS_INVALID_PARAMETER, 0); } - status = PosixsrvClaimFdInheritanceLocked(snapshot, client->Process); - if (!KSUCCESS(status)) { - PosixsrvUnlockRegistry(); - return PosixsrvReply(client, messageId, NTL_POSIX_OP_IMPORT_FD_INHERITANCE, status, 0); + if (!execImport) { + status = PosixsrvClaimFdInheritanceLocked(snapshot, client->Process); + if (!KSUCCESS(status)) { + PosixsrvUnlockRegistry(); + return PosixsrvReply(client, messageId, NTL_POSIX_OP_IMPORT_FD_INHERITANCE, status, 0); + } } uint32_t count = snapshot->DescriptionCount - descriptionIndex; @@ -1247,7 +1993,7 @@ static KSTATUS PosixsrvReplyFdInheritance(POSIXSRV_CLIENT* client, KSTATUS replyStatus = PosixsrvSendReplyWithHandles(client, messageId, &reply, transfers, handleCount); - if (KSUCCESS(replyStatus) && descriptionIndex + count == snapshot->DescriptionCount) + if (KSUCCESS(replyStatus) && descriptionIndex + count == snapshot->DescriptionCount && !execImport) PosixsrvDestroyFdInheritanceLocked(snapshot); PosixsrvUnlockRegistry(); return replyStatus; @@ -1288,54 +2034,285 @@ static KSTATUS PosixsrvReplyGroup(POSIXSRV_CLIENT* client, // Waits for one native process generation and records genuine process termination. static void PosixsrvProcessMonitorThread(uintptr_t argument) { - POSIXSRV_PROCESS_RECORD* record = (POSIXSRV_PROCESS_RECORD*)argument; - if (!record) { + POSIXSRV_PROCESS_MONITOR_CONTEXT* monitor = + (POSIXSRV_PROCESS_MONITOR_CONTEXT*)argument; + if (!monitor || !monitor->Process || monitor->ProcessHandle == SYS_HANDLE_INVALID || + monitor->Generation == 0) { + free(monitor); SysExitThread(1); return; } PosixsrvSetCurrentThreadName("posixsrv.process"); - KSTATUS waitStatus = SysWait(record->ProcessHandle, SYS_WAIT_FLAG_NONE); + POSIXSRV_PROCESS_RECORD* record = monitor->Process; + SYS_HANDLE processHandle = monitor->ProcessHandle; + uint32_t generation = monitor->Generation; + KSTATUS waitStatus = SysWait(processHandle, SYS_WAIT_FLAG_NONE); int32_t exitCode = (int32_t)NTLSTATUS_PROCESS_UNKNOWN_FAULT; if (KSUCCESS(waitStatus)) - (void)SysGetProcessExitCode(record->ProcessHandle, &exitCode); + (void)SysGetProcessExitCode(processHandle, &exitCode); POSIXSRV_PROCESS_RECORD* freeRecord = (POSIXSRV_PROCESS_RECORD*)0; - SYS_HANDLE closeHandle = SYS_HANDLE_INVALID; + SYS_HANDLE closeCommittedHandle = SYS_HANDLE_INVALID; + SYS_HANDLE abortPendingHandle = SYS_HANDLE_INVALID; + int genuineExit = 0; if (KSUCCESS(PosixsrvLockRegistry())) { - if (KSUCCESS(waitStatus) && record->State == NTL_POSIX_PROCESS_STATE_RUNNING) { + if (KSUCCESS(waitStatus) && record->Generation == generation && + (record->State == NTL_POSIX_PROCESS_STATE_RUNNING || + record->State == NTL_POSIX_PROCESS_STATE_EXEC_PREPARING)) { + genuineExit = 1; + if (record->State == NTL_POSIX_PROCESS_STATE_EXEC_PREPARING) + abortPendingHandle = record->PendingProcessHandle; PosixsrvReleaseFdInheritanceForProcessLocked(record); PosixsrvReleaseAllOpenDescriptionsLocked(record); + PosixsrvReleaseThreadsLocked(record); + for (uint32_t i = 0; i < NTL_POSIX_TRANSACTION_CAPACITY; ++i) + PosixsrvDestroyTransaction(&record->Transactions[i]); + record->ActiveTransactions = 0; record->State = NTL_POSIX_PROCESS_STATE_EXITED; record->ExitCode = exitCode; - closeHandle = record->ProcessHandle; + record->AlarmDeadlineTicks = 0; + ++record->AlarmGeneration; + if (record->AlarmTimer != SYS_HANDLE_INVALID) + (void)SysCancelTimer(record->AlarmTimer); + if (record->AlarmControlEvent != SYS_HANDLE_INVALID) + (void)SysSignalEvent(record->AlarmControlEvent); + closeCommittedHandle = record->ProcessHandle; record->ProcessHandle = SYS_HANDLE_INVALID; + PosixsrvSignalWaitersForChildLocked(record); + PosixsrvReleaseBorrowedChildrenForHostLocked(record); + POSIXSRV_PROCESS_RECORD* parent = + PosixsrvFindPosixProcessLocked(record->ParentPosixPid); + if (parent && PosixsrvProcessHasCommittedGeneration(parent)) + (void)PosixsrvRouteSignalLocked(parent, NTL_POSIX_SIGNAL_CHLD); + PosixsrvOrphanChildrenLocked(record->PosixPid); } - record->MonitorActive = 0; - if (record->State == NTL_POSIX_PROCESS_STATE_EXITED && record->ConnectionCount == 0) { + if (record->MonitorActive != 0) --record->MonitorActive; + if (PosixsrvCanFreeProcessLocked(record)) { PosixsrvUnlinkProcessLocked(record); freeRecord = record; } PosixsrvUnlockRegistry(); } - if (closeHandle != SYS_HANDLE_INVALID) (void)SysCloseHandle(closeHandle); + if (abortPendingHandle != SYS_HANDLE_INVALID) + (void)SysKillProcess(abortPendingHandle, (int32_t)NTLSTATUS_PROCESS_KILLED); + (void)SysCloseHandle(processHandle); + if (closeCommittedHandle != SYS_HANDLE_INVALID) (void)SysCloseHandle(closeCommittedHandle); + free(monitor); if (freeRecord) PosixsrvFreeProcessRecord(freeRecord); - SysExitThread(KSUCCESS(waitStatus) ? 0 : 1); + SysExitThread(KSUCCESS(waitStatus) || genuineExit ? 0 : 1); +} + +// Starts one generation-tagged native process monitor and transfers handle ownership to it. +static KSTATUS PosixsrvStartProcessMonitorLocked(POSIXSRV_PROCESS_RECORD* record, + SYS_HANDLE processHandle, + uint32_t generation) { + if (!record || processHandle == SYS_HANDLE_INVALID || generation == 0) + return NTLSTATUS_INVALID_PARAMETER; + SYS_HANDLE monitorHandle = SYS_HANDLE_INVALID; + SYS_DERIVE_HANDLE_REQUEST derive; + memset(&derive, 0, sizeof(derive)); + derive.Size = sizeof(derive); + derive.SourceHandle = processHandle; + derive.DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT; + derive.DerivedHandleOut = &monitorHandle; + KSTATUS status = SysDeriveHandle(&derive); + if (!KSUCCESS(status)) return status; + POSIXSRV_PROCESS_MONITOR_CONTEXT* monitor = + (POSIXSRV_PROCESS_MONITOR_CONTEXT*)calloc(1, sizeof(*monitor)); + if (!monitor) { + (void)SysCloseHandle(monitorHandle); + return NTLSTATUS_NO_MEMORY; + } + monitor->Process = record; + monitor->ProcessHandle = monitorHandle; + monitor->Generation = generation; + + SYS_CREATE_THREAD_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)PosixsrvProcessMonitorThread; + create.Argument = (uintptr_t)monitor; + create.Priority = 1; + create.Flags = SYS_CREATE_THREAD_FLAG_NONE; + status = SysCreateThread(&create); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(monitorHandle); + free(monitor); + return status; + } + ++record->MonitorActive; + return NTLSTATUS_SUCCESS; +} + +// Waits for one borrowed vfork thread and owns the terminal state transition. +static void PosixsrvBorrowedMonitorThread(uintptr_t argument) { + POSIXSRV_BORROWED_MONITOR_CONTEXT* monitor = + (POSIXSRV_BORROWED_MONITOR_CONTEXT*)argument; + if (!monitor || !monitor->Process || monitor->ThreadHandle == SYS_HANDLE_INVALID || + monitor->BorrowedToken == 0) { + free(monitor); + SysExitThread(1); + return; + } + PosixsrvSetCurrentThreadName("posixsrv.vfork"); + + POSIXSRV_PROCESS_RECORD* record = monitor->Process; + KSTATUS waitStatus = SysWait(monitor->ThreadHandle, SYS_WAIT_FLAG_NONE); + SYS_HANDLE releaseEvent = SYS_HANDLE_INVALID; + SYS_HANDLE abortPending = SYS_HANDLE_INVALID; + int completed = 0; + + if (KSUCCESS(PosixsrvLockRegistry())) { + if (KSUCCESS(waitStatus) && record->BorrowedToken == monitor->BorrowedToken) { + if (record->BorrowedTerminalKind == POSIXSRV_VFORK_TERMINAL_EXEC && + record->State == NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING && + record->PendingExecHolderPlan && + record->PendingProcessHandle != SYS_HANDLE_INVALID && + record->PendingGeneration != 0 && record->PendingFdInheritanceToken != 0) { + POSIXSRV_FD_INHERITANCE* snapshot = + PosixsrvFindFdInheritanceLocked(record->PendingFdInheritanceToken); + if (snapshot && snapshot->SourceProcess == record && !snapshot->ClaimProcess && + SysWaitSingle(record->PendingProcessHandle, SYS_WAIT_FLAG_NONE, 0u) == NTLSTATUS_TIMEOUT) { + PosixsrvApplyExecHolderPlanLocked(record, snapshot, + record->PendingExecHolderPlan); + free(record->PendingExecHolderPlan); + record->PendingExecHolderPlan = (POSIXSRV_EXEC_HOLDER_PLAN*)0; + PosixsrvReleaseThreadsLocked(record); + record->ProcessHandle = record->PendingProcessHandle; + record->NativeProcessId = record->PendingNativeProcessId; + record->Generation = record->PendingGeneration; + record->State = NTL_POSIX_PROCESS_STATE_RUNNING; + record->PendingProcessHandle = SYS_HANDLE_INVALID; + record->PendingNativeProcessId = 0; + record->PendingGeneration = 0; + record->PendingBootstrapToken = 0; + record->PendingFdInheritanceToken = 0; + record->BorrowedHostProcess = (POSIXSRV_PROCESS_RECORD*)0; + record->BorrowedHostGeneration = 0; + record->BorrowedNativeThreadId = 0; + record->BorrowedToken = 0; + record->BorrowedTerminalKind = POSIXSRV_VFORK_TERMINAL_NONE; + record->IgnoredSignals = record->ExplicitIgnoredSignals | + NTL_POSIX_SIGNAL_BIT(NTL_POSIX_SIGNAL_CHLD); + PosixsrvDestroyFdInheritanceLocked(snapshot); + PosixsrvDrainPendingSignalsLocked(record); + releaseEvent = record->PendingBootstrapReleaseEvent; + completed = 1; + } + } + + if (!completed) { + if (record->PendingProcessHandle != SYS_HANDLE_INVALID) { + abortPending = record->PendingProcessHandle; + record->PendingProcessHandle = SYS_HANDLE_INVALID; + } + if (record->PendingExecHolderPlan) { + PosixsrvDestroyExecHolderPlan(record->PendingExecHolderPlan); + free(record->PendingExecHolderPlan); + record->PendingExecHolderPlan = (POSIXSRV_EXEC_HOLDER_PLAN*)0; + } + if (record->PendingFdInheritanceToken != 0) { + POSIXSRV_FD_INHERITANCE* snapshot = + PosixsrvFindFdInheritanceLocked(record->PendingFdInheritanceToken); + if (snapshot && snapshot->SourceProcess == record && !snapshot->ClaimProcess) + PosixsrvDestroyFdInheritanceLocked(snapshot); + } + PosixsrvReleaseFdInheritanceForProcessLocked(record); + PosixsrvReleaseAllOpenDescriptionsLocked(record); + PosixsrvReleaseThreadsLocked(record); + for (uint32_t i = 0; i < NTL_POSIX_TRANSACTION_CAPACITY; ++i) + PosixsrvDestroyTransaction(&record->Transactions[i]); + record->ActiveTransactions = 0; + record->State = NTL_POSIX_PROCESS_STATE_EXITED; + record->ExitCode = record->BorrowedTerminalKind == POSIXSRV_VFORK_TERMINAL_EXIT + ? record->BorrowedExitCode + : (int32_t)NTLSTATUS_PROCESS_UNKNOWN_FAULT; + record->PendingNativeProcessId = 0; + record->PendingGeneration = 0; + record->PendingBootstrapToken = 0; + record->PendingFdInheritanceToken = 0; + record->BorrowedHostProcess = (POSIXSRV_PROCESS_RECORD*)0; + record->BorrowedHostGeneration = 0; + record->BorrowedNativeThreadId = 0; + record->BorrowedToken = 0; + PosixsrvSignalWaitersForChildLocked(record); + POSIXSRV_PROCESS_RECORD* parent = + PosixsrvFindPosixProcessLocked(record->ParentPosixPid); + if (parent && PosixsrvProcessHasCommittedGeneration(parent)) + (void)PosixsrvRouteSignalLocked(parent, NTL_POSIX_SIGNAL_CHLD); + PosixsrvOrphanChildrenLocked(record->PosixPid); + completed = 1; + } + } + if (record->BorrowedMonitorActive != 0) --record->BorrowedMonitorActive; + POSIXSRV_PROCESS_RECORD* freeRecord = (POSIXSRV_PROCESS_RECORD*)0; + if (PosixsrvCanFreeProcessLocked(record)) { + PosixsrvUnlinkProcessLocked(record); + freeRecord = record; + } + PosixsrvUnlockRegistry(); + if (freeRecord) PosixsrvFreeProcessRecord(freeRecord); + } + + if (abortPending != SYS_HANDLE_INVALID) + (void)SysKillProcess(abortPending, (int32_t)NTLSTATUS_PROCESS_KILLED); + if (releaseEvent != SYS_HANDLE_INVALID) (void)SysSignalEvent(releaseEvent); + (void)SysCloseHandle(monitor->ThreadHandle); + free(monitor); + SysExitThread(KSUCCESS(waitStatus) && completed ? 0 : 1); +} + +// Starts the server-owned monitor that finalizes a borrowed child only after thread death. +static KSTATUS PosixsrvStartBorrowedMonitorLocked(POSIXSRV_PROCESS_RECORD* record, + SYS_HANDLE threadHandle) { + if (!record || threadHandle == SYS_HANDLE_INVALID || record->BorrowedToken == 0 || + record->BorrowedMonitorActive) + return NTLSTATUS_INVALID_PARAMETER; + SYS_HANDLE waitHandle = SYS_HANDLE_INVALID; + SYS_DERIVE_HANDLE_REQUEST derive; + memset(&derive, 0, sizeof(derive)); + derive.Size = sizeof(derive); + derive.SourceHandle = threadHandle; + derive.DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT; + derive.DerivedHandleOut = &waitHandle; + KSTATUS status = SysDeriveHandle(&derive); + if (!KSUCCESS(status)) return status; + + POSIXSRV_BORROWED_MONITOR_CONTEXT* monitor = + (POSIXSRV_BORROWED_MONITOR_CONTEXT*)calloc(1u, sizeof(*monitor)); + if (!monitor) { + (void)SysCloseHandle(waitHandle); + return NTLSTATUS_NO_MEMORY; + } + monitor->Process = record; + monitor->ThreadHandle = waitHandle; + monitor->BorrowedToken = record->BorrowedToken; + + SYS_CREATE_THREAD_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)PosixsrvBorrowedMonitorThread; + create.Argument = (uintptr_t)monitor; + create.Priority = 1; + create.Flags = SYS_CREATE_THREAD_FLAG_NONE; + status = SysCreateThread(&create); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(waitHandle); + free(monitor); + return status; + } + record->BorrowedMonitorActive = 1u; + return NTLSTATUS_SUCCESS; } // Registers the connection's native process and assigns or recovers its stable POSIX identity. static KSTATUS PosixsrvRegisterProcess(POSIXSRV_CLIENT* client, - const NTL_POSIX_REQUEST* request, - SYS_HANDLE* transferredProcessHandle) { - if (!client || !request || !transferredProcessHandle || - *transferredProcessHandle == SYS_HANDLE_INVALID) + const NTL_POSIX_REQUEST* request) { + if (!client || !request) return NTLSTATUS_INVALID_PARAMETER; - // Reserved bootstrap identities become active when server-owned process construction lands. - if (request->BootstrapToken != 0 || request->BootstrapPosixPid != 0 || - request->BootstrapGeneration != 0) - return NTLSTATUS_NOT_SUPPORTED; if (!PosixsrvValidateCurrentDirectory(request) || request->NameLength != 0 || request->TargetPosixPid != 0 || request->UserId != 0 || request->GroupId != 0 || request->ProcessGroupId != 0 || request->LookupKind != NTL_POSIX_LOOKUP_NONE || @@ -1343,36 +2320,108 @@ static KSTATUS PosixsrvRegisterProcess(POSIXSRV_CLIENT* client, request->OpenDescriptionId != 0) return NTLSTATUS_INVALID_PARAMETER; - uint32_t nativePid = 0; - KSTATUS status = SysGetProcessPid(*transferredProcessHandle, &nativePid); + SYS_HANDLE peerProcessHandle = SYS_HANDLE_INVALID; + KSTATUS status = SysOpenPortPeerProcess( + client->Endpoint, + SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_RIGHT_DERIVE | + SYS_PROCESS_RIGHT_TERMINATE | SYS_PROCESS_RIGHT_SECURITY_CONTEXT, + &peerProcessHandle); if (!KSUCCESS(status)) return status; - if (nativePid != client->Connection.ClientProcessId) + + uint32_t nativePid = 0; + status = SysGetProcessPid(peerProcessHandle, &nativePid); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(peerProcessHandle); + return status; + } + if (nativePid != client->Connection.ClientProcessId) { + (void)SysCloseHandle(peerProcessHandle); return NTLSTATUS_ACCESS_DENIED; + } + + if (request->BootstrapToken != 0 || request->BootstrapPosixPid != 0 || + request->BootstrapGeneration != 0) { + if (request->BootstrapToken == 0 || request->BootstrapPosixPid == 0 || + request->BootstrapGeneration == 0) { + (void)SysCloseHandle(peerProcessHandle); + return NTLSTATUS_INVALID_PARAMETER; + } + KSTATUS lockStatus = PosixsrvLockRegistry(); + if (!KSUCCESS(lockStatus)) { + (void)SysCloseHandle(peerProcessHandle); + return lockStatus; + } + POSIXSRV_PROCESS_RECORD* record = PosixsrvFindPosixProcessLocked(request->BootstrapPosixPid); + int constructing = record && record->State == NTL_POSIX_PROCESS_STATE_CONSTRUCTING && + record->BootstrapToken == request->BootstrapToken && + record->Generation == request->BootstrapGeneration && + record->NativeProcessId == nativePid; + int replacing = record && + (record->State == NTL_POSIX_PROCESS_STATE_EXEC_PREPARING || + record->State == NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING) && + record->PendingBootstrapToken == request->BootstrapToken && + record->PendingGeneration == request->BootstrapGeneration && + record->PendingNativeProcessId == nativePid; + if (!record || (!constructing && !replacing) || + record->SecurityId != client->Connection.ClientSecurityId || + strcmp(record->CurrentDirectory, request->Path) != 0) { + PosixsrvUnlockRegistry(); + (void)SysCloseHandle(peerProcessHandle); + return record ? NTLSTATUS_ACCESS_DENIED : NTLSTATUS_NOT_FOUND; + } + if (!client->Process) { + ++record->ConnectionCount; + client->Process = record; + client->NativeGeneration = request->BootstrapGeneration; + } else if (client->Process != record || + client->NativeGeneration != request->BootstrapGeneration) { + PosixsrvUnlockRegistry(); + (void)SysCloseHandle(peerProcessHandle); + return NTLSTATUS_ACCESS_DENIED; + } + PosixsrvUnlockRegistry(); + (void)SysCloseHandle(peerProcessHandle); + return NTLSTATUS_SUCCESS; + } POSIXSRV_PROCESS_RECORD* candidate = (POSIXSRV_PROCESS_RECORD*)calloc(1, sizeof(*candidate)); - if (!candidate) return NTLSTATUS_NO_MEMORY; + if (!candidate) { + (void)SysCloseHandle(peerProcessHandle); + return NTLSTATUS_NO_MEMORY; + } candidate->ProcessHandle = SYS_HANDLE_INVALID; + candidate->BootstrapReadyEvent = SYS_HANDLE_INVALID; + candidate->PendingBootstrapReadyEvent = SYS_HANDLE_INVALID; + candidate->PendingBootstrapReleaseEvent = SYS_HANDLE_INVALID; + candidate->PendingProcessHandle = SYS_HANDLE_INVALID; + candidate->AlarmTimer = SYS_HANDLE_INVALID; + candidate->AlarmControlEvent = SYS_HANDLE_INVALID; + candidate->IgnoredSignals = NTL_POSIX_SIGNAL_BIT(NTL_POSIX_SIGNAL_CHLD); status = PosixsrvLockRegistry(); if (!KSUCCESS(status)) { free(candidate); + (void)SysCloseHandle(peerProcessHandle); return status; } if (client->Process) { PosixsrvUnlockRegistry(); free(candidate); + (void)SysCloseHandle(peerProcessHandle); return NTLSTATUS_SUCCESS; } POSIXSRV_PROCESS_RECORD* existing = - PosixsrvFindProcessLocked(nativePid, client->Connection.ClientSecurityId); - if (existing && existing->State == NTL_POSIX_PROCESS_STATE_RUNNING) { + PosixsrvFindCommittedProcessLocked(nativePid, client->Connection.ClientSecurityId); + if (existing) { existing->ConnectionCount++; client->Process = existing; + client->NativeGeneration = existing->Generation; PosixsrvUnlockRegistry(); free(candidate); + (void)SysCloseHandle(peerProcessHandle); return NTLSTATUS_SUCCESS; } @@ -1381,6 +2430,7 @@ static KSTATUS PosixsrvRegisterProcess(POSIXSRV_CLIENT* client, if (!KSUCCESS(status)) { PosixsrvUnlockRegistry(); free(candidate); + (void)SysCloseHandle(peerProcessHandle); return status; } @@ -1391,7 +2441,7 @@ static KSTATUS PosixsrvRegisterProcess(POSIXSRV_CLIENT* client, candidate->Generation = 1u; candidate->State = NTL_POSIX_PROCESS_STATE_RUNNING; candidate->ConnectionCount = 1u; - candidate->MonitorActive = 1u; + candidate->MonitorActive = 0u; candidate->UserId = 0; candidate->EffectiveUserId = 0; candidate->GroupId = 0; @@ -1401,34 +2451,86 @@ static KSTATUS PosixsrvRegisterProcess(POSIXSRV_CLIENT* client, candidate->SupplementaryGroupCount = 0; (void)PosixsrvCopyText(candidate->LoginName, sizeof(candidate->LoginName), "root"); (void)PosixsrvCopyText(candidate->CurrentDirectory, sizeof(candidate->CurrentDirectory), request->Path); - candidate->ProcessHandle = *transferredProcessHandle; + candidate->ProcessHandle = peerProcessHandle; + peerProcessHandle = SYS_HANDLE_INVALID; candidate->Next = gProcessRegistry; gProcessRegistry = candidate; ++gRegisteredProcesses; client->Process = candidate; + client->NativeGeneration = candidate->Generation; - SYS_CREATE_THREAD_REQUEST create; - memset(&create, 0, sizeof(create)); - create.Size = sizeof(create); - create.Entry = (uintptr_t)PosixsrvProcessMonitorThread; - create.Argument = (uintptr_t)candidate; - create.Priority = 1; - create.Flags = SYS_CREATE_THREAD_FLAG_NONE; - status = SysCreateThread(&create); + status = PosixsrvStartProcessMonitorLocked(candidate, candidate->ProcessHandle, + candidate->Generation); if (!KSUCCESS(status)) { client->Process = (POSIXSRV_PROCESS_RECORD*)0; + client->NativeGeneration = 0; PosixsrvUnlinkProcessLocked(candidate); + SYS_HANDLE failedProcessHandle = candidate->ProcessHandle; candidate->ProcessHandle = SYS_HANDLE_INVALID; PosixsrvUnlockRegistry(); + if (failedProcessHandle != SYS_HANDLE_INVALID) (void)SysCloseHandle(failedProcessHandle); free(candidate); return status; } - *transferredProcessHandle = SYS_HANDLE_INVALID; PosixsrvUnlockRegistry(); return NTLSTATUS_SUCCESS; } +// Commits one server-built image after its POSIX runtime imported inherited state. +static KSTATUS PosixsrvBootstrapReady(POSIXSRV_CLIENT* client, + const NTL_POSIX_REQUEST* request) { + if (!client || !client->Process || !request || request->BootstrapToken == 0 || + request->BootstrapPosixPid == 0 || request->BootstrapGeneration == 0) + return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_PROCESS_RECORD* process = client->Process; + + if (process->State == NTL_POSIX_PROCESS_STATE_CONSTRUCTING && + process->BootstrapToken == request->BootstrapToken && + process->PosixPid == request->BootstrapPosixPid && + process->Generation == request->BootstrapGeneration && + client->NativeGeneration == process->Generation && + process->BootstrapReadyEvent != SYS_HANDLE_INVALID) { + process->State = NTL_POSIX_PROCESS_STATE_RUNNING; + PosixsrvDrainPendingSignalsLocked(process); + SYS_HANDLE readyEvent = process->BootstrapReadyEvent; + PosixsrvUnlockRegistry(); + return SysSignalEvent(readyEvent); + } + + if ((process->State == NTL_POSIX_PROCESS_STATE_EXEC_PREPARING || + process->State == NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING) && + process->PendingBootstrapToken == request->BootstrapToken && + process->PosixPid == request->BootstrapPosixPid && + process->PendingGeneration == request->BootstrapGeneration && + client->NativeGeneration == process->PendingGeneration && + process->PendingBootstrapReadyEvent != SYS_HANDLE_INVALID && + process->PendingBootstrapReleaseEvent != SYS_HANDLE_INVALID) { + SYS_HANDLE readyEvent = process->PendingBootstrapReadyEvent; + SYS_HANDLE releaseEvent = process->PendingBootstrapReleaseEvent; + process->PendingBootstrapReadyObserved = 1u; + PosixsrvUnlockRegistry(); + status = SysSignalEvent(readyEvent); + if (KSUCCESS(status)) status = SysWait(releaseEvent, SYS_WAIT_FLAG_NONE); + if (KSUCCESS(PosixsrvLockRegistry())) { + if (process->PendingBootstrapReadyEvent == readyEvent) + process->PendingBootstrapReadyEvent = SYS_HANDLE_INVALID; + if (process->PendingBootstrapReleaseEvent == releaseEvent) + process->PendingBootstrapReleaseEvent = SYS_HANDLE_INVALID; + process->PendingBootstrapReadyObserved = 0; + PosixsrvUnlockRegistry(); + } + (void)SysCloseHandle(readyEvent); + (void)SysCloseHandle(releaseEvent); + return status; + } + + PosixsrvUnlockRegistry(); + return NTLSTATUS_ACCESS_DENIED; +} + // Detaches one Port connection without treating transport loss as process termination. static void PosixsrvDetachClientProcess(POSIXSRV_CLIENT* client) { if (!client || !client->Process) return; @@ -1438,8 +2540,7 @@ static void PosixsrvDetachClientProcess(POSIXSRV_CLIENT* client) { POSIXSRV_PROCESS_RECORD* record = client->Process; client->Process = (POSIXSRV_PROCESS_RECORD*)0; if (record->ConnectionCount != 0) --record->ConnectionCount; - if (record->State == NTL_POSIX_PROCESS_STATE_EXITED && record->ConnectionCount == 0 && - !record->MonitorActive) { + if (PosixsrvCanFreeProcessLocked(record)) { PosixsrvUnlinkProcessLocked(record); freeRecord = record; } @@ -1447,6 +2548,324 @@ static void PosixsrvDetachClientProcess(POSIXSRV_CLIENT* client) { if (freeRecord) PosixsrvFreeProcessRecord(freeRecord); } +// Creates one server-owned logical child that temporarily borrows the parent's native process. +static KSTATUS PosixsrvBeginVfork(POSIXSRV_CLIENT* client, + const NTL_POSIX_REQUEST* request, + uint32_t* childPidOut, + uint64_t* tokenOut) { + if (!client || !client->Process || !request || !childPidOut || !tokenOut || + request->FdInheritanceToken == 0) + return NTLSTATUS_INVALID_PARAMETER; + *childPidOut = 0; + *tokenOut = 0; + + POSIXSRV_PROCESS_RECORD* child = + (POSIXSRV_PROCESS_RECORD*)calloc(1u, sizeof(*child)); + if (!child) return NTLSTATUS_NO_MEMORY; + child->ProcessHandle = SYS_HANDLE_INVALID; + child->BootstrapReadyEvent = SYS_HANDLE_INVALID; + child->PendingProcessHandle = SYS_HANDLE_INVALID; + child->PendingBootstrapReadyEvent = SYS_HANDLE_INVALID; + child->PendingBootstrapReleaseEvent = SYS_HANDLE_INVALID; + child->AlarmTimer = SYS_HANDLE_INVALID; + child->AlarmControlEvent = SYS_HANDLE_INVALID; + + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { free(child); return status; } + POSIXSRV_PROCESS_RECORD* parent = client->Process; + POSIXSRV_FD_INHERITANCE* snapshot = + PosixsrvFindFdInheritanceLocked(request->FdInheritanceToken); + if (!PosixsrvCommittedGenerationOperational(client) || + parent->State != NTL_POSIX_PROCESS_STATE_RUNNING || !snapshot || + snapshot->SourceProcess != parent || snapshot->ClaimProcess) { + status = snapshot ? NTLSTATUS_ACCESS_DENIED : NTLSTATUS_NOT_FOUND; + PosixsrvUnlockRegistry(); + free(child); + return status; + } + if (gNextVforkToken == 0) { + PosixsrvUnlockRegistry(); + free(child); + return NTLSTATUS_ID_EXHAUSTED; + } + status = PosixsrvAllocatePidLocked(&child->PosixPid); + if (!KSUCCESS(status)) { + PosixsrvUnlockRegistry(); + free(child); + return status; + } + + child->ParentPosixPid = parent->PosixPid; + child->NativeProcessId = parent->NativeProcessId; + child->SecurityId = parent->SecurityId; + child->Generation = 1u; + child->State = NTL_POSIX_PROCESS_STATE_VFORK_BORROWED; + child->BorrowedHostProcess = parent; + child->BorrowedHostGeneration = parent->Generation; + child->BorrowedToken = gNextVforkToken++; + child->UserId = parent->UserId; + child->EffectiveUserId = parent->EffectiveUserId; + child->GroupId = parent->GroupId; + child->EffectiveGroupId = parent->EffectiveGroupId; + child->ProcessGroupId = parent->ProcessGroupId; + child->SessionId = parent->SessionId; + child->SupplementaryGroupCount = parent->SupplementaryGroupCount; + memcpy(child->SupplementaryGroups, parent->SupplementaryGroups, + sizeof(child->SupplementaryGroups)); + child->IgnoredSignals = parent->IgnoredSignals; + child->ExplicitIgnoredSignals = parent->ExplicitIgnoredSignals; + (void)PosixsrvCopyText(child->LoginName, sizeof(child->LoginName), parent->LoginName); + (void)PosixsrvCopyText(child->CurrentDirectory, sizeof(child->CurrentDirectory), + parent->CurrentDirectory); + + child->Next = gProcessRegistry; + gProcessRegistry = child; + ++gRegisteredProcesses; + status = PosixsrvClaimFdInheritanceLocked(snapshot, child); + if (!KSUCCESS(status)) { + PosixsrvUnlinkProcessLocked(child); + PosixsrvUnlockRegistry(); + free(child); + return status; + } + + *childPidOut = child->PosixPid; + *tokenOut = child->BorrowedToken; + PosixsrvUnlockRegistry(); + return NTLSTATUS_SUCCESS; +} + +// Binds one native thread in the host process to its borrowed logical child. +static KSTATUS PosixsrvAttachVfork(POSIXSRV_CLIENT* client, + const NTL_POSIX_REQUEST* request, + const SYS_PORT_MESSAGE_INFO* info, + SYS_HANDLE* transferredThreadHandle) { + if (!client || !client->Process || !request || !info || !transferredThreadHandle || + *transferredThreadHandle == SYS_HANDLE_INVALID || request->TargetPosixPid == 0 || + request->VforkToken == 0 || info->SenderThreadId == 0) + return NTLSTATUS_INVALID_PARAMETER; + + uint32_t nativeThreadId = 0; + KSTATUS status = SysGetThreadTid(*transferredThreadHandle, &nativeThreadId); + if (!KSUCCESS(status)) return status; + if (nativeThreadId != info->SenderThreadId) return NTLSTATUS_ACCESS_DENIED; + + POSIXSRV_THREAD_RECORD* thread = + (POSIXSRV_THREAD_RECORD*)calloc(1u, sizeof(*thread)); + if (!thread) return NTLSTATUS_NO_MEMORY; + thread->NativeThreadId = nativeThreadId; + thread->Generation = 1u; + thread->ThreadHandle = *transferredThreadHandle; + thread->BlockedSignals = request->SignalMask & NTL_POSIX_SIGNAL_VALID_MASK & + ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + thread->SignalApcRoutine = request->SignalApcRoutine; + + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { free(thread); return status; } + POSIXSRV_PROCESS_RECORD* host = client->Process; + POSIXSRV_PROCESS_RECORD* child = + PosixsrvFindPosixProcessLocked(request->TargetPosixPid); + if (!PosixsrvCommittedGenerationOperational(client) || + host->State != NTL_POSIX_PROCESS_STATE_RUNNING || !child || + child->State != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED || + child->BorrowedHostProcess != host || + child->BorrowedHostGeneration != client->NativeGeneration || + child->BorrowedToken != request->VforkToken || child->Threads || + child->BorrowedNativeThreadId != 0) { + status = child ? NTLSTATUS_ACCESS_DENIED : NTLSTATUS_NOT_FOUND; + PosixsrvUnlockRegistry(); + free(thread); + return status; + } + + child->BorrowedNativeThreadId = nativeThreadId; + thread->Generation = child->Generation; + child->Threads = thread; + ++gRegisteredThreads; + status = PosixsrvStartBorrowedMonitorLocked(child, thread->ThreadHandle); + if (!KSUCCESS(status)) { + child->Threads = (POSIXSRV_THREAD_RECORD*)0; + child->BorrowedNativeThreadId = 0; + if (gRegisteredThreads != 0) --gRegisteredThreads; + PosixsrvUnlockRegistry(); + free(thread); + return status; + } + *transferredThreadHandle = SYS_HANDLE_INVALID; + PosixsrvUnlockRegistry(); + return NTLSTATUS_SUCCESS; +} + +// Cancels one unpublished borrowed child after setup failure or after its native thread is dead. +static KSTATUS PosixsrvCancelVfork(POSIXSRV_CLIENT* client, + uint32_t childPid, + uint64_t token) { + if (!client || !client->Process || childPid == 0 || token == 0) + return NTLSTATUS_INVALID_PARAMETER; + + POSIXSRV_PROCESS_RECORD* freeChild = (POSIXSRV_PROCESS_RECORD*)0; + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_PROCESS_RECORD* child = PosixsrvFindPosixProcessLocked(childPid); + if (!child) status = NTLSTATUS_NOT_FOUND; + else if (child->State != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED || + child->BorrowedHostProcess != client->Process || + child->BorrowedHostGeneration != client->NativeGeneration || + child->BorrowedToken != token) + status = NTLSTATUS_ACCESS_DENIED; + else if (child->Threads && child->Threads->ThreadHandle != SYS_HANDLE_INVALID) { + KSTATUS waitStatus = SysWaitSingle(child->Threads->ThreadHandle, SYS_WAIT_FLAG_NONE, 0u); + if (waitStatus == NTLSTATUS_TIMEOUT) status = NTLSTATUS_WOULD_BLOCK; + else if (!KSUCCESS(waitStatus)) status = waitStatus; + } + if (KSUCCESS(status) && child) { + PosixsrvReleaseFdInheritanceForProcessLocked(child); + PosixsrvReleaseAllOpenDescriptionsLocked(child); + PosixsrvReleaseThreadsLocked(child); + PosixsrvUnlinkProcessLocked(child); + freeChild = child; + } + PosixsrvUnlockRegistry(); + if (freeChild) PosixsrvFreeProcessRecord(freeChild); + return status; +} + +// Records terminal exit intent; the borrowed monitor publishes exit only after thread death. +static KSTATUS PosixsrvExitVfork(POSIXSRV_CLIENT* client, + uint32_t childPid, + uint64_t token, + int32_t exitCode) { + if (!client || !client->Process || childPid == 0 || token == 0) + return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_PROCESS_RECORD* child = client->Process; + if (!PosixsrvClientIsBorrowedProcess(client) || child->PosixPid != childPid || + child->BorrowedToken != token || child->BorrowedTerminalKind != POSIXSRV_VFORK_TERMINAL_NONE) + status = NTLSTATUS_ACCESS_DENIED; + else { + child->BorrowedTerminalKind = POSIXSRV_VFORK_TERMINAL_EXIT; + child->BorrowedExitCode = exitCode; + status = NTLSTATUS_SUCCESS; + } + PosixsrvUnlockRegistry(); + return status; +} + +// Waits for and atomically collects one child selected by POSIX waitpid rules. +static KSTATUS PosixsrvWaitProcess(POSIXSRV_CLIENT* client, + int32_t waitPid, + uint32_t options, + int32_t* waitedPidOut, + int32_t* exitCodeOut) { + if (!client || !waitedPidOut || !exitCodeOut || + (options & ~NTL_POSIX_WAIT_NOHANG) != 0) + return NTLSTATUS_INVALID_PARAMETER; + *waitedPidOut = 0; + *exitCodeOut = 0; + + POSIXSRV_WAITER selector; + memset(&selector, 0, sizeof(selector)); + selector.WaitPid = waitPid; + selector.Event = SYS_HANDLE_INVALID; + + POSIXSRV_WAITER* waiter = (POSIXSRV_WAITER*)0; + if ((options & NTL_POSIX_WAIT_NOHANG) == 0) { + waiter = (POSIXSRV_WAITER*)calloc(1u, sizeof(*waiter)); + if (!waiter) return NTLSTATUS_NO_MEMORY; + waiter->WaitPid = waitPid; + KSTATUS status = SysCreateEvent(0u, 0u, &waiter->Event); + if (!KSUCCESS(status)) { + free(waiter); + return status; + } + } + + KSTATUS result = NTLSTATUS_SUCCESS; + int waiterOwned = waiter != (POSIXSRV_WAITER*)0; + for (;;) { + POSIXSRV_PROCESS_RECORD* freeRecord = (POSIXSRV_PROCESS_RECORD*)0; + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { result = status; break; } + + POSIXSRV_PROCESS_RECORD* parent = client->Process; + if (!parent || !PosixsrvCommittedGenerationOperational(client)) { + PosixsrvUnlockRegistry(); + result = NTLSTATUS_OBJECT_DEAD; + break; + } + + POSIXSRV_WAITER* match = waiter ? waiter : &selector; + match->ParentPosixPid = parent->PosixPid; + match->ParentProcessGroupId = parent->ProcessGroupId; + POSIXSRV_PROCESS_RECORD* exited = (POSIXSRV_PROCESS_RECORD*)0; + int hasMatchingChild = 0; + for (POSIXSRV_PROCESS_RECORD* child = gProcessRegistry; child; child = child->Next) { + if (!PosixsrvProcessHasCommittedGeneration(child) && + child->State != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED && + child->State != NTL_POSIX_PROCESS_STATE_EXITED) + continue; + if (!PosixsrvWaiterMatchesChild(match, child)) continue; + hasMatchingChild = 1; + if (child->State == NTL_POSIX_PROCESS_STATE_EXITED && + (!exited || child->PosixPid < exited->PosixPid)) + exited = child; + } + + if (exited) { + *waitedPidOut = (int32_t)exited->PosixPid; + *exitCodeOut = exited->ExitCode; + exited->ParentPosixPid = 0; + if (PosixsrvCanFreeProcessLocked(exited)) { + PosixsrvUnlinkProcessLocked(exited); + freeRecord = exited; + } + PosixsrvUnlockRegistry(); + if (freeRecord) PosixsrvFreeProcessRecord(freeRecord); + result = NTLSTATUS_SUCCESS; + break; + } + + if (!hasMatchingChild) { + PosixsrvUnlockRegistry(); + result = NTLSTATUS_NO_CHILD; + break; + } + if (options & NTL_POSIX_WAIT_NOHANG) { + PosixsrvUnlockRegistry(); + result = NTLSTATUS_SUCCESS; + break; + } + + waiter->Next = gWaiters; + gWaiters = waiter; + PosixsrvUnlockRegistry(); + + status = SysWait(waiter->Event, SYS_WAIT_FLAG_NONE); + + KSTATUS lockStatus = PosixsrvLockRegistry(); + if (!KSUCCESS(lockStatus)) { + // Keep the heap waiter and event alive if registry recovery is impossible. + waiterOwned = 0; + result = lockStatus; + break; + } + POSIXSRV_WAITER** link = &gWaiters; + while (*link && *link != waiter) link = &(*link)->Next; + if (*link == waiter) *link = waiter->Next; + waiter->Next = (POSIXSRV_WAITER*)0; + PosixsrvUnlockRegistry(); + + if (!KSUCCESS(status)) { result = status; break; } + } + + if (waiterOwned) { + if (waiter->Event != SYS_HANDLE_INVALID) (void)SysCloseHandle(waiter->Event); + free(waiter); + } + return result; +} + // Replaces the current process's authoritative POSIX current directory. static KSTATUS PosixsrvSetCurrentDirectory(POSIXSRV_CLIENT* client, const NTL_POSIX_REQUEST* request) { @@ -1459,7 +2878,8 @@ static KSTATUS PosixsrvSetCurrentDirectory(POSIXSRV_CLIENT* client, if (!KSUCCESS(status)) return status; if (client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING) { PosixsrvUnlockRegistry(); - return NTLSTATUS_OBJECT_DEAD; + return PosixsrvCommittedGenerationOperational(client) ? + NTLSTATUS_WOULD_BLOCK : NTLSTATUS_OBJECT_DEAD; } (void)PosixsrvCopyText(client->Process->CurrentDirectory, sizeof(client->Process->CurrentDirectory), request->Path); @@ -1474,7 +2894,8 @@ static KSTATUS PosixsrvSetUid(POSIXSRV_CLIENT* client, uint32_t userId) { if (!KSUCCESS(status)) return status; POSIXSRV_PROCESS_RECORD* process = client->Process; if (process->State != NTL_POSIX_PROCESS_STATE_RUNNING) { - status = NTLSTATUS_OBJECT_DEAD; + status = PosixsrvCommittedGenerationOperational(client) ? + NTLSTATUS_WOULD_BLOCK : NTLSTATUS_OBJECT_DEAD; } else if (process->EffectiveUserId == 0) { process->UserId = userId; process->EffectiveUserId = userId; @@ -1492,7 +2913,8 @@ static KSTATUS PosixsrvSetGid(POSIXSRV_CLIENT* client, uint32_t groupId) { if (!KSUCCESS(status)) return status; POSIXSRV_PROCESS_RECORD* process = client->Process; if (process->State != NTL_POSIX_PROCESS_STATE_RUNNING) { - status = NTLSTATUS_OBJECT_DEAD; + status = PosixsrvCommittedGenerationOperational(client) ? + NTLSTATUS_WOULD_BLOCK : NTLSTATUS_OBJECT_DEAD; } else if (process->EffectiveUserId == 0) { process->GroupId = groupId; process->EffectiveGroupId = groupId; @@ -1514,10 +2936,14 @@ static KSTATUS PosixsrvSetProcessGroup(POSIXSRV_CLIENT* client, POSIXSRV_PROCESS_RECORD* caller = client->Process; if (targetPid == 0) targetPid = caller->PosixPid; POSIXSRV_PROCESS_RECORD* target = PosixsrvFindPosixProcessLocked(targetPid); - if (!target || target->State != NTL_POSIX_PROCESS_STATE_RUNNING) { + if (!target || !PosixsrvProcessHasCommittedGeneration(target)) { status = NTLSTATUS_NOT_FOUND; goto done; } + if (target->State != NTL_POSIX_PROCESS_STATE_RUNNING) { + status = NTLSTATUS_WOULD_BLOCK; + goto done; + } if (target != caller && target->ParentPosixPid != caller->PosixPid) { status = NTLSTATUS_ACCESS_DENIED; goto done; @@ -1547,7 +2973,8 @@ static KSTATUS PosixsrvCreateSession(POSIXSRV_CLIENT* client) { if (!KSUCCESS(status)) return status; POSIXSRV_PROCESS_RECORD* process = client->Process; if (process->State != NTL_POSIX_PROCESS_STATE_RUNNING) { - status = NTLSTATUS_OBJECT_DEAD; + status = PosixsrvCommittedGenerationOperational(client) ? + NTLSTATUS_WOULD_BLOCK : NTLSTATUS_OBJECT_DEAD; } else if (PosixsrvProcessGroupExistsLocked(process->SessionId, process->PosixPid)) { status = NTLSTATUS_ACCESS_DENIED; } else { @@ -1558,43 +2985,1264 @@ static KSTATUS PosixsrvCreateSession(POSIXSRV_CLIENT* client) { return status; } -// Allocates one bounded connection-local transaction identifier for future spawn/exec work. +// Releases dynamically owned data from one construction transaction. +static void PosixsrvDestroyTransaction(POSIXSRV_TRANSACTION* transaction) { + if (!transaction) return; + POSIXSRV_CONSTRUCTION* construction = transaction->Construction; + if (construction) { + free(construction->Executable); + for (uint32_t i = 0; i < construction->ArgumentCount; ++i) + free(construction->Arguments[i].Data); + for (uint32_t i = 0; i < construction->EnvironmentCount; ++i) + free(construction->Environment[i].Data); + free(construction->Arguments); + free(construction->Environment); + free(construction); + } + POSIXSRV_SPAWN_POLICY* spawn = transaction->Spawn; + if (spawn) { + for (uint32_t i = 0; i < spawn->ActionCount; ++i) free(spawn->Actions[i].Path); + free(spawn); + } + memset(transaction, 0, sizeof(*transaction)); +} + +// Finds one transaction owned by a POSIX process while the registry mutex is held. +static POSIXSRV_TRANSACTION* PosixsrvFindTransactionLocked(POSIXSRV_PROCESS_RECORD* process, + uint32_t transactionId, + uint32_t kind) { + if (!process || transactionId == 0) return (POSIXSRV_TRANSACTION*)0; + for (uint32_t i = 0; i < NTL_POSIX_TRANSACTION_CAPACITY; ++i) { + POSIXSRV_TRANSACTION* transaction = &process->Transactions[i]; + if (transaction->Id == transactionId && (kind == NTL_POSIX_TRANSACTION_NONE || + transaction->Kind == kind)) + return transaction; + } + return (POSIXSRV_TRANSACTION*)0; +} + +// Allocates one bounded process-owned construction transaction. static KSTATUS PosixsrvBeginTransaction(POSIXSRV_CLIENT* client, uint32_t kind, uint32_t* transactionIdOut) { - if (!client || !transactionIdOut) return NTLSTATUS_INVALID_PARAMETER; + if (!client || !client->Process || !transactionIdOut) return NTLSTATUS_INVALID_PARAMETER; *transactionIdOut = 0; if (kind != NTL_POSIX_TRANSACTION_SPAWN && kind != NTL_POSIX_TRANSACTION_EXEC) return NTLSTATUS_INVALID_PARAMETER; + POSIXSRV_TRANSACTION fresh; + memset(&fresh, 0, sizeof(fresh)); + fresh.Kind = kind; + fresh.Construction = (POSIXSRV_CONSTRUCTION*)calloc(1, sizeof(*fresh.Construction)); + if (!fresh.Construction) return NTLSTATUS_NO_MEMORY; + if (kind == NTL_POSIX_TRANSACTION_SPAWN) { + fresh.Spawn = (POSIXSRV_SPAWN_POLICY*)calloc(1, sizeof(*fresh.Spawn)); + if (!fresh.Spawn) { + PosixsrvDestroyTransaction(&fresh); + return NTLSTATUS_NO_MEMORY; + } + } + + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { + PosixsrvDestroyTransaction(&fresh); + return status; + } + POSIXSRV_PROCESS_RECORD* process = client->Process; + if ((kind == NTL_POSIX_TRANSACTION_EXEC && + !((PosixsrvCommittedGenerationOperational(client) && + process->State == NTL_POSIX_PROCESS_STATE_RUNNING) || + PosixsrvClientIsBorrowedProcess(client))) || + (kind == NTL_POSIX_TRANSACTION_SPAWN && + !PosixsrvCommittedGenerationOperational(client))) { + status = NTLSTATUS_OBJECT_DEAD; + goto done; + } + uint32_t slot = NTL_POSIX_TRANSACTION_CAPACITY; for (uint32_t i = 0; i < NTL_POSIX_TRANSACTION_CAPACITY; ++i) { - if (client->Transactions[i].Id == 0) { + if (process->Transactions[i].Id == 0) { slot = i; break; } } - if (slot == NTL_POSIX_TRANSACTION_CAPACITY) return NTLSTATUS_LIMIT_REACHED; + if (slot == NTL_POSIX_TRANSACTION_CAPACITY) { + status = NTLSTATUS_LIMIT_REACHED; + goto done; + } - uint32_t id = ++client->NextTransactionId; - if (id == 0) id = ++client->NextTransactionId; - client->Transactions[slot].Id = id; - client->Transactions[slot].Kind = kind; - ++client->ActiveTransactions; + uint32_t id = ++process->NextTransactionId; + if (id == 0) id = ++process->NextTransactionId; + fresh.Id = id; + process->Transactions[slot] = fresh; + memset(&fresh, 0, sizeof(fresh)); + ++process->ActiveTransactions; *transactionIdOut = id; + status = NTLSTATUS_SUCCESS; + +done: + PosixsrvUnlockRegistry(); + PosixsrvDestroyTransaction(&fresh); + return status; +} + +// Detaches one process-owned transaction so a commit can outlive registry locking. +static KSTATUS PosixsrvTakeTransaction(POSIXSRV_CLIENT* client, + uint32_t transactionId, + uint32_t kind, + POSIXSRV_TRANSACTION* transactionOut) { + if (!client || !client->Process || !transactionOut || transactionId == 0) + return NTLSTATUS_INVALID_PARAMETER; + memset(transactionOut, 0, sizeof(*transactionOut)); + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_TRANSACTION* transaction = + PosixsrvFindTransactionLocked(client->Process, transactionId, kind); + if (!transaction) status = NTLSTATUS_NOT_FOUND; + else { + *transactionOut = *transaction; + memset(transaction, 0, sizeof(*transaction)); + if (client->Process->ActiveTransactions != 0) --client->Process->ActiveTransactions; + status = NTLSTATUS_SUCCESS; + } + PosixsrvUnlockRegistry(); + return status; +} + +// Cancels one transaction owned by the caller's stable POSIX process. +static KSTATUS PosixsrvCancelTransaction(POSIXSRV_CLIENT* client, uint32_t transactionId) { + if (!client || !client->Process || transactionId == 0) return NTLSTATUS_INVALID_PARAMETER; + POSIXSRV_TRANSACTION detached; + memset(&detached, 0, sizeof(detached)); + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_TRANSACTION* transaction = + PosixsrvFindTransactionLocked(client->Process, transactionId, NTL_POSIX_TRANSACTION_NONE); + if (!transaction) status = NTLSTATUS_NOT_FOUND; + else { + detached = *transaction; + memset(transaction, 0, sizeof(*transaction)); + if (client->Process->ActiveTransactions != 0) --client->Process->ActiveTransactions; + status = NTLSTATUS_SUCCESS; + } + PosixsrvUnlockRegistry(); + PosixsrvDestroyTransaction(&detached); + return status; +} + +// Copies one length-delimited protocol path/text payload into server-owned storage. +static KSTATUS PosixsrvCopyRequestPath(const NTL_POSIX_REQUEST* request, char** textOut) { + if (!request || !textOut || request->PathLength == 0 || request->PathLength > SYS_PATH_MAX || + request->Path[request->PathLength] != '\0' || strlen(request->Path) != request->PathLength) + return NTLSTATUS_INVALID_PARAMETER; + char* text = (char*)malloc((size_t)request->PathLength + 1u); + if (!text) return NTLSTATUS_NO_MEMORY; + memcpy(text, request->Path, (size_t)request->PathLength + 1u); + *textOut = text; return NTLSTATUS_SUCCESS; } -// Cancels one transaction owned by the current connection. -static KSTATUS PosixsrvCancelTransaction(POSIXSRV_CLIENT* client, uint32_t transactionId) { - if (!client || transactionId == 0) return NTLSTATUS_INVALID_PARAMETER; - for (uint32_t i = 0; i < NTL_POSIX_TRANSACTION_CAPACITY; ++i) { - if (client->Transactions[i].Id != transactionId) continue; - memset(&client->Transactions[i], 0, sizeof(client->Transactions[i])); - if (client->ActiveTransactions != 0) --client->ActiveTransactions; +// Records the executable pathname for one spawn/exec construction transaction. +static KSTATUS PosixsrvConstructionSetExecutable(POSIXSRV_CLIENT* client, + const NTL_POSIX_REQUEST* request) { + if (!client || !client->Process || !request) return NTLSTATUS_INVALID_PARAMETER; + char* executable = (char*)0; + KSTATUS status = PosixsrvCopyRequestPath(request, &executable); + if (!KSUCCESS(status)) return status; + + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { + free(executable); + return status; + } + POSIXSRV_TRANSACTION* transaction = PosixsrvFindTransactionLocked( + client->Process, request->TransactionId, NTL_POSIX_TRANSACTION_NONE); + if (!transaction || !transaction->Construction || transaction->Construction->Executable) { + status = NTLSTATUS_INVALID_PARAMETER; + } else if (request->PathLength > NTL_POSIX_CONSTRUCT_DATA_MAX - + transaction->Construction->TotalDataBytes) { + status = NTLSTATUS_LIMIT_REACHED; + } else { + transaction->Construction->Executable = executable; + transaction->Construction->TotalDataBytes += request->PathLength; + executable = (char*)0; + status = NTLSTATUS_SUCCESS; + } + PosixsrvUnlockRegistry(); + free(executable); + return status; +} + +// Appends one bounded chunk to an argv/envp entry while preserving index order. +static KSTATUS PosixsrvConstructionWriteVector(POSIXSRV_CLIENT* client, + const NTL_POSIX_REQUEST* request) { + if (!client || !client->Process || !request || request->PathLength > SYS_PATH_MAX || + request->Path[request->PathLength] != '\0' || strlen(request->Path) != request->PathLength || + (request->ConstructionVectorFlags & ~NTL_POSIX_CONSTRUCT_VECTOR_CHUNK_FINAL) != 0) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_TRANSACTION* transaction = PosixsrvFindTransactionLocked( + client->Process, request->TransactionId, NTL_POSIX_TRANSACTION_NONE); + if (!transaction || !transaction->Construction) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_INVALID_PARAMETER; + } + + POSIXSRV_CONSTRUCTION* construction = transaction->Construction; + POSIXSRV_CONSTRUCTION_STRING** vector = (POSIXSRV_CONSTRUCTION_STRING**)0; + uint32_t* count = (uint32_t*)0; + uint32_t* capacity = (uint32_t*)0; + if (request->ConstructionVectorKind == NTL_POSIX_CONSTRUCT_VECTOR_ARGUMENT) { + vector = &construction->Arguments; + count = &construction->ArgumentCount; + capacity = &construction->ArgumentCapacity; + } else if (request->ConstructionVectorKind == NTL_POSIX_CONSTRUCT_VECTOR_ENVIRONMENT) { + vector = &construction->Environment; + count = &construction->EnvironmentCount; + capacity = &construction->EnvironmentCapacity; + } else { + PosixsrvUnlockRegistry(); + return NTLSTATUS_INVALID_PARAMETER; + } + if (request->ConstructionVectorIndex > *count || + request->ConstructionVectorIndex >= NTL_POSIX_CONSTRUCT_VECTOR_MAX) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_INVALID_PARAMETER; + } + if (request->ConstructionVectorIndex == *count) { + if (*count == *capacity) { + uint32_t newCapacity = *capacity ? *capacity * 2u : 8u; + if (newCapacity > NTL_POSIX_CONSTRUCT_VECTOR_MAX) + newCapacity = NTL_POSIX_CONSTRUCT_VECTOR_MAX; + POSIXSRV_CONSTRUCTION_STRING* grown = (POSIXSRV_CONSTRUCTION_STRING*)realloc( + *vector, (size_t)newCapacity * sizeof(**vector)); + if (!grown) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_NO_MEMORY; + } + memset(grown + *capacity, 0, (size_t)(newCapacity - *capacity) * sizeof(*grown)); + *vector = grown; + *capacity = newCapacity; + } + ++*count; + } + + POSIXSRV_CONSTRUCTION_STRING* string = &(*vector)[request->ConstructionVectorIndex]; + if (string->Complete || request->ConstructionVectorOffset != string->Length) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_INVALID_PARAMETER; + } + if (request->PathLength > NTL_POSIX_CONSTRUCT_DATA_MAX - construction->TotalDataBytes) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_LIMIT_REACHED; + } + uint32_t required = string->Length + request->PathLength + 1u; + if (required < string->Length) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_LIMIT_REACHED; + } + if (required > string->Capacity) { + uint32_t newCapacity = string->Capacity ? string->Capacity : 64u; + while (newCapacity < required) { + if (newCapacity > NTL_POSIX_CONSTRUCT_DATA_MAX / 2u) { + newCapacity = required; + break; + } + newCapacity *= 2u; + } + char* grown = (char*)realloc(string->Data, newCapacity); + if (!grown) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_NO_MEMORY; + } + string->Data = grown; + string->Capacity = newCapacity; + } + if (request->PathLength) + memcpy(string->Data + string->Length, request->Path, request->PathLength); + string->Length += request->PathLength; + string->Data[string->Length] = '\0'; + construction->TotalDataBytes += request->PathLength; + if (request->ConstructionVectorFlags & NTL_POSIX_CONSTRUCT_VECTOR_CHUNK_FINAL) + string->Complete = 1u; + PosixsrvUnlockRegistry(); + return NTLSTATUS_SUCCESS; +} + +// Appends one ordered close/dup2/open action to a spawn transaction. +static KSTATUS PosixsrvSpawnAddFileAction(POSIXSRV_CLIENT* client, + const NTL_POSIX_REQUEST* request) { + if (!client || !client->Process || !request) return NTLSTATUS_INVALID_PARAMETER; + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_TRANSACTION* transaction = PosixsrvFindTransactionLocked( + client->Process, request->TransactionId, NTL_POSIX_TRANSACTION_SPAWN); + if (!transaction || !transaction->Construction || !transaction->Spawn) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_INVALID_PARAMETER; + } + POSIXSRV_CONSTRUCTION* construction = transaction->Construction; + POSIXSRV_SPAWN_POLICY* spawn = transaction->Spawn; + if (spawn->ActionCount >= NTL_POSIX_SPAWN_ACTION_MAX || request->SpawnFileDescriptor < 0 || + request->SpawnFileDescriptor >= (int32_t)NTL_POSIX_FD_INHERIT_MAX) { + PosixsrvUnlockRegistry(); + return NTLSTATUS_LIMIT_REACHED; + } + + POSIXSRV_SPAWN_ACTION action; + memset(&action, 0, sizeof(action)); + action.Kind = request->SpawnActionKind; + action.FileDescriptor = request->SpawnFileDescriptor; + action.NewFileDescriptor = request->SpawnNewFileDescriptor; + action.OpenFlags = request->SpawnOpenFlags; + action.OpenMode = request->SpawnOpenMode; + if (action.Kind == NTL_POSIX_SPAWN_ACTION_CLOSE) { + if (request->PathLength != 0 || action.NewFileDescriptor != 0 || action.OpenFlags != 0 || + action.OpenMode != 0) status = NTLSTATUS_INVALID_PARAMETER; + } else if (action.Kind == NTL_POSIX_SPAWN_ACTION_DUP2) { + if (request->PathLength != 0 || action.NewFileDescriptor < 0 || + action.NewFileDescriptor >= (int32_t)NTL_POSIX_FD_INHERIT_MAX || + action.OpenFlags != 0 || action.OpenMode != 0) + status = NTLSTATUS_INVALID_PARAMETER; + } else if (action.Kind == NTL_POSIX_SPAWN_ACTION_OPEN) { + if ((action.OpenFlags & ~NTL_POSIX_SPAWN_OPEN_VALID_MASK) != 0 || + (action.OpenFlags & NTL_POSIX_OPEN_STATUS_ACCMODE) == NTL_POSIX_OPEN_STATUS_ACCMODE) { + status = NTLSTATUS_INVALID_PARAMETER; + } else if (request->PathLength > NTL_POSIX_CONSTRUCT_DATA_MAX - + construction->TotalDataBytes) { + status = NTLSTATUS_LIMIT_REACHED; + } else { + status = PosixsrvCopyRequestPath(request, &action.Path); + if (KSUCCESS(status)) construction->TotalDataBytes += request->PathLength; + } + } else { + status = NTLSTATUS_INVALID_PARAMETER; + } + if (KSUCCESS(status)) spawn->Actions[spawn->ActionCount++] = action; + PosixsrvUnlockRegistry(); + if (!KSUCCESS(status)) free(action.Path); + return status; +} + +// Records spawn-only attributes whose semantics are authoritative in P5/P6. +static KSTATUS PosixsrvSpawnSetAttributes(POSIXSRV_CLIENT* client, + const NTL_POSIX_REQUEST* request) { + if (!client || !client->Process || !request) return NTLSTATUS_INVALID_PARAMETER; + const uint32_t validFlags = NTL_POSIX_SPAWN_ATTR_SETPGROUP | + NTL_POSIX_SPAWN_ATTR_SETSIGMASK | + NTL_POSIX_SPAWN_ATTR_SETSIGDEF; + if ((request->SpawnAttributeFlags & ~validFlags) != 0 || + (request->SpawnSignalMask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0 || + (request->SpawnSignalDefaultMask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) return status; + POSIXSRV_TRANSACTION* transaction = PosixsrvFindTransactionLocked( + client->Process, request->TransactionId, NTL_POSIX_TRANSACTION_SPAWN); + if (!transaction || !transaction->Spawn) status = NTLSTATUS_INVALID_PARAMETER; + else { + transaction->Spawn->AttributeFlags = request->SpawnAttributeFlags; + transaction->Spawn->ProcessGroupId = request->ProcessGroupId; + transaction->Spawn->SignalMask = request->SpawnSignalMask & + ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + transaction->Spawn->SignalDefaultMask = request->SpawnSignalDefaultMask; + status = NTLSTATUS_SUCCESS; + } + PosixsrvUnlockRegistry(); + return status; +} + +// Returns whether one absolute path already names a native global namespace root. +static int PosixsrvPathUsesNativeRoot(const char* path) { + static const char* roots[] = { "/SystemRoot", "/Device", "/Sessions", "/Services" }; + if (!path || path[0] != '/') return 0; + for (size_t i = 0; i < sizeof(roots) / sizeof(roots[0]); ++i) { + size_t length = strlen(roots[i]); + if (strncmp(path, roots[i], length) == 0 && (path[length] == '\0' || path[length] == '/')) + return 1; + } + return 0; +} + +// Resolves one POSIX pathname against the server-owned cwd into native global syntax. +static KSTATUS PosixsrvResolveSpawnPath(const POSIXSRV_PROCESS_RECORD* parent, + const char* path, + char output[SYS_PATH_MAX + 1u]) { + if (!parent || !path || !path[0] || !output) return NTLSTATUS_INVALID_PARAMETER; + char combined[SYS_PATH_MAX + 1u]; + size_t pathLength = strlen(path); + if (pathLength > SYS_PATH_MAX) return NTLSTATUS_NAME_TOO_LONG; + if (PosixsrvPathUsesNativeRoot(path)) { + memcpy(combined, path, pathLength + 1u); + } else if (path[0] == '/') { + static const char prefix[] = "/SystemRoot"; + size_t prefixLength = sizeof(prefix) - 1u; + if (prefixLength + pathLength > SYS_PATH_MAX) return NTLSTATUS_NAME_TOO_LONG; + memcpy(combined, prefix, prefixLength); + memcpy(combined + prefixLength, path, pathLength + 1u); + } else { + size_t cwdLength = strlen(parent->CurrentDirectory); + size_t separator = cwdLength && parent->CurrentDirectory[cwdLength - 1u] != '/' ? 1u : 0u; + if (cwdLength + separator + pathLength > SYS_PATH_MAX) return NTLSTATUS_NAME_TOO_LONG; + memcpy(combined, parent->CurrentDirectory, cwdLength); + if (separator) combined[cwdLength++] = '/'; + memcpy(combined + cwdLength, path, pathLength + 1u); + } + size_t required = 0; + return NtlRealPath(SysSvFromCstr(combined), output, SYS_PATH_MAX + 1u, &required); +} + +// Allocates one globally registered open description with no process holder yet. +// The caller must hold the registry mutex and immediately attach an inheritance reference. +static KSTATUS PosixsrvCreateUnheldDescriptionLocked(uint32_t type, + uint32_t statusFlags, + SYS_HANDLE masterHandle, + POSIXSRV_OPEN_DESCRIPTION** descriptionOut) { + if (!descriptionOut || !PosixsrvOpenDescriptionTypeValid(type) || + (statusFlags & ~NTL_POSIX_OPEN_STATUS_VALID_MASK) != 0 || + (statusFlags & NTL_POSIX_OPEN_STATUS_ACCMODE) == NTL_POSIX_OPEN_STATUS_ACCMODE) + return NTLSTATUS_INVALID_PARAMETER; + *descriptionOut = (POSIXSRV_OPEN_DESCRIPTION*)0; + if (gNextOpenDescriptionId == 0) return NTLSTATUS_ID_EXHAUSTED; + + POSIXSRV_OPEN_DESCRIPTION* description = + (POSIXSRV_OPEN_DESCRIPTION*)calloc(1, sizeof(*description)); + NTL_POSIX_SHARED_OPEN_DESCRIPTION* shared = + (NTL_POSIX_SHARED_OPEN_DESCRIPTION*)calloc(1, sizeof(*shared)); + if (!description || !shared) { + free(description); + free(shared); + return NTLSTATUS_NO_MEMORY; + } + description->DescriptionId = gNextOpenDescriptionId++; + description->Type = type; + description->MasterHandle = masterHandle; + description->Shared = shared; + shared->Magic = NTL_POSIX_SHARED_OPEN_MAGIC; + shared->Version = NTL_POSIX_SHARED_OPEN_VERSION; + shared->Type = type; + shared->DescriptionId = description->DescriptionId; + shared->StatusFlags = (long)statusFlags; + description->Next = gOpenDescriptionRegistry; + gOpenDescriptionRegistry = description; + ++gRegisteredOpenDescriptions; + *descriptionOut = description; + return NTLSTATUS_SUCCESS; +} + +// Returns the manifest index for one logical descriptor or UINT32_MAX when absent. +static uint32_t PosixsrvFindSnapshotDescriptor(const POSIXSRV_FD_INHERITANCE* snapshot, + int32_t descriptor) { + if (!snapshot) return UINT32_MAX; + for (uint32_t i = 0; i < snapshot->EntryCount; ++i) { + if (snapshot->Entries[i].Manifest.Descriptor == descriptor) return i; + } + return UINT32_MAX; +} + +// Removes one logical descriptor from an inheritance manifest while preserving order. +static void PosixsrvRemoveSnapshotEntry(POSIXSRV_FD_INHERITANCE* snapshot, uint32_t index) { + if (!snapshot || index >= snapshot->EntryCount) return; + for (uint32_t i = index + 1u; i < snapshot->EntryCount; ++i) + snapshot->Entries[i - 1u] = snapshot->Entries[i]; + if (snapshot->EntryCount) --snapshot->EntryCount; + memset(&snapshot->Entries[snapshot->EntryCount], 0, sizeof(snapshot->Entries[0])); +} + +// Rebuilds the snapshot's unique-description pin set after ordered file actions mutate entries. +static void PosixsrvRebuildSnapshotDescriptionsLocked(POSIXSRV_FD_INHERITANCE* snapshot) { + if (!snapshot) return; + POSIXSRV_OPEN_DESCRIPTION* previous[NTL_POSIX_FD_INHERIT_MAX]; + uint32_t previousCount = snapshot->DescriptionCount; + for (uint32_t i = 0; i < previousCount; ++i) { + previous[i] = snapshot->Descriptions[i]; + if (previous[i] && previous[i]->InheritanceRefCount) + --previous[i]->InheritanceRefCount; + } + memset(snapshot->Descriptions, 0, sizeof(snapshot->Descriptions)); + snapshot->DescriptionCount = 0; + for (uint32_t i = 0; i < snapshot->EntryCount; ++i) { + POSIXSRV_OPEN_DESCRIPTION* description = snapshot->Entries[i].Description; + uint32_t found = 0; + for (uint32_t j = 0; j < snapshot->DescriptionCount; ++j) { + if (snapshot->Descriptions[j] == description) { found = 1; break; } + } + if (!found && description) { + snapshot->Descriptions[snapshot->DescriptionCount++] = description; + ++description->InheritanceRefCount; + } + } + for (uint32_t i = 0; i < previousCount; ++i) + PosixsrvDestroyOpenDescriptionLocked(previous[i]); +} + +// Opens one file action target and creates the shared description inherited by the child. +static KSTATUS PosixsrvOpenSpawnDescriptionLocked(POSIXSRV_PROCESS_RECORD* parent, + const POSIXSRV_SPAWN_ACTION* action, + POSIXSRV_OPEN_DESCRIPTION** descriptionOut) { + if (!parent || !action || !action->Path || !descriptionOut) return NTLSTATUS_INVALID_PARAMETER; + *descriptionOut = (POSIXSRV_OPEN_DESCRIPTION*)0; + uint32_t statusFlags = action->OpenFlags & NTL_POSIX_OPEN_STATUS_VALID_MASK; + if (strcmp(action->Path, "/dev/null") == 0) + return PosixsrvCreateUnheldDescriptionLocked(NTL_POSIX_OPEN_TYPE_DEV_NULL, statusFlags, + SYS_HANDLE_INVALID, descriptionOut); + if (strcmp(action->Path, "/dev/zero") == 0) + return PosixsrvCreateUnheldDescriptionLocked(NTL_POSIX_OPEN_TYPE_DEV_ZERO, statusFlags, + SYS_HANDLE_INVALID, descriptionOut); + + char native[SYS_PATH_MAX + 1u]; + KSTATUS status = PosixsrvResolveSpawnPath(parent, action->Path, native); + if (!KSUCCESS(status)) return status; + uint32_t access = SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK; + uint32_t mode = action->OpenFlags & NTL_POSIX_OPEN_STATUS_ACCMODE; + if (mode == NTL_POSIX_OPEN_STATUS_RDWR) + access |= SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE; + else if (mode == NTL_POSIX_OPEN_STATUS_WRONLY) + access |= SYS_FILE_ACCESS_WRITE; + else + access |= SYS_FILE_ACCESS_READ; + if (action->OpenFlags & NTL_POSIX_OPEN_STATUS_APPEND) access |= SYS_FILE_ACCESS_APPEND; + uint32_t disposition = SYS_FILE_OPEN_EXISTING; + if ((action->OpenFlags & NTL_POSIX_OPEN_CREATE) && (action->OpenFlags & NTL_POSIX_OPEN_TRUNCATE)) + disposition = SYS_FILE_CREATE_ALWAYS; + else if (action->OpenFlags & NTL_POSIX_OPEN_CREATE) + disposition = SYS_FILE_OPEN_ALWAYS; + else if (action->OpenFlags & NTL_POSIX_OPEN_TRUNCATE) + disposition = SYS_FILE_TRUNCATE_EXISTING; + + SYS_HANDLE file = SYS_HANDLE_INVALID; + status = NtlOpenFile(SysSvFromCstr(native), access, disposition, SYS_FILE_FLAG_NONE, &file); + if (!KSUCCESS(status)) return status; + status = PosixsrvCreateUnheldDescriptionLocked(NTL_POSIX_OPEN_TYPE_FILE, statusFlags, + file, descriptionOut); + if (!KSUCCESS(status)) (void)SysCloseHandle(file); + return status; +} + +// Applies ordered POSIX spawn file actions and only then closes remaining CLOEXEC descriptors. +static KSTATUS PosixsrvApplySpawnFileActionsLocked(POSIXSRV_PROCESS_RECORD* parent, + POSIXSRV_FD_INHERITANCE* snapshot, + const POSIXSRV_SPAWN_POLICY* spawn) { + if (!parent || !snapshot || !spawn) return NTLSTATUS_INVALID_PARAMETER; + for (uint32_t i = 0; i < spawn->ActionCount; ++i) { + const POSIXSRV_SPAWN_ACTION* action = &spawn->Actions[i]; + uint32_t target = PosixsrvFindSnapshotDescriptor(snapshot, action->FileDescriptor); + if (action->Kind == NTL_POSIX_SPAWN_ACTION_CLOSE) { + if (target == UINT32_MAX) return NTLSTATUS_INVALID_HANDLE; + PosixsrvRemoveSnapshotEntry(snapshot, target); + } else if (action->Kind == NTL_POSIX_SPAWN_ACTION_DUP2) { + uint32_t source = target; + if (source == UINT32_MAX) return NTLSTATUS_INVALID_HANDLE; + POSIXSRV_FD_INHERITANCE_ENTRY sourceEntry = snapshot->Entries[source]; + if (action->FileDescriptor == action->NewFileDescriptor) continue; + uint32_t replacement = PosixsrvFindSnapshotDescriptor(snapshot, action->NewFileDescriptor); + if (replacement != UINT32_MAX) { + PosixsrvRemoveSnapshotEntry(snapshot, replacement); + source = PosixsrvFindSnapshotDescriptor(snapshot, action->FileDescriptor); + sourceEntry = snapshot->Entries[source]; + } + if (snapshot->EntryCount >= NTL_POSIX_FD_INHERIT_MAX) return NTLSTATUS_LIMIT_REACHED; + sourceEntry.Manifest.Descriptor = action->NewFileDescriptor; + sourceEntry.Manifest.DescriptorFlags = 0; + snapshot->Entries[snapshot->EntryCount++] = sourceEntry; + } else if (action->Kind == NTL_POSIX_SPAWN_ACTION_OPEN) { + if (target != UINT32_MAX) PosixsrvRemoveSnapshotEntry(snapshot, target); + if (snapshot->EntryCount >= NTL_POSIX_FD_INHERIT_MAX) return NTLSTATUS_LIMIT_REACHED; + POSIXSRV_OPEN_DESCRIPTION* description = (POSIXSRV_OPEN_DESCRIPTION*)0; + KSTATUS status = PosixsrvOpenSpawnDescriptionLocked(parent, action, &description); + if (!KSUCCESS(status)) return status; + POSIXSRV_FD_INHERITANCE_ENTRY* entry = &snapshot->Entries[snapshot->EntryCount++]; + memset(entry, 0, sizeof(*entry)); + entry->Manifest.Descriptor = action->FileDescriptor; + entry->Manifest.OpenDescriptionId = description->DescriptionId; + entry->Description = description; + } + PosixsrvRebuildSnapshotDescriptionsLocked(snapshot); + } + for (uint32_t i = 0; i < snapshot->EntryCount;) { + if (snapshot->Entries[i].Manifest.DescriptorFlags & NTL_POSIX_FD_DESCRIPTOR_CLOEXEC) + PosixsrvRemoveSnapshotEntry(snapshot, i); + else + ++i; + } + PosixsrvRebuildSnapshotDescriptionsLocked(snapshot); + return NTLSTATUS_SUCCESS; +} + +// Verifies that every streamed argv/envp element reached a final chunk. +static int PosixsrvConstructionVectorsComplete(const POSIXSRV_CONSTRUCTION* construction) { + if (!construction) return 0; + for (uint32_t i = 0; i < construction->ArgumentCount; ++i) { + if (!construction->Arguments[i].Complete) return 0; + } + for (uint32_t i = 0; i < construction->EnvironmentCount; ++i) { + if (!construction->Environment[i].Complete) return 0; + } + return 1; +} + +// Marks a failed constructing child non-waitable and releases its server-owned state. +static void PosixsrvFailConstructingProcess(POSIXSRV_PROCESS_RECORD* child, int32_t exitCode) { + if (!child) return; + POSIXSRV_PROCESS_RECORD* freeRecord = (POSIXSRV_PROCESS_RECORD*)0; + SYS_HANDLE closeProcess = SYS_HANDLE_INVALID; + SYS_HANDLE closeReady = SYS_HANDLE_INVALID; + if (!KSUCCESS(PosixsrvLockRegistry())) return; + child->ParentPosixPid = 0; + PosixsrvReleaseFdInheritanceForProcessLocked(child); + PosixsrvReleaseAllOpenDescriptionsLocked(child); + child->State = NTL_POSIX_PROCESS_STATE_EXITED; + child->ExitCode = exitCode; + closeProcess = child->ProcessHandle; + child->ProcessHandle = SYS_HANDLE_INVALID; + closeReady = child->BootstrapReadyEvent; + child->BootstrapReadyEvent = SYS_HANDLE_INVALID; + child->BootstrapToken = 0; + if (child->ConnectionCount == 0 && !child->MonitorActive) { + PosixsrvUnlinkProcessLocked(child); + freeRecord = child; + } + PosixsrvUnlockRegistry(); + if (closeProcess != SYS_HANDLE_INVALID) (void)SysCloseHandle(closeProcess); + if (closeReady != SYS_HANDLE_INVALID) (void)SysCloseHandle(closeReady); + if (freeRecord) PosixsrvFreeProcessRecord(freeRecord); +} + +// Prepares one inert native image from the canonical construction payload. +static KSTATUS PosixsrvPrepareConstruction(POSIXSRV_PROCESS_RECORD* process, + const POSIXSRV_CONSTRUCTION* construction, + SYS_HANDLE securityContext, + const NTL_POSIX_BOOTSTRAP_DATA* bootstrap, + int synthesizeArgumentZero, + NTL_PROCESS_BUILDER* builderOut, + uint32_t* nativePidOut) { + if (!process || !construction || !construction->Executable || + securityContext == SYS_HANDLE_INVALID || !bootstrap || !builderOut || !nativePidOut) + return NTLSTATUS_INVALID_PARAMETER; + memset(builderOut, 0, sizeof(*builderOut)); + *nativePidOut = 0; + + char executable[SYS_PATH_MAX + 1u]; + KSTATUS status = PosixsrvResolveSpawnPath(process, construction->Executable, executable); + if (!KSUCCESS(status)) return status; + + SYS_HANDLE imageFile = SYS_HANDLE_INVALID; + status = NtlOpenFile(SysSvFromCstr(executable), SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &imageFile); + if (!KSUCCESS(status)) return status; + SYS_HANDLE systemRoot = SYS_HANDLE_INVALID; + status = NtlResourceOpenSystemRoot(SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN, &systemRoot); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(imageFile); + return status; + } + + uint32_t argumentCount = construction->ArgumentCount; + if (argumentCount == 0 && synthesizeArgumentZero) argumentCount = 1u; + SYS_STRING_VIEW* arguments = argumentCount + ? (SYS_STRING_VIEW*)calloc(argumentCount, sizeof(SYS_STRING_VIEW)) + : (SYS_STRING_VIEW*)0; + SYS_STRING_VIEW* environment = construction->EnvironmentCount + ? (SYS_STRING_VIEW*)calloc(construction->EnvironmentCount, sizeof(SYS_STRING_VIEW)) + : (SYS_STRING_VIEW*)0; + if ((argumentCount && !arguments) || (construction->EnvironmentCount && !environment)) { + free(arguments); + free(environment); + (void)SysCloseHandle(systemRoot); + (void)SysCloseHandle(imageFile); + return NTLSTATUS_NO_MEMORY; + } + if (construction->ArgumentCount) { + for (uint32_t i = 0; i < construction->ArgumentCount; ++i) + arguments[i] = SysSvFromParts(construction->Arguments[i].Data, + construction->Arguments[i].Length); + } else if (synthesizeArgumentZero) { + arguments[0] = SysSvFromCstr(construction->Executable); + } + for (uint32_t i = 0; i < construction->EnvironmentCount; ++i) + environment[i] = SysSvFromParts(construction->Environment[i].Data, + construction->Environment[i].Length); + + NTL_PROCESS_BUILDER_REQUEST build; + memset(&build, 0, sizeof(build)); + build.Size = sizeof(build); + build.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + build.RootHandle = systemRoot; + build.Path = SysSvFromCstr(executable); + build.CommandLine = argumentCount ? arguments[0] : SysSvFromCstr(construction->Executable); + build.CurrentDirectory = SysSvFromCstr(process->CurrentDirectory); + build.SecurityContext = securityContext; + build.ModuleRootHandle = systemRoot; + build.ModuleDirectory = SYS_SV_LITERAL("system"); + build.ResolveModuleName = NtlResolveSystemModuleName; + build.Arguments.Entries = arguments; + build.Arguments.Count = argumentCount; + build.Environment.Entries = environment; + build.Environment.Count = construction->EnvironmentCount; + build.StartupData = bootstrap; + build.StartupDataSize = sizeof(*bootstrap); + + status = NtlProcessBuilderPrepare(builderOut, &build, imageFile); + free(arguments); + free(environment); + (void)SysCloseHandle(systemRoot); + (void)SysCloseHandle(imageFile); + if (!KSUCCESS(status)) return status; + + SYS_HANDLE preparedProcess = SYS_HANDLE_INVALID; + status = NtlProcessBuilderGetProcessHandle(builderOut, &preparedProcess); + if (!KSUCCESS(status)) { + NtlProcessBuilderAbort(builderOut); + return status; + } + status = SysGetProcessPid(preparedProcess, nativePidOut); + if (!KSUCCESS(status) || *nativePidOut == 0) { + NtlProcessBuilderAbort(builderOut); + return KSUCCESS(status) ? NTLSTATUS_FAILURE : status; + } + return NTLSTATUS_SUCCESS; +} + +// Builds and starts one POSIX child entirely under posixsrv authority. +static KSTATUS PosixsrvCommitSpawn(POSIXSRV_CLIENT* client, + const NTL_POSIX_REQUEST* request, + SYS_HANDLE securityContext, + const POSIXSRV_TRANSACTION* transaction, + uint32_t* childPidOut) { + if (!client || !client->Process || !request || securityContext == SYS_HANDLE_INVALID || + !transaction || !childPidOut || request->FdInheritanceToken == 0) + return NTLSTATUS_INVALID_PARAMETER; + *childPidOut = 0; + + if (transaction->Kind != NTL_POSIX_TRANSACTION_SPAWN || !transaction->Construction || + !transaction->Spawn || !transaction->Construction->Executable || + !PosixsrvConstructionVectorsComplete(transaction->Construction)) + return NTLSTATUS_INVALID_PARAMETER; + const POSIXSRV_CONSTRUCTION* construction = transaction->Construction; + const POSIXSRV_SPAWN_POLICY* spawn = transaction->Spawn; + + SYS_SECURITY_CONTEXT_INFO securityInfo; + memset(&securityInfo, 0, sizeof(securityInfo)); + securityInfo.Size = sizeof(securityInfo); + KSTATUS status = SysQuerySecurityContext(securityContext, &securityInfo); + if (!KSUCCESS(status)) return status; + + POSIXSRV_PROCESS_RECORD* child = + (POSIXSRV_PROCESS_RECORD*)calloc(1, sizeof(*child)); + if (!child) return NTLSTATUS_NO_MEMORY; + child->ProcessHandle = SYS_HANDLE_INVALID; + child->BootstrapReadyEvent = SYS_HANDLE_INVALID; + child->PendingProcessHandle = SYS_HANDLE_INVALID; + child->PendingBootstrapReadyEvent = SYS_HANDLE_INVALID; + child->PendingBootstrapReleaseEvent = SYS_HANDLE_INVALID; + child->AlarmTimer = SYS_HANDLE_INVALID; + child->AlarmControlEvent = SYS_HANDLE_INVALID; + + status = SysCreateEvent(0u, 0u, &child->BootstrapReadyEvent); + if (!KSUCCESS(status)) { free(child); return status; } + + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { PosixsrvFreeProcessRecord(child); return status; } + POSIXSRV_PROCESS_RECORD* parent = client->Process; + POSIXSRV_FD_INHERITANCE* snapshot = + PosixsrvFindFdInheritanceLocked(request->FdInheritanceToken); + if (!PosixsrvCommittedGenerationOperational(client) || !snapshot || + snapshot->SourceProcess != parent || snapshot->ClaimProcess) { + status = snapshot ? NTLSTATUS_ACCESS_DENIED : NTLSTATUS_NOT_FOUND; + PosixsrvUnlockRegistry(); + PosixsrvFreeProcessRecord(child); + return status; + } + + status = PosixsrvAllocatePidLocked(&child->PosixPid); + if (KSUCCESS(status)) { + if (gNextBootstrapToken == 0) status = NTLSTATUS_ID_EXHAUSTED; + else child->BootstrapToken = gNextBootstrapToken++; + } + if (KSUCCESS(status)) status = PosixsrvApplySpawnFileActionsLocked(parent, snapshot, spawn); + if (!KSUCCESS(status)) { + PosixsrvDestroyFdInheritanceLocked(snapshot); + PosixsrvUnlockRegistry(); + PosixsrvFreeProcessRecord(child); + return status; + } + + child->ParentPosixPid = parent->PosixPid; + child->SecurityId = securityInfo.SecurityId; + child->Generation = 1u; + child->State = NTL_POSIX_PROCESS_STATE_CONSTRUCTING; + child->UserId = parent->UserId; + child->EffectiveUserId = parent->EffectiveUserId; + child->GroupId = parent->GroupId; + child->EffectiveGroupId = parent->EffectiveGroupId; + child->SessionId = parent->SessionId; + child->SupplementaryGroupCount = parent->SupplementaryGroupCount; + memcpy(child->SupplementaryGroups, parent->SupplementaryGroups, + sizeof(child->SupplementaryGroups)); + child->ExplicitIgnoredSignals = parent->ExplicitIgnoredSignals & ~spawn->SignalDefaultMask; + child->IgnoredSignals = child->ExplicitIgnoredSignals | + NTL_POSIX_SIGNAL_BIT(NTL_POSIX_SIGNAL_CHLD); + (void)PosixsrvCopyText(child->LoginName, sizeof(child->LoginName), parent->LoginName); + (void)PosixsrvCopyText(child->CurrentDirectory, sizeof(child->CurrentDirectory), + parent->CurrentDirectory); + if (spawn->AttributeFlags & NTL_POSIX_SPAWN_ATTR_SETPGROUP) { + uint32_t requestedGroup = spawn->ProcessGroupId ? spawn->ProcessGroupId : child->PosixPid; + if (requestedGroup != child->PosixPid && + !PosixsrvProcessGroupExistsLocked(parent->SessionId, requestedGroup)) + status = NTLSTATUS_ACCESS_DENIED; + else + child->ProcessGroupId = requestedGroup; + } else { + child->ProcessGroupId = parent->ProcessGroupId; + } + if (!KSUCCESS(status)) { + PosixsrvDestroyFdInheritanceLocked(snapshot); + PosixsrvUnlockRegistry(); + PosixsrvFreeProcessRecord(child); + return status; + } + PosixsrvUnlockRegistry(); + + NTL_POSIX_BOOTSTRAP_DATA bootstrap; + memset(&bootstrap, 0, sizeof(bootstrap)); + bootstrap.Magic = NTL_POSIX_BOOTSTRAP_MAGIC; + bootstrap.Version = NTL_POSIX_BOOTSTRAP_VERSION; + bootstrap.Size = sizeof(bootstrap); + bootstrap.Token = child->BootstrapToken; + bootstrap.PosixPid = child->PosixPid; + bootstrap.Generation = child->Generation; + bootstrap.FdInheritanceToken = request->FdInheritanceToken; + bootstrap.InitialSignalMask = spawn->SignalMask; + bootstrap.IgnoredSignals = child->ExplicitIgnoredSignals; + + NTL_PROCESS_BUILDER builder; + status = PosixsrvPrepareConstruction(child, construction, securityContext, &bootstrap, 1, + &builder, &child->NativeProcessId); + if (!KSUCCESS(status)) goto before_insert_fail; + + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { NtlProcessBuilderAbort(&builder); goto before_insert_fail; } + snapshot = PosixsrvFindFdInheritanceLocked(request->FdInheritanceToken); + if (!snapshot || snapshot->SourceProcess != parent || snapshot->ClaimProcess || + !PosixsrvCommittedGenerationOperational(client)) { + status = snapshot ? NTLSTATUS_ACCESS_DENIED : NTLSTATUS_NOT_FOUND; + PosixsrvUnlockRegistry(); + NtlProcessBuilderAbort(&builder); + goto before_insert_fail; + } + child->Next = gProcessRegistry; + gProcessRegistry = child; + ++gRegisteredProcesses; + status = PosixsrvClaimFdInheritanceLocked(snapshot, child); + if (!KSUCCESS(status)) { + PosixsrvUnlinkProcessLocked(child); + PosixsrvUnlockRegistry(); + NtlProcessBuilderAbort(&builder); + goto before_insert_fail; + } + PosixsrvUnlockRegistry(); + + SYS_HANDLE processHandle = SYS_HANDLE_INVALID; + status = NtlProcessBuilderStart(&builder, &processHandle); + if (!KSUCCESS(status)) { + PosixsrvFailConstructingProcess(child, (int32_t)status); + return status; + } + if (KSUCCESS(PosixsrvLockRegistry())) { + child->ProcessHandle = processHandle; + PosixsrvUnlockRegistry(); + } else { + (void)SysKillProcess(processHandle, (int32_t)NTLSTATUS_PROCESS_KILLED); + (void)SysCloseHandle(processHandle); + child->ProcessHandle = SYS_HANDLE_INVALID; + PosixsrvFailConstructingProcess(child, (int32_t)NTLSTATUS_FAILURE); + return NTLSTATUS_FAILURE; + } + + SYS_HANDLE waits[2] = { child->BootstrapReadyEvent, processHandle }; + uint32_t signaled = UINT32_MAX; + SYS_WAIT_MANY_REQUEST wait; + memset(&wait, 0, sizeof(wait)); + wait.Size = sizeof(wait); + wait.Flags = SYS_WAIT_FLAG_NONE; + wait.Handles = waits; + wait.Count = 2u; + wait.SignaledIndexOut = &signaled; + wait.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE; + status = SysWaitMany(&wait); + if (!KSUCCESS(status) || signaled != 0u) { + int32_t exitCode = (int32_t)NTLSTATUS_PROCESS_KILLED; + if (KSUCCESS(status) && signaled == 1u) + (void)SysGetProcessExitCode(processHandle, &exitCode); + else + (void)SysKillProcess(processHandle, exitCode); + PosixsrvFailConstructingProcess(child, exitCode); + return KSUCCESS(status) ? NTLSTATUS_PROCESS_KILLED : status; + } + + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { + (void)SysKillProcess(processHandle, (int32_t)NTLSTATUS_PROCESS_KILLED); + PosixsrvFailConstructingProcess(child, (int32_t)status); + return status; + } + status = PosixsrvStartProcessMonitorLocked(child, processHandle, child->Generation); + if (KSUCCESS(status)) { + if (child->BootstrapReadyEvent != SYS_HANDLE_INVALID) { + (void)SysCloseHandle(child->BootstrapReadyEvent); + child->BootstrapReadyEvent = SYS_HANDLE_INVALID; + } + child->BootstrapToken = 0; + } + PosixsrvUnlockRegistry(); + if (!KSUCCESS(status)) { + (void)SysKillProcess(processHandle, (int32_t)NTLSTATUS_PROCESS_KILLED); + PosixsrvFailConstructingProcess(child, (int32_t)status); + return status; + } + + *childPidOut = child->PosixPid; + return NTLSTATUS_SUCCESS; + +before_insert_fail: + if (KSUCCESS(PosixsrvLockRegistry())) { + POSIXSRV_FD_INHERITANCE* cleanup = + PosixsrvFindFdInheritanceLocked(request->FdInheritanceToken); + if (cleanup && cleanup->SourceProcess == client->Process && !cleanup->ClaimProcess) + PosixsrvDestroyFdInheritanceLocked(cleanup); + PosixsrvUnlockRegistry(); + } + PosixsrvFreeProcessRecord(child); + return status; +} + +// Rolls an uncommitted exec replacement back to the still-valid committed generation. +static void PosixsrvRollbackExec(POSIXSRV_PROCESS_RECORD* process, + uint64_t fdInheritanceToken, + SYS_HANDLE pendingProcess, + int replacementReachedReady) { + if (!process) return; + SYS_HANDLE readyEvent = SYS_HANDLE_INVALID; + SYS_HANDLE releaseEvent = SYS_HANDLE_INVALID; + if (KSUCCESS(PosixsrvLockRegistry())) { + POSIXSRV_FD_INHERITANCE* snapshot = + PosixsrvFindFdInheritanceLocked(fdInheritanceToken); + if (snapshot && snapshot->SourceProcess == process && !snapshot->ClaimProcess) + PosixsrvDestroyFdInheritanceLocked(snapshot); + if (process->PendingFdInheritanceToken == fdInheritanceToken) { + if (process->State == NTL_POSIX_PROCESS_STATE_EXEC_PREPARING || + process->State == NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING) { + if (process->BorrowedHostProcess) + process->State = NTL_POSIX_PROCESS_STATE_VFORK_BORROWED; + else if (process->Generation != 0 && process->ProcessHandle != SYS_HANDLE_INVALID) + process->State = NTL_POSIX_PROCESS_STATE_RUNNING; + } + process->BorrowedTerminalKind = POSIXSRV_VFORK_TERMINAL_NONE; + if (process->PendingExecHolderPlan) { + PosixsrvDestroyExecHolderPlan(process->PendingExecHolderPlan); + free(process->PendingExecHolderPlan); + process->PendingExecHolderPlan = (POSIXSRV_EXEC_HOLDER_PLAN*)0; + } + readyEvent = process->PendingBootstrapReadyEvent; + releaseEvent = process->PendingBootstrapReleaseEvent; + process->PendingNativeProcessId = 0; + process->PendingGeneration = 0; + process->PendingBootstrapToken = 0; + process->PendingBootstrapReadyObserved = 0; + process->PendingProcessHandle = SYS_HANDLE_INVALID; + process->PendingFdInheritanceToken = 0; + if (!replacementReachedReady) { + process->PendingBootstrapReadyEvent = SYS_HANDLE_INVALID; + process->PendingBootstrapReleaseEvent = SYS_HANDLE_INVALID; + } + } + PosixsrvUnlockRegistry(); + } + + if (pendingProcess != SYS_HANDLE_INVALID) { + (void)SysKillProcess(pendingProcess, (int32_t)NTLSTATUS_PROCESS_KILLED); + (void)SysCloseHandle(pendingProcess); + } + if (replacementReachedReady) { + if (releaseEvent != SYS_HANDLE_INVALID) (void)SysSignalEvent(releaseEvent); + } else { + if (readyEvent != SYS_HANDLE_INVALID) (void)SysCloseHandle(readyEvent); + if (releaseEvent != SYS_HANDLE_INVALID) (void)SysCloseHandle(releaseEvent); + } +} + +// Replaces one native process generation while preserving the stable POSIX process record. +static KSTATUS PosixsrvCommitExec(POSIXSRV_CLIENT* client, + const NTL_POSIX_REQUEST* request, + const POSIXSRV_TRANSACTION* transaction) { + if (!client || !client->Process || !request || !transaction || + request->FdInheritanceToken == 0) + return NTLSTATUS_INVALID_PARAMETER; + if (transaction->Kind != NTL_POSIX_TRANSACTION_EXEC || !transaction->Construction || + transaction->Spawn || !transaction->Construction->Executable || + !PosixsrvConstructionVectorsComplete(transaction->Construction)) + return NTLSTATUS_INVALID_PARAMETER; + if ((request->SignalMask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) + return NTLSTATUS_INVALID_PARAMETER; + + KSTATUS status = NTLSTATUS_SUCCESS; + SYS_HANDLE securityContext = SYS_HANDLE_INVALID; + SYS_HANDLE readyEvent = SYS_HANDLE_INVALID; + SYS_HANDLE releaseEvent = SYS_HANDLE_INVALID; + status = SysCreateEvent(0u, 0u, &readyEvent); + if (!KSUCCESS(status)) return status; + status = SysCreateEvent(0u, 0u, &releaseEvent); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(readyEvent); + return status; + } + + POSIXSRV_PROCESS_RECORD* process = client->Process; + int borrowedExec = PosixsrvClientIsBorrowedProcess(client); + uint32_t pendingGeneration = 0; + uint64_t bootstrapToken = 0; + uint64_t fdToken = request->FdInheritanceToken; + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(readyEvent); + (void)SysCloseHandle(releaseEvent); + return status; + } + POSIXSRV_FD_INHERITANCE* snapshot = PosixsrvFindFdInheritanceLocked(fdToken); + if (!((PosixsrvCommittedGenerationOperational(client) && + process->State == NTL_POSIX_PROCESS_STATE_RUNNING) || borrowedExec) || !snapshot || + snapshot->SourceProcess != process || snapshot->ClaimProcess || + process->Generation == UINT32_MAX || gNextBootstrapToken == 0) { + status = !snapshot ? NTLSTATUS_NOT_FOUND : NTLSTATUS_ACCESS_DENIED; + PosixsrvUnlockRegistry(); + (void)SysCloseHandle(readyEvent); + (void)SysCloseHandle(releaseEvent); + return status; + } + + SYS_HANDLE securityProcess = borrowedExec && process->BorrowedHostProcess + ? process->BorrowedHostProcess->ProcessHandle + : process->ProcessHandle; + status = SysOpenProcessSecurityContext( + securityProcess, SYS_RIGHT_QUERY | SYS_SECURITY_CONTEXT_RIGHT_ASSIGN, + &securityContext); + if (KSUCCESS(status)) { + SYS_SECURITY_CONTEXT_INFO securityInfo; + memset(&securityInfo, 0, sizeof(securityInfo)); + securityInfo.Size = sizeof(securityInfo); + status = SysQuerySecurityContext(securityContext, &securityInfo); + if (KSUCCESS(status) && securityInfo.SecurityId != process->SecurityId) + status = NTLSTATUS_ACCESS_DENIED; + } + if (!KSUCCESS(status)) { + if (securityContext != SYS_HANDLE_INVALID) (void)SysCloseHandle(securityContext); + PosixsrvUnlockRegistry(); + (void)SysCloseHandle(readyEvent); + (void)SysCloseHandle(releaseEvent); + return status; + } + if (process->PendingBootstrapReadyEvent != SYS_HANDLE_INVALID) + (void)SysCloseHandle(process->PendingBootstrapReadyEvent); + if (process->PendingBootstrapReleaseEvent != SYS_HANDLE_INVALID) + (void)SysCloseHandle(process->PendingBootstrapReleaseEvent); + pendingGeneration = process->Generation + 1u; + bootstrapToken = gNextBootstrapToken++; + process->PendingGeneration = pendingGeneration; + process->PendingBootstrapToken = bootstrapToken; + process->PendingBootstrapReadyEvent = readyEvent; + process->PendingBootstrapReleaseEvent = releaseEvent; + process->PendingBootstrapReadyObserved = 0; + process->PendingProcessHandle = SYS_HANDLE_INVALID; + process->PendingNativeProcessId = 0; + process->PendingFdInheritanceToken = fdToken; + process->State = NTL_POSIX_PROCESS_STATE_EXEC_PREPARING; + PosixsrvUnlockRegistry(); + + NTL_POSIX_BOOTSTRAP_DATA bootstrap; + memset(&bootstrap, 0, sizeof(bootstrap)); + bootstrap.Magic = NTL_POSIX_BOOTSTRAP_MAGIC; + bootstrap.Version = NTL_POSIX_BOOTSTRAP_VERSION; + bootstrap.Size = sizeof(bootstrap); + bootstrap.Token = bootstrapToken; + bootstrap.PosixPid = process->PosixPid; + bootstrap.Generation = pendingGeneration; + bootstrap.FdInheritanceToken = fdToken; + bootstrap.InitialSignalMask = request->SignalMask & ~NTL_POSIX_SIGNAL_UNBLOCKABLE_MASK; + bootstrap.IgnoredSignals = process->ExplicitIgnoredSignals; + + NTL_PROCESS_BUILDER builder; + uint32_t pendingNativePid = 0; + status = PosixsrvPrepareConstruction(process, transaction->Construction, securityContext, + &bootstrap, 0, &builder, &pendingNativePid); + (void)SysCloseHandle(securityContext); + securityContext = SYS_HANDLE_INVALID; + if (!KSUCCESS(status)) { + PosixsrvRollbackExec(process, fdToken, SYS_HANDLE_INVALID, 0); + return status; + } + + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { + NtlProcessBuilderAbort(&builder); + PosixsrvRollbackExec(process, fdToken, SYS_HANDLE_INVALID, 0); + return status; + } + if (process->State != NTL_POSIX_PROCESS_STATE_EXEC_PREPARING || + process->PendingGeneration != pendingGeneration || + process->PendingBootstrapToken != bootstrapToken || + process->PendingFdInheritanceToken != fdToken) { + PosixsrvUnlockRegistry(); + NtlProcessBuilderAbort(&builder); + PosixsrvRollbackExec(process, fdToken, SYS_HANDLE_INVALID, 0); + return NTLSTATUS_OBJECT_DEAD; + } + process->PendingNativeProcessId = pendingNativePid; + PosixsrvUnlockRegistry(); + + SYS_HANDLE pendingProcess = SYS_HANDLE_INVALID; + status = NtlProcessBuilderStart(&builder, &pendingProcess); + if (!KSUCCESS(status)) { + PosixsrvRollbackExec(process, fdToken, SYS_HANDLE_INVALID, 0); + return status; + } + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { + PosixsrvRollbackExec(process, fdToken, pendingProcess, 0); + return status; + } + if (process->State != NTL_POSIX_PROCESS_STATE_EXEC_PREPARING || + process->PendingGeneration != pendingGeneration) { + PosixsrvUnlockRegistry(); + PosixsrvRollbackExec(process, fdToken, pendingProcess, 0); + return NTLSTATUS_OBJECT_DEAD; + } + process->PendingProcessHandle = pendingProcess; + PosixsrvUnlockRegistry(); + + SYS_HANDLE waits[2] = { readyEvent, pendingProcess }; + uint32_t signaled = UINT32_MAX; + SYS_WAIT_MANY_REQUEST wait; + memset(&wait, 0, sizeof(wait)); + wait.Size = sizeof(wait); + wait.Flags = SYS_WAIT_FLAG_NONE; + wait.Handles = waits; + wait.Count = 2u; + wait.SignaledIndexOut = &signaled; + wait.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE; + status = SysWaitMany(&wait); + if (!KSUCCESS(status) || signaled != 0u) { + int32_t exitCode = (int32_t)NTLSTATUS_PROCESS_KILLED; + if (KSUCCESS(status) && signaled == 1u) + (void)SysGetProcessExitCode(pendingProcess, &exitCode); + PosixsrvRollbackExec(process, fdToken, pendingProcess, 0); + return KSUCCESS(status) ? NTLSTATUS_PROCESS_KILLED : status; + } + + POSIXSRV_EXEC_HOLDER_PLAN holderPlan; + memset(&holderPlan, 0, sizeof(holderPlan)); + POSIXSRV_EXEC_HOLDER_PLAN* borrowedPlan = (POSIXSRV_EXEC_HOLDER_PLAN*)0; + status = PosixsrvLockRegistry(); + if (!KSUCCESS(status)) { + PosixsrvRollbackExec(process, fdToken, pendingProcess, 1); + return status; + } + snapshot = PosixsrvFindFdInheritanceLocked(fdToken); + if (process->State != NTL_POSIX_PROCESS_STATE_EXEC_PREPARING || + process->PendingGeneration != pendingGeneration || !snapshot || + snapshot->SourceProcess != process || snapshot->ClaimProcess) { + PosixsrvUnlockRegistry(); + PosixsrvRollbackExec(process, fdToken, pendingProcess, 1); + return NTLSTATUS_OBJECT_DEAD; + } + status = PosixsrvPrepareExecHolderPlanLocked(process, snapshot, &holderPlan); + if (!KSUCCESS(status)) { + PosixsrvUnlockRegistry(); + PosixsrvRollbackExec(process, fdToken, pendingProcess, 1); + return status; + } + if (borrowedExec) { + borrowedPlan = (POSIXSRV_EXEC_HOLDER_PLAN*)calloc(1u, sizeof(*borrowedPlan)); + if (!borrowedPlan) { + PosixsrvUnlockRegistry(); + PosixsrvDestroyExecHolderPlan(&holderPlan); + PosixsrvRollbackExec(process, fdToken, pendingProcess, 1); + return NTLSTATUS_NO_MEMORY; + } + *borrowedPlan = holderPlan; + memset(&holderPlan, 0, sizeof(holderPlan)); + } + status = PosixsrvStartProcessMonitorLocked(process, pendingProcess, pendingGeneration); + if (!KSUCCESS(status)) { + PosixsrvUnlockRegistry(); + PosixsrvDestroyExecHolderPlan(&holderPlan); + if (borrowedPlan) { + PosixsrvDestroyExecHolderPlan(borrowedPlan); + free(borrowedPlan); + } + PosixsrvRollbackExec(process, fdToken, pendingProcess, 1); + return status; + } + + process->State = NTL_POSIX_PROCESS_STATE_EXEC_COMMITTING; + SYS_HANDLE oldProcess = process->ProcessHandle; + uint32_t oldGeneration = process->Generation; + + // A zero-timeout wait reports TIMEOUT while the replacement remains unsignaled. + status = SysWaitSingle(pendingProcess, SYS_WAIT_FLAG_NONE, 0u); + if (status != NTLSTATUS_TIMEOUT) { + process->State = NTL_POSIX_PROCESS_STATE_EXEC_PREPARING; + PosixsrvUnlockRegistry(); + PosixsrvDestroyExecHolderPlan(&holderPlan); + if (borrowedPlan) { + PosixsrvDestroyExecHolderPlan(borrowedPlan); + free(borrowedPlan); + } + PosixsrvRollbackExec(process, fdToken, pendingProcess, 1); + return KSUCCESS(status) ? NTLSTATUS_PROCESS_KILLED : status; + } + + if (borrowedExec) { + // The replacement is READY but remains behind its barrier until the borrowed thread dies. + process->PendingExecHolderPlan = borrowedPlan; + process->BorrowedTerminalKind = POSIXSRV_VFORK_TERMINAL_EXEC; + PosixsrvUnlockRegistry(); return NTLSTATUS_SUCCESS; } - return NTLSTATUS_NOT_FOUND; + + status = SysKillProcess(oldProcess, (int32_t)NTLSTATUS_PROCESS_KILLED); + if (!KSUCCESS(status)) { + process->State = NTL_POSIX_PROCESS_STATE_EXEC_PREPARING; + PosixsrvUnlockRegistry(); + PosixsrvDestroyExecHolderPlan(&holderPlan); + PosixsrvRollbackExec(process, fdToken, pendingProcess, 1); + return status; + } + + // No fallible allocation or lock acquisition remains after the old image is retired. + (void)SysCloseHandle(oldProcess); + PosixsrvApplyExecHolderPlanLocked(process, snapshot, &holderPlan); + PosixsrvReleaseThreadsForGenerationLocked(process, oldGeneration); + process->ProcessHandle = pendingProcess; + process->NativeProcessId = pendingNativePid; + process->Generation = pendingGeneration; + process->State = NTL_POSIX_PROCESS_STATE_RUNNING; + process->PendingProcessHandle = SYS_HANDLE_INVALID; + process->PendingNativeProcessId = 0; + process->PendingGeneration = 0; + process->PendingBootstrapToken = 0; + process->PendingFdInheritanceToken = 0; + process->IgnoredSignals = process->ExplicitIgnoredSignals | + NTL_POSIX_SIGNAL_BIT(NTL_POSIX_SIGNAL_CHLD); + PosixsrvDestroyFdInheritanceLocked(snapshot); + PosixsrvDrainPendingSignalsLocked(process); + SYS_HANDLE release = process->PendingBootstrapReleaseEvent; + PosixsrvUnlockRegistry(); + + (void)SysSignalEvent(release); + return NTLSTATUS_SUCCESS; } // Returns whether operation-specific bootstrap fields are all unused. @@ -1627,12 +4275,54 @@ static int PosixsrvHasNoFdInheritancePayload(const NTL_POSIX_REQUEST* request) { return 1; } +// Returns whether one request carries no P5 spawn transaction payload. +static int PosixsrvHasNoSpawnPayload(const NTL_POSIX_REQUEST* request) { + return request && request->ConstructionVectorKind == 0 && request->ConstructionVectorIndex == 0 && + request->ConstructionVectorOffset == 0 && request->ConstructionVectorFlags == 0 && + request->SpawnActionKind == 0 && request->SpawnFileDescriptor == 0 && + request->SpawnNewFileDescriptor == 0 && request->SpawnOpenFlags == 0 && + request->SpawnOpenMode == 0 && request->SpawnAttributeFlags == 0 && + request->SpawnSignalMask == 0 && request->SpawnSignalDefaultMask == 0; +} + +// Returns whether one request carries no child-wait selection payload. +static int PosixsrvHasNoWaitPayload(const NTL_POSIX_REQUEST* request) { + return request && request->WaitPid == 0 && request->WaitOptions == 0; +} + +// Returns whether one request carries no P6 signal payload. +static int PosixsrvHasNoSignalPayload(const NTL_POSIX_REQUEST* request) { + return request && request->SignalTargetPid == 0 && request->SignalNumber == 0 && + request->SignalDisposition == 0 && request->SignalReserved == 0 && + request->SignalMask == 0 && request->SignalApcRoutine == 0; +} + +// Returns whether one request carries no alarm payload. +static int PosixsrvHasNoAlarmPayload(const NTL_POSIX_REQUEST* request) { + return request && request->AlarmSeconds == 0 && request->AlarmReserved == 0; +} + +// Returns whether one request carries no borrowed-vfork lifecycle payload. +static int PosixsrvHasNoVforkPayload(const NTL_POSIX_REQUEST* request) { + return request && request->VforkToken == 0 && request->VforkExitCode == 0 && + request->VforkReserved == 0; +} + // Returns whether one request carries no transaction state. static int PosixsrvHasNoTransaction(const NTL_POSIX_REQUEST* request) { return request && request->TransactionId == 0 && request->TransactionKind == NTL_POSIX_TRANSACTION_NONE; } +// Returns whether all payload domains other than P6 signals are unused. +static int PosixsrvHasOnlySignalPayload(const NTL_POSIX_REQUEST* request) { + return request && PosixsrvHasNoBootstrap(request) && PosixsrvHasNoTransaction(request) && + PosixsrvHasNoAttributePayload(request) && PosixsrvHasNoFdInheritancePayload(request) && + PosixsrvHasNoSpawnPayload(request) && PosixsrvHasNoWaitPayload(request) && + PosixsrvHasNoAlarmPayload(request) && PosixsrvHasNoVforkPayload(request); +} + + // Dispatches one validated bootstrap/process request and consumes transferred handles explicitly. static KSTATUS PosixsrvDispatch(POSIXSRV_CLIENT* client, const NTL_POSIX_REQUEST* request, @@ -1646,24 +4336,353 @@ static KSTATUS PosixsrvDispatch(POSIXSRV_CLIENT* client, return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_ACCESS_DENIED, 0); if (request->Size != sizeof(*request) || request->Flags != NTL_POSIX_REQUEST_FLAG_NONE || - request->Reserved[0] != 0 || request->Reserved[1] != 0) + request->SignalReserved != 0 || request->AlarmReserved != 0 || request->VforkReserved != 0) return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); if (request->Version != NTL_POSIX_PROTOCOL_VERSION) return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_NOT_SUPPORTED, 0); + if (operation != NTL_POSIX_OP_VFORK_ATTACH && operation != NTL_POSIX_OP_VFORK_CANCEL && + operation != NTL_POSIX_OP_VFORK_EXIT && !PosixsrvHasNoVforkPayload(request)) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + + int signalOperation = operation == NTL_POSIX_OP_UPDATE_SIGNAL_MASK || + operation == NTL_POSIX_OP_SET_SIGNAL_DISPOSITION || + operation == NTL_POSIX_OP_GET_PENDING_SIGNALS || + operation == NTL_POSIX_OP_SEND_SIGNAL; + if (operation != NTL_POSIX_OP_REGISTER_THREAD && !signalOperation && + operation != NTL_POSIX_OP_EXEC_COMMIT && operation != NTL_POSIX_OP_VFORK_ATTACH && + !PosixsrvHasNoSignalPayload(request)) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + if (operation != NTL_POSIX_OP_SET_ALARM && !PosixsrvHasNoAlarmPayload(request)) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); if (operation == NTL_POSIX_OP_REGISTER_PROCESS) { if (!PosixsrvHasNoTransaction(request) || !PosixsrvHasNoFdInheritancePayload(request) || - info->HandleCount != 1 || !handles) + !PosixsrvHasNoSpawnPayload(request) || !PosixsrvHasNoWaitPayload(request) || + info->HandleCount != 0) return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); - KSTATUS status = PosixsrvRegisterProcess(client, request, &handles[0]); + KSTATUS status = PosixsrvRegisterProcess(client, request); + return PosixsrvReply(client, info->MessageId, operation, status, 0); + } + + if (operation == NTL_POSIX_OP_BOOTSTRAP_READY) { + if (info->HandleCount != 0 || !PosixsrvHasNoTransaction(request) || + !PosixsrvHasNoFdInheritancePayload(request) || !PosixsrvHasNoSpawnPayload(request) || + !PosixsrvHasNoWaitPayload(request) || + request->TargetPosixPid != 0 || request->UserId != 0 || request->GroupId != 0 || + request->ProcessGroupId != 0 || request->LookupKind != NTL_POSIX_LOOKUP_NONE || + request->OpenDescriptionType != 0 || request->OpenStatusFlags != 0 || + request->OpenDescriptionId != 0 || request->NameLength != 0 || request->PathLength != 0 || + request->Name[0] != '\0' || request->Path[0] != '\0') + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + KSTATUS status = PosixsrvBootstrapReady(client, request); return PosixsrvReply(client, info->MessageId, operation, status, 0); } if (!PosixsrvHasNoBootstrap(request)) return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + if (operation == NTL_POSIX_OP_VFORK_BEGIN) { + if (!client->Process || info->HandleCount != 0 || !PosixsrvHasNoTransaction(request) || + request->FdInheritanceToken == 0 || request->FdInheritanceIndex != 0 || + request->FdInheritanceCount != 0 || request->FdInheritanceFlags != 0 || + !PosixsrvHasNoSpawnPayload(request) || !PosixsrvHasNoWaitPayload(request) || + !PosixsrvHasNoSignalPayload(request) || !PosixsrvHasNoAlarmPayload(request) || + !PosixsrvHasNoVforkPayload(request) || request->TargetPosixPid != 0 || + request->UserId != 0 || request->GroupId != 0 || request->ProcessGroupId != 0 || + request->LookupKind != NTL_POSIX_LOOKUP_NONE || request->OpenDescriptionType != 0 || + request->OpenStatusFlags != 0 || request->OpenDescriptionId != 0 || + request->NameLength != 0 || request->PathLength != 0 || request->Name[0] != '\0' || + request->Path[0] != '\0') + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + uint32_t childPid = 0; + uint64_t token = 0; + KSTATUS status = PosixsrvBeginVfork(client, request, &childPid, &token); + NTL_POSIX_REPLY reply; + PosixsrvInitializeReply(client, &reply, operation, status, 0); + if (KSUCCESS(status)) { + reply.SpawnedPosixPid = childPid; + reply.VforkToken = token; + } + return PosixsrvSendReply(client, info->MessageId, &reply); + } + + if (operation == NTL_POSIX_OP_VFORK_ATTACH) { + if (!client->Process || info->HandleCount != 1 || !handles || + !PosixsrvHasNoTransaction(request) || !PosixsrvHasNoFdInheritancePayload(request) || + !PosixsrvHasNoSpawnPayload(request) || !PosixsrvHasNoWaitPayload(request) || + !PosixsrvHasNoAlarmPayload(request) || request->TargetPosixPid == 0 || + request->VforkToken == 0 || request->UserId != 0 || request->GroupId != 0 || + request->ProcessGroupId != 0 || request->LookupKind != NTL_POSIX_LOOKUP_NONE || + request->OpenDescriptionType != 0 || request->OpenStatusFlags != 0 || + request->OpenDescriptionId != 0 || request->NameLength != 0 || request->PathLength != 0 || + request->Name[0] != '\0' || request->Path[0] != '\0' || + request->SignalTargetPid != 0 || request->SignalNumber != 0 || + request->SignalDisposition != 0 || + (request->SignalMask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0 || + request->SignalApcRoutine == 0 || request->VforkExitCode != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + KSTATUS status = PosixsrvAttachVfork(client, request, info, &handles[0]); + NTL_POSIX_REPLY reply; + PosixsrvInitializeReply(client, &reply, operation, status, 0); + if (KSUCCESS(status)) { + reply.PosixPid = request->TargetPosixPid; + reply.NativeGeneration = 1u; + reply.ProcessState = NTL_POSIX_PROCESS_STATE_VFORK_BORROWED; + reply.VforkToken = request->VforkToken; + } + return PosixsrvSendReply(client, info->MessageId, &reply); + } + + if (operation == NTL_POSIX_OP_VFORK_CANCEL) { + if (!client->Process || info->HandleCount != 0 || !PosixsrvHasNoTransaction(request) || + !PosixsrvHasNoFdInheritancePayload(request) || !PosixsrvHasNoSpawnPayload(request) || + !PosixsrvHasNoWaitPayload(request) || !PosixsrvHasNoSignalPayload(request) || + !PosixsrvHasNoAlarmPayload(request) || request->TargetPosixPid == 0 || + request->VforkToken == 0 || request->UserId != 0 || request->GroupId != 0 || + request->ProcessGroupId != 0 || request->LookupKind != NTL_POSIX_LOOKUP_NONE || + request->OpenDescriptionType != 0 || request->OpenStatusFlags != 0 || + request->OpenDescriptionId != 0 || request->NameLength != 0 || request->PathLength != 0 || + request->Name[0] != '\0' || request->Path[0] != '\0' || request->VforkExitCode != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + KSTATUS status = PosixsrvCancelVfork(client, request->TargetPosixPid, request->VforkToken); + return PosixsrvReply(client, info->MessageId, operation, status, 0); + } + + POSIXSRV_CLIENT routedClient; + if (client->Process && info->SenderThreadId != 0) { + KSTATUS routeStatus = PosixsrvLockRegistry(); + if (!KSUCCESS(routeStatus)) + return PosixsrvReply(client, info->MessageId, operation, routeStatus, 0); + POSIXSRV_PROCESS_RECORD* borrowed = PosixsrvFindBorrowedChildLocked( + client->Process, client->NativeGeneration, info->SenderThreadId); + if (borrowed) { + routedClient = *client; + routedClient.Process = borrowed; + routedClient.NativeGeneration = borrowed->Generation; + client = &routedClient; + } + PosixsrvUnlockRegistry(); + } + + if (operation == NTL_POSIX_OP_VFORK_EXIT) { + if (info->HandleCount != 0 || !PosixsrvHasNoTransaction(request) || + !PosixsrvHasNoFdInheritancePayload(request) || !PosixsrvHasNoSpawnPayload(request) || + !PosixsrvHasNoWaitPayload(request) || !PosixsrvHasNoSignalPayload(request) || + !PosixsrvHasNoAlarmPayload(request) || request->TargetPosixPid == 0 || + request->VforkToken == 0 || request->UserId != 0 || request->GroupId != 0 || + request->ProcessGroupId != 0 || request->LookupKind != NTL_POSIX_LOOKUP_NONE || + request->OpenDescriptionType != 0 || request->OpenStatusFlags != 0 || + request->OpenDescriptionId != 0 || request->NameLength != 0 || request->PathLength != 0 || + request->Name[0] != '\0' || request->Path[0] != '\0') + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + KSTATUS status = PosixsrvExitVfork(client, request->TargetPosixPid, request->VforkToken, + request->VforkExitCode); + return PosixsrvReply(client, info->MessageId, operation, status, 0); + } + + if (operation == NTL_POSIX_OP_REGISTER_THREAD) { + if (!client->Process || info->HandleCount != 1 || !handles || + !PosixsrvHasNoTransaction(request) || !PosixsrvHasNoFdInheritancePayload(request) || + !PosixsrvHasNoSpawnPayload(request) || !PosixsrvHasNoWaitPayload(request) || + !PosixsrvHasNoAttributePayload(request) || request->SignalTargetPid != 0 || + request->SignalNumber != 0 || request->SignalDisposition != 0 || + request->SignalApcRoutine == 0 || + (request->SignalMask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + KSTATUS status = PosixsrvRegisterThread(client, request, info, &handles[0]); + return PosixsrvReply(client, info->MessageId, operation, status, 0); + } + + if (operation == NTL_POSIX_OP_UNREGISTER_THREAD) { + if (!client->Process || info->HandleCount != 0 || + !PosixsrvHasNoTransaction(request) || !PosixsrvHasNoFdInheritancePayload(request) || + !PosixsrvHasNoSpawnPayload(request) || !PosixsrvHasNoWaitPayload(request) || + !PosixsrvHasNoAttributePayload(request)) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + KSTATUS status = PosixsrvUnregisterThread(client, info); + return PosixsrvReply(client, info->MessageId, operation, status, 0); + } + + if (signalOperation) { + if (!client->Process || info->HandleCount != 0 || !PosixsrvHasOnlySignalPayload(request) || + request->SignalApcRoutine != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + + if (operation == NTL_POSIX_OP_UPDATE_SIGNAL_MASK) { + if (request->SignalTargetPid != 0 || request->SignalNumber != 0 || + request->SignalDisposition != 0 || + (request->SignalMask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + KSTATUS status = PosixsrvUpdateSignalMask(client, info, request->SignalMask); + return PosixsrvReply(client, info->MessageId, operation, status, 0); + } + + if (operation == NTL_POSIX_OP_SET_SIGNAL_DISPOSITION) { + if (request->SignalTargetPid != 0 || request->SignalMask != 0 || + !NtlPosixSignalValid(request->SignalNumber)) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + KSTATUS status = PosixsrvSetSignalDisposition(client, request->SignalNumber, + request->SignalDisposition); + return PosixsrvReply(client, info->MessageId, operation, status, 0); + } + + if (operation == NTL_POSIX_OP_GET_PENDING_SIGNALS) { + if (request->SignalTargetPid != 0 || request->SignalNumber != 0 || + request->SignalDisposition != 0 || request->SignalMask != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + NTL_POSIX_REPLY reply; + PosixsrvInitializeReply(client, &reply, operation, NTLSTATUS_SUCCESS, 0); + KSTATUS status = PosixsrvLockRegistry(); + if (KSUCCESS(status)) { + reply.PendingSignals = client->Process->ProcessPendingSignals; + PosixsrvUnlockRegistry(); + } else reply.Status = status; + return PosixsrvSendReply(client, info->MessageId, &reply); + } + + if (request->SignalDisposition != 0 || request->SignalMask != 0 || + request->SignalNumber < 0 || request->SignalNumber > NTL_POSIX_SIGNAL_MAX) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + KSTATUS status = PosixsrvSendSignal(client, request->SignalTargetPid, request->SignalNumber); + return PosixsrvReply(client, info->MessageId, operation, status, 0); + } + + if (operation == NTL_POSIX_OP_SET_ALARM) { + if (!client->Process || info->HandleCount != 0 || + !PosixsrvHasNoTransaction(request) || !PosixsrvHasNoAttributePayload(request) || + !PosixsrvHasNoFdInheritancePayload(request) || !PosixsrvHasNoSpawnPayload(request) || + !PosixsrvHasNoWaitPayload(request) || !PosixsrvHasNoSignalPayload(request)) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + uint32_t previousSeconds = 0; + KSTATUS status = PosixsrvSetAlarm(client, request->AlarmSeconds, &previousSeconds); + NTL_POSIX_REPLY reply; + PosixsrvInitializeReply(client, &reply, operation, status, 0); + if (KSUCCESS(status)) reply.AlarmRemainingSeconds = previousSeconds; + return PosixsrvSendReply(client, info->MessageId, &reply); + } + + if (operation == NTL_POSIX_OP_CONSTRUCT_SET_EXECUTABLE || + operation == NTL_POSIX_OP_CONSTRUCT_WRITE_VECTOR || + operation == NTL_POSIX_OP_SPAWN_ADD_FILE_ACTION || + operation == NTL_POSIX_OP_SPAWN_SET_ATTRIBUTES || + operation == NTL_POSIX_OP_SPAWN_COMMIT || + operation == NTL_POSIX_OP_EXEC_COMMIT) { + if (!client->Process || + (!PosixsrvCommittedGenerationOperational(client) && !PosixsrvClientIsBorrowedProcess(client)) || + request->TransactionId == 0 || request->TransactionKind != NTL_POSIX_TRANSACTION_NONE || + request->TargetPosixPid != 0 || request->UserId != 0 || request->GroupId != 0 || + request->LookupKind != NTL_POSIX_LOOKUP_NONE || request->OpenDescriptionType != 0 || + request->OpenStatusFlags != 0 || request->OpenDescriptionId != 0 || + request->NameLength != 0 || request->Name[0] != '\0' || + !PosixsrvHasNoWaitPayload(request)) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + + KSTATUS status = NTLSTATUS_INVALID_PARAMETER; + if (operation == NTL_POSIX_OP_CONSTRUCT_SET_EXECUTABLE) { + if (info->HandleCount != 0 || !PosixsrvHasNoFdInheritancePayload(request) || + request->ProcessGroupId != 0 || request->ConstructionVectorKind != 0 || + request->ConstructionVectorIndex != 0 || request->ConstructionVectorOffset != 0 || + request->ConstructionVectorFlags != 0 || request->SpawnActionKind != 0 || + request->SpawnFileDescriptor != 0 || request->SpawnNewFileDescriptor != 0 || + request->SpawnOpenFlags != 0 || request->SpawnOpenMode != 0 || + request->SpawnAttributeFlags != 0 || request->SpawnSignalMask != 0 || + request->SpawnSignalDefaultMask != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + status = PosixsrvConstructionSetExecutable(client, request); + } else if (operation == NTL_POSIX_OP_CONSTRUCT_WRITE_VECTOR) { + if (info->HandleCount != 0 || !PosixsrvHasNoFdInheritancePayload(request) || + request->ProcessGroupId != 0 || request->SpawnActionKind != 0 || + request->SpawnFileDescriptor != 0 || request->SpawnNewFileDescriptor != 0 || + request->SpawnOpenFlags != 0 || request->SpawnOpenMode != 0 || + request->SpawnAttributeFlags != 0 || request->SpawnSignalMask != 0 || + request->SpawnSignalDefaultMask != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + status = PosixsrvConstructionWriteVector(client, request); + } else if (operation == NTL_POSIX_OP_SPAWN_ADD_FILE_ACTION) { + if (info->HandleCount != 0 || !PosixsrvHasNoFdInheritancePayload(request) || + request->ProcessGroupId != 0 || request->ConstructionVectorKind != 0 || + request->ConstructionVectorIndex != 0 || request->ConstructionVectorOffset != 0 || + request->ConstructionVectorFlags != 0 || request->SpawnAttributeFlags != 0 || + request->SpawnSignalMask != 0 || request->SpawnSignalDefaultMask != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + status = PosixsrvSpawnAddFileAction(client, request); + } else if (operation == NTL_POSIX_OP_SPAWN_SET_ATTRIBUTES) { + if (info->HandleCount != 0 || !PosixsrvHasNoFdInheritancePayload(request) || + request->PathLength != 0 || request->Path[0] != '\0' || + request->ConstructionVectorKind != 0 || request->ConstructionVectorIndex != 0 || + request->ConstructionVectorOffset != 0 || request->ConstructionVectorFlags != 0 || + request->SpawnActionKind != 0 || request->SpawnFileDescriptor != 0 || + request->SpawnNewFileDescriptor != 0 || request->SpawnOpenFlags != 0 || + request->SpawnOpenMode != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + status = PosixsrvSpawnSetAttributes(client, request); + } else if (operation == NTL_POSIX_OP_SPAWN_COMMIT) { + if (info->HandleCount != 1 || !handles || request->FdInheritanceToken == 0 || + request->FdInheritanceIndex != 0 || request->FdInheritanceCount != 0 || + request->FdInheritanceFlags != 0 || request->ProcessGroupId != 0 || + request->PathLength != 0 || request->Path[0] != '\0' || + request->ConstructionVectorKind != 0 || request->ConstructionVectorIndex != 0 || + request->ConstructionVectorOffset != 0 || request->ConstructionVectorFlags != 0 || + request->SpawnActionKind != 0 || request->SpawnFileDescriptor != 0 || + request->SpawnNewFileDescriptor != 0 || request->SpawnOpenFlags != 0 || + request->SpawnOpenMode != 0 || request->SpawnAttributeFlags != 0 || + request->SpawnSignalMask != 0 || request->SpawnSignalDefaultMask != 0 || + !PosixsrvHasNoSignalPayload(request)) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + POSIXSRV_TRANSACTION transaction; + status = PosixsrvTakeTransaction(client, request->TransactionId, + NTL_POSIX_TRANSACTION_SPAWN, &transaction); + if (!KSUCCESS(status)) + return PosixsrvReply(client, info->MessageId, operation, status, 0); + uint32_t childPid = 0; + status = PosixsrvCommitSpawn(client, request, handles[0], &transaction, &childPid); + PosixsrvDestroyTransaction(&transaction); + NTL_POSIX_REPLY reply; + PosixsrvInitializeReply(client, &reply, operation, status, 0); + if (KSUCCESS(status)) reply.SpawnedPosixPid = childPid; + return PosixsrvSendReply(client, info->MessageId, &reply); + } else { + if (info->HandleCount != 0 || request->FdInheritanceToken == 0 || + request->FdInheritanceIndex != 0 || request->FdInheritanceCount != 0 || + request->FdInheritanceFlags != 0 || request->ProcessGroupId != 0 || + request->PathLength != 0 || request->Path[0] != '\0' || + request->ConstructionVectorKind != 0 || request->ConstructionVectorIndex != 0 || + request->ConstructionVectorOffset != 0 || request->ConstructionVectorFlags != 0 || + request->SpawnActionKind != 0 || request->SpawnFileDescriptor != 0 || + request->SpawnNewFileDescriptor != 0 || request->SpawnOpenFlags != 0 || + request->SpawnOpenMode != 0 || request->SpawnAttributeFlags != 0 || + request->SpawnSignalMask != 0 || request->SpawnSignalDefaultMask != 0 || + request->SignalTargetPid != 0 || request->SignalNumber != 0 || + request->SignalDisposition != 0 || request->SignalApcRoutine != 0 || + (request->SignalMask & ~NTL_POSIX_SIGNAL_VALID_MASK) != 0) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + POSIXSRV_TRANSACTION transaction; + status = PosixsrvTakeTransaction(client, request->TransactionId, + NTL_POSIX_TRANSACTION_EXEC, &transaction); + if (!KSUCCESS(status)) + return PosixsrvReply(client, info->MessageId, operation, status, 0); + status = PosixsrvCommitExec(client, request, &transaction); + PosixsrvDestroyTransaction(&transaction); + if (!KSUCCESS(status)) + return PosixsrvReply(client, info->MessageId, operation, status, 0); + if (client->Process && + client->Process->BorrowedTerminalKind == POSIXSRV_VFORK_TERMINAL_EXEC) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_SUCCESS, 0); + // Ordinary successful exec kills the requesting native generation; there is no reply. + return NTLSTATUS_SUCCESS; + } + return PosixsrvReply(client, info->MessageId, operation, status, request->TransactionId); + } + + if (!PosixsrvHasNoSpawnPayload(request)) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + + if (operation != NTL_POSIX_OP_WAIT_PROCESS && !PosixsrvHasNoWaitPayload(request)) + return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + if (operation == NTL_POSIX_OP_CREATE_FD_INHERITANCE) { - if (!client->Process || client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING || + if (!client->Process || + (!PosixsrvCommittedGenerationOperational(client) && !PosixsrvClientIsBorrowedProcess(client)) || info->HandleCount != 0 || !PosixsrvHasNoTransaction(request) || request->TargetPosixPid != 0 || request->UserId != 0 || request->GroupId != 0 || request->ProcessGroupId != 0 || request->LookupKind != NTL_POSIX_LOOKUP_NONE || @@ -1686,7 +4705,11 @@ static KSTATUS PosixsrvDispatch(POSIXSRV_CLIENT* client, } if (operation == NTL_POSIX_OP_IMPORT_FD_INHERITANCE) { - if (!client->Process || client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING || + if (!client->Process || + (client->Process->State != NTL_POSIX_PROCESS_STATE_CONSTRUCTING && + client->Process->State != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED && + !PosixsrvCommittedGenerationOperational(client) && + !PosixsrvClientIsPendingExecGeneration(client)) || info->HandleCount != 0 || !PosixsrvHasNoTransaction(request) || request->FdInheritanceToken == 0 || request->FdInheritanceCount != 0 || request->FdInheritanceFlags != 0 || request->TargetPosixPid != 0 || @@ -1708,7 +4731,7 @@ static KSTATUS PosixsrvDispatch(POSIXSRV_CLIENT* client, } if (operation == NTL_POSIX_OP_CANCEL_FD_INHERITANCE) { - if (!client->Process || client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING || + if (!client->Process || !PosixsrvCommittedGenerationOperational(client) || info->HandleCount != 0 || !PosixsrvHasNoTransaction(request) || request->FdInheritanceToken == 0 || request->FdInheritanceIndex != 0 || request->FdInheritanceCount != 0 || request->FdInheritanceFlags != 0 || @@ -1728,32 +4751,11 @@ static KSTATUS PosixsrvDispatch(POSIXSRV_CLIENT* client, return PosixsrvReply(client, info->MessageId, operation, status, 0); } - if (operation == NTL_POSIX_OP_ARM_FD_INHERITANCE) { - if (!client->Process || client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING || - info->HandleCount != 1 || !handles || !PosixsrvHasNoTransaction(request) || - request->FdInheritanceToken == 0 || request->FdInheritanceIndex != 0 || - request->FdInheritanceCount != 0 || request->FdInheritanceFlags != 0 || - request->TargetPosixPid != 0 || request->UserId != 0 || request->GroupId != 0 || - request->ProcessGroupId != 0 || request->LookupKind != NTL_POSIX_LOOKUP_NONE || - request->OpenDescriptionType != 0 || request->OpenStatusFlags != 0 || - request->OpenDescriptionId != 0 || request->NameLength != 0 || request->PathLength != 0 || - request->Name[0] != '\0' || request->Path[0] != '\0') - return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); - for (uint32_t i = 0; i < NTL_POSIX_FD_INHERIT_MAX; ++i) { - if (request->FdManifest[i].Descriptor != 0 || - request->FdManifest[i].DescriptorFlags != 0 || - request->FdManifest[i].OpenDescriptionId != 0) - return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); - } - KSTATUS status = PosixsrvArmFdInheritance(client, request->FdInheritanceToken, &handles[0]); - return PosixsrvReply(client, info->MessageId, operation, status, 0); - } - if (!PosixsrvHasNoFdInheritancePayload(request)) return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); if (operation == NTL_POSIX_OP_CREATE_OPEN_DESCRIPTION) { - if (!client->Process || client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING || + if (!client->Process || !PosixsrvCommittedGenerationOperational(client) || !PosixsrvHasNoTransaction(request) || request->TargetPosixPid != 0 || request->UserId != 0 || request->GroupId != 0 || request->ProcessGroupId != 0 || request->LookupKind != NTL_POSIX_LOOKUP_NONE || request->OpenDescriptionId != 0 || @@ -1773,7 +4775,7 @@ static KSTATUS PosixsrvDispatch(POSIXSRV_CLIENT* client, } if (operation == NTL_POSIX_OP_CREATE_PIPE) { - if (!client->Process || client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING || + if (!client->Process || !PosixsrvCommittedGenerationOperational(client) || info->HandleCount != 0 || !PosixsrvHasNoTransaction(request) || !PosixsrvHasNoAttributePayload(request)) return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); @@ -1827,11 +4829,33 @@ static KSTATUS PosixsrvDispatch(POSIXSRV_CLIENT* client, KSUCCESS(status) ? request->TransactionId : 0); } - if (!client->Process || client->Process->State != NTL_POSIX_PROCESS_STATE_RUNNING) + if (!client->Process || + (!PosixsrvCommittedGenerationOperational(client) && + !PosixsrvClientIsBorrowedProcess(client) && + !(operation == NTL_POSIX_OP_RELEASE_OPEN_DESCRIPTION && + PosixsrvClientIsPendingExecGeneration(client)))) return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_NOT_FOUND, 0); if (!PosixsrvHasNoTransaction(request)) return PosixsrvReply(client, info->MessageId, operation, NTLSTATUS_INVALID_PARAMETER, 0); + if (operation == NTL_POSIX_OP_WAIT_PROCESS) { + if (!PosixsrvHasNoAttributePayload(request) || + !PosixsrvHasNoFdInheritancePayload(request)) + return PosixsrvReply(client, info->MessageId, operation, + NTLSTATUS_INVALID_PARAMETER, 0); + int32_t waitedPid = 0; + int32_t exitCode = 0; + KSTATUS status = PosixsrvWaitProcess(client, request->WaitPid, request->WaitOptions, + &waitedPid, &exitCode); + NTL_POSIX_REPLY reply; + PosixsrvInitializeReply(client, &reply, operation, status, 0); + if (KSUCCESS(status)) { + reply.WaitedPosixPid = waitedPid; + reply.ExitCode = exitCode; + } + return PosixsrvSendReply(client, info->MessageId, &reply); + } + if (operation == NTL_POSIX_OP_RELEASE_OPEN_DESCRIPTION) { if (request->OpenDescriptionId == 0 || request->OpenDescriptionType != 0 || request->OpenStatusFlags != 0 || request->TargetPosixPid != 0 || @@ -1951,7 +4975,7 @@ static void PosixsrvCloseReceivedHandles(SYS_HANDLE* handles, uint32_t count) { } } -// Serves one client until disconnect and drops connection-owned transactions on exit. +// Serves one client until disconnect; process-owned transactions survive transport loss. static void PosixsrvClientThread(uintptr_t argument) { POSIXSRV_CLIENT* client = (POSIXSRV_CLIENT*)argument; if (!client) { diff --git a/code/user/system/rssrv/input.c b/code/user/system/rssrv/input.c index e26357b..390dd55 100644 --- a/code/user/system/rssrv/input.c +++ b/code/user/system/rssrv/input.c @@ -8,6 +8,8 @@ #define RSSRV_EVENT_MANUAL_RESET 1u #define RSSRV_INPUT_KEY_QUEUE_MASK (RSSRV_INPUT_KEY_QUEUE_SIZE - 1u) #define RSSRV_INPUT_MOUSE_QUEUE_MASK (RSSRV_INPUT_MOUSE_QUEUE_SIZE - 1u) +#define RSSRV_INPUT_REPEAT_INITIAL_MS 400u +#define RSSRV_INPUT_REPEAT_PERIOD_MS 33u typedef struct RSSRV_INPUT_SESSION { RSSRV_INPUT_STATE* Input; @@ -87,6 +89,29 @@ static int RssrvInputKeyIsRepeatable(INPUT_KEY key) { } } +// Arms RSSRV's logical typematic timer for one repeatable key. +static void RssrvInputStartRepeatLocked(RSSRV_INPUT_STATE* input, INPUT_KEY key) { + if (!input || input->RepeatTimer == SYS_HANDLE_INVALID || !RssrvInputKeyIsRepeatable(key)) + return; + input->RepeatKey = key; + SYS_SET_TIMER_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.TimerHandle = input->RepeatTimer; + request.Flags = SYS_TIMER_FLAG_NONE; + request.DueTimeMilliseconds = RSSRV_INPUT_REPEAT_INITIAL_MS; + request.PeriodMilliseconds = RSSRV_INPUT_REPEAT_PERIOD_MS; + (void)SysSetTimer(&request); +} + +// Stops RSSRV typematic when the active repeat key is released. +static void RssrvInputStopRepeatLocked(RSSRV_INPUT_STATE* input, INPUT_KEY key) { + if (!input || input->RepeatKey != key) return; + input->RepeatKey = INPUT_KEY_NONE; + if (input->RepeatTimer != SYS_HANDLE_INVALID) + (void)SysCancelTimer(input->RepeatTimer); +} + // Applies modifier and lock-key state for one first make or final break. static void RssrvInputUpdateModifierLocked(RSSRV_INPUT_STATE* input, INPUT_KEY key, int pressed) { uint32_t bit = 0; @@ -172,14 +197,14 @@ static void RssrvInputApplyKey(RSSRV_INPUT_STATE* input, const INPUT_KEY_EVENT* input->Generation++; RssrvInputQueueKeyLocked(input, key, &routedRecord); routeRecord = 1; - } else if (RssrvInputKeyIsRepeatable(key)) { - // Hardware typematic make events remain useful until RSSRV grows its own repeat timer. - RssrvInputQueueKeyLocked(input, key, &routedRecord); - routeRecord = 1; + RssrvInputStartRepeatLocked(input, key); } + // Repeated hardware make events are intentionally ignored. RSSRV owns + // logical typematic so PS/2 and USB input share one repeat policy. } else if (wasDown) { RssrvInputSetKeyDownLocked(input, key, 0); RssrvInputUpdateModifierLocked(input, key, 0); + RssrvInputStopRepeatLocked(input, key); input->Generation++; } RssrvInputUnlock(input); @@ -190,6 +215,35 @@ static void RssrvInputApplyKey(RSSRV_INPUT_STATE* input, const INPUT_KEY_EVENT* } } +// Emits logical typematic records while the active repeat key remains down. +static void RssrvInputRepeatThread(uintptr_t argument) { + RSSRV_INPUT_STATE* input = (RSSRV_INPUT_STATE*)argument; + RssrvSetCurrentThreadName("rssrv.input.repeat"); + if (!input || input->RepeatTimer == SYS_HANDLE_INVALID) SysExitThread(1); + + for (;;) { + KSTATUS status = SysWait(input->RepeatTimer, SYS_WAIT_FLAG_NONE); + if (!KSUCCESS(status)) SysExitThread(2); + + INPUT_KEY_RECORD routedRecord; + memset(&routedRecord, 0, sizeof(routedRecord)); + int routeRecord = 0; + if (KSUCCESS(RssrvInputLock(input))) { + INPUT_KEY key = input->RepeatKey; + if (RssrvInputKeyIsRepeatable(key) && RssrvInputKeyDownLocked(input, key)) { + RssrvInputQueueKeyLocked(input, key, &routedRecord); + routeRecord = 1; + } + RssrvInputUnlock(input); + } + + if (routeRecord) { + RssrvSessionObserveKey(input->Session, &routedRecord); + RssrvSessionRouteKey(input->Session, &routedRecord); + } + } +} + // Applies one normalized relative mouse event and lets Session 0 own cursor and target routing policy. static void RssrvInputApplyMouse(RSSRV_INPUT_STATE* input, const INPUT_MOUSE_EVENT* event) { if (!input || !event) return; @@ -506,7 +560,9 @@ KSTATUS RssrvInputInitialize(RSSRV_INPUT_STATE* input, input->Mutex = SYS_HANDLE_INVALID; input->KeyEvent = SYS_HANDLE_INVALID; input->MouseEvent = SYS_HANDLE_INVALID; + input->RepeatTimer = SYS_HANDLE_INVALID; input->Listener = SYS_HANDLE_INVALID; + input->RepeatKey = INPUT_KEY_NONE; KSTATUS status = SysCreateMutex(&input->Mutex); if (!KSUCCESS(status)) return status; @@ -514,6 +570,8 @@ KSTATUS RssrvInputInitialize(RSSRV_INPUT_STATE* input, if (!KSUCCESS(status)) return status; status = SysCreateEvent(RSSRV_EVENT_MANUAL_RESET, 0, &input->MouseEvent); if (!KSUCCESS(status)) return status; + status = SysCreateTimer(&input->RepeatTimer); + if (!KSUCCESS(status)) return status; status = RssrvSessionQueryDisplayBounds(session, &input->MouseWidth, &input->MouseHeight); if (!KSUCCESS(status)) return status; @@ -554,6 +612,15 @@ KSTATUS RssrvInputStart(RSSRV_INPUT_STATE* input) { status = SysCreateThread(&create); if (!KSUCCESS(status)) return status; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)RssrvInputRepeatThread; + create.Argument = (uintptr_t)input; + create.Priority = 1; + create.Flags = SYS_CREATE_THREAD_FLAG_NONE; + status = SysCreateThread(&create); + if (!KSUCCESS(status)) return status; + memset(&create, 0, sizeof(create)); create.Size = sizeof(create); create.Entry = (uintptr_t)RssrvInputListenerThread; diff --git a/code/user/system/rssrv/rssrv_internal.h b/code/user/system/rssrv/rssrv_internal.h index a947dee..3176744 100644 --- a/code/user/system/rssrv/rssrv_internal.h +++ b/code/user/system/rssrv/rssrv_internal.h @@ -87,12 +87,14 @@ typedef struct RSSRV_INPUT_STATE { SYS_HANDLE Mutex; SYS_HANDLE KeyEvent; SYS_HANDLE MouseEvent; + SYS_HANDLE RepeatTimer; SYS_HANDLE Listener; RSSRV_NAMESPACE* Namespace; RSSRV_SESSION_STATE* Session; uint8_t KeyDown[NTL_INPUT_KEY_BYTES]; uint32_t Modifiers; + INPUT_KEY RepeatKey; uint64_t Generation; INPUT_KEY_RECORD KeyQueue[RSSRV_INPUT_KEY_QUEUE_SIZE]; uint32_t KeyHead; diff --git a/code/user/tests/apc_return_smoke/apc_return_smoke.c b/code/user/tests/apc_return_smoke/apc_return_smoke.c index 3677b83..4aefe12 100644 --- a/code/user/tests/apc_return_smoke/apc_return_smoke.c +++ b/code/user/tests/apc_return_smoke/apc_return_smoke.c @@ -66,6 +66,53 @@ typedef struct APC_RETURN_STATE { volatile uint64_t Information; } APC_RETURN_STATE; +typedef struct DIRECT_APC_STATE { + SYS_HANDLE ReadyEvent; + volatile uint32_t Delivered; + volatile uint32_t ArgumentsValid; +} DIRECT_APC_STATE; + +// Records one directly queued generic user APC. +static void direct_apc(void* context, void* argument1, void* argument2) { + DIRECT_APC_STATE* state = (DIRECT_APC_STATE*)context; + if (!state) return; + state->ArgumentsValid = ((uintptr_t)argument1 == 0x11223344u && + (uintptr_t)argument2 == 0x55667788u); + state->Delivered++; +} + +// Registers the native dispatcher and waits alertably for a direct APC. +static void direct_apc_worker(uintptr_t argument) { + DIRECT_APC_STATE* state = (DIRECT_APC_STATE*)argument; + if (!state) SysExitThread(1); + if (SysSetUserApcDispatcher((void*)SysUserApcDispatcher) != NTLSTATUS_SUCCESS) + SysExitThread(2); + if (SysSignalEvent(state->ReadyEvent) != NTLSTATUS_SUCCESS) + SysExitThread(3); + + while (!state->Delivered) { + KSTATUS status = SysWaitSingle(state->ReadyEvent, SYS_WAIT_FLAG_ALERTABLE, + SYS_WAIT_TIMEOUT_INFINITE); + if (status != NTLSTATUS_USER_APC && status != NTLSTATUS_SUCCESS) + SysExitThread(4); + } + SysExitThread(state->ArgumentsValid ? 0 : 5); +} + +// Registers the native dispatcher and remains compute-runnable until interrupted. +static void direct_interrupt_apc_worker(uintptr_t argument) { + DIRECT_APC_STATE* state = (DIRECT_APC_STATE*)argument; + if (!state) SysExitThread(6); + if (SysSetUserApcDispatcher((void*)SysUserApcDispatcher) != NTLSTATUS_SUCCESS) + SysExitThread(7); + if (SysSignalEvent(state->ReadyEvent) != NTLSTATUS_SUCCESS) + SysExitThread(8); + + while (!state->Delivered) + (void)SysYield(); + SysExitThread(state->ArgumentsValid ? 0 : 9); +} + // Records that the user APC callback ran and observed the I/O status block. static void file_apc(void* context, SYS_IO_STATUS_BLOCK* iosb, uint64_t information) { APC_RETURN_STATE* state = (APC_RETURN_STATE*)context; @@ -105,6 +152,107 @@ int main(void) { if (status != NTLSTATUS_SUCCESS) fail_status("set dispatcher", status, NTLSTATUS_SUCCESS); + SYS_HANDLE currentThread = SYS_HANDLE_INVALID; + status = SysOpenCurrentThread(SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | SYS_THREAD_RIGHT_NOTIFY, + ¤tThread); + if (status != NTLSTATUS_SUCCESS || currentThread == SYS_HANDLE_INVALID) + fail_status("open current thread", status, NTLSTATUS_SUCCESS); + uint32_t currentTid = 0; + status = SysGetThreadTid(currentThread, ¤tTid); + if (status != NTLSTATUS_SUCCESS || currentTid != SysGetCurrentTid()) + fail_status("query current thread identity", status, NTLSTATUS_SUCCESS); + SysCloseHandle(currentThread); + + SYS_HANDLE readyEvent = SYS_HANDLE_INVALID; + status = SysCreateEvent(EVENT_AUTO_RESET, 0, &readyEvent); + if (status != NTLSTATUS_SUCCESS) + fail_status("create direct APC event", status, NTLSTATUS_SUCCESS); + + DIRECT_APC_STATE directState; + memset(&directState, 0, sizeof(directState)); + directState.ReadyEvent = readyEvent; + + SYS_HANDLE worker = SYS_HANDLE_INVALID; + SYS_CREATE_THREAD_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)direct_apc_worker; + create.Argument = (uintptr_t)&directState; + create.Priority = 1u; + create.Flags = SYS_CREATE_THREAD_FLAG_NONE; + create.ThreadHandleOut = &worker; + status = SysCreateThread(&create); + if (status != NTLSTATUS_SUCCESS || worker == SYS_HANDLE_INVALID) + fail_status("create direct APC worker", status, NTLSTATUS_SUCCESS); + + status = SysWaitSingle(readyEvent, SYS_WAIT_FLAG_NONE, SYS_WAIT_TIMEOUT_INFINITE); + if (status != NTLSTATUS_SUCCESS) + fail_status("wait direct APC ready", status, NTLSTATUS_SUCCESS); + + SYS_QUEUE_USER_APC_REQUEST queue; + memset(&queue, 0, sizeof(queue)); + queue.Size = sizeof(queue); + queue.ThreadHandle = worker; + queue.Routine = direct_apc; + queue.NormalContext = &directState; + queue.SystemArgument1 = (void*)(uintptr_t)0x11223344u; + queue.SystemArgument2 = (void*)(uintptr_t)0x55667788u; + status = SysQueueUserApc(&queue); + if (status != NTLSTATUS_SUCCESS) + fail_status("queue direct APC", status, NTLSTATUS_SUCCESS); + + status = SysWaitSingle(worker, SYS_WAIT_FLAG_NONE, SYS_WAIT_TIMEOUT_INFINITE); + if (status != NTLSTATUS_SUCCESS) + fail_status("wait direct APC worker", status, NTLSTATUS_SUCCESS); + if (directState.Delivered != 1 || !directState.ArgumentsValid) + fail_status("direct APC delivery", NTLSTATUS_FAILURE, NTLSTATUS_SUCCESS); + + SysCloseHandle(worker); + SysCloseHandle(readyEvent); + + status = SysCreateEvent(EVENT_AUTO_RESET, 0, &readyEvent); + if (status != NTLSTATUS_SUCCESS) + fail_status("create interrupt APC event", status, NTLSTATUS_SUCCESS); + memset(&directState, 0, sizeof(directState)); + directState.ReadyEvent = readyEvent; + + worker = SYS_HANDLE_INVALID; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)direct_interrupt_apc_worker; + create.Argument = (uintptr_t)&directState; + create.Priority = 1u; + create.Flags = SYS_CREATE_THREAD_FLAG_NONE; + create.ThreadHandleOut = &worker; + status = SysCreateThread(&create); + if (status != NTLSTATUS_SUCCESS || worker == SYS_HANDLE_INVALID) + fail_status("create interrupt APC worker", status, NTLSTATUS_SUCCESS); + + status = SysWaitSingle(readyEvent, SYS_WAIT_FLAG_NONE, SYS_WAIT_TIMEOUT_INFINITE); + if (status != NTLSTATUS_SUCCESS) + fail_status("wait interrupt APC ready", status, NTLSTATUS_SUCCESS); + + memset(&queue, 0, sizeof(queue)); + queue.Size = sizeof(queue); + queue.ThreadHandle = worker; + queue.Routine = direct_apc; + queue.NormalContext = &directState; + queue.SystemArgument1 = (void*)(uintptr_t)0x11223344u; + queue.SystemArgument2 = (void*)(uintptr_t)0x55667788u; + queue.Flags = SYS_QUEUE_USER_APC_FLAG_INTERRUPT; + status = SysQueueUserApc(&queue); + if (status != NTLSTATUS_SUCCESS) + fail_status("queue interrupt APC", status, NTLSTATUS_SUCCESS); + + status = SysWaitSingle(worker, SYS_WAIT_FLAG_NONE, SYS_WAIT_TIMEOUT_INFINITE); + if (status != NTLSTATUS_SUCCESS) + fail_status("wait interrupt APC worker", status, NTLSTATUS_SUCCESS); + if (directState.Delivered != 1 || !directState.ArgumentsValid) + fail_status("interrupt APC delivery", NTLSTATUS_FAILURE, NTLSTATUS_SUCCESS); + + SysCloseHandle(worker); + SysCloseHandle(readyEvent); + uint32_t file = 0; status = open_test_file(&file); if (status != NTLSTATUS_SUCCESS) diff --git a/code/user/tests/port_ipc_smoke/port_ipc_smoke.c b/code/user/tests/port_ipc_smoke/port_ipc_smoke.c index 0a230ef..27e76c2 100644 --- a/code/user/tests/port_ipc_smoke/port_ipc_smoke.c +++ b/code/user/tests/port_ipc_smoke/port_ipc_smoke.c @@ -48,16 +48,24 @@ static KSTATUS query_port_stats(SYS_PORT_STATS* stats) { return SysQueryKernelStats(SYS_KERNEL_STATS_PORT, stats, sizeof(*stats), (size_t*)0); } -// Returns a newly created listener Port. -static KSTATUS create_listener(uint32_t backlog, SYS_HANDLE* handleOut) { +// Returns a listener with an explicit ceiling for accepted-endpoint peer process authority. +static KSTATUS create_listener_with_peer_rights(uint32_t backlog, + SYS_RIGHTS peerProcessRights, + SYS_HANDLE* handleOut) { SYS_CREATE_PORT_REQUEST request; memset(&request, 0, sizeof(request)); request.Size = sizeof(request); request.Backlog = backlog; + request.PeerProcessRights = peerProcessRights; request.HandleOut = handleOut; return SysCreatePort(&request); } +// Returns a listener that grants no process authority to accepted endpoints. +static KSTATUS create_listener(uint32_t backlog, SYS_HANDLE* handleOut) { + return create_listener_with_peer_rights(backlog, 0, handleOut); +} + // Connects directly to one listener handle. static KSTATUS connect_listener(SYS_HANDLE listener, SYS_HANDLE* endpointOut) { SYS_CONNECT_PORT_REQUEST request; @@ -176,6 +184,64 @@ static int run_connection_lifetime_test(void) { return 0; } +// Verifies accepted-endpoint peer process authority, asymmetry, and the listener rights ceiling. +static int run_peer_process_test(void) { + SYS_HANDLE listener = SYS_HANDLE_INVALID; + KSTATUS status = create_listener_with_peer_rights( + 1, SYS_RIGHT_QUERY | SYS_PROCESS_RIGHT_SECURITY_CONTEXT, &listener); + if (!KSUCCESS(status)) return fail("create peer-process listener", status); + + SYS_HANDLE client = SYS_HANDLE_INVALID; + status = connect_listener(listener, &client); + if (!KSUCCESS(status)) return fail("connect peer-process listener", status); + + SYS_HANDLE server = SYS_HANDLE_INVALID; + status = accept_listener(listener, 0, &server, (SYS_PORT_CONNECTION_INFO*)0); + if (!KSUCCESS(status)) return fail("accept peer-process listener", status); + + SYS_HANDLE denied = SYS_HANDLE_INVALID; + status = SysOpenPortPeerProcess(client, SYS_RIGHT_QUERY, &denied); + if (status != NTLSTATUS_ACCESS_DENIED) + return fail("client peer-process authority", status); + + status = SysOpenPortPeerProcess(server, SYS_RIGHT_QUERY | SYS_PROCESS_RIGHT_TERMINATE, &denied); + if (status != NTLSTATUS_ACCESS_DENIED) + return fail("peer-process rights ceiling", status); + + SYS_HANDLE peer = SYS_HANDLE_INVALID; + status = SysOpenPortPeerProcess( + server, SYS_RIGHT_QUERY | SYS_PROCESS_RIGHT_SECURITY_CONTEXT, &peer); + if (!KSUCCESS(status)) return fail("open accepted peer process", status); + uint32_t peerPid = 0; + status = SysGetProcessPid(peer, &peerPid); + if (!KSUCCESS(status) || peerPid != SysGetCurrentPid()) + return fail("peer-process identity", KSUCCESS(status) ? NTLSTATUS_FAILURE : status); + + SYS_HANDLE context = SYS_HANDLE_INVALID; + status = SysOpenProcessSecurityContext( + peer, SYS_RIGHT_QUERY | SYS_SECURITY_CONTEXT_RIGHT_ASSIGN, &context); + if (!KSUCCESS(status)) return fail("open peer security context", status); + SYS_SECURITY_CONTEXT_INFO currentInfo; + SYS_SECURITY_CONTEXT_INFO peerInfo; + memset(¤tInfo, 0, sizeof(currentInfo)); + memset(&peerInfo, 0, sizeof(peerInfo)); + currentInfo.Size = sizeof(currentInfo); + peerInfo.Size = sizeof(peerInfo); + status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, ¤tInfo); + if (!KSUCCESS(status)) return fail("query current security context", status); + status = SysQuerySecurityContext(context, &peerInfo); + if (!KSUCCESS(status)) return fail("query peer security context", status); + if (peerInfo.SecurityId != currentInfo.SecurityId) + return fail("peer security identity", NTLSTATUS_FAILURE); + + (void)SysCloseHandle(context); + (void)SysCloseHandle(peer); + (void)SysCloseHandle(server); + (void)SysCloseHandle(client); + (void)SysCloseHandle(listener); + return 0; +} + // Exercises same-process connection semantics, waitability, buffering, and queue quotas. static int run_local_transport_test(void) { SYS_HANDLE listener = SYS_HANDLE_INVALID; @@ -494,6 +560,7 @@ int main(void) { print("port_ipc_smoke: start\n"); if (run_connection_lifetime_test() != 0) return 1; + if (run_peer_process_test() != 0) return 1; if (run_local_transport_test() != 0) return 1; if (run_service_test() != 0) return 1; print("port_ipc_smoke: PASS\n"); diff --git a/code/user/tests/posix_fd_smoke/posix_fd_smoke.c b/code/user/tests/posix_fd_smoke/posix_fd_smoke.c index 9428d9b..9abbcc2 100644 --- a/code/user/tests/posix_fd_smoke/posix_fd_smoke.c +++ b/code/user/tests/posix_fd_smoke/posix_fd_smoke.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,32 @@ static void fail(const char* message) { exit(1); } +// Prints one decimal integer without depending on stdio. +static void print_int(int value) { + char buffer[16]; + unsigned int magnitude = value < 0 ? (unsigned int)(-(value + 1)) + 1u : (unsigned int)value; + size_t count = 0; + if (value < 0) print("-"); + do { + buffer[count++] = (char)('0' + magnitude % 10u); + magnitude /= 10u; + } while (magnitude && count < sizeof(buffer)); + while (count) { + char c[2] = { buffer[--count], '\0' }; + print(c); + } +} + +// Terminates the smoke test with one POSIX error value. +static void fail_error(const char* message, int error) { + print("posix_fd_smoke: "); + print(message); + print(" error="); + print_int(error); + print("\n"); + exit(1); +} + #define INHERIT_FILE_FD 10 #define INHERIT_PIPE_A_READ 11 #define INHERIT_PIPE_A_WRITE 12 @@ -127,8 +154,9 @@ static void run_cross_process_inheritance(void) { char* childArgv[] = { self, "--inherit-child", NULL }; pid_t child = 0; - if (posix_spawn(&child, self, NULL, NULL, childArgv, NULL) != 0) - fail("inheritance child spawn failed"); + int spawnError = posix_spawn(&child, self, NULL, NULL, childArgv, NULL); + if (spawnError != 0) + fail_error("inheritance child spawn failed", spawnError); if (close(INHERIT_PIPE_A_WRITE) != 0 || close(INHERIT_PIPE_B_READ) != 0) fail("parent endpoint close failed"); @@ -147,9 +175,14 @@ static void run_cross_process_inheritance(void) { fail("write while inherited reader alive failed"); if (wait_control_byte() != 'C') fail("unexpected child close signal"); + sighandler_t previousPipe = signal(SIGPIPE, SIG_IGN); + if (previousPipe == SIG_ERR) + fail("ignore inherited SIGPIPE failed"); errno = 0; if (write(INHERIT_PIPE_B_WRITE, "y", 1) != -1 || errno != EPIPE) fail("final cross-process reader close did not produce EPIPE"); + if (signal(SIGPIPE, previousPipe) == SIG_ERR) + fail("restore inherited SIGPIPE failed"); int childStatus = 0; if (waitpid(child, &childStatus, 0) != child || childStatus != 0) diff --git a/code/user/tests/posix_pipe_smoke/posix_pipe_smoke.c b/code/user/tests/posix_pipe_smoke/posix_pipe_smoke.c index 95fc101..5361fa8 100644 --- a/code/user/tests/posix_pipe_smoke/posix_pipe_smoke.c +++ b/code/user/tests/posix_pipe_smoke/posix_pipe_smoke.c @@ -6,6 +6,7 @@ #include #include #include +#include #include // Writes one NUL-terminated diagnostic to stdout. @@ -87,9 +88,14 @@ int main(void) { fail("second pipe failed"); if (close(descriptors[0]) != 0) fail("close second read endpoint failed"); + sighandler_t previousPipe = signal(SIGPIPE, SIG_IGN); + if (previousPipe == SIG_ERR) + fail("ignore SIGPIPE failed"); errno = 0; if (write(descriptors[1], "x", 1) != -1 || errno != EPIPE) fail("write without readers did not return EPIPE"); + if (signal(SIGPIPE, previousPipe) == SIG_ERR) + fail("restore SIGPIPE failed"); if (close(descriptors[1]) != 0) fail("close broken writer failed"); diff --git a/code/user/tests/posix_process_smoke/posix_process_smoke.c b/code/user/tests/posix_process_smoke/posix_process_smoke.c new file mode 100644 index 0000000..7a862c5 --- /dev/null +++ b/code/user/tests/posix_process_smoke/posix_process_smoke.c @@ -0,0 +1,555 @@ +// User-mode smoke test for server-owned POSIX spawn and child lifecycle semantics. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONTROL_READ_FD 40 +#define ACTION_OPEN_FD 41 +#define ACTION_DUP_FD 42 +#define CONTROL_WRITE_FD 43 +#define CONTROL_WRITE_A_FD 44 +#define EXEC_PARENT_READ_FD 45 +#define EXEC_CHILD_WRITE_FD 46 +#define EXEC_CHILD_READ_FD 47 +#define EXEC_PARENT_WRITE_FD 48 +#define EXEC_RETAIN_FD 49 +#define EXEC_CLOEXEC_FD 50 + +static const char* gExecFile = "posix_process_exec.tmp"; +static volatile uint32_t gVforkChildTouched; + +// Writes one NUL-terminated diagnostic to stdout. +static void print(const char* text) { + (void)write(1, text, strlen(text)); +} + +// Terminates the smoke test with one diagnostic. +static void fail(const char* message) { + print("posix_process_smoke: "); + print(message); + print("\n"); + exit(1); +} + +// Returns whether the command-line compatibility view contains one child marker. +static int child_mode(const char* marker) { + const char* command = posix_get_command_line(); + return command && strstr(command, marker) != (char*)0; +} + +// Moves one descriptor to an exact number and closes the source alias. +static void move_fd(int source, int target) { + if (source < 0 || dup2(source, target) != target) + fail("control dup2 failed"); + if (source != target && close(source) != 0) + fail("control source close failed"); +} + +// Builds one inherited control pipe with a close-on-spawn parent write endpoint. +static void make_control_pipe(int writeFd) { + int pipefd[2]; + if (pipe(pipefd) != 0) + fail("control pipe failed"); + move_fd(pipefd[0], CONTROL_READ_FD); + move_fd(pipefd[1], writeFd); + if (fcntl(writeFd, F_SETFD, FD_CLOEXEC) != 0) + fail("control CLOEXEC failed"); +} + +// Resolves the current executable path from the compatibility command line. +static void get_self_path(char* path, size_t capacity) { + const char* command = posix_get_command_line(); + size_t length = 0; + while (command && command[length] && command[length] != ' ' && command[length] != '\t' && + length + 1u < capacity) { + path[length] = command[length]; + ++length; + } + path[length] = '\0'; + if (length == 0 || (command[length] && command[length] != ' ' && command[length] != '\t')) + fail("could not resolve self path"); +} + +// Converts one positive 32-bit value to decimal without depending on stdio. +static void format_u32(uint32_t value, char* text, size_t capacity) { + char reverse[16]; + size_t count = 0; + if (!text || capacity == 0) return; + do { + reverse[count++] = (char)('0' + value % 10u); + value /= 10u; + } while (value && count < sizeof(reverse)); + if (count + 1u > capacity) fail("numeric format overflow"); + for (size_t i = 0; i < count; ++i) text[i] = reverse[count - i - 1u]; + text[count] = '\0'; +} + +// Parses one nonzero decimal 32-bit value used by exec smoke stages. +static uint32_t parse_u32(const char* text) { + if (!text || !text[0]) return 0; + uint32_t value = 0; + for (size_t i = 0; text[i]; ++i) { + if (text[i] < '0' || text[i] > '9') return 0; + uint32_t digit = (uint32_t)(text[i] - '0'); + if (value > (UINT32_MAX - digit) / 10u) return 0; + value = value * 10u + digit; + } + return value; +} + +// Reports one exec-stage failure before preserving its distinct exit code. +static void exec_stage_fail(uint32_t code, const char* stage) { + char codeText[16]; + format_u32(code, codeText, sizeof(codeText)); + print("posix_process_smoke: exec stage failed: "); + print(stage); + print(" exit="); + print(codeText); + print("\n"); + _exit((int)code); +} + +// Exists only so exec can prove caught dispositions reset to default. +static void exec_caught_handler(int signalNumber) { + (void)signalNumber; +} + +// First child stage captures state and replaces its native generation. +static void run_exec_stage1(const char* self) { + pid_t stablePid = getpid(); + uint32_t nativePid = SysGetCurrentPid(); + if (stablePid <= 0 || nativePid == 0) _exit(120); + + int retained = open(gExecFile, O_CREAT | O_TRUNC | O_RDWR, 0666); + if (retained < 0) _exit(121); + move_fd(retained, EXEC_RETAIN_FD); + int cloexec = dup(EXEC_RETAIN_FD); + if (cloexec < 0) _exit(122); + move_fd(cloexec, EXEC_CLOEXEC_FD); + if (fcntl(EXEC_CLOEXEC_FD, F_SETFD, FD_CLOEXEC) != 0) _exit(123); + + struct sigaction ignored; + memset(&ignored, 0, sizeof(ignored)); + ignored.sa_handler = SIG_IGN; + if (sigaction(SIGUSR1, &ignored, (struct sigaction*)0) != 0) _exit(124); + struct sigaction caught; + memset(&caught, 0, sizeof(caught)); + caught.sa_handler = exec_caught_handler; + if (sigaction(SIGUSR2, &caught, (struct sigaction*)0) != 0) _exit(125); + sigset_t blocked; + if (sigemptyset(&blocked) != 0 || sigaddset(&blocked, SIGTERM) != 0 || + sigprocmask(SIG_BLOCK, &blocked, (sigset_t*)0) != 0) + _exit(126); + + char stableText[16]; + char nativeText[16]; + format_u32((uint32_t)stablePid, stableText, sizeof(stableText)); + format_u32(nativePid, nativeText, sizeof(nativeText)); + char* nextArgv[] = { (char*)self, "--p7-exec2", stableText, nativeText, (char*)0 }; + char* nextEnv[] = { "P7_ENV=replaced", (char*)0 }; + (void)execve(self, nextArgv, nextEnv); + { + char errnoText[16]; + format_u32((uint32_t)errno, errnoText, sizeof(errnoText)); + print("posix_process_smoke: execve stage1 returned errno="); + print(errnoText); + print("\n"); + _exit(127); + } +} + +// Second child stage validates one committed replacement and performs a repeated exec. +static void run_exec_stage2(int argc, char** argv) { + if (argc != 4) exec_stage_fail(128, "argv"); + uint32_t stablePid = parse_u32(argv[2]); + uint32_t oldNativePid = parse_u32(argv[3]); + uint32_t nativePid = SysGetCurrentPid(); + if (stablePid == 0 || oldNativePid == 0 || (uint32_t)getpid() != stablePid || + nativePid == 0 || nativePid == oldNativePid) + exec_stage_fail(129, "stable/native pid"); + const char* marker = getenv("P7_ENV"); + if (!marker || strcmp(marker, "replaced") != 0) exec_stage_fail(130, "environment"); + if (fcntl(EXEC_RETAIN_FD, F_GETFD) < 0) exec_stage_fail(131, "retained fd"); + errno = 0; + if (fcntl(EXEC_CLOEXEC_FD, F_GETFD) != -1 || errno != EBADF) + exec_stage_fail(132, "cloexec fd"); + + struct sigaction action; + memset(&action, 0, sizeof(action)); + if (sigaction(SIGUSR1, (const struct sigaction*)0, &action) != 0 || + action.sa_handler != SIG_IGN) + exec_stage_fail(133, "ignored disposition"); + if (sigaction(SIGUSR2, (const struct sigaction*)0, &action) != 0 || + action.sa_handler != SIG_DFL) + exec_stage_fail(134, "caught disposition reset"); + sigset_t blocked = 0; + if (sigprocmask(SIG_BLOCK, (const sigset_t*)0, &blocked) != 0 || + sigismember(&blocked, SIGTERM) != 1) + exec_stage_fail(135, "blocked mask"); + + if (write(EXEC_CHILD_WRITE_FD, "E", 1) != 1) exec_stage_fail(136, "marker write"); + char release = 0; + if (read(EXEC_CHILD_READ_FD, &release, 1) != 1 || release != 'R') _exit(137); + + char stableText[16]; + char nativeText[16]; + format_u32(stablePid, stableText, sizeof(stableText)); + format_u32(nativePid, nativeText, sizeof(nativeText)); + char self[512]; + get_self_path(self, sizeof(self)); + char* nextArgv[] = { self, "--p7-exec3", stableText, nativeText, (char*)0 }; + char* nextEnv[] = { "P7_ENV=second", (char*)0 }; + (void)execve(self, nextArgv, nextEnv); + _exit(138); +} + +// Final child stage proves repeated native generations retain the same POSIX PID. +static void run_exec_stage3(int argc, char** argv) { + if (argc != 4) _exit(139); + uint32_t stablePid = parse_u32(argv[2]); + uint32_t oldNativePid = parse_u32(argv[3]); + uint32_t nativePid = SysGetCurrentPid(); + if (stablePid == 0 || oldNativePid == 0 || (uint32_t)getpid() != stablePid || + nativePid == 0 || nativePid == oldNativePid) + _exit(140); + const char* marker = getenv("P7_ENV"); + if (!marker || strcmp(marker, "second") != 0 || fcntl(EXEC_RETAIN_FD, F_GETFD) < 0) + _exit(141); + if (write(EXEC_CHILD_WRITE_FD, "D", 1) != 1) _exit(142); + (void)close(EXEC_RETAIN_FD); + (void)close(EXEC_CHILD_WRITE_FD); + (void)close(EXEC_CHILD_READ_FD); + (void)unlink(gExecFile); + _exit(47); +} + +// Executes with Linux-style null argv/envp. The replacement retains the spawned child parent association. +static void run_null_exec_stage(const char* self) { + (void)execve(self, (char* const*)0, (char* const*)0); + _exit(143); +} + +// Exercises replacement, rollback, wait isolation, repeated generations, and null vectors. +static void run_exec_parent(const char* self) { + pid_t beforePid = getpid(); + uint32_t beforeNativePid = SysGetCurrentPid(); + char* missingArgv[] = { "/system/p7_missing_image.exe", (char*)0 }; + errno = 0; + if (execve(missingArgv[0], missingArgv, (char* const*)0) != -1 || + getpid() != beforePid || SysGetCurrentPid() != beforeNativePid) + fail("failed exec did not preserve old image"); + + int childToParent[2]; + int parentToChild[2]; + if (pipe(childToParent) != 0 || pipe(parentToChild) != 0) + fail("exec control pipe failed"); + move_fd(childToParent[0], EXEC_PARENT_READ_FD); + move_fd(childToParent[1], EXEC_CHILD_WRITE_FD); + move_fd(parentToChild[0], EXEC_CHILD_READ_FD); + move_fd(parentToChild[1], EXEC_PARENT_WRITE_FD); + + posix_spawn_file_actions_t actions; + if (posix_spawn_file_actions_init(&actions) != 0 || + posix_spawn_file_actions_addclose(&actions, EXEC_PARENT_READ_FD) != 0 || + posix_spawn_file_actions_addclose(&actions, EXEC_PARENT_WRITE_FD) != 0) + fail("exec spawn actions failed"); + char* childArgv[] = { (char*)self, "--p7-exec1", (char*)0 }; + pid_t child = (pid_t)-1; + int error = posix_spawn(&child, self, &actions, (const posix_spawnattr_t*)0, + childArgv, (char* const*)0); + if (error != 0 || child <= 0) fail("exec child spawn failed"); + if (posix_spawn_file_actions_destroy(&actions) != 0) fail("exec actions destroy failed"); + if (close(EXEC_CHILD_WRITE_FD) != 0 || close(EXEC_CHILD_READ_FD) != 0) + fail("exec parent child-end close failed"); + + char marker = 0; + if (read(EXEC_PARENT_READ_FD, &marker, 1) != 1 || marker != 'E') { + int earlyStatus = -1; + pid_t early = waitpid(child, &earlyStatus, WNOHANG); + if (early == child && WIFEXITED(earlyStatus)) { + char exitText[16]; + format_u32((uint32_t)WEXITSTATUS(earlyStatus), exitText, sizeof(exitText)); + print("posix_process_smoke: exec child exited before marker exit="); + print(exitText); + print("\n"); + } else if (early == 0) { + print("posix_process_smoke: exec child still running after marker channel closed\n"); + } else { + print("posix_process_smoke: exec child wait diagnostic unavailable\n"); + } + fail("exec replacement marker missing"); + } + int childStatus = -1; + if (waitpid(child, &childStatus, WNOHANG) != 0) + fail("exec retirement became waitable"); + if (write(EXEC_PARENT_WRITE_FD, "R", 1) != 1) + fail("exec replacement release failed"); + marker = 0; + if (read(EXEC_PARENT_READ_FD, &marker, 1) != 1 || marker != 'D') + fail("repeated exec marker missing"); + if (close(EXEC_PARENT_READ_FD) != 0 || close(EXEC_PARENT_WRITE_FD) != 0) + fail("exec parent pipe close failed"); + if (waitpid(child, &childStatus, 0) != child || !WIFEXITED(childStatus) || + WEXITSTATUS(childStatus) != 47) + fail("repeated exec child wait failed"); + + char* nullArgv[] = { (char*)self, "--p7-null", (char*)0 }; + child = (pid_t)-1; + error = posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0, + (const posix_spawnattr_t*)0, nullArgv, (char* const*)0); + if (error != 0 || child <= 0) fail("null-vector child spawn failed"); + childStatus = -1; + if (waitpid(child, &childStatus, 0) != child || !WIFEXITED(childStatus) || + WEXITSTATUS(childStatus) != 48) + fail("null argv/env exec failed"); +} + +// Validates the independent native generation created by vfork()+execve(). +static void run_vfork_exec_stage(int argc, char** argv) { + if (argc != 4) _exit(150); + uint32_t stablePid = parse_u32(argv[2]); + uint32_t parentNativePid = parse_u32(argv[3]); + if (stablePid == 0 || parentNativePid == 0 || (uint32_t)getpid() != stablePid || + SysGetCurrentPid() == 0 || SysGetCurrentPid() == parentNativePid) + _exit(151); + _exit(52); +} + +// Exercises both terminal vfork paths and the child-zero/parent-PID continuation split. +static void run_vfork_parent(const char* self) { + gVforkChildTouched = 0; + pid_t child = vfork(); + if (child < 0) fail("vfork _exit creation failed"); + if (child == 0) { + gVforkChildTouched = 1u; + _exit(51); + } + if (!gVforkChildTouched) fail("vfork parent resumed before child terminal path"); + int status = -1; + if (waitpid(child, &status, 0) != child || !WIFEXITED(status) || WEXITSTATUS(status) != 51) + fail("vfork _exit wait failed"); + + uint32_t parentNativePid = SysGetCurrentPid(); + child = vfork(); + if (child < 0) fail("vfork exec creation failed"); + if (child == 0) { + char stableText[16]; + char parentNativeText[16]; + format_u32((uint32_t)getpid(), stableText, sizeof(stableText)); + format_u32(parentNativePid, parentNativeText, sizeof(parentNativeText)); + char* nextArgv[] = { (char*)self, "--p7-vfork-exec", stableText, + parentNativeText, (char*)0 }; + (void)execve(self, nextArgv, (char* const*)0); + _exit(152); + } + status = -1; + if (waitpid(child, &status, 0) != child || !WIFEXITED(status) || WEXITSTATUS(status) != 52) + fail("vfork exec wait failed"); +} + +// Validates server-owned bootstrap state, then blocks until the parent releases the child. +static void run_full_child(void) { + pid_t pid = getpid(); + pid_t parent = getppid(); + if (pid <= 0 || parent <= 0 || parent == pid) + _exit(101); + if (getpgrp() != pid) + _exit(102); + + const char* marker = getenv("P5_ENV"); + if (!marker || strcmp(marker, "server-owned") != 0) + _exit(103); + const char* expectedCwd = getenv("P5_CWD"); + char cwd[512]; + if (!expectedCwd || !getcwd(cwd, sizeof(cwd)) || strcmp(cwd, expectedCwd) != 0) + _exit(104); + + errno = 0; + if (fcntl(ACTION_OPEN_FD, F_GETFD) != -1 || errno != EBADF) + _exit(105); + if (write(ACTION_DUP_FD, "ok", 2) != 2) + _exit(106); + if (close(ACTION_DUP_FD) != 0) + _exit(107); + errno = 0; + if (fcntl(CONTROL_WRITE_FD, F_GETFD) != -1 || errno != EBADF) + _exit(108); + + char release = 0; + if (read(CONTROL_READ_FD, &release, 1) != 1 || release != 'R') + _exit(109); + (void)close(CONTROL_READ_FD); + _exit(23); +} + +// Blocks one child until the parent supplies the exit code byte. +static void run_wait_child(void) { + if (getppid() <= 0 || getppid() == getpid()) + _exit(110); + char exitCode = 0; + if (read(CONTROL_READ_FD, &exitCode, 1) != 1) + _exit(111); + (void)close(CONTROL_READ_FD); + _exit((unsigned char)exitCode); +} + +// Spawns one child that waits on CONTROL_READ_FD. +static pid_t spawn_wait_child(const char* self, int writeFd) { + make_control_pipe(writeFd); + char* argv[] = { (char*)self, "--p5-wait", (char*)0 }; + pid_t child = (pid_t)-1; + int error = posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0, + (const posix_spawnattr_t*)0, argv, (char* const*)0); + if (error != 0 || child <= 0) + fail("wait child spawn failed"); + if (close(CONTROL_READ_FD) != 0) + fail("parent control read close failed"); + return child; +} + +// Exercises the P5 server-owned process construction and wait contract. +int main(int argc, char** argv) { + if (argc == 0) { + pid_t parent = getppid(); + if (parent < 0) _exit(145); + if (parent == 0) { + char self[512]; + get_self_path(self, sizeof(self)); + char* rootArgv[] = { self, "--p5-root", (char*)0 }; + pid_t child = (pid_t)-1; + int error = posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0, + (const posix_spawnattr_t*)0, rootArgv, environ); + if (error != 0 || child <= 0) _exit(146); + int childStatus = -1; + if (waitpid(child, &childStatus, 0) != child || !WIFEXITED(childStatus)) _exit(147); + _exit(WEXITSTATUS(childStatus)); + } + if (!environ || environ[0] != (char*)0) _exit(144); + _exit(48); + } + if (argc >= 2 && strcmp(argv[1], "--p7-exec1") == 0) run_exec_stage1(argv[0]); + if (argc >= 2 && strcmp(argv[1], "--p7-exec2") == 0) run_exec_stage2(argc, argv); + if (argc >= 2 && strcmp(argv[1], "--p7-exec3") == 0) run_exec_stage3(argc, argv); + if (argc >= 2 && strcmp(argv[1], "--p7-null") == 0) run_null_exec_stage(argv[0]); + if (argc >= 2 && strcmp(argv[1], "--p7-vfork-exec") == 0) run_vfork_exec_stage(argc, argv); + if (child_mode("--p5-full")) run_full_child(); + if (child_mode("--p5-wait")) run_wait_child(); + + char self[512]; + get_self_path(self, sizeof(self)); + + char cwd[512]; + if (!getcwd(cwd, sizeof(cwd))) + fail("parent getcwd failed"); + const char cwdPrefix[] = "P5_CWD="; + char cwdEnvironment[sizeof(cwdPrefix) + sizeof(cwd)]; + size_t prefixLength = sizeof(cwdPrefix) - 1u; + size_t cwdLength = strlen(cwd); + if (prefixLength + cwdLength + 1u > sizeof(cwdEnvironment)) + fail("cwd environment overflow"); + memcpy(cwdEnvironment, cwdPrefix, prefixLength); + memcpy(cwdEnvironment + prefixLength, cwd, cwdLength + 1u); + + const char* actionPath = "posix_process_spawn.tmp"; + const char* collisionPath = "posix_process_collision.tmp"; + (void)unlink(actionPath); + (void)unlink(collisionPath); + make_control_pipe(CONTROL_WRITE_FD); + + // Seed the dup2 target with an unrelated inherited description so the child + // action must replace an already-occupied descriptor number. + int collision = open(collisionPath, O_CREAT | O_TRUNC | O_RDWR, 0666); + if (collision < 0) + fail("spawn collision source open failed"); + move_fd(collision, ACTION_DUP_FD); + + posix_spawn_file_actions_t actions; + if (posix_spawn_file_actions_init(&actions) != 0 || + posix_spawn_file_actions_addopen(&actions, ACTION_OPEN_FD, actionPath, + O_CREAT | O_TRUNC | O_RDWR, 0666) != 0 || + posix_spawn_file_actions_adddup2(&actions, ACTION_OPEN_FD, ACTION_DUP_FD) != 0 || + posix_spawn_file_actions_addclose(&actions, ACTION_OPEN_FD) != 0) + fail("spawn file actions setup failed"); + + posix_spawnattr_t attributes; + if (posix_spawnattr_init(&attributes) != 0 || + posix_spawnattr_setpgroup(&attributes, 0) != 0 || + posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETPGROUP) != 0) + fail("spawn attributes setup failed"); + + char* childArgv[] = { self, "--p5-full", (char*)0 }; + char* childEnv[] = { "P5_ENV=server-owned", cwdEnvironment, (char*)0 }; + pid_t child = (pid_t)-1; + int spawnError = posix_spawn(&child, self, &actions, &attributes, childArgv, childEnv); + if (spawnError != 0 || child <= 0) + fail("full child spawn failed"); + if (posix_spawn_file_actions_destroy(&actions) != 0 || + posix_spawnattr_destroy(&attributes) != 0) + fail("spawn setup destroy failed"); + if (close(ACTION_DUP_FD) != 0) + fail("parent collision descriptor close failed"); + if (unlink(collisionPath) != 0) + fail("parent collision source cleanup failed"); + if (close(CONTROL_READ_FD) != 0) + fail("full parent control read close failed"); + + int childStatus = -1; + if (waitpid(child, &childStatus, WNOHANG) != 0) + fail("WNOHANG did not report running child"); + if (write(CONTROL_WRITE_FD, "R", 1) != 1 || close(CONTROL_WRITE_FD) != 0) + fail("full child release failed"); + if (waitpid(child, &childStatus, 0) != child || !WIFEXITED(childStatus) || + WEXITSTATUS(childStatus) != 23) + fail("exact child wait failed"); + + int result = open(actionPath, O_RDONLY); + char actionBytes[3] = {0, 0, 0}; + if (result < 0 || read(result, actionBytes, 2) != 2 || strcmp(actionBytes, "ok") != 0) + fail("spawn file action result was wrong"); + if (close(result) != 0 || unlink(actionPath) != 0) + fail("spawn action cleanup failed"); + + errno = 0; + if (waitpid(child, &childStatus, WNOHANG) != (pid_t)-1 || errno != ECHILD) + fail("collected child remained waitable"); + + pid_t failedPid = 123; + if (posix_spawn(&failedPid, "/system/p5_missing_image.exe", + (const posix_spawn_file_actions_t*)0, (const posix_spawnattr_t*)0, + (char* const*)0, (char* const*)0) == 0 || failedPid != (pid_t)-1) + fail("failed construction returned a child"); + + pid_t first = spawn_wait_child(self, CONTROL_WRITE_A_FD); + pid_t second = spawn_wait_child(self, CONTROL_WRITE_FD); + char secondCode = 32; + if (write(CONTROL_WRITE_FD, &secondCode, 1) != 1 || close(CONTROL_WRITE_FD) != 0) + fail("second child release failed"); + childStatus = -1; + if (wait(&childStatus) != second || WEXITSTATUS(childStatus) != 32) + fail("wait did not collect second child first"); + + char firstCode = 31; + if (write(CONTROL_WRITE_A_FD, &firstCode, 1) != 1 || close(CONTROL_WRITE_A_FD) != 0) + fail("first child release failed"); + childStatus = -1; + if (wait(&childStatus) != first || WEXITSTATUS(childStatus) != 31) + fail("wait did not collect first child second"); + + errno = 0; + if (wait((int*)0) != (pid_t)-1 || errno != ECHILD) + fail("wait without children did not return ECHILD"); + + run_exec_parent(self); + run_vfork_parent(self); + + print("posix_process_smoke: PASS\n"); + return 0; +} diff --git a/code/user/tests/posix_service_smoke/posix_service_smoke.c b/code/user/tests/posix_service_smoke/posix_service_smoke.c index 5cd7272..8ad0e0e 100644 --- a/code/user/tests/posix_service_smoke/posix_service_smoke.c +++ b/code/user/tests/posix_service_smoke/posix_service_smoke.c @@ -147,33 +147,52 @@ static NTL_POSIX_REQUEST make_request(uint32_t operation) { return request; } -// Registers the current native process by transferring a restricted self process capability. +// Registers the current native process through the accepted Port peer capability. static KSTATUS register_current_process(SYS_HANDLE endpoint, NTL_POSIX_REPLY* replyOut) { + NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_REGISTER_PROCESS); + size_t cwdRequired = 0; + KSTATUS status = NtlGetCurrentDirectory(request.Path, sizeof(request.Path), &cwdRequired); + if (!KSUCCESS(status)) return status; + request.PathLength = (uint32_t)strlen(request.Path); + return call_posixsrv(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, replyOut); +} + +// Provides a valid user APC target for protocol-level thread registration tests. +static void __stdcall signal_probe_apc(void* context, void* argument1, void* argument2) { + (void)context; + (void)argument1; + (void)argument2; +} + +// Registers the current thread by transferring a restricted self capability. +static KSTATUS register_current_thread(SYS_HANDLE endpoint, NTL_POSIX_REPLY* replyOut) { SYS_HANDLE self = SYS_HANDLE_INVALID; - KSTATUS status = SysOpenProcess(SysGetCurrentPid(), - SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_RIGHT_TRANSFER, - &self); + KSTATUS status = SysOpenCurrentThread(SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | SYS_THREAD_RIGHT_NOTIFY, + &self); if (!KSUCCESS(status)) return status; SYS_PORT_HANDLE_TRANSFER transfer; memset(&transfer, 0, sizeof(transfer)); transfer.Handle = self; - transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT; + transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_THREAD_RIGHT_NOTIFY; transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE; - NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_REGISTER_PROCESS); - size_t cwdRequired = 0; - status = NtlGetCurrentDirectory(request.Path, sizeof(request.Path), &cwdRequired); - if (!KSUCCESS(status)) { - (void)SysCloseHandle(self); - return status; - } - request.PathLength = (uint32_t)strlen(request.Path); + NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_REGISTER_THREAD); + request.SignalApcRoutine = (uint64_t)(uintptr_t)signal_probe_apc; status = call_posixsrv(endpoint, &request, &transfer, 1u, replyOut); (void)SysCloseHandle(self); return status; } +// Unregisters the calling thread using the Port sender identity. +static KSTATUS unregister_current_thread(SYS_HANDLE endpoint, NTL_POSIX_REPLY* replyOut) { + NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_UNREGISTER_THREAD); + KSTATUS status = call_posixsrv(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, + replyOut); + if (!KSUCCESS(status)) return status; + return replyOut->Status; +} + // Creates one handle-less server-owned description and returns its stable identity. static uint64_t create_synthetic_description(SYS_HANDLE endpoint, uint32_t type, @@ -200,6 +219,146 @@ static KSTATUS release_description(SYS_HANDLE endpoint, uint64_t descriptionId, return replyOut->Status; } +typedef struct VFORK_SUBSTRATE_CONTEXT { + SYS_HANDLE Endpoint; + uint32_t ParentPid; + uint32_t ChildPid; + uint64_t Token; + uint64_t FdToken; + uint64_t DescriptionId; + volatile uint32_t Result; +} VFORK_SUBSTRATE_CONTEXT; + +// Attaches one native thread to a borrowed logical process and verifies routed FD ownership. +static void vfork_substrate_thread(uintptr_t argument) { + VFORK_SUBSTRATE_CONTEXT* context = (VFORK_SUBSTRATE_CONTEXT*)argument; + if (!context) SysExitThread(1); + + SYS_HANDLE self = SYS_HANDLE_INVALID; + KSTATUS status = SysOpenCurrentThread(SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_RIGHT_DERIVE | + SYS_RIGHT_TRANSFER | SYS_THREAD_RIGHT_NOTIFY, &self); + if (!KSUCCESS(status)) { context->Result = 1u; SysExitThread(1); } + SYS_PORT_HANDLE_TRANSFER transfer; + memset(&transfer, 0, sizeof(transfer)); + transfer.Handle = self; + transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_RIGHT_WAIT | SYS_RIGHT_DERIVE | + SYS_THREAD_RIGHT_NOTIFY; + + NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_VFORK_ATTACH); + request.TargetPosixPid = context->ChildPid; + request.VforkToken = context->Token; + request.SignalApcRoutine = (uint64_t)(uintptr_t)signal_probe_apc; + NTL_POSIX_REPLY reply; + status = call_posixsrv(context->Endpoint, &request, &transfer, 1u, &reply); + (void)SysCloseHandle(self); + if (!KSUCCESS(status) || !KSUCCESS(reply.Status) || reply.PosixPid != context->ChildPid || + reply.ProcessState != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED || + reply.VforkToken != context->Token) { + context->Result = 2u; + SysExitThread(2); + } + + request = make_request(NTL_POSIX_OP_GET_PROCESS_ATTRIBUTES); + status = call_posixsrv(context->Endpoint, &request, + (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply); + if (!KSUCCESS(status) || !KSUCCESS(reply.Status) || reply.PosixPid != context->ChildPid || + reply.ParentPosixPid != context->ParentPid || + reply.ProcessState != NTL_POSIX_PROCESS_STATE_VFORK_BORROWED) { + context->Result = 3u; + SysExitThread(3); + } + + request = make_request(NTL_POSIX_OP_IMPORT_FD_INHERITANCE); + request.FdInheritanceToken = context->FdToken; + request.FdInheritanceIndex = 0; + status = call_posixsrv_exchange(context->Endpoint, &request, + (const SYS_PORT_HANDLE_TRANSFER*)0, 0, + (SYS_HANDLE*)0, 0, 0, &reply); + if (!KSUCCESS(status) || !KSUCCESS(reply.Status) || reply.FdInheritanceCount != 1u || + reply.FdDescriptionTotal != 1u || reply.FdDescriptionCount != 1u || + reply.FdManifest[0].Descriptor != 7 || + reply.FdManifest[0].OpenDescriptionId != context->DescriptionId || + reply.FdDescriptions[0].OpenDescriptionId != context->DescriptionId) { + context->Result = 4u; + SysExitThread(4); + } + + request = make_request(NTL_POSIX_OP_RELEASE_OPEN_DESCRIPTION); + request.OpenDescriptionId = context->DescriptionId; + status = call_posixsrv(context->Endpoint, &request, + (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply); + if (!KSUCCESS(status) || !KSUCCESS(reply.Status)) { + context->Result = 5u; + SysExitThread(5); + } + + context->Result = 0u; + SysExitThread(0); +} + +// Exercises the server-owned borrowed-process record and sender-TID logical routing. +static void test_vfork_substrate(SYS_HANDLE endpoint, + uint32_t parentPid, + uint64_t descriptionId, + uint32_t baselineProcesses, + uint32_t baselineThreads) { + NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_CREATE_FD_INHERITANCE); + request.FdInheritanceCount = 1u; + request.FdManifest[0].Descriptor = 7; + request.FdManifest[0].OpenDescriptionId = descriptionId; + NTL_POSIX_REPLY reply; + if (!KSUCCESS(call_posixsrv(endpoint, &request, + (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) || + !KSUCCESS(reply.Status) || reply.FdInheritanceToken == 0) + fail("vfork descriptor snapshot failed"); + uint64_t fdToken = reply.FdInheritanceToken; + + request = make_request(NTL_POSIX_OP_VFORK_BEGIN); + request.FdInheritanceToken = fdToken; + if (!KSUCCESS(call_posixsrv(endpoint, &request, + (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) || + !KSUCCESS(reply.Status) || reply.SpawnedPosixPid == 0 || reply.VforkToken == 0 || + reply.RegisteredProcesses != baselineProcesses + 1u) + fail("vfork borrowed child creation failed"); + + VFORK_SUBSTRATE_CONTEXT context; + memset(&context, 0, sizeof(context)); + context.Endpoint = endpoint; + context.ParentPid = parentPid; + context.ChildPid = reply.SpawnedPosixPid; + context.Token = reply.VforkToken; + context.FdToken = fdToken; + context.DescriptionId = descriptionId; + context.Result = UINT32_MAX; + + SYS_HANDLE thread = SYS_HANDLE_INVALID; + uint32_t tid = 0; + SYS_CREATE_THREAD_REQUEST create; + memset(&create, 0, sizeof(create)); + create.Size = sizeof(create); + create.Entry = (uintptr_t)vfork_substrate_thread; + create.Argument = (uintptr_t)&context; + create.Priority = 1u; + create.ThreadHandleOut = &thread; + create.ThreadIdOut = &tid; + if (!KSUCCESS(SysCreateThread(&create)) || thread == SYS_HANDLE_INVALID || tid == 0) + fail("vfork borrowed native thread creation failed"); + if (!KSUCCESS(SysWait(thread, SYS_WAIT_FLAG_NONE))) + fail("vfork borrowed native thread wait failed"); + (void)SysCloseHandle(thread); + if (context.Result != 0u) + fail("vfork borrowed thread routing failed"); + + request = make_request(NTL_POSIX_OP_VFORK_CANCEL); + request.TargetPosixPid = context.ChildPid; + request.VforkToken = context.Token; + if (!KSUCCESS(call_posixsrv(endpoint, &request, + (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) || + !KSUCCESS(reply.Status) || reply.RegisteredProcesses != baselineProcesses || + reply.RegisteredThreads != baselineThreads) + fail("vfork borrowed child cleanup failed"); +} + // Waits for the process monitor to retire one process and all of its shared holders. static void wait_registry_counts(SYS_HANDLE endpoint, uint32_t expectedProcesses, @@ -354,7 +513,22 @@ int main(void) { strcmp(reply.CurrentDirectory, registeredCwd) != 0) fail("registered cwd mismatch"); + uint32_t baselineProcesses = reply.RegisteredProcesses; uint32_t baselineDescriptions = reply.RegisteredOpenDescriptions; + uint32_t baselineThreads = reply.RegisteredThreads; + + if (!KSUCCESS(register_current_thread(first, &reply)) || !KSUCCESS(reply.Status) || + reply.RegisteredThreads != baselineThreads + 1u) + fail("thread registration failed"); + if (!KSUCCESS(register_current_thread(first, &reply)) || !KSUCCESS(reply.Status) || + reply.RegisteredThreads != baselineThreads + 1u) + fail("duplicate thread registration was not idempotent"); + if (!KSUCCESS(unregister_current_thread(first, &reply)) || + reply.RegisteredThreads != baselineThreads) + fail("thread unregister failed"); + if (unregister_current_thread(first, &reply) != NTLSTATUS_NOT_FOUND) + fail("duplicate thread unregister was accepted"); + NTL_POSIX_REPLY descriptionReply; uint64_t descriptionId = create_synthetic_description( first, NTL_POSIX_OPEN_TYPE_DEV_NULL, NTL_POSIX_OPEN_STATUS_WRONLY, @@ -381,6 +555,9 @@ int main(void) { (uint32_t)shared->StatusFlags != request.OpenStatusFlags) fail("server-serialized status update failed"); + test_vfork_substrate(first, stablePid, descriptionId, + baselineProcesses, baselineThreads); + if (!KSUCCESS(release_description(first, descriptionId, &descriptionReply)) || descriptionReply.RegisteredOpenDescriptions != baselineDescriptions) fail("shared description release failed"); @@ -469,8 +646,8 @@ int main(void) { request = make_request(NTL_POSIX_OP_QUERY); if (!KSUCCESS(call_posixsrv(second, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) || - !KSUCCESS(reply.Status) || reply.ActiveTransactions != 0 || reply.PosixPid != stablePid) - fail("connection transaction isolation failed"); + !KSUCCESS(reply.Status) || reply.ActiveTransactions != 1 || reply.PosixPid != stablePid) + fail("process transaction sharing failed"); request = make_request(NTL_POSIX_OP_QUERY); request.Version = NTL_POSIX_PROTOCOL_VERSION + 1u; @@ -480,9 +657,9 @@ int main(void) { request = make_request(NTL_POSIX_OP_CANCEL_TRANSACTION); request.TransactionId = transactionId; - if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) || + if (!KSUCCESS(call_posixsrv(second, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) || !KSUCCESS(reply.Status) || reply.TransactionId != transactionId || reply.ActiveTransactions != 0) - fail("transaction cancel failed"); + fail("cross-connection transaction cancel failed"); request = make_request(NTL_POSIX_OP_CANCEL_TRANSACTION); request.TransactionId = transactionId; @@ -493,8 +670,9 @@ int main(void) { request = make_request(NTL_POSIX_OP_BEGIN_TRANSACTION); request.TransactionKind = NTL_POSIX_TRANSACTION_EXEC; if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) || - !KSUCCESS(reply.Status)) + !KSUCCESS(reply.Status) || reply.TransactionId == 0 || reply.ActiveTransactions != 1) fail("exec transaction begin failed"); + uint32_t reconnectTransactionId = reply.TransactionId; (void)SysDisconnectPort(first); (void)SysCloseHandle(first); @@ -504,8 +682,13 @@ int main(void) { SYS_HANDLE third = SYS_HANDLE_INVALID; if (!KSUCCESS(connect_posixsrv(&third))) fail("reconnect failed"); if (!KSUCCESS(register_current_process(third, &reply)) || !KSUCCESS(reply.Status) || - reply.PosixPid != stablePid || reply.NativeGeneration != 1u || reply.ActiveTransactions != 0) - fail("live process lost identity after disconnect"); + reply.PosixPid != stablePid || reply.NativeGeneration != 1u || reply.ActiveTransactions != 1) + fail("live process lost process-owned transaction after disconnect"); + request = make_request(NTL_POSIX_OP_CANCEL_TRANSACTION); + request.TransactionId = reconnectTransactionId; + if (!KSUCCESS(call_posixsrv(third, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) || + !KSUCCESS(reply.Status) || reply.ActiveTransactions != 0) + fail("reconnected transaction cancel failed"); (void)SysDisconnectPort(third); (void)SysCloseHandle(third); diff --git a/code/user/tests/posix_signal_smoke/posix_signal_smoke.c b/code/user/tests/posix_signal_smoke/posix_signal_smoke.c new file mode 100644 index 0000000..9cebc77 --- /dev/null +++ b/code/user/tests/posix_signal_smoke/posix_signal_smoke.c @@ -0,0 +1,459 @@ +// Runtime coverage for P6 POSIX signal delivery, waits, spawn state, and exceptions. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GROUP_READ_FD 50 +#define GROUP_WRITE_FD 51 + +static volatile sig_atomic_t gHandlerCount; +static volatile uint32_t gHandlerTid; +static volatile int gWorkerReady; +static volatile int gWorkerInheritedBlock; +static volatile int gWorkerStop; +static volatile sig_atomic_t gNestedCount; +static volatile sig_atomic_t gAlarmCount; +static volatile sig_atomic_t gPipeCount; + +// Writes one test diagnostic line to the native console. +static void print(const char* text) { + SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) }; + (void)SysWriteConsole(&request); +} + +// Exits immediately after one failed assertion. +static void fail(const char* message) { + print("posix_signal_smoke: FAIL: "); + print(message); + print("\n"); + _exit(1); +} + +// Returns whether the compatibility command line contains one child marker. +static int child_mode(const char* marker) { + const char* command = posix_get_command_line(); + return command && marker && strstr(command, marker) != (char*)0; +} + +// Resolves the current executable path from the compatibility command line. +static void get_self_path(char* path, size_t capacity) { + const char* command = posix_get_command_line(); + size_t length = 0; + while (command && command[length] && command[length] != ' ' && command[length] != '\t' && + length + 1u < capacity) { + path[length] = command[length]; + ++length; + } + path[length] = '\0'; + if (length == 0 || (command[length] && command[length] != ' ' && command[length] != '\t')) + _exit(90); +} + +// Moves one descriptor to an exact number and closes the source alias. +static void move_fd(int source, int target) { + if (source < 0 || dup2(source, target) != target) + fail("control dup2 failed"); + if (source != target && close(source) != 0) + fail("control source close failed"); +} + +// Records the native thread selected for one caught user signal. +static void signal_handler(int signalNumber) { + if (signalNumber != SIGUSR1) return; + gHandlerTid = SysGetCurrentTid(); + ++gHandlerCount; +} + +// Queues the same signal once while its first handler activation masks it. +static void nested_handler(int signalNumber) { + if (signalNumber != SIGUSR1) return; + ++gNestedCount; + if (gNestedCount == 1 && raise(SIGUSR1) != 0) + _exit(91); +} + +// Records one SIGALRM used to interrupt alertable POSIX waits. +static void alarm_handler(int signalNumber) { + if (signalNumber == SIGALRM) ++gAlarmCount; +} + +// Records SIGPIPE generation while allowing write() to return EPIPE. +static void pipe_handler(int signalNumber) { + if (signalNumber == SIGPIPE) ++gPipeCount; +} + +// Terminates the process-group child from asynchronous SIGUSR1 delivery. +static void group_handler(int signalNumber) { + if (signalNumber == SIGUSR1) _exit(61); +} + +// Terminates the exception child after native access-fault translation. +static void fault_handler(int signalNumber) { + if (signalNumber == SIGSEGV) _exit(62); +} + +// Keeps one registered unblocked POSIX thread available for process-directed delivery. +static void* signal_worker(void* argument) { + (void)argument; + sigset_t current = 0; + if (sigprocmask(SIG_SETMASK, (const sigset_t*)0, ¤t) != 0) + return (void*)(uintptr_t)1u; + gWorkerInheritedBlock = sigismember(¤t, SIGUSR1) == 1; + + sigset_t unblock = 0; + if (sigemptyset(&unblock) != 0 || sigaddset(&unblock, SIGUSR1) != 0 || + sigprocmask(SIG_UNBLOCK, &unblock, (sigset_t*)0) != 0) + return (void*)(uintptr_t)1u; + + gWorkerReady = 1; + while (!gWorkerStop && gHandlerCount < 3) (void)SysYield(); + return (void*)0; +} + +// Validates signal mask/default attributes before ordinary child entry proceeds. +static void run_spawn_attribute_child(void) { + sigset_t current = 0; + if (sigprocmask(SIG_SETMASK, (const sigset_t*)0, ¤t) != 0 || + sigismember(¤t, SIGUSR1) != 1) + _exit(70); + struct sigaction action; + if (sigaction(SIGUSR2, (const struct sigaction*)0, &action) != 0 || + action.sa_handler != SIG_DFL) + _exit(71); + _exit(0); +} + +// Announces readiness, then waits for one process-group-directed SIGUSR1. +static void run_group_child(void) { + struct sigaction action; + action.sa_handler = group_handler; + action.sa_mask = 0; + action.sa_flags = 0; + if (sigaction(SIGUSR1, &action, (struct sigaction*)0) != 0) + _exit(72); + if (write(GROUP_WRITE_FD, "R", 1) != 1) + _exit(73); + (void)close(GROUP_WRITE_FD); + for (;;) (void)pause(); +} + +// Waits for the parent to exercise one ordinary default-terminate disposition. +static void run_default_child(void) { + for (;;) (void)pause(); +} + +// Triggers one native access fault after installing a POSIX SIGSEGV handler. +static void run_exception_child(void) { + struct sigaction action; + action.sa_handler = fault_handler; + action.sa_mask = 0; + action.sa_flags = 0; + if (sigaction(SIGSEGV, &action, (struct sigaction*)0) != 0) + _exit(74); + *(volatile uint64_t*)(uintptr_t)1u = 0x1234u; + _exit(75); +} + +// Spawns one child and requires one exact ordinary exit result. +static void wait_for_child(pid_t child, int expectedExit) { + int status = -1; + if (waitpid(child, &status, 0) != child || !WIFEXITED(status) || + WEXITSTATUS(status) != expectedExit) + fail("child exit result"); +} + +// Verifies spawn-time signal masks and default-disposition reset policy. +static void test_spawn_signal_attributes(const char* self) { + struct sigaction ignore; + ignore.sa_handler = SIG_IGN; + ignore.sa_mask = 0; + ignore.sa_flags = 0; + if (sigaction(SIGUSR2, &ignore, (struct sigaction*)0) != 0) + fail("install inherited ignore"); + + sigset_t mask = 0; + sigset_t defaults = 0; + if (sigemptyset(&mask) != 0 || sigaddset(&mask, SIGUSR1) != 0 || + sigemptyset(&defaults) != 0 || sigaddset(&defaults, SIGUSR2) != 0) + fail("spawn signal sets"); + + posix_spawnattr_t attributes; + if (posix_spawnattr_init(&attributes) != 0 || + posix_spawnattr_setsigmask(&attributes, &mask) != 0 || + posix_spawnattr_setsigdefault(&attributes, &defaults) != 0 || + posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF) != 0) + fail("spawn signal attributes"); + sigset_t check = 0; + if (posix_spawnattr_getsigmask(&attributes, &check) != 0 || check != mask || + posix_spawnattr_getsigdefault(&attributes, &check) != 0 || check != defaults) + fail("spawn signal attribute getters"); + + char* argv[] = { (char*)self, "--p6-spawn-attrs", (char*)0 }; + pid_t child = (pid_t)-1; + if (posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0, + &attributes, argv, (char* const*)0) != 0 || child <= 0) + fail("spawn signal child"); + if (posix_spawnattr_destroy(&attributes) != 0) + fail("spawn attribute destroy"); + wait_for_child(child, 0); + + struct sigaction reset; + reset.sa_handler = SIG_DFL; + reset.sa_mask = 0; + reset.sa_flags = 0; + if (sigaction(SIGUSR2, &reset, (struct sigaction*)0) != 0) + fail("restore SIGUSR2 default"); +} + +// Verifies actual process-group routing to a child in a newly-created group. +static void test_process_group_delivery(const char* self) { + int descriptors[2]; + if (pipe(descriptors) != 0) + fail("group pipe"); + move_fd(descriptors[0], GROUP_READ_FD); + move_fd(descriptors[1], GROUP_WRITE_FD); + if (fcntl(GROUP_READ_FD, F_SETFD, FD_CLOEXEC) != 0) + fail("group read CLOEXEC"); + + posix_spawnattr_t attributes; + if (posix_spawnattr_init(&attributes) != 0 || + posix_spawnattr_setpgroup(&attributes, 0) != 0 || + posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETPGROUP) != 0) + fail("group spawn attributes"); + char* argv[] = { (char*)self, "--p6-group-child", (char*)0 }; + pid_t child = (pid_t)-1; + if (posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0, + &attributes, argv, (char* const*)0) != 0 || child <= 0) + fail("group child spawn"); + if (posix_spawnattr_destroy(&attributes) != 0) + fail("group spawn attribute destroy"); + if (close(GROUP_WRITE_FD) != 0) + fail("group parent write close"); + + char ready = 0; + if (read(GROUP_READ_FD, &ready, 1) != 1 || ready != 'R') + fail("group child readiness"); + if (close(GROUP_READ_FD) != 0) + fail("group parent read close"); + if (kill(-child, SIGUSR1) != 0) + fail("process-group signal delivery"); + wait_for_child(child, 61); +} + +// Verifies an ordinary default disposition terminates the target process. +static void test_default_disposition(const char* self) { + char* argv[] = { (char*)self, "--p6-default", (char*)0 }; + pid_t child = (pid_t)-1; + if (posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0, + (const posix_spawnattr_t*)0, argv, (char* const*)0) != 0 || child <= 0) + fail("default-disposition child spawn"); + if (kill(child, SIGTERM) != 0) + fail("default-disposition signal"); + wait_for_child(child, 128 + SIGTERM); +} + +// Verifies selected native exceptions enter the POSIX signal adapter. +static void test_exception_translation(const char* self) { + char* argv[] = { (char*)self, "--p6-exception", (char*)0 }; + pid_t child = (pid_t)-1; + if (posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0, + (const posix_spawnattr_t*)0, argv, (char* const*)0) != 0 || child <= 0) + fail("exception child spawn"); + wait_for_child(child, 62); +} + +// Verifies alarm-backed waits and EINTR behavior for a blocked pipe read. +static void test_signal_waits(void) { + struct sigaction action; + action.sa_handler = alarm_handler; + action.sa_mask = 0; + action.sa_flags = 0; + if (sigaction(SIGALRM, &action, (struct sigaction*)0) != 0) + fail("install SIGALRM handler"); + + if (alarm(3) != 0) + fail("initial alarm remaining"); + unsigned int remaining = alarm(0); + if (remaining < 2u || remaining > 3u) + fail("alarm replacement remaining"); + + sigset_t alarmSet = 0; + sigset_t previous = 0; + if (sigemptyset(&alarmSet) != 0 || sigaddset(&alarmSet, SIGALRM) != 0 || + sigprocmask(SIG_BLOCK, &alarmSet, &previous) != 0) + fail("block SIGALRM"); + sigset_t temporary = previous; + if (sigdelset(&temporary, SIGALRM) != 0) + fail("sigsuspend mask"); + sig_atomic_t before = gAlarmCount; + (void)alarm(1); + errno = 0; + if (sigsuspend(&temporary) != -1 || errno != EINTR || gAlarmCount != before + 1) + fail("sigsuspend signal wake"); + sigset_t current = 0; + if (sigprocmask(SIG_SETMASK, (const sigset_t*)0, ¤t) != 0 || + sigismember(¤t, SIGALRM) != 1) + fail("sigsuspend mask restoration"); + if (sigprocmask(SIG_SETMASK, &previous, (sigset_t*)0) != 0) + fail("restore SIGALRM mask"); + + before = gAlarmCount; + (void)alarm(1); + errno = 0; + if (pause() != -1 || errno != EINTR || gAlarmCount != before + 1) + fail("pause signal wake"); + + before = gAlarmCount; + (void)alarm(1); + remaining = sleep(3); + if (gAlarmCount != before + 1 || remaining < 1u || remaining > 2u) + fail("sleep interruption"); + + int descriptors[2]; + if (pipe(descriptors) != 0) + fail("EINTR pipe create"); + before = gAlarmCount; + (void)alarm(1); + char byte = 0; + errno = 0; + if (read(descriptors[0], &byte, 1) != -1 || errno != EINTR || gAlarmCount != before + 1) + fail("blocked pipe read EINTR"); + (void)alarm(0); + if (close(descriptors[0]) != 0 || close(descriptors[1]) != 0) + fail("EINTR pipe cleanup"); + + action.sa_handler = pipe_handler; + action.sa_mask = 0; + action.sa_flags = 0; + if (sigaction(SIGPIPE, &action, (struct sigaction*)0) != 0) + fail("install SIGPIPE handler"); + if (pipe(descriptors) != 0 || close(descriptors[0]) != 0) + fail("SIGPIPE pipe setup"); + sig_atomic_t pipeBefore = gPipeCount; + errno = 0; + if (write(descriptors[1], "P", 1) != -1 || errno != EPIPE || + gPipeCount != pipeBefore + 1) + fail("SIGPIPE broken-pipe delivery"); + if (close(descriptors[1]) != 0) + fail("SIGPIPE pipe cleanup"); +} + +// Exercises the complete P6 signal contract used by later exec/vfork stages. +int main(void) { + if (child_mode("--p6-spawn-attrs")) run_spawn_attribute_child(); + if (child_mode("--p6-group-child")) run_group_child(); + if (child_mode("--p6-default")) run_default_child(); + if (child_mode("--p6-exception")) run_exception_child(); + + print("posix_signal_smoke: "); + + sigset_t set; + if (sigemptyset(&set) != 0 || set != 0) fail("sigemptyset"); + if (sigaddset(&set, SIGUSR1) != 0 || sigismember(&set, SIGUSR1) != 1) + fail("sigaddset/sigismember"); + if (sigdelset(&set, SIGUSR1) != 0 || sigismember(&set, SIGUSR1) != 0) + fail("sigdelset"); + if (sigfillset(&set) != 0 || sigismember(&set, SIGKILL) != 1) + fail("sigfillset"); + errno = 0; + if (sigaddset(&set, 0) != -1 || errno != EINVAL) fail("invalid signal set member"); + + struct sigaction action; + action.sa_handler = signal_handler; + action.sa_mask = 0; + action.sa_flags = 0; + if (sigaction(SIGUSR1, &action, (struct sigaction*)0) != 0) + fail("install handler"); + + if (raise(SIGUSR1) != 0 || gHandlerCount != 1 || gHandlerTid != SysGetCurrentTid()) + fail("raise delivery"); + + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &set, (sigset_t*)0) != 0) fail("block signal"); + if (raise(SIGUSR1) != 0 || gHandlerCount != 1) fail("blocked raise delivered"); + sigset_t pending = 0; + if (sigpending(&pending) != 0 || sigismember(&pending, SIGUSR1) != 1) + fail("thread pending signal"); + if (sigprocmask(SIG_UNBLOCK, &set, (sigset_t*)0) != 0 || gHandlerCount != 2) + fail("pending delivery after unblock"); + if (sigpending(&pending) != 0 || sigismember(&pending, SIGUSR1) != 0) + fail("pending signal not cleared"); + + struct sigaction nested; + nested.sa_handler = nested_handler; + nested.sa_mask = 0; + nested.sa_flags = 0; + if (sigaction(SIGUSR1, &nested, (struct sigaction*)0) != 0 || + raise(SIGUSR1) != 0 || gNestedCount != 2) + fail("nested pending delivery"); + if (sigaction(SIGUSR1, &action, (struct sigaction*)0) != 0) + fail("restore handler after nested delivery"); + + struct sigaction ignore; + ignore.sa_handler = SIG_IGN; + ignore.sa_mask = 0; + ignore.sa_flags = 0; + if (sigaction(SIGUSR1, &ignore, (struct sigaction*)0) != 0) fail("ignore disposition"); + if (kill(getpid(), SIGUSR1) != 0) fail("ignored kill"); + for (uint32_t i = 0; i < 32u; ++i) (void)SysYield(); + if (gHandlerCount != 2) fail("ignored signal reached handler"); + if (sigaction(SIGUSR1, &action, (struct sigaction*)0) != 0) fail("restore handler"); + + if (sigprocmask(SIG_BLOCK, &set, (sigset_t*)0) != 0) fail("block main for routing"); + pthread_t worker; + if (pthread_create(&worker, (const pthread_attr_t*)0, signal_worker, (void*)0) != 0) + fail("pthread_create"); + while (!gWorkerReady) (void)SysYield(); + if (!gWorkerInheritedBlock) fail("pthread signal-mask inheritance"); + + if (kill(getpid(), SIGUSR1) != 0) fail("process-directed kill"); + for (uint32_t i = 0; i < 10000u && gHandlerCount < 3; ++i) (void)SysYield(); + if (gHandlerCount != 3 || gHandlerTid != worker.Tid) + fail("process-directed target selection"); + gWorkerStop = 1; + void* workerResult = (void*)0; + if (pthread_join(worker, &workerResult) != 0 || workerResult != (void*)0) + fail("pthread_join"); + if (sigprocmask(SIG_UNBLOCK, &set, (sigset_t*)0) != 0) fail("unblock main"); + + if (kill(0, 0) != 0) fail("current process-group existence"); + pid_t group = getpgrp(); + if (group <= 0 || kill(-group, 0) != 0) fail("explicit process-group existence"); + errno = 0; + if (kill((pid_t)0x7fffffff, 0) != -1 || errno != ESRCH) + fail("missing process ESRCH"); + + struct sigaction killAction; + killAction.sa_handler = signal_handler; + killAction.sa_mask = 0; + killAction.sa_flags = 0; + errno = 0; + if (sigaction(SIGKILL, &killAction, (struct sigaction*)0) != -1 || errno != EINVAL) + fail("SIGKILL catch accepted"); + sigset_t killSet; + sigemptyset(&killSet); + sigaddset(&killSet, SIGKILL); + if (sigprocmask(SIG_BLOCK, &killSet, &pending) != 0) fail("SIGKILL block request"); + if (sigprocmask(SIG_SETMASK, &pending, (sigset_t*)0) != 0) fail("restore signal mask"); + + char self[512]; + get_self_path(self, sizeof(self)); + test_spawn_signal_attributes(self); + test_process_group_delivery(self); + test_default_disposition(self); + test_exception_translation(self); + test_signal_waits(); + + print("PASS\n"); + return 0; +} diff --git a/code/user/tests/process_builder_smoke/process_builder_smoke.c b/code/user/tests/process_builder_smoke/process_builder_smoke.c index 06c1ac6..2788a5f 100644 --- a/code/user/tests/process_builder_smoke/process_builder_smoke.c +++ b/code/user/tests/process_builder_smoke/process_builder_smoke.c @@ -1,139 +1,157 @@ -// Process-construction smoke test. The same import-free PE is loaded a second time entirely by userspace. +// Userspace process-builder smoke test for inert construction inputs and child bootstrap vectors. #include #include -#include +#include #include #include #include #define TEST_STARTUP_EVENT_TAG 0x50425445u +#define TEST_STARTUP_WORD 0x31503550u +// Exits without relying on a hosted runtime. static void exit_now(int code) { for (;;) SysExitProcess(code); } -// Writes one string through the native console syscall. -static void print(const char* text) { - SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) }; - (void)SysWriteConsole(&request); -} - -// Writes one 32-bit value as hexadecimal without requiring a formatting runtime. -static void print_hex32(uint32_t value) { - static const char digits[] = "0123456789ABCDEF"; - char buffer[11] = "0x00000000"; - for (uint32_t i = 0; i < 8; ++i) - buffer[2 + i] = digits[(value >> ((7u - i) * 4u)) & 0xFu]; - print(buffer); +// Compares one native string view with a C string. +static int view_equals_cstr(SYS_STRING_VIEW view, const char* text) { + size_t length = strlen(text); + return view.Length == length && (!length || memcmp(view.Data, text, length) == 0); } +// Detects the child instance through the diagnostic command line. static int is_child(void) { SYS_PEB* peb = SysGetPeb(); static const char marker[] = "process-builder-child"; - return peb && peb->CommandLine.Length == sizeof(marker) - 1 && - memcmp(peb->CommandLine.Data, marker, sizeof(marker) - 1) == 0; + return peb && peb->CommandLine.Length == sizeof(marker) - 1u && + memcmp(peb->CommandLine.Data, marker, sizeof(marker) - 1u) == 0; } -static SYS_HANDLE find_startup(uint32_t tag) { - SYS_PEB* peb = SysGetPeb(); - if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION) - return SYS_HANDLE_INVALID; - for (uint32_t i = 0; i < peb->StartupCapabilityCount; ++i) { - if (peb->StartupCapabilities[i].Tag == tag) - return peb->StartupCapabilities[i].Handle; - } - return SYS_HANDLE_INVALID; +// Verifies the serialized generic argv/envp vectors and opaque startup data. +static void verify_child_bootstrap(void) { + uint32_t count = 0; + SYS_STRING_VIEW entry; + if (NtlGetProcessArgumentCount(&count) != NTLSTATUS_SUCCESS || count != 3u) exit_now(20); + if (NtlGetProcessArgument(0, &entry) != NTLSTATUS_SUCCESS || + !view_equals_cstr(entry, "process_builder_smoke")) exit_now(21); + if (NtlGetProcessArgument(1, &entry) != NTLSTATUS_SUCCESS || + !view_equals_cstr(entry, "vector-child")) exit_now(22); + if (NtlGetProcessArgument(2, &entry) != NTLSTATUS_SUCCESS || + !view_equals_cstr(entry, "arg two")) exit_now(23); + + if (NtlGetProcessEnvironmentCount(&count) != NTLSTATUS_SUCCESS || count != 2u) exit_now(24); + if (NtlGetProcessEnvironment(0, &entry) != NTLSTATUS_SUCCESS || + !view_equals_cstr(entry, "P5_TEST=builder")) exit_now(25); + if (NtlGetProcessEnvironment(1, &entry) != NTLSTATUS_SUCCESS || + !view_equals_cstr(entry, "EMPTY=")) exit_now(26); + + uint32_t startupWord = 0; + size_t startupSize = 0; + if (NtlGetProcessStartupData(&startupWord, sizeof(startupWord), &startupSize) != NTLSTATUS_SUCCESS || + startupSize != sizeof(startupWord) || startupWord != TEST_STARTUP_WORD) + exit_now(27); } -int main(void) { - if (is_child()) { - SYS_HANDLE event = find_startup(TEST_STARTUP_EVENT_TAG); - if (event == SYS_HANDLE_INVALID) exit_now(10); - if (SysSignalEvent(event) != NTLSTATUS_SUCCESS) exit_now(11); - exit_now(0); - } +// Runs the child half and signals the inherited parent event after validation. +static void run_child(void) { + SYS_HANDLE event = SysGetStartupCapability(TEST_STARTUP_EVENT_TAG); + if (event == SYS_HANDLE_INVALID) exit_now(10); + verify_child_bootstrap(); + if (SysSignalEvent(event) != NTLSTATUS_SUCCESS) exit_now(11); + exit_now(0); +} +// Runs the parent half through the reusable userspace process builder wrapper. +static int run_parent(void) { SYS_PEB* peb = SysGetPeb(); if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION) - exit_now(1); + return 1; - SYS_HANDLE file = SYS_HANDLE_INVALID; - KSTATUS s = NtlOpenFile(peb->ImagePath, - SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, - SYS_FILE_OPEN_EXISTING, - SYS_FILE_FLAG_NONE, - &file); - if (!KSUCCESS(s)) exit_now(2); - - SYS_HANDLE child = SYS_HANDLE_INVALID; - SYS_CREATE_PROCESS_OBJECT_REQUEST create; - memset(&create, 0, sizeof(create)); - create.Size = sizeof(create); - create.Flags = SYS_CREATE_PROCESS_OBJECT_FLAG_NONE; - create.ImagePath = peb->ImagePath; - create.CommandLine = SYS_SV_LITERAL("process-builder-child"); - create.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; - create.ProcessHandleOut = &child; - s = SysCreateProcessObject(&create); - if (!KSUCCESS(s)) exit_now(3); - - NTL_PE_IMAGE image; - memset(&image, 0, sizeof(image)); - NTL_PE_LOAD_REQUEST load; - memset(&load, 0, sizeof(load)); - load.Size = sizeof(load); - load.ProcessHandle = child; - load.FileHandle = file; - load.Flags = NTL_PE_LOAD_FLAG_NONE; - load.ImageOut = ℑ - s = NtlLoadPeImage(&load); - SysCloseHandle(file); - if (!KSUCCESS(s) || !image.Base || !image.EntryPoint) { - print("process_builder_smoke: NtlLoadPeImage failed status="); - print_hex32(s); - print("\n"); - exit_now(4); - } + SYS_HANDLE imageFile = SYS_HANDLE_INVALID; + KSTATUS status = NtlOpenFile(peb->ImagePath, + SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY, + SYS_FILE_OPEN_EXISTING, + SYS_FILE_FLAG_NONE, + &imageFile); + if (!KSUCCESS(status)) return 2; SYS_HANDLE event = SYS_HANDLE_INVALID; - s = SysCreateEvent(0, 0, &event); - if (!KSUCCESS(s)) exit_now(5); + status = SysCreateEvent(0, 0, &event); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(imageFile); + return 3; + } - SYS_HANDLE childEvent = SYS_HANDLE_INVALID; - SYS_DUPLICATE_HANDLE_REQUEST duplicate; - memset(&duplicate, 0, sizeof(duplicate)); - duplicate.Size = sizeof(duplicate); - duplicate.SourceHandle = event; - duplicate.TargetProcessHandle = child; - duplicate.DesiredRights = SYS_EVENT_RIGHT_SIGNAL | SYS_RIGHT_QUERY; - duplicate.Flags = SYS_DUPLICATE_HANDLE_FLAG_NONE; - duplicate.TargetHandleOut = &childEvent; - s = SysDuplicateHandle(&duplicate); - if (!KSUCCESS(s)) exit_now(6); + NTL_PROCESS_STARTUP_CAPABILITY startupCapability; + memset(&startupCapability, 0, sizeof(startupCapability)); + startupCapability.Tag = TEST_STARTUP_EVENT_TAG; + startupCapability.SourceHandle = event; + startupCapability.DesiredRights = SYS_EVENT_RIGHT_SIGNAL | SYS_RIGHT_QUERY; + startupCapability.DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE; - SYS_STARTUP_CAPABILITY startup = { TEST_STARTUP_EVENT_TAG, childEvent }; - SYS_START_PROCESS_REQUEST start; - memset(&start, 0, sizeof(start)); - start.Size = sizeof(start); - start.ProcessHandle = child; - start.Flags = SYS_START_PROCESS_FLAG_NONE; - start.MainImageBase = image.Base; - start.MainImageSize = image.Size; - start.EntryPoint = image.EntryPoint; - start.StartupCapabilities = &startup; - start.StartupCapabilityCount = 1; - s = SysStartProcess(&start); - if (!KSUCCESS(s)) exit_now(7); + SYS_STRING_VIEW arguments[] = { + SYS_SV_LITERAL("process_builder_smoke"), + SYS_SV_LITERAL("vector-child"), + SYS_SV_LITERAL("arg two") + }; + SYS_STRING_VIEW environment[] = { + SYS_SV_LITERAL("P5_TEST=builder"), + SYS_SV_LITERAL("EMPTY=") + }; + uint32_t startupWord = TEST_STARTUP_WORD; + SYS_HANDLE child = SYS_HANDLE_INVALID; + + NTL_CREATE_SYSTEM_PROCESS_REQUEST request; + memset(&request, 0, sizeof(request)); + request.Size = sizeof(request); + request.Flags = NTL_PROCESS_LOAD_FLAG_NONE; + request.ImageFile = imageFile; + request.Path = peb->ImagePath; + request.CommandLine = SYS_SV_LITERAL("process-builder-child"); + request.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT; + request.ResolveModuleName = NtlResolveSystemModuleName; + request.StartupCapabilities = &startupCapability; + request.StartupCapabilityCount = 1u; + request.Arguments.Entries = arguments; + request.Arguments.Count = (uint32_t)(sizeof(arguments) / sizeof(arguments[0])); + request.Environment.Entries = environment; + request.Environment.Count = (uint32_t)(sizeof(environment) / sizeof(environment[0])); + request.StartupData = &startupWord; + request.StartupDataSize = sizeof(startupWord); + request.ProcessHandleOut = &child; + status = NtlCreateSystemProcess(&request); + (void)SysCloseHandle(imageFile); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(event); + return 4; + } + + status = SysWait(event, SYS_WAIT_FLAG_NONE); + if (!KSUCCESS(status)) { + (void)SysKillProcess(child, (int32_t)NTLSTATUS_PROCESS_KILLED); + (void)SysCloseHandle(child); + (void)SysCloseHandle(event); + return 5; + } + status = SysWait(child, SYS_WAIT_FLAG_NONE); + if (!KSUCCESS(status)) { + (void)SysCloseHandle(child); + (void)SysCloseHandle(event); + return 6; + } - s = SysWait(event, SYS_WAIT_FLAG_NONE); - if (!KSUCCESS(s)) exit_now(8); - s = SysWait(child, SYS_WAIT_FLAG_NONE); - if (!KSUCCESS(s)) exit_now(9); int32_t exitCode = -1; - s = SysGetProcessExitCode(child, &exitCode); - SysCloseHandle(event); - SysCloseHandle(child); - if (!KSUCCESS(s) || exitCode != 0) exit_now(12); + status = SysGetProcessExitCode(child, &exitCode); + (void)SysCloseHandle(child); + (void)SysCloseHandle(event); + if (!KSUCCESS(status) || exitCode != 0) return 7; return 0; } + +// Exercises generic userspace process construction in parent and child roles. +int main(void) { + if (is_child()) run_child(); + return run_parent(); +} diff --git a/code/user/tests/security_smoke/security_smoke.c b/code/user/tests/security_smoke/security_smoke.c index c582cb6..382285f 100644 --- a/code/user/tests/security_smoke/security_smoke.c +++ b/code/user/tests/security_smoke/security_smoke.c @@ -169,6 +169,10 @@ static int run_restricted_child(uint32_t parentPid) { status = SysCloseHandle(process); if (!KSUCCESS(status)) return 28; + process = SYS_HANDLE_INVALID; + status = SysOpenProcess(selfPid, SYS_RIGHT_QUERY | SYS_PROCESS_RIGHT_SECURITY_CONTEXT, &process); + if (status != NTLSTATUS_ACCESS_DENIED) return 29; + return 0; } @@ -203,6 +207,38 @@ static int run_parent(void) { rootInfo.DelegableServiceClaims != SYS_SERVICE_CLAIM_ALL) fail_status("bootstrap context authority", NTLSTATUS_FAILURE); + SYS_HANDLE selfProcess = SYS_HANDLE_INVALID; + status = SysOpenProcess(SysGetCurrentPid(), + SYS_RIGHT_QUERY | SYS_PROCESS_RIGHT_SECURITY_CONTEXT, + &selfProcess); + if (!KSUCCESS(status)) fail_status("open self security-context process", status); + SYS_HANDLE exactContext = SYS_HANDLE_INVALID; + status = SysOpenProcessSecurityContext( + selfProcess, SYS_RIGHT_QUERY | SYS_SECURITY_CONTEXT_RIGHT_ASSIGN, &exactContext); + if (!KSUCCESS(status)) fail_status("open exact process security context", status); + SYS_SECURITY_CONTEXT_INFO exactInfo; + memset(&exactInfo, 0, sizeof(exactInfo)); + exactInfo.Size = sizeof(exactInfo); + status = SysQuerySecurityContext(exactContext, &exactInfo); + if (!KSUCCESS(status)) fail_status("query exact process security context", status); + if (exactInfo.SecurityId != rootInfo.SecurityId || + exactInfo.EffectivePrivileges != rootInfo.EffectivePrivileges || + exactInfo.DelegablePrivileges != rootInfo.DelegablePrivileges || + exactInfo.ServiceClaims != rootInfo.ServiceClaims || + exactInfo.DelegableServiceClaims != rootInfo.DelegableServiceClaims) + fail_status("exact process security context identity", NTLSTATUS_FAILURE); + (void)SysCloseHandle(exactContext); + (void)SysCloseHandle(selfProcess); + + SYS_HANDLE queryOnlyProcess = SYS_HANDLE_INVALID; + status = SysOpenProcess(SysGetCurrentPid(), SYS_RIGHT_QUERY, &queryOnlyProcess); + if (!KSUCCESS(status)) fail_status("open query-only self process", status); + exactContext = SYS_HANDLE_INVALID; + status = SysOpenProcessSecurityContext(queryOnlyProcess, SYS_RIGHT_QUERY, &exactContext); + (void)SysCloseHandle(queryOnlyProcess); + if (status != NTLSTATUS_ACCESS_DENIED) + fail_status("process security-context right", status); + SYS_HANDLE restricted = derive_stripped_context(); SYS_SECURITY_CONTEXT_INFO restrictedInfo; memset(&restrictedInfo, 0, sizeof(restrictedInfo)); diff --git a/code/user/tests/thread_name_smoke/thread_name_smoke.c b/code/user/tests/thread_name_smoke/thread_name_smoke.c index aa086a0..2ae20b8 100644 --- a/code/user/tests/thread_name_smoke/thread_name_smoke.c +++ b/code/user/tests/thread_name_smoke/thread_name_smoke.c @@ -116,12 +116,9 @@ int main(void) { fail("worker missing from thread list"); uint32_t previousSuspendCount = 0; - status = SysResumeThread(thread, &previousSuspendCount); + status = SysResumeThreadAndWait(thread, &previousSuspendCount); if (status != NTLSTATUS_SUCCESS || previousSuspendCount != 1u) - fail("resume worker failed"); - status = SysWait(thread, SYS_WAIT_FLAG_NONE); - if (status != NTLSTATUS_SUCCESS) - fail("wait worker failed"); + fail("resume-and-wait worker failed"); (void)SysCloseHandle(thread); SYS_HANDLE detachedReady = SYS_HANDLE_INVALID;