Files
nt-lite/code/user/tests/posix_service_smoke/posix_service_smoke.c
T

698 lines
33 KiB
C

// Native smoke test for posixsrv discovery, stable PID registration, and bounded transactions.
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <ntlite/module_loader.h>
#include <ntlite/posix_service.h>
#include <ntlite/process_loader.h>
#include <ntlite/path.h>
#include <ntlite/resource.h>
#include <ntlite/security.h>
#include <ntlite/syscall.h>
#define CHILD_ARGUMENT "--pid-child"
static void print(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { sizeof(request), SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
// Terminates the smoke test with one visible failure reason.
static void fail(const char* text) {
print("posix_service_smoke: ");
print(text);
print("\n");
for (;;) SysExitProcess(1);
}
// Returns whether the current command line contains one ASCII token.
static bool command_contains(const char* token) {
SYS_PEB* peb = SysGetPeb();
if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION || !token)
return false;
size_t tokenLength = strlen(token);
if (tokenLength == 0 || tokenLength > peb->CommandLine.Length) return false;
for (size_t i = 0; i + tokenLength <= peb->CommandLine.Length; ++i) {
if (memcmp(peb->CommandLine.Data + i, token, tokenLength) == 0)
return true;
}
return false;
}
// Resolves /Services/posix through RSSRV and connects one independent client endpoint.
static KSTATUS connect_posixsrv(SYS_HANDLE* endpointOut) {
if (!endpointOut) return NTLSTATUS_INVALID_PARAMETER;
*endpointOut = SYS_HANDLE_INVALID;
SYS_HANDLE resource = SYS_HANDLE_INVALID;
KSTATUS status = NtlResourceConnect(&resource);
if (!KSUCCESS(status)) return status;
SYS_HANDLE listener = SYS_HANDLE_INVALID;
status = NtlResourceLookup(resource, SYS_SV_LITERAL(NTL_POSIX_SERVICE_PATH),
SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT, &listener);
(void)NtlResourceClose(resource);
if (!KSUCCESS(status)) return status;
SYS_CONNECT_PORT_REQUEST connect;
memset(&connect, 0, sizeof(connect));
connect.Size = sizeof(connect);
connect.ListenerHandle = listener;
connect.Flags = SYS_PORT_CONNECT_FLAG_NONE;
connect.EndpointHandleOut = endpointOut;
status = SysConnectPort(&connect);
(void)SysCloseHandle(listener);
return status;
}
// Performs one fixed-size request with optional capability transfer and validates reply framing.
static KSTATUS call_posixsrv_exchange(SYS_HANDLE endpoint,
const NTL_POSIX_REQUEST* request,
const SYS_PORT_HANDLE_TRANSFER* requestHandles,
uint32_t requestHandleCount,
SYS_HANDLE* replyHandlesOut,
uint32_t replyHandleCapacity,
uint32_t expectedReplyHandleCount,
NTL_POSIX_REPLY* replyOut) {
if (endpoint == SYS_HANDLE_INVALID || !request || !replyOut ||
expectedReplyHandleCount > replyHandleCapacity ||
(replyHandleCapacity != 0 && !replyHandlesOut))
return NTLSTATUS_INVALID_PARAMETER;
memset(replyOut, 0, sizeof(*replyOut));
if (replyHandlesOut) {
for (uint32_t i = 0; i < replyHandleCapacity; ++i)
replyHandlesOut[i] = SYS_HANDLE_INVALID;
}
SYS_PORT_MESSAGE_INFO info;
memset(&info, 0, sizeof(info));
info.Size = sizeof(info);
SYS_CALL_PORT_REQUEST call;
memset(&call, 0, sizeof(call));
call.Size = sizeof(call);
call.EndpointHandle = endpoint;
call.Flags = SYS_PORT_FLAG_NONE;
call.RequestPayload = request;
call.RequestLength = sizeof(*request);
call.RequestHandles = requestHandles;
call.RequestHandleCount = requestHandleCount;
call.ReplyPayload = replyOut;
call.ReplyCapacity = sizeof(*replyOut);
call.ReplyHandlesOut = replyHandlesOut;
call.ReplyHandleCapacity = replyHandleCapacity;
call.ReplyInfoOut = &info;
call.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
KSTATUS status = SysCallPort(&call);
if (!KSUCCESS(status)) return status;
uint32_t expectedHandles = KSUCCESS(replyOut->Status) ? expectedReplyHandleCount : 0u;
if (info.Type != SYS_PORT_MESSAGE_REPLY || info.HandleCount != expectedHandles ||
info.PayloadLength != sizeof(*replyOut) || replyOut->Size != sizeof(*replyOut) ||
replyOut->Version != NTL_POSIX_PROTOCOL_VERSION ||
replyOut->Operation != request->Operation ||
replyOut->Flags != NTL_POSIX_REQUEST_FLAG_NONE) {
if (replyHandlesOut) {
uint32_t closeCount = info.HandleCount < replyHandleCapacity ?
info.HandleCount : replyHandleCapacity;
for (uint32_t i = 0; i < closeCount; ++i) {
if (replyHandlesOut[i] != SYS_HANDLE_INVALID)
(void)SysCloseHandle(replyHandlesOut[i]);
replyHandlesOut[i] = SYS_HANDLE_INVALID;
}
}
return NTLSTATUS_FAILURE;
}
return NTLSTATUS_SUCCESS;
}
// Performs one request that expects no capabilities in the reply.
static KSTATUS call_posixsrv(SYS_HANDLE endpoint,
const NTL_POSIX_REQUEST* request,
const SYS_PORT_HANDLE_TRANSFER* handles,
uint32_t handleCount,
NTL_POSIX_REPLY* replyOut) {
return call_posixsrv_exchange(endpoint, request, handles, handleCount,
(SYS_HANDLE*)0, 0, 0, replyOut);
}
// Constructs the common fixed-size request header used by the service protocol.
static NTL_POSIX_REQUEST make_request(uint32_t operation) {
NTL_POSIX_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_POSIX_PROTOCOL_VERSION;
request.Operation = operation;
request.Flags = NTL_POSIX_REQUEST_FLAG_NONE;
return request;
}
// 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 = 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_THREAD_RIGHT_NOTIFY;
transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE;
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,
uint32_t statusFlags,
NTL_POSIX_REPLY* replyOut) {
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_CREATE_OPEN_DESCRIPTION);
request.OpenDescriptionType = type;
request.OpenStatusFlags = statusFlags;
if (!KSUCCESS(call_posixsrv(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0,
replyOut)) || !KSUCCESS(replyOut->Status) ||
replyOut->OpenDescriptionId == 0 || replyOut->SharedOpenDescription == 0)
fail("shared description creation failed");
return replyOut->OpenDescriptionId;
}
// Releases one server-owned description held by the current registered process.
static KSTATUS release_description(SYS_HANDLE endpoint, uint64_t descriptionId,
NTL_POSIX_REPLY* replyOut) {
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_RELEASE_OPEN_DESCRIPTION);
request.OpenDescriptionId = descriptionId;
KSTATUS status = call_posixsrv(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0,
replyOut);
if (!KSUCCESS(status)) return status;
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,
uint32_t expectedDescriptions) {
for (uint32_t attempt = 0; attempt < 4096u; ++attempt) {
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_QUERY);
NTL_POSIX_REPLY reply;
if (!KSUCCESS(call_posixsrv(endpoint, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
!KSUCCESS(reply.Status))
fail("registry query failed");
if (reply.RegisteredProcesses == expectedProcesses &&
reply.RegisteredOpenDescriptions == expectedDescriptions)
return;
(void)SysYield();
}
fail("exited process registry state was not retired");
}
// Runs a second native process so the server must allocate a distinct stable POSIX PID.
static uint32_t spawn_child_posix_pid(uint32_t* nativePidOut) {
if (nativePidOut) *nativePidOut = 0;
SYS_PEB* peb = SysGetPeb();
if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION ||
!peb->ImagePath.Data || peb->ImagePath.Length == 0 || peb->ImagePath.Length > SYS_PATH_MAX)
fail("invalid self image path");
char path[SYS_PATH_MAX + 1u];
memcpy(path, peb->ImagePath.Data, peb->ImagePath.Length);
path[peb->ImagePath.Length] = '\0';
char command[SYS_PATH_MAX + 32u];
size_t pathLength = strlen(path);
size_t argumentLength = strlen(CHILD_ARGUMENT);
if (pathLength + 1u + argumentLength + 1u > sizeof(command))
fail("child command line too long");
memcpy(command, path, pathLength);
command[pathLength] = ' ';
memcpy(command + pathLength + 1u, CHILD_ARGUMENT, argumentLength + 1u);
SYS_HANDLE process = SYS_HANDLE_INVALID;
NTL_CREATE_SYSTEM_PROCESS_REQUEST create;
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
create.ImageFile = SYS_HANDLE_INVALID;
create.Path = SysSvFromCstr(path);
create.CommandLine = SysSvFromCstr(command);
create.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
create.ResolveModuleName = NtlResolveSystemModuleName;
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);
create.StartupData = &bootstrap;
create.StartupDataSize = sizeof(bootstrap);
create.ProcessHandleOut = &process;
KSTATUS status = NtlCreateSystemProcess(&create);
if (!KSUCCESS(status)) fail("child process creation failed");
uint32_t nativePid = 0;
if (!KSUCCESS(SysGetProcessPid(process, &nativePid)))
fail("child native pid query failed");
if (nativePidOut) *nativePidOut = nativePid;
if (!KSUCCESS(SysWait(process, SYS_WAIT_FLAG_NONE)))
fail("child process wait failed");
int32_t exitCode = 0;
if (!KSUCCESS(SysGetProcessExitCode(process, &exitCode)))
fail("child exit query failed");
(void)SysCloseHandle(process);
if (exitCode <= 0) fail("child returned invalid POSIX pid");
return (uint32_t)exitCode;
}
// Registers and returns the stable PID when running as the self-spawned child.
static void run_child_mode(void) {
NTL_POSIX_BOOTSTRAP_DATA bootstrap;
size_t bootstrapSize = 0;
if (!KSUCCESS(NtlGetProcessStartupData(&bootstrap, sizeof(bootstrap), &bootstrapSize)) ||
bootstrapSize != sizeof(bootstrap) || bootstrap.Magic != NTL_POSIX_BOOTSTRAP_MAGIC ||
bootstrap.Version != NTL_POSIX_BOOTSTRAP_VERSION || bootstrap.Size != sizeof(bootstrap) ||
bootstrap.Reserved != 0 || bootstrap.Token != 0 || bootstrap.PosixPid != 0 ||
bootstrap.Generation != 0 || bootstrap.FdInheritanceToken != 0)
for (;;) SysExitProcess(4);
SYS_HANDLE endpoint = SYS_HANDLE_INVALID;
if (!KSUCCESS(connect_posixsrv(&endpoint)))
for (;;) SysExitProcess(2);
NTL_POSIX_REPLY reply;
if (!KSUCCESS(register_current_process(endpoint, &reply)) || !KSUCCESS(reply.Status) ||
reply.PosixPid == 0 || reply.PosixPid > 0x7FFFFFFFu ||
reply.ProcessState != NTL_POSIX_PROCESS_STATE_RUNNING)
for (;;) SysExitProcess(3);
uint32_t pid = reply.PosixPid;
NTL_POSIX_REPLY descriptionReply;
uint64_t leakedDescription = create_synthetic_description(
endpoint, NTL_POSIX_OPEN_TYPE_DEV_NULL, NTL_POSIX_OPEN_STATUS_WRONLY,
&descriptionReply);
NTL_POSIX_SHARED_OPEN_DESCRIPTION* shared =
(NTL_POSIX_SHARED_OPEN_DESCRIPTION*)(uintptr_t)descriptionReply.SharedOpenDescription;
if (leakedDescription == 0 || !shared || shared->DescriptionId != leakedDescription ||
shared->Magic != NTL_POSIX_SHARED_OPEN_MAGIC ||
shared->Version != NTL_POSIX_SHARED_OPEN_VERSION)
for (;;) SysExitProcess(5);
// Intentionally leak the holder; the server's native-process monitor must reclaim it.
(void)SysDisconnectPort(endpoint);
(void)SysCloseHandle(endpoint);
for (;;) SysExitProcess((int32_t)pid);
}
// Exercises service discovery, stable identity, protocol versions, and transaction boundaries.
int main(void) {
if (command_contains(CHILD_ARGUMENT)) run_child_mode();
print("posix_service_smoke: start\n");
SYS_HANDLE first = SYS_HANDLE_INVALID;
if (!KSUCCESS(connect_posixsrv(&first))) fail("first connect failed");
SYS_SECURITY_CONTEXT_INFO security;
memset(&security, 0, sizeof(security));
security.Size = sizeof(security);
if (!KSUCCESS(SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &security)))
fail("security query failed");
NTL_POSIX_REQUEST request = make_request(NTL_POSIX_OP_QUERY);
NTL_POSIX_REPLY reply;
if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
!KSUCCESS(reply.Status))
fail("query failed");
if (reply.ClientProcessId != SysGetCurrentPid() || reply.ClientSecurityId != security.SecurityId ||
reply.ActiveTransactions != 0 || reply.TransactionCapacity != NTL_POSIX_TRANSACTION_CAPACITY ||
reply.PosixPid != 0)
fail("pre-registration identity mismatch");
if (!KSUCCESS(register_current_process(first, &reply)) || !KSUCCESS(reply.Status))
fail("process registration failed");
uint32_t stablePid = reply.PosixPid;
if (stablePid == 0 || stablePid == SysGetCurrentPid() || reply.NativeGeneration != 1u ||
reply.ProcessState != NTL_POSIX_PROCESS_STATE_RUNNING)
fail("stable process identity mismatch");
if (reply.UserId != 0 || reply.EffectiveUserId != 0 || reply.GroupId != 0 ||
reply.EffectiveGroupId != 0 || reply.ProcessGroupId != stablePid ||
reply.SessionId != stablePid || reply.SupplementaryGroupCount != 0 ||
reply.NameLength != 4u || strcmp(reply.Name, "root") != 0)
fail("initial shared process attributes mismatch");
char registeredCwd[SYS_PATH_MAX + 1u];
size_t registeredCwdRequired = 0;
if (!KSUCCESS(NtlGetCurrentDirectory(registeredCwd, sizeof(registeredCwd), &registeredCwdRequired)) ||
reply.CurrentDirectoryLength != strlen(registeredCwd) ||
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,
&descriptionReply);
if (descriptionReply.RegisteredOpenDescriptions != baselineDescriptions + 1u ||
descriptionReply.OpenDescriptionType != NTL_POSIX_OPEN_TYPE_DEV_NULL ||
descriptionReply.OpenStatusFlags != NTL_POSIX_OPEN_STATUS_WRONLY)
fail("shared description registry mismatch");
NTL_POSIX_SHARED_OPEN_DESCRIPTION* shared =
(NTL_POSIX_SHARED_OPEN_DESCRIPTION*)(uintptr_t)descriptionReply.SharedOpenDescription;
if (!shared || shared->Magic != NTL_POSIX_SHARED_OPEN_MAGIC ||
shared->Version != NTL_POSIX_SHARED_OPEN_VERSION ||
shared->Type != NTL_POSIX_OPEN_TYPE_DEV_NULL ||
shared->DescriptionId != descriptionId ||
(uint32_t)shared->StatusFlags != NTL_POSIX_OPEN_STATUS_WRONLY)
fail("shared description pointer mismatch");
request = make_request(NTL_POSIX_OP_SET_OPEN_STATUS);
request.OpenDescriptionId = descriptionId;
request.OpenStatusFlags = NTL_POSIX_OPEN_STATUS_WRONLY | NTL_POSIX_OPEN_STATUS_NONBLOCK;
if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &descriptionReply)) ||
!KSUCCESS(descriptionReply.Status) || descriptionReply.OpenDescriptionId != descriptionId ||
descriptionReply.OpenStatusFlags != request.OpenStatusFlags ||
(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");
if (release_description(first, descriptionId, &descriptionReply) != NTLSTATUS_NOT_FOUND)
fail("duplicate shared description release was accepted");
request = make_request(NTL_POSIX_OP_CREATE_PIPE);
SYS_HANDLE pipeHandles[2] = { SYS_HANDLE_INVALID, SYS_HANDLE_INVALID };
if (!KSUCCESS(call_posixsrv_exchange(first, &request,
(const SYS_PORT_HANDLE_TRANSFER*)0, 0,
pipeHandles, 2u, 2u, &descriptionReply)) ||
!KSUCCESS(descriptionReply.Status) ||
descriptionReply.RegisteredOpenDescriptions != baselineDescriptions + 2u ||
descriptionReply.OpenDescriptionType != NTL_POSIX_OPEN_TYPE_PIPE_READ ||
descriptionReply.OpenDescriptionType2 != NTL_POSIX_OPEN_TYPE_PIPE_WRITE ||
descriptionReply.OpenStatusFlags != NTL_POSIX_OPEN_STATUS_RDONLY ||
descriptionReply.OpenStatusFlags2 != NTL_POSIX_OPEN_STATUS_WRONLY ||
descriptionReply.OpenDescriptionId == 0 || descriptionReply.OpenDescriptionId2 == 0 ||
descriptionReply.OpenDescriptionId == descriptionReply.OpenDescriptionId2 ||
pipeHandles[0] == SYS_HANDLE_INVALID || pipeHandles[1] == SYS_HANDLE_INVALID)
fail("server-owned pipe creation failed");
NTL_POSIX_SHARED_OPEN_DESCRIPTION* pipeReadShared =
(NTL_POSIX_SHARED_OPEN_DESCRIPTION*)(uintptr_t)descriptionReply.SharedOpenDescription;
NTL_POSIX_SHARED_OPEN_DESCRIPTION* pipeWriteShared =
(NTL_POSIX_SHARED_OPEN_DESCRIPTION*)(uintptr_t)descriptionReply.SharedOpenDescription2;
SYS_PIPE_SHARED_STATE* pipeShared = pipeReadShared ?
(SYS_PIPE_SHARED_STATE*)(uintptr_t)pipeReadShared->SharedObjectData :
(SYS_PIPE_SHARED_STATE*)0;
if (!pipeReadShared || !pipeWriteShared || !pipeShared ||
pipeReadShared->Magic != NTL_POSIX_SHARED_OPEN_MAGIC ||
pipeWriteShared->Magic != NTL_POSIX_SHARED_OPEN_MAGIC ||
pipeReadShared->Version != NTL_POSIX_SHARED_OPEN_VERSION ||
pipeWriteShared->Version != NTL_POSIX_SHARED_OPEN_VERSION ||
pipeReadShared->DescriptionId != descriptionReply.OpenDescriptionId ||
pipeWriteShared->DescriptionId != descriptionReply.OpenDescriptionId2 ||
pipeReadShared->SharedObjectData != pipeWriteShared->SharedObjectData ||
pipeShared->Magic != SYS_PIPE_SHARED_MAGIC || pipeShared->Version != SYS_PIPE_SHARED_VERSION ||
pipeShared->Capacity != SYS_PIPE_BUFFER_CAPACITY ||
pipeShared->HeaderBytes != SYS_PIPE_SHARED_HEADER_BYTES ||
pipeShared->ReaderOpen == 0 || pipeShared->WriterOpen == 0)
fail("server-owned pipe shared state mismatch");
if (!KSUCCESS(release_description(first, descriptionReply.OpenDescriptionId, &reply)) ||
reply.RegisteredOpenDescriptions != baselineDescriptions + 1u ||
pipeShared->ReaderOpen == 0)
fail("pipe read master release invalidated live alias");
if (!KSUCCESS(SysCloseHandle(pipeHandles[0])) || pipeShared->ReaderOpen != 0)
fail("pipe read alias close did not publish reader shutdown");
pipeHandles[0] = SYS_HANDLE_INVALID;
if (!KSUCCESS(SysCloseHandle(pipeHandles[1])))
fail("pipe write alias close failed");
pipeHandles[1] = SYS_HANDLE_INVALID;
if (!KSUCCESS(release_description(first, descriptionReply.OpenDescriptionId2, &reply)) ||
reply.RegisteredOpenDescriptions != baselineDescriptions)
fail("server-owned pipe description release failed");
request = make_request(NTL_POSIX_OP_LOOKUP_USER);
request.LookupKind = NTL_POSIX_LOOKUP_BY_NAME;
memcpy(request.Name, "user", 5u);
request.NameLength = 4u;
if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
!KSUCCESS(reply.Status) || reply.UserId != 1000u || reply.GroupId != 1000u ||
strcmp(reply.Name, "user") != 0)
fail("identity database lookup failed");
uint32_t childNativePid = 0;
uint32_t childPosixPid = spawn_child_posix_pid(&childNativePid);
if (childPosixPid == stablePid || childPosixPid == childNativePid)
fail("child POSIX identity was not independent");
wait_registry_counts(first, 1u, baselineDescriptions);
request = make_request(NTL_POSIX_OP_BEGIN_TRANSACTION);
request.TransactionKind = NTL_POSIX_TRANSACTION_SPAWN;
if (!KSUCCESS(call_posixsrv(first, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
!KSUCCESS(reply.Status) || reply.TransactionId == 0 || reply.ActiveTransactions != 1)
fail("transaction begin failed");
uint32_t transactionId = reply.TransactionId;
SYS_HANDLE second = SYS_HANDLE_INVALID;
if (!KSUCCESS(connect_posixsrv(&second))) fail("second connect failed");
if (!KSUCCESS(register_current_process(second, &reply)) || !KSUCCESS(reply.Status) ||
reply.PosixPid != stablePid || reply.NativeGeneration != 1u)
fail("same-process reconnect changed POSIX identity");
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 != 1 || reply.PosixPid != stablePid)
fail("process transaction sharing failed");
request = make_request(NTL_POSIX_OP_QUERY);
request.Version = NTL_POSIX_PROTOCOL_VERSION + 1u;
if (!KSUCCESS(call_posixsrv(second, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
reply.Status != NTLSTATUS_NOT_SUPPORTED)
fail("version rejection failed");
request = make_request(NTL_POSIX_OP_CANCEL_TRANSACTION);
request.TransactionId = transactionId;
if (!KSUCCESS(call_posixsrv(second, &request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, &reply)) ||
!KSUCCESS(reply.Status) || reply.TransactionId != transactionId || reply.ActiveTransactions != 0)
fail("cross-connection transaction cancel failed");
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)) ||
reply.Status != NTLSTATUS_NOT_FOUND)
fail("unknown transaction rejection failed");
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) || reply.TransactionId == 0 || reply.ActiveTransactions != 1)
fail("exec transaction begin failed");
uint32_t reconnectTransactionId = reply.TransactionId;
(void)SysDisconnectPort(first);
(void)SysCloseHandle(first);
(void)SysDisconnectPort(second);
(void)SysCloseHandle(second);
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 != 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);
print("posix_service_smoke: PASS\n");
return 0;
}