diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 14b4bbe..b8233de 100644 Binary files a/OVMF/OVMF_VARS_4M_d.fd and b/OVMF/OVMF_VARS_4M_d.fd differ 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;