move POSIX spawn, exec and child tracking into posixsrv, keep process transactions alive across reconnects and preserve stable PIDs across image replacement, flesh out spawn with ordered file actions, process-group selection and signal inheritance, implement execve with bootstrap validation and rollback before retiring the old process, add vfork children that share the native process while keeping separate POSIX descriptor and signal state, replace local spawn bookkeeping with child reaping, nonblocking and process-group waits and orphan cleanup, add signal dispositions, per-thread masks, pending delivery, process/group signalling and alarms, translate native exceptions into POSIX signals and inherit masks when creating threads, make pipe waits interruptible and raise SIGPIPE for broken pipes, pass arguments and environment into main and add getenv/environ, add direct user APC notifications that can run without an alertable wait, add current-thread capabilities and resume-and-wait, let accepted IPC endpoints open restricted peer process capabilities and preserve caller security during spawn and exec, make process preparation reusable and improve DLL lookup across current, executable and fallback directories, move keyboard repeat into RSSRV so PS/2 and USB share one repeat policy, fix video presentation, report launch failure stages and status codes, mooaaar tests.

This commit is contained in:
maksw committed 2026-09-23 09:39:42 +02:00
1 parent 73abd6f90d
commit 899225f551
57 files changed
+8817 -1216

No files matched your search

+65 -7
View File
@@ -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;
}
+54 -2
View File
@@ -10,6 +10,7 @@
#include <intrin.h>
#include <ntlite/syscall.h>
#include <ntlite/console_service.h>
#include <ntlite/desktop.h>
#include <ntlite/path.h>
#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");
+49 -53
View File
@@ -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;
+8 -8
View File
@@ -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);
+77 -12
View File
@@ -13,6 +13,7 @@
#include <sys/wait.h>
#include <pthread.h>
#include <spawn.h>
#include <signal.h>
#include <dlfcn.h>
#include <sys/mman.h>
#include <sys/socket.h>
@@ -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,
+21 -2
View File
@@ -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;
+615 -165
View File
@@ -1,8 +1,10 @@
// POSIX compatibility support for posix proc.
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/security.h>
#include <ntlite/path.h>
#include <ntlite/process_loader.h>
#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.
+396 -27
View File
@@ -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), &currentDirectorySize);
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;
}
@@ -0,0 +1,476 @@
// POSIX signal policy and user-mode dispatch over generic thread APC delivery.
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <ntlite/exception.h>
#include <ntlite/syscall.h>
#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;
}
+8 -1
View File
@@ -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;
@@ -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
+331
View File
@@ -0,0 +1,331 @@
// POSIX borrowed-child substrate used by the later public vfork continuation layer.
#include <stdlib.h>
#include <string.h>
#include <ntlite/status.h>
#include <ntlite/syscall.h>
#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;
}
@@ -0,0 +1,37 @@
// Shared layout for the x64 vfork continuation context.
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <ntlite/syscall.h>
#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);
+13 -2
View File
@@ -5,8 +5,11 @@
#include <ntlite/exception.h>
#include <ntlite/syscall.h>
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) {}
}
+251 -76
View File
@@ -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(&region, 0, sizeof(region));
KSTATUS status = SysQueryVm((void*)peb->LoaderData, &region);
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;
}
+9 -2
View File
@@ -4,14 +4,19 @@
#include <ntlite/module_loader.h>
#include <ntlite/pe_loader.h>
#include <ntlite/process_loader.h>
#include <ntlite/syscall.h>
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);
+282 -95
View File
@@ -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), &currentDirectorySize);
} else {
status = NtlGetCurrentDirectory(currentDirectoryBuffer, sizeof(currentDirectoryBuffer),
&currentDirectorySize);
}
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, &currentDirectoryHandle);
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);
+167 -161
View File
@@ -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
+6
View File
@@ -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
File diff suppressed because it is too large. Load diff
+71 -4
View File
@@ -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;
+2
View File
@@ -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;
@@ -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,
&currentThread);
if (status != NTLSTATUS_SUCCESS || currentThread == SYS_HANDLE_INVALID)
fail_status("open current thread", status, NTLSTATUS_SUCCESS);
uint32_t currentTid = 0;
status = SysGetThreadTid(currentThread, &currentTid);
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)
@@ -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(&currentInfo, 0, sizeof(currentInfo));
memset(&peerInfo, 0, sizeof(peerInfo));
currentInfo.Size = sizeof(currentInfo);
peerInfo.Size = sizeof(peerInfo);
status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &currentInfo);
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");
@@ -7,6 +7,7 @@
#include <errno.h>
#include <poll.h>
#include <spawn.h>
#include <signal.h>
#include <sys/wait.h>
#include <ntlite/posix_extensions.h>
@@ -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)
@@ -6,6 +6,7 @@
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <sys/stat.h>
// 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");
@@ -0,0 +1,555 @@
// User-mode smoke test for server-owned POSIX spawn and child lifecycle semantics.
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <spawn.h>
#include <signal.h>
#include <sys/wait.h>
#include <ntlite/syscall.h>
#include <ntlite/posix_extensions.h>
#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;
}
@@ -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);
@@ -0,0 +1,459 @@
// Runtime coverage for P6 POSIX signal delivery, waits, spawn state, and exceptions.
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <spawn.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <ntlite/syscall.h>
#include <ntlite/posix_extensions.h>
#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, &current) != 0)
return (void*)(uintptr_t)1u;
gWorkerInheritedBlock = sigismember(&current, 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, &current) != 0 ||
sigismember(&current, 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, &current) != 0 ||
sigismember(&current, 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;
}
@@ -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 <ntlite/syscall.h>
#include <ntlite/path.h>
#include <ntlite/pe_loader.h>
#include <ntlite/process_loader.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#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 = &image;
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();
}
@@ -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));
@@ -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;