782 lines
34 KiB
C
782 lines
34 KiB
C
// Init service manager. The kernel bootstraps only init; init starts and supervises user-mode system services.
|
|
|
|
#include <stdint.h>
|
|
#include <ntlite/bootstrap_capabilities.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <ntlite/exception.h>
|
|
#include <ntlite/input_service.h>
|
|
#include <ntlite/console_service.h>
|
|
#include <ntlite/desktop.h>
|
|
#include <ntlite/process_loader.h>
|
|
#include <ntlite/resource.h>
|
|
#include <ntlite/security.h>
|
|
#include <ntlite/service_control.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
#include "service_config.h"
|
|
#include "restart_budget.h"
|
|
|
|
typedef enum INIT_SERVICE_STATE {
|
|
InitServiceStopped = 0,
|
|
InitServiceStarting,
|
|
InitServiceReady,
|
|
InitServiceStopping,
|
|
InitServiceFailed,
|
|
} INIT_SERVICE_STATE;
|
|
|
|
typedef struct INIT_SERVICE {
|
|
const INIT_SERVICE_DESCRIPTOR* Descriptor;
|
|
SYS_HANDLE Process;
|
|
SYS_HANDLE ControlEndpoint;
|
|
INIT_SERVICE_STATE State;
|
|
} INIT_SERVICE;
|
|
|
|
static INIT_SERVICE_CONFIG gServiceConfig;
|
|
static INIT_SERVICE gServices[INIT_SERVICE_MAX_COUNT];
|
|
|
|
// Capability sources are referenced by name; the catalog owns READY handles
|
|
// and borrows kernel-provided bootstrap handles for the lifetime of init.
|
|
typedef struct INIT_GRANT_SOURCE {
|
|
char Name[SYS_STARTUP_CAPABILITY_NAME_MAX + 1u];
|
|
SYS_HANDLE Handle;
|
|
SYS_RIGHTS RightsCeiling;
|
|
const INIT_SERVICE_DESCRIPTOR* Provider;
|
|
bool OwnsHandle;
|
|
} INIT_GRANT_SOURCE;
|
|
|
|
#define INIT_GRANT_SOURCE_MAX (8u + INIT_SERVICE_MAX_COUNT * SYS_PORT_MAX_TRANSFER_HANDLES)
|
|
static INIT_GRANT_SOURCE gGrantSources[INIT_GRANT_SOURCE_MAX];
|
|
static uint32_t gGrantSourceCount;
|
|
|
|
// Records one source and its strict rights ceiling without granting any authority.
|
|
static KSTATUS add_grant_source(const char* name, SYS_HANDLE handle, SYS_RIGHTS rights,
|
|
const INIT_SERVICE_DESCRIPTOR* provider, bool ownsHandle) {
|
|
if (!name || !name[0] || handle == SYS_HANDLE_INVALID || !rights ||
|
|
gGrantSourceCount >= INIT_GRANT_SOURCE_MAX) return NTLSTATUS_INVALID_PARAMETER;
|
|
size_t length = strlen(name);
|
|
if (length > SYS_STARTUP_CAPABILITY_NAME_MAX) return NTLSTATUS_NAME_TOO_LONG;
|
|
for (uint32_t i = 0; i < gGrantSourceCount; ++i)
|
|
if (strcmp(gGrantSources[i].Name, name) == 0) return NTLSTATUS_OBJECT_NAME_COLLISION;
|
|
INIT_GRANT_SOURCE* source = &gGrantSources[gGrantSourceCount++];
|
|
memset(source, 0, sizeof(*source));
|
|
memcpy(source->Name, name, length + 1u);
|
|
source->Handle = handle;
|
|
source->RightsCeiling = rights;
|
|
source->Provider = provider;
|
|
source->OwnsHandle = ownsHandle;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Drops all sources exported by a service generation before its handles expire.
|
|
static void forget_service_grants(const INIT_SERVICE_DESCRIPTOR* provider) {
|
|
if (!provider) return;
|
|
for (uint32_t i = 0; i < gGrantSourceCount;) {
|
|
INIT_GRANT_SOURCE* source = &gGrantSources[i];
|
|
if (source->Provider != provider) { ++i; continue; }
|
|
if (source->OwnsHandle && source->Handle != SYS_HANDLE_INVALID)
|
|
(void)SysCloseHandle(source->Handle);
|
|
gGrantSources[i] = gGrantSources[--gGrantSourceCount];
|
|
memset(&gGrantSources[gGrantSourceCount], 0, sizeof(gGrantSources[0]));
|
|
}
|
|
}
|
|
|
|
// Keeps the named READY exports for subsequent dependent services. Ownership of
|
|
// an export handle passes to the catalog only when retainHandles is true.
|
|
static KSTATUS register_ready_exports(const INIT_SERVICE_DESCRIPTOR* provider,
|
|
const NTL_SERVICE_CONTROL_MESSAGE* message,
|
|
const SYS_HANDLE* handles, uint32_t count,
|
|
bool retainHandles) {
|
|
if (!provider || !message || count != message->ExportCount) return NTLSTATUS_INVALID_PARAMETER;
|
|
if (gGrantSourceCount + count > INIT_GRANT_SOURCE_MAX) return NTLSTATUS_LIMIT_REACHED;
|
|
char names[SYS_PORT_MAX_TRANSFER_HANDLES][SYS_STARTUP_CAPABILITY_NAME_MAX + 1u];
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
size_t left = strlen(provider->Id), right = strlen(message->ExportNames[i]);
|
|
if (left + right + 1u > SYS_STARTUP_CAPABILITY_NAME_MAX ||
|
|
handles[i] == SYS_HANDLE_INVALID) return NTLSTATUS_INVALID_PARAMETER;
|
|
memcpy(names[i], provider->Id, left);
|
|
names[i][left] = '.';
|
|
memcpy(names[i] + left + 1u, message->ExportNames[i], right + 1u);
|
|
for (uint32_t j = 0; j < gGrantSourceCount; ++j)
|
|
if (strcmp(names[i], gGrantSources[j].Name) == 0) return NTLSTATUS_OBJECT_NAME_COLLISION;
|
|
}
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
KSTATUS status = add_grant_source(names[i], handles[i],
|
|
SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT,
|
|
provider, retainHandles);
|
|
if (!KSUCCESS(status)) {
|
|
// All names and available capacity were validated before mutation.
|
|
forget_service_grants(provider);
|
|
return status;
|
|
}
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Populates the trusted bootstrap catalog from kernel-issued, init-owned handles.
|
|
static KSTATUS initialize_boot_grants(SYS_HANDLE namespaceVolume, SYS_HANDLE volumeManager,
|
|
SYS_HANDLE inputManager, SYS_HANDLE displayManager,
|
|
SYS_HANDLE registrationAuthority) {
|
|
gGrantSourceCount = 0;
|
|
KSTATUS status;
|
|
#define ADD_BOOT(name, handle, rights) \
|
|
do { status = add_grant_source(name, handle, rights, (const INIT_SERVICE_DESCRIPTOR*)0, false); \
|
|
if (!KSUCCESS(status)) return status; } while (0)
|
|
ADD_BOOT(NTL_BOOT_CAP_NAMESPACE_ROOT, namespaceVolume,
|
|
SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER | SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY);
|
|
ADD_BOOT(NTL_BOOT_CAP_VOLUME_MANAGER, volumeManager,
|
|
SYS_RIGHT_QUERY | SYS_VOLUME_MANAGER_RIGHT_ENUMERATE |
|
|
SYS_VOLUME_MANAGER_RIGHT_OPEN | SYS_VOLUME_MANAGER_RIGHT_WAIT);
|
|
ADD_BOOT(NTL_BOOT_CAP_INPUT_MANAGER, inputManager, SYS_RIGHT_QUERY | SYS_INPUT_MANAGER_RIGHT_READ);
|
|
ADD_BOOT(NTL_BOOT_CAP_DISPLAY_POLICY, displayManager,
|
|
SYS_RIGHT_DERIVE | SYS_RIGHT_TRANSFER | SYS_RIGHT_QUERY |
|
|
SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE | SYS_DISPLAY_MANAGER_RIGHT_MODESET |
|
|
SYS_DISPLAY_MANAGER_RIGHT_CONSOLE);
|
|
ADD_BOOT(NTL_BOOT_CAP_DISPLAY_PRESENT, displayManager,
|
|
SYS_RIGHT_QUERY | SYS_DISPLAY_MANAGER_RIGHT_ENUMERATE |
|
|
SYS_DISPLAY_MANAGER_RIGHT_PRESENT | SYS_DISPLAY_MANAGER_RIGHT_CURSOR);
|
|
ADD_BOOT(NTL_BOOT_CAP_RESOURCE_REGISTRATION, registrationAuthority,
|
|
SYS_RIGHT_QUERY | SYS_SERVICE_REGISTRATION_RIGHT_REGISTER |
|
|
SYS_SERVICE_REGISTRATION_RIGHT_UNREGISTER);
|
|
#undef ADD_BOOT
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Resolves configured names into attenuated child capabilities before creation.
|
|
static KSTATUS resolve_service_grants(const INIT_SERVICE_DESCRIPTOR* descriptor,
|
|
NTL_PROCESS_STARTUP_CAPABILITY* resolved,
|
|
uint32_t* countOut) {
|
|
if (!descriptor || !resolved || !countOut || descriptor->GrantCount >= SYS_STARTUP_CAPABILITY_MAX)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
*countOut = 0;
|
|
for (uint32_t i = 0; i < descriptor->GrantCount; ++i) {
|
|
const INIT_GRANT_SOURCE* found = (const INIT_GRANT_SOURCE*)0;
|
|
for (uint32_t j = 0; j < gGrantSourceCount; ++j)
|
|
if (strcmp(descriptor->Grants[i], gGrantSources[j].Name) == 0) {
|
|
found = &gGrantSources[j]; break;
|
|
}
|
|
if (!found) return NTLSTATUS_NOT_FOUND;
|
|
NTL_PROCESS_STARTUP_CAPABILITY* destination = &resolved[i];
|
|
memset(destination, 0, sizeof(*destination));
|
|
memcpy(destination->Name, found->Name, strlen(found->Name) + 1u);
|
|
destination->SourceHandle = found->Handle;
|
|
destination->DesiredRights = found->RightsCeiling;
|
|
destination->DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE;
|
|
++*countOut;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
// Writes one string through the privileged kernel debug channel.
|
|
static void debug_print(const char* text) {
|
|
SYS_PRINT_DEBUG_REQUEST request = { sizeof(request), SysSvFromCstr(text) };
|
|
(void)SysPrintDebug(&request);
|
|
}
|
|
|
|
// Writes one status value in hexadecimal without pulling in a formatting runtime.
|
|
static void debug_status(KSTATUS status) {
|
|
static const char digits[] = "0123456789ABCDEF";
|
|
char buffer[11] = "0x00000000";
|
|
for (uint32_t i = 0; i < 8; ++i)
|
|
buffer[2 + i] = digits[(status >> ((7u - i) * 4u)) & 0xFu];
|
|
debug_print(buffer);
|
|
}
|
|
|
|
// Writes one unsigned decimal value without pulling in a formatting runtime.
|
|
static void debug_u32(uint32_t value) {
|
|
char buffer[11];
|
|
uint32_t length = 0;
|
|
do {
|
|
buffer[length++] = (char)('0' + value % 10u);
|
|
value /= 10u;
|
|
} while (value != 0u);
|
|
for (uint32_t i = 0; i < length / 2u; ++i) {
|
|
char temporary = buffer[i];
|
|
buffer[i] = buffer[length - 1u - i];
|
|
buffer[length - 1u - i] = temporary;
|
|
}
|
|
buffer[length] = '\0';
|
|
debug_print(buffer);
|
|
}
|
|
|
|
// Writes one signed decimal value without pulling in a formatting runtime.
|
|
static void debug_i32(int32_t value) {
|
|
if (value < 0) debug_print("-");
|
|
uint32_t magnitude = value < 0 ? (uint32_t)(-(int64_t)value) : (uint32_t)value;
|
|
debug_u32(magnitude);
|
|
}
|
|
|
|
// Reports a managed service that terminated before completing its READY contract.
|
|
static void debug_early_service_exit(const INIT_SERVICE* service) {
|
|
if (!service || !service->Descriptor || service->Process == SYS_HANDLE_INVALID) return;
|
|
KSTATUS waitStatus = SysWaitSingle(service->Process, SYS_WAIT_FLAG_NONE, 0);
|
|
if (waitStatus != NTLSTATUS_SUCCESS) return;
|
|
|
|
int32_t exitCode = -1;
|
|
if (SysGetProcessExitCode(service->Process, &exitCode) != NTLSTATUS_SUCCESS) return;
|
|
debug_print("[init] service ");
|
|
debug_print(service->Descriptor->Id);
|
|
debug_print(" exited during startup with code ");
|
|
debug_i32(exitCode);
|
|
debug_print("\n");
|
|
}
|
|
|
|
// Exits init without relying on a compiler runtime return path.
|
|
static void exit_now(int code) {
|
|
for (;;) SysExitProcess(code);
|
|
}
|
|
|
|
// Initializes one managed-service runtime record in the stopped state.
|
|
static void initialize_service(INIT_SERVICE* service, const INIT_SERVICE_DESCRIPTOR* descriptor) {
|
|
memset(service, 0, sizeof(*service));
|
|
service->Descriptor = descriptor;
|
|
service->Process = SYS_HANDLE_INVALID;
|
|
service->ControlEndpoint = SYS_HANDLE_INVALID;
|
|
service->State = InitServiceStopped;
|
|
}
|
|
|
|
// Derives the unprivileged security context used by replaceable user-mode services.
|
|
static KSTATUS derive_unprivileged_service_context(SYS_HANDLE* contextOut) {
|
|
if (!contextOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
*contextOut = SYS_HANDLE_INVALID;
|
|
SYS_DERIVE_SECURITY_CONTEXT_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.SourceContext = SYS_SECURITY_CONTEXT_CURRENT;
|
|
request.EffectivePrivileges = 0;
|
|
request.DelegablePrivileges = 0;
|
|
request.Flags = SYS_SECURITY_CONTEXT_FLAG_NONE;
|
|
request.ContextHandleOut = contextOut;
|
|
return SysDeriveSecurityContext(&request);
|
|
}
|
|
|
|
// Creates one private Port listener used only between init and a managed service.
|
|
static KSTATUS create_control_listener(SYS_HANDLE* listenerOut) {
|
|
SYS_CREATE_PORT_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Flags = SYS_PORT_FLAG_NONE;
|
|
request.Backlog = 1;
|
|
request.HandleOut = listenerOut;
|
|
return SysCreatePort(&request);
|
|
}
|
|
|
|
// Connects init to the private control listener delegated to the child.
|
|
static KSTATUS connect_control_listener(SYS_HANDLE listener, SYS_HANDLE* endpointOut) {
|
|
SYS_CONNECT_PORT_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.ListenerHandle = listener;
|
|
request.Flags = SYS_PORT_CONNECT_FLAG_NONE;
|
|
request.EndpointHandleOut = endpointOut;
|
|
return SysConnectPort(&request);
|
|
}
|
|
|
|
// Closes the generic parent-side handles retained for one managed service.
|
|
static void close_service_handles(INIT_SERVICE* service) {
|
|
if (!service) return;
|
|
forget_service_grants(service->Descriptor);
|
|
if (service->ControlEndpoint != SYS_HANDLE_INVALID) {
|
|
(void)SysDisconnectPort(service->ControlEndpoint);
|
|
(void)SysCloseHandle(service->ControlEndpoint);
|
|
}
|
|
if (service->Process != SYS_HANDLE_INVALID)
|
|
(void)SysCloseHandle(service->Process);
|
|
service->Process = SYS_HANDLE_INVALID;
|
|
service->ControlEndpoint = SYS_HANDLE_INVALID;
|
|
}
|
|
|
|
// Starts one managed service process and establishes its private control connection.
|
|
static KSTATUS start_service_process(SYS_HANDLE systemVolume,
|
|
SYS_HANDLE securityContext,
|
|
INIT_SERVICE* service) {
|
|
if (!service || !service->Descriptor || service->Descriptor->ImagePath[0] == '\0' ||
|
|
service->Descriptor->GrantCount > SYS_STARTUP_CAPABILITY_MAX - 1u)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (service->Process != SYS_HANDLE_INVALID || service->ControlEndpoint != SYS_HANDLE_INVALID ||
|
|
(service->State != InitServiceStopped && service->State != InitServiceFailed))
|
|
return NTLSTATUS_FAILURE;
|
|
|
|
service->State = InitServiceStarting;
|
|
|
|
SYS_HANDLE controlListener = SYS_HANDLE_INVALID;
|
|
KSTATUS status = create_control_listener(&controlListener);
|
|
if (!KSUCCESS(status)) {
|
|
service->State = InitServiceFailed;
|
|
return status;
|
|
}
|
|
|
|
NTL_PROCESS_STARTUP_CAPABILITY startup[SYS_STARTUP_CAPABILITY_MAX];
|
|
memset(startup, 0, sizeof(startup));
|
|
uint32_t startupCapabilityCount = 0;
|
|
status = resolve_service_grants(service->Descriptor, startup, &startupCapabilityCount);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysCloseHandle(controlListener);
|
|
service->State = InitServiceFailed;
|
|
return status;
|
|
}
|
|
memcpy(startup[startupCapabilityCount].Name, "service.control", sizeof("service.control"));
|
|
startup[startupCapabilityCount].SourceHandle = controlListener;
|
|
startup[startupCapabilityCount].DesiredRights = SYS_RIGHT_QUERY | SYS_PORT_RIGHT_ACCEPT;
|
|
startup[startupCapabilityCount].DuplicateFlags = SYS_DUPLICATE_HANDLE_FLAG_NONE;
|
|
|
|
uint32_t capabilityCount = startupCapabilityCount + 1u;
|
|
|
|
SYS_HANDLE process = SYS_HANDLE_INVALID;
|
|
SYS_STRING_VIEW imagePath = SysSvFromCstr(service->Descriptor->ImagePath);
|
|
NTL_CREATE_PROCESS_AT_REQUEST create;
|
|
memset(&create, 0, sizeof(create));
|
|
create.Size = sizeof(create);
|
|
create.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
|
|
create.RootHandle = systemVolume;
|
|
create.Path = imagePath;
|
|
create.CommandLine = imagePath;
|
|
create.SecurityContext = securityContext;
|
|
create.ModuleRootHandle = systemVolume;
|
|
create.ModuleDirectory = SYS_SV_LITERAL("system");
|
|
create.ResolveModuleName = NtlResolveModuleNameIdentity;
|
|
create.ResolveContext = (void*)0;
|
|
create.StartupCapabilities = startup;
|
|
create.StartupCapabilityCount = capabilityCount;
|
|
create.ProcessHandleOut = &process;
|
|
status = NtlCreateProcessAt(&create);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysCloseHandle(controlListener);
|
|
service->State = InitServiceFailed;
|
|
return status;
|
|
}
|
|
|
|
SYS_HANDLE controlEndpoint = SYS_HANDLE_INVALID;
|
|
status = connect_control_listener(controlListener, &controlEndpoint);
|
|
(void)SysCloseHandle(controlListener);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysKillProcess(process, 1);
|
|
(void)SysCloseHandle(process);
|
|
service->State = InitServiceFailed;
|
|
return status;
|
|
}
|
|
|
|
service->Process = process;
|
|
service->ControlEndpoint = controlEndpoint;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Waits for one service READY notification and receives its service-defined export handles.
|
|
static KSTATUS wait_service_ready(INIT_SERVICE* service,
|
|
SYS_HANDLE* exportsOut,
|
|
uint32_t exportCapacity,
|
|
uint32_t* exportCountOut,
|
|
NTL_SERVICE_CONTROL_MESSAGE* messageOut) {
|
|
if (!service || !service->Descriptor || !exportCountOut || !messageOut ||
|
|
exportCapacity > SYS_PORT_MAX_TRANSFER_HANDLES || (exportCapacity && !exportsOut))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
for (uint32_t i = 0; i < exportCapacity; ++i) exportsOut[i] = SYS_HANDLE_INVALID;
|
|
*exportCountOut = 0;
|
|
|
|
NTL_SERVICE_CONTROL_MESSAGE message;
|
|
SYS_PORT_MESSAGE_INFO info;
|
|
memset(&message, 0, sizeof(message));
|
|
memset(&info, 0, sizeof(info));
|
|
info.Size = sizeof(info);
|
|
|
|
SYS_RECEIVE_PORT_REQUEST receive;
|
|
memset(&receive, 0, sizeof(receive));
|
|
receive.Size = sizeof(receive);
|
|
receive.EndpointHandle = service->ControlEndpoint;
|
|
receive.Flags = SYS_PORT_FLAG_NONE;
|
|
receive.Payload = &message;
|
|
receive.PayloadCapacity = sizeof(message);
|
|
receive.HandlesOut = exportsOut;
|
|
receive.HandleCapacity = exportCapacity;
|
|
receive.InfoOut = &info;
|
|
receive.TimeoutMilliseconds = service->Descriptor->ReadyTimeoutMilliseconds;
|
|
KSTATUS status = SysReceivePort(&receive);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
bool valid = info.Type == SYS_PORT_MESSAGE_NOTIFICATION &&
|
|
info.PayloadLength == sizeof(message) &&
|
|
info.HandleCount <= exportCapacity &&
|
|
NtlServiceControlValid(&message, NTL_SERVICE_CONTROL_READY, info.HandleCount);
|
|
if (!valid || !KSUCCESS(message.Status)) {
|
|
for (uint32_t i = 0; i < info.HandleCount && i < exportCapacity; ++i) {
|
|
if (exportsOut[i] != SYS_HANDLE_INVALID) {
|
|
(void)SysCloseHandle(exportsOut[i]);
|
|
exportsOut[i] = SYS_HANDLE_INVALID;
|
|
}
|
|
}
|
|
return valid ? message.Status : NTLSTATUS_FAILURE;
|
|
}
|
|
|
|
*exportCountOut = info.HandleCount;
|
|
*messageOut = message;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Every service uses the same process, READY, export and failure path.
|
|
static KSTATUS start_managed_service(SYS_HANDLE systemVolume,
|
|
SYS_HANDLE securityContext, INIT_SERVICE* service) {
|
|
KSTATUS status = start_service_process(systemVolume, securityContext, service);
|
|
if (!KSUCCESS(status)) {
|
|
debug_print("[init] process or startup grant creation failed for ");
|
|
debug_print(service->Descriptor->Id);
|
|
debug_print(": ");
|
|
debug_status(status);
|
|
debug_print("\n");
|
|
return status;
|
|
}
|
|
|
|
SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES];
|
|
uint32_t count = 0;
|
|
NTL_SERVICE_CONTROL_MESSAGE ready;
|
|
status = wait_service_ready(service, handles, SYS_PORT_MAX_TRANSFER_HANDLES, &count, &ready);
|
|
if (!KSUCCESS(status)) {
|
|
debug_print("[init] READY receive failed for ");
|
|
debug_print(service->Descriptor->Id);
|
|
debug_print(": ");
|
|
debug_status(status);
|
|
debug_print("\n");
|
|
}
|
|
if (KSUCCESS(status)) {
|
|
const INIT_SERVICE_DESCRIPTOR* descriptor = service->Descriptor;
|
|
if (count != descriptor->ExportCount) status = NTLSTATUS_INVALID_PARAMETER;
|
|
for (uint32_t i = 0; KSUCCESS(status) && i < count; ++i) {
|
|
bool declared = false;
|
|
for (uint32_t j = 0; j < descriptor->ExportCount; ++j)
|
|
if (strcmp(ready.ExportNames[i], descriptor->Exports[j]) == 0) declared = true;
|
|
if (!declared || handles[i] == SYS_HANDLE_INVALID)
|
|
status = NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
if (KSUCCESS(status)) {
|
|
status = register_ready_exports(descriptor, &ready, handles, count, true);
|
|
if (KSUCCESS(status))
|
|
for (uint32_t i = 0; i < count; ++i) handles[i] = SYS_HANDLE_INVALID;
|
|
}
|
|
}
|
|
for (uint32_t i = 0; i < count; ++i)
|
|
if (handles[i] != SYS_HANDLE_INVALID) (void)SysCloseHandle(handles[i]);
|
|
if (!KSUCCESS(status)) {
|
|
debug_early_service_exit(service);
|
|
(void)SysKillProcess(service->Process, 1);
|
|
(void)SysWait(service->Process, SYS_WAIT_FLAG_NONE);
|
|
close_service_handles(service);
|
|
service->State = InitServiceFailed;
|
|
return status;
|
|
}
|
|
service->State = InitServiceReady;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Requests graceful STOP and bounds the fallback termination of any service.
|
|
static void stop_service(INIT_SERVICE* service) {
|
|
if (!service || service->Process == SYS_HANDLE_INVALID) return;
|
|
service->State = InitServiceStopping;
|
|
NTL_SERVICE_CONTROL_MESSAGE message = {0};
|
|
message.Size = sizeof(message);
|
|
message.Version = NTL_SERVICE_CONTROL_VERSION;
|
|
message.Operation = NTL_SERVICE_CONTROL_STOP;
|
|
SYS_SEND_PORT_REQUEST send = {0};
|
|
send.Size = sizeof(send);
|
|
send.EndpointHandle = service->ControlEndpoint;
|
|
send.Payload = &message;
|
|
send.PayloadLength = sizeof(message);
|
|
send.TimeoutMilliseconds = service->Descriptor->StopTimeoutMilliseconds;
|
|
(void)SysSendPort(&send);
|
|
KSTATUS status = SysWaitSingle(service->Process, SYS_WAIT_FLAG_NONE,
|
|
service->Descriptor->StopTimeoutMilliseconds);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysKillProcess(service->Process, 1);
|
|
status = SysWaitSingle(service->Process, SYS_WAIT_FLAG_NONE, 2000u);
|
|
if (!KSUCCESS(status)) debug_print("[init] service termination timed out\n");
|
|
}
|
|
close_service_handles(service);
|
|
service->State = InitServiceStopped;
|
|
}
|
|
|
|
// Shuts down the declared graph in reverse dependency order.
|
|
static void stop_all_services(void) {
|
|
for (uint32_t order = gServiceConfig.StartupCount; order > 0; --order)
|
|
stop_service(&gServices[gServiceConfig.StartupOrder[order - 1u]]);
|
|
}
|
|
|
|
// Tests a transitive hard dependency in the validated acyclic service graph.
|
|
static bool service_depends_on(uint16_t serviceIndex, uint16_t providerIndex) {
|
|
const INIT_SERVICE_DESCRIPTOR* desc = &gServiceConfig.Services[serviceIndex];
|
|
for (uint32_t i = 0; i < desc->DependencyCount; ++i) {
|
|
uint16_t dep = desc->Dependencies[i];
|
|
if (dep == providerIndex || service_depends_on(dep, providerIndex)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Before restarting a provider, quiesce every transitive consumer in reverse order.
|
|
static void stop_service_dependents(uint16_t providerIndex) {
|
|
for (uint32_t order = gServiceConfig.StartupCount; order > 0; --order) {
|
|
uint16_t index = gServiceConfig.StartupOrder[order - 1u];
|
|
if (index != providerIndex && service_depends_on(index, providerIndex))
|
|
stop_service(&gServices[index]);
|
|
}
|
|
}
|
|
|
|
// Starts eligible descendants again using the dependency-first startup order.
|
|
static KSTATUS restart_service_dependents(uint16_t providerIndex, SYS_HANDLE volume,
|
|
SYS_HANDLE context) {
|
|
for (uint32_t order = 0; order < gServiceConfig.StartupCount; ++order) {
|
|
uint16_t index = gServiceConfig.StartupOrder[order];
|
|
if (!service_depends_on(index, providerIndex)) continue;
|
|
KSTATUS status = start_managed_service(volume, context, &gServices[index]);
|
|
if (!KSUCCESS(status)) return status;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Starts the configured native session process from the system-volume capability.
|
|
static KSTATUS start_configured_child(SYS_HANDLE systemVolume, SYS_STRING_VIEW path, SYS_HANDLE* processOut) {
|
|
NTL_CREATE_PROCESS_AT_REQUEST create;
|
|
memset(&create, 0, sizeof(create));
|
|
create.Size = sizeof(create);
|
|
create.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
|
|
create.RootHandle = systemVolume;
|
|
create.Path = path;
|
|
create.CommandLine = path;
|
|
create.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
|
|
create.ModuleRootHandle = systemVolume;
|
|
create.ModuleDirectory = SYS_SV_LITERAL("system");
|
|
create.ResolveModuleName = NtlResolveSystemModuleName;
|
|
create.ProcessHandleOut = processOut;
|
|
return NtlCreateProcessAt(&create);
|
|
}
|
|
|
|
// Best-effort diagnostic name for init's current thread.
|
|
static void init_set_current_thread_name(const char* name) {
|
|
SYS_SET_THREAD_NAME_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.ThreadHandle = SYS_HANDLE_INVALID;
|
|
request.Name = SysSvFromCstr(name);
|
|
(void)SysSetThreadName(&request);
|
|
}
|
|
|
|
// Initializes init state and supervises configured services plus the native session process.
|
|
void _start(void) {
|
|
KSTATUS status = NtlInitializeExceptionRuntime();
|
|
if (!KSUCCESS(status)) exit_now((int)status);
|
|
init_set_current_thread_name("init.main");
|
|
|
|
SYS_HANDLE systemVolume = SysGetStartupCapability(NTL_BOOT_CAP_SYSTEM_VOLUME);
|
|
SYS_HANDLE namespaceVolume = SysGetStartupCapability(NTL_BOOT_CAP_NAMESPACE_ROOT);
|
|
SYS_HANDLE volumeManager = SysGetStartupCapability(NTL_BOOT_CAP_VOLUME_MANAGER);
|
|
SYS_HANDLE inputManager = SysGetStartupCapability(NTL_BOOT_CAP_INPUT_MANAGER);
|
|
SYS_HANDLE displayManager = SysGetStartupCapability(NTL_BOOT_CAP_DISPLAY_POLICY);
|
|
SYS_HANDLE registrationAuthority = SysGetStartupCapability(NTL_BOOT_CAP_RESOURCE_REGISTRATION);
|
|
SYS_HANDLE powerEvent = SYS_HANDLE_INVALID;
|
|
status = SysOpenPowerEvent(&powerEvent);
|
|
if (!KSUCCESS(status)) {
|
|
debug_print("[init] FATAL: power coordinator unavailable\n");
|
|
exit_now(1);
|
|
}
|
|
// Report the missing name so bootstrap ABI mismatches can be diagnosed directly.
|
|
const struct {
|
|
const char* Name;
|
|
SYS_HANDLE Handle;
|
|
} requiredBootstrap[] = {
|
|
{ NTL_BOOT_CAP_SYSTEM_VOLUME, systemVolume },
|
|
{ NTL_BOOT_CAP_NAMESPACE_ROOT, namespaceVolume },
|
|
{ NTL_BOOT_CAP_VOLUME_MANAGER, volumeManager },
|
|
{ NTL_BOOT_CAP_INPUT_MANAGER, inputManager },
|
|
{ NTL_BOOT_CAP_DISPLAY_POLICY, displayManager },
|
|
{ NTL_BOOT_CAP_RESOURCE_REGISTRATION, registrationAuthority },
|
|
};
|
|
for (uint32_t i = 0; i < sizeof(requiredBootstrap) / sizeof(requiredBootstrap[0]); ++i) {
|
|
if (requiredBootstrap[i].Handle != SYS_HANDLE_INVALID) continue;
|
|
debug_print("[init] FATAL: missing bootstrap capability: ");
|
|
debug_print(requiredBootstrap[i].Name);
|
|
debug_print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
status = initialize_boot_grants(namespaceVolume, volumeManager, inputManager,
|
|
displayManager, registrationAuthority);
|
|
if (!KSUCCESS(status)) {
|
|
debug_print("[init] FATAL: bootstrap grant catalog initialization failed\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
uint32_t configErrorLine = 0;
|
|
status = InitLoadServiceConfig(systemVolume, &gServiceConfig, &configErrorLine);
|
|
if (!KSUCCESS(status)) {
|
|
debug_print("[init] FATAL: invalid or unreadable services.ini");
|
|
if (configErrorLine != 0u) {
|
|
debug_print(" at line ");
|
|
debug_u32(configErrorLine);
|
|
}
|
|
debug_print(": ");
|
|
debug_status(status);
|
|
debug_print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
SYS_HANDLE unprivilegedServiceContext = SYS_HANDLE_INVALID;
|
|
status = derive_unprivileged_service_context(&unprivilegedServiceContext);
|
|
if (!KSUCCESS(status)) {
|
|
debug_print("[init] FATAL: cannot derive unprivileged service context: ");
|
|
debug_status(status);
|
|
debug_print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
SYS_STRING_VIEW childPath = SysGetPeb()->CommandLine;
|
|
if (!childPath.Data || childPath.Length == 0) {
|
|
debug_print("[init] FATAL: no configured session process\n");
|
|
if (unprivilegedServiceContext != SYS_HANDLE_INVALID) (void)SysCloseHandle(unprivilegedServiceContext);
|
|
exit_now(1);
|
|
}
|
|
|
|
uint32_t autoCount = gServiceConfig.StartupCount;
|
|
if (autoCount + 2u > SYS_WAIT_MANY_MAX_HANDLES) {
|
|
debug_print("[init] FATAL: service graph exceeds supervision wait capacity\n");
|
|
exit_now(1);
|
|
}
|
|
for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i)
|
|
initialize_service(&gServices[i], &gServiceConfig.Services[i]);
|
|
|
|
for (uint32_t order = 0; order < gServiceConfig.StartupCount; ++order) {
|
|
uint16_t index = gServiceConfig.StartupOrder[order];
|
|
INIT_SERVICE* service = &gServices[index];
|
|
status = start_managed_service(systemVolume, unprivilegedServiceContext, service);
|
|
debug_print("[init] service ");
|
|
debug_print(service->Descriptor->Id);
|
|
if (KSUCCESS(status)) debug_print(" READY\n");
|
|
else {
|
|
debug_print(" startup failed: ");
|
|
debug_status(status);
|
|
debug_print("\n");
|
|
if (service->Descriptor->Critical) {
|
|
stop_all_services();
|
|
exit_now(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
INIT_RESTART_BUDGET restartBudgets[INIT_SERVICE_MAX_COUNT] = {0};
|
|
uint64_t tickFrequency = 0;
|
|
status = SysGetTickFrequency(&tickFrequency);
|
|
if (!KSUCCESS(status) || tickFrequency == 0u) {
|
|
debug_print("[init] FATAL: monotonic clock unavailable for supervision\n");
|
|
stop_all_services();
|
|
exit_now(1);
|
|
}
|
|
for (;;) {
|
|
SYS_HANDLE child = SYS_HANDLE_INVALID;
|
|
status = start_configured_child(systemVolume, childPath, &child);
|
|
if (!KSUCCESS(status)) {
|
|
debug_print("[init] FATAL: session start failed: ");
|
|
debug_status(status);
|
|
debug_print("\n");
|
|
stop_all_services();
|
|
exit_now(1);
|
|
}
|
|
debug_print("[init] session process started\n");
|
|
|
|
bool restartChild = false;
|
|
for (;;) {
|
|
SYS_HANDLE waits[SYS_WAIT_MANY_MAX_HANDLES];
|
|
uint32_t kinds[SYS_WAIT_MANY_MAX_HANDLES];
|
|
uint32_t waitCount = 0;
|
|
waits[waitCount] = powerEvent;
|
|
kinds[waitCount++] = UINT32_MAX;
|
|
waits[waitCount] = child;
|
|
kinds[waitCount++] = UINT32_MAX - 1u;
|
|
for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) {
|
|
if (gServices[i].State != InitServiceReady ||
|
|
gServices[i].Process == SYS_HANDLE_INVALID) continue;
|
|
waits[waitCount] = gServices[i].Process;
|
|
kinds[waitCount++] = i;
|
|
}
|
|
uint32_t signaled = 0;
|
|
SYS_WAIT_MANY_REQUEST wait = {0};
|
|
wait.Size = sizeof(wait);
|
|
wait.Handles = waits;
|
|
wait.Count = waitCount;
|
|
wait.SignaledIndexOut = &signaled;
|
|
wait.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE;
|
|
status = SysWaitMany(&wait);
|
|
if (!KSUCCESS(status)) {
|
|
debug_print("[init] FATAL: service supervision wait failed\n");
|
|
(void)SysKillProcess(child, 1);
|
|
(void)SysWait(child, SYS_WAIT_FLAG_NONE);
|
|
(void)SysCloseHandle(child);
|
|
stop_all_services();
|
|
exit_now(1);
|
|
}
|
|
if (kinds[signaled] == UINT32_MAX) {
|
|
uint32_t action = 0;
|
|
status = SysQuerySystemPower(&action);
|
|
if (!KSUCCESS(status) || !action) {
|
|
debug_print("[init] FATAL: invalid power request\n");
|
|
exit_now(1);
|
|
}
|
|
(void)SysKillProcess(child, 0);
|
|
(void)SysWaitSingle(child, SYS_WAIT_FLAG_NONE, 3000u);
|
|
(void)SysCloseHandle(child);
|
|
stop_all_services();
|
|
(void)SysCloseHandle(unprivilegedServiceContext);
|
|
(void)SysCloseHandle(powerEvent);
|
|
status = SysCompleteSystemPower(action);
|
|
if (!KSUCCESS(status)) {
|
|
debug_print("[init] FATAL: power commit rejected\n");
|
|
exit_now(1);
|
|
}
|
|
exit_now(0);
|
|
}
|
|
if (kinds[signaled] == UINT32_MAX - 1u) {
|
|
int32_t exitCode = 1;
|
|
(void)SysGetProcessExitCode(child, &exitCode);
|
|
(void)SysCloseHandle(child);
|
|
if (exitCode == 0) debug_print("[init] session ended; restarting\n");
|
|
else debug_print("[init] session crashed; restarting\n");
|
|
restartChild = true;
|
|
break;
|
|
}
|
|
uint16_t failedIndex = (uint16_t)kinds[signaled];
|
|
INIT_SERVICE* failed = &gServices[failedIndex];
|
|
int32_t exitCode = -1;
|
|
(void)SysGetProcessExitCode(failed->Process, &exitCode);
|
|
debug_print("[init] service exited: ");
|
|
debug_print(failed->Descriptor->Id);
|
|
debug_print(" code=");
|
|
debug_i32(exitCode);
|
|
debug_print("\n");
|
|
stop_service_dependents(failedIndex);
|
|
close_service_handles(failed);
|
|
failed->State = InitServiceFailed;
|
|
if (failed->Descriptor->RestartPolicy == InitServiceRestartOnFailure &&
|
|
InitConsumeRestartBudget(&restartBudgets[failedIndex],
|
|
SysGetTick(), tickFrequency)) {
|
|
status = start_managed_service(systemVolume, unprivilegedServiceContext, failed);
|
|
if (KSUCCESS(status))
|
|
status = restart_service_dependents(failedIndex, systemVolume, unprivilegedServiceContext);
|
|
if (KSUCCESS(status)) continue;
|
|
debug_print("[init] service restart failed\n");
|
|
}
|
|
// Any affected critical service is a fatal dependency loss.
|
|
bool fatal = failed->Descriptor->Critical;
|
|
for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i)
|
|
if (gServices[i].Descriptor->Critical && service_depends_on((uint16_t)i, failedIndex))
|
|
fatal = true;
|
|
if (fatal) {
|
|
(void)SysKillProcess(child, 1);
|
|
(void)SysWait(child, SYS_WAIT_FLAG_NONE);
|
|
(void)SysCloseHandle(child);
|
|
stop_all_services();
|
|
debug_print("[init] FATAL: critical service dependency lost\n");
|
|
exit_now(1);
|
|
}
|
|
}
|
|
if (!restartChild) break;
|
|
}
|
|
exit_now(1);
|
|
}
|