Files
nt-lite/code/user/personalities/posix/posix_vfork.c
T

332 lines
13 KiB
C

// 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;
}