1008 lines
44 KiB
C
1008 lines
44 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,
|
|
InitServiceRestartPending,
|
|
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;
|
|
uint64_t StartedTick;
|
|
uint64_t RestartTick;
|
|
} INIT_SERVICE;
|
|
|
|
static INIT_SERVICE_CONFIG gServiceConfig;
|
|
static INIT_SERVICE gServices[INIT_SERVICE_MAX_COUNT];
|
|
static uint64_t gTickFrequency;
|
|
static INIT_RESTART_BUDGET gRestartBudgets[INIT_SERVICE_MAX_COUNT];
|
|
_Static_assert(INIT_SERVICE_MAX_COUNT + 2u <= SYS_WAIT_MANY_MAX_HANDLES,
|
|
"Service supervisor exceeds WaitMany capacity");
|
|
|
|
// 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]));
|
|
}
|
|
}
|
|
|
|
// READY exports enter the catalog atomically after name and capacity validation.
|
|
static KSTATUS register_ready_exports(const INIT_SERVICE_DESCRIPTOR* provider,
|
|
const NTL_SERVICE_CONTROL_MESSAGE* message,
|
|
const SYS_HANDLE* handles, uint32_t count) {
|
|
if (!provider || !message || (count && !handles) || count != message->ExportCount ||
|
|
count > SYS_PORT_MAX_TRANSFER_HANDLES) 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 j = 0; j < i; ++j)
|
|
if (strcmp(names[i], names[j]) == 0) return NTLSTATUS_OBJECT_NAME_COLLISION;
|
|
}
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
INIT_GRANT_SOURCE* source = &gGrantSources[gGrantSourceCount++];
|
|
memset(source, 0, sizeof(*source));
|
|
memcpy(source->Name, names[i], strlen(names[i]) + 1u);
|
|
source->Handle = handles[i];
|
|
source->RightsCeiling = SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT;
|
|
source->Provider = provider;
|
|
source->OwnsHandle = true;
|
|
}
|
|
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 &&
|
|
service->State != InitServiceRestartPending))
|
|
return NTLSTATUS_FAILURE;
|
|
|
|
service->State = InitServiceStarting;
|
|
service->StartedTick = SysGetTick();
|
|
|
|
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;
|
|
SYS_HANDLE privilegedContext = SYS_HANDLE_INVALID;
|
|
if (service->Descriptor->Privileges) {
|
|
SYS_DERIVE_SECURITY_CONTEXT_REQUEST derive = {0};
|
|
derive.Size = sizeof(derive);
|
|
derive.SourceContext = SYS_SECURITY_CONTEXT_CURRENT;
|
|
derive.EffectivePrivileges = service->Descriptor->Privileges;
|
|
derive.ContextHandleOut = &privilegedContext;
|
|
status = SysDeriveSecurityContext(&derive);
|
|
if (!KSUCCESS(status)) {
|
|
(void)SysCloseHandle(controlListener);
|
|
service->State = InitServiceFailed;
|
|
return status;
|
|
}
|
|
create.SecurityContext = privilegedContext;
|
|
}
|
|
status = NtlCreateProcessAt(&create);
|
|
if (privilegedContext != SYS_HANDLE_INVALID) (void)SysCloseHandle(privilegedContext);
|
|
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, uint64_t timeout,
|
|
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 = timeout;
|
|
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;
|
|
}
|
|
|
|
// Consumes one READY record, transferring published handles on success.
|
|
static KSTATUS finish_service_ready(INIT_SERVICE* service, uint64_t timeout) {
|
|
KSTATUS status;
|
|
SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES];
|
|
uint32_t count = 0;
|
|
NTL_SERVICE_CONTROL_MESSAGE ready;
|
|
status = wait_service_ready(service, timeout, 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);
|
|
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;
|
|
}
|
|
|
|
// Schedules a bounded retry for failed startup or a matching process exit.
|
|
static bool queue_service_restart(INIT_SERVICE* service) {
|
|
if (!service || service->Descriptor->RestartPolicy == InitServiceRestartNever) return false;
|
|
uint32_t index = (uint32_t)(service - gServices);
|
|
if (index >= gServiceConfig.ServiceCount ||
|
|
!InitConsumeRestartBudget(&gRestartBudgets[index], SysGetTick(), gTickFrequency,
|
|
service->Descriptor->RestartLimit,
|
|
service->Descriptor->RestartWindowMilliseconds)) return false;
|
|
uint64_t now = SysGetTick();
|
|
uint64_t delay = (uint64_t)service->Descriptor->RestartDelayMilliseconds * gTickFrequency;
|
|
delay = delay / 1000u + (delay % 1000u != 0u);
|
|
service->RestartTick = delay <= UINT64_MAX - now ? now + delay : UINT64_MAX;
|
|
service->State = InitServiceRestartPending;
|
|
return true;
|
|
}
|
|
|
|
// Starts eligible services, leaving READY completion to the supervisor.
|
|
static KSTATUS launch_services(bool includeDelayed, SYS_HANDLE volume,
|
|
SYS_HANDLE context) {
|
|
for (uint32_t order = 0; order < gServiceConfig.StartupCount; ++order) {
|
|
uint16_t index = gServiceConfig.StartupOrder[order];
|
|
INIT_SERVICE* service = &gServices[index];
|
|
const INIT_SERVICE_DESCRIPTOR* descriptor = service->Descriptor;
|
|
if (service->State != InitServiceStopped ||
|
|
(descriptor->StartPolicy != InitServiceStartAuto &&
|
|
(!includeDelayed || descriptor->StartPolicy != InitServiceStartDelayed))) continue;
|
|
bool pending = false, failed = false;
|
|
for (uint32_t d = 0; d < descriptor->DependencyCount; ++d) {
|
|
INIT_SERVICE_STATE state = gServices[descriptor->Dependencies[d]].State;
|
|
if (state == InitServiceFailed) failed = true;
|
|
else if (state != InitServiceReady) pending = true;
|
|
}
|
|
if (pending && !failed) continue;
|
|
if (!failed) {
|
|
for (uint32_t d = 0; d < descriptor->AfterCount; ++d) {
|
|
const INIT_SERVICE* predecessor = &gServices[descriptor->After[d]];
|
|
INIT_SERVICE_STATE state = predecessor->State;
|
|
if (predecessor->Descriptor->StartPolicy == InitServiceStartDisabled) continue;
|
|
if (state == InitServiceStopped || state == InitServiceStarting ||
|
|
state == InitServiceRestartPending) {
|
|
pending = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (pending && !failed) continue;
|
|
if (failed) {
|
|
service->State = InitServiceFailed;
|
|
debug_print("[init] service blocked: ");
|
|
debug_print(descriptor->Id);
|
|
debug_print("\n");
|
|
} else {
|
|
KSTATUS status = start_service_process(volume, context, service);
|
|
if (KSUCCESS(status)) continue;
|
|
debug_print("[init] service launch failed: ");
|
|
debug_print(descriptor->Id);
|
|
debug_print(" ");
|
|
debug_status(status);
|
|
debug_print("\n");
|
|
}
|
|
if (!failed && service->State == InitServiceFailed && queue_service_restart(service)) continue;
|
|
if (descriptor->Critical) return NTLSTATUS_FAILURE;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Critical consumers make their provider's startup a critical dependency.
|
|
static bool critical_service_affected(uint16_t providerIndex) {
|
|
if (gServices[providerIndex].Descriptor->Critical) return true;
|
|
for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i)
|
|
if (gServices[i].Descriptor->Critical &&
|
|
service_depends_on((uint16_t)i, providerIndex)) return true;
|
|
return false;
|
|
}
|
|
|
|
// Advances one completed READY handshake from the existing Port wait event.
|
|
static KSTATUS complete_starting_service(INIT_SERVICE* service) {
|
|
KSTATUS status = finish_service_ready(service, 0u);
|
|
if (KSUCCESS(status)) {
|
|
debug_print("service: ready id=");
|
|
debug_print(service->Descriptor->Id);
|
|
debug_print("\n");
|
|
} else {
|
|
debug_print("[init] service startup failed: ");
|
|
debug_print(service->Descriptor->Id);
|
|
debug_print(" ");
|
|
debug_status(status);
|
|
debug_print("\n");
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// Uses the original launch timestamp for each independent READY deadline.
|
|
static uint64_t starting_timeout_ms(uint64_t now) {
|
|
uint64_t nearest = SYS_WAIT_TIMEOUT_INFINITE;
|
|
for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) {
|
|
const INIT_SERVICE* service = &gServices[i];
|
|
if (service->State != InitServiceStarting) continue;
|
|
uint64_t elapsed = now >= service->StartedTick ? now - service->StartedTick : 0u;
|
|
uint64_t limit = ((uint64_t)service->Descriptor->ReadyTimeoutMilliseconds *
|
|
gTickFrequency + 999u) / 1000u;
|
|
if (elapsed >= limit) return 0u;
|
|
uint64_t remaining = limit - elapsed;
|
|
uint64_t ms = (remaining / gTickFrequency) * 1000u +
|
|
((remaining % gTickFrequency) * 1000u + gTickFrequency - 1u) / gTickFrequency;
|
|
if (ms < nearest) nearest = ms;
|
|
}
|
|
return nearest;
|
|
}
|
|
|
|
// Starts due restart attempts and reports a failed critical provider.
|
|
static KSTATUS start_pending_restarts(SYS_HANDLE volume, SYS_HANDLE context) {
|
|
uint64_t now = SysGetTick();
|
|
for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) {
|
|
INIT_SERVICE* service = &gServices[i];
|
|
if (service->State != InitServiceRestartPending || now < service->RestartTick) continue;
|
|
KSTATUS status = start_service_process(volume, context, service);
|
|
if (KSUCCESS(status)) continue;
|
|
debug_print("[init] service restart failed: ");
|
|
debug_print(service->Descriptor->Id);
|
|
debug_print("\n");
|
|
if (!queue_service_restart(service) && critical_service_affected((uint16_t)i))
|
|
return NTLSTATUS_FAILURE;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static uint64_t restart_timeout_ms(uint64_t now) {
|
|
uint64_t nearest = SYS_WAIT_TIMEOUT_INFINITE;
|
|
for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) {
|
|
INIT_SERVICE* service = &gServices[i];
|
|
if (service->State != InitServiceRestartPending) continue;
|
|
if (now >= service->RestartTick) return 0u;
|
|
uint64_t ticks = service->RestartTick - now;
|
|
uint64_t ms = (ticks / gTickFrequency) * 1000u +
|
|
((ticks % gTickFrequency) * 1000u + gTickFrequency - 1u) / gTickFrequency;
|
|
if (ms < nearest) nearest = ms;
|
|
}
|
|
return nearest;
|
|
}
|
|
|
|
// Expires all overdue handshakes even if another endpoint remains busy.
|
|
static KSTATUS expire_starting_services(void) {
|
|
uint64_t now = SysGetTick();
|
|
for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) {
|
|
INIT_SERVICE* service = &gServices[i];
|
|
if (service->State != InitServiceStarting) continue;
|
|
uint64_t elapsed = now >= service->StartedTick ? now - service->StartedTick : 0u;
|
|
uint64_t limit = ((uint64_t)service->Descriptor->ReadyTimeoutMilliseconds *
|
|
gTickFrequency + 999u) / 1000u;
|
|
if (elapsed < limit) continue;
|
|
debug_print("[init] READY timeout: ");
|
|
debug_print(service->Descriptor->Id);
|
|
debug_print("\n");
|
|
(void)SysKillProcess(service->Process, 1);
|
|
close_service_handles(service);
|
|
service->State = InitServiceFailed;
|
|
if (!queue_service_restart(service) && critical_service_affected((uint16_t)i))
|
|
return NTLSTATUS_FAILURE;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Waits for the automatic graph to settle before the shell starts.
|
|
static KSTATUS start_automatic_services(SYS_HANDLE volume, SYS_HANDLE context) {
|
|
for (;;) {
|
|
KSTATUS status = start_pending_restarts(volume, context);
|
|
if (KSUCCESS(status)) status = launch_services(false, volume, context);
|
|
if (!KSUCCESS(status)) return status;
|
|
SYS_HANDLE waits[INIT_SERVICE_MAX_COUNT];
|
|
uint16_t indices[INIT_SERVICE_MAX_COUNT];
|
|
uint32_t count = 0;
|
|
for (uint32_t i = 0; i < gServiceConfig.ServiceCount; ++i) {
|
|
if (gServices[i].State != InitServiceStarting) continue;
|
|
waits[count] = gServices[i].ControlEndpoint;
|
|
indices[count++] = (uint16_t)i;
|
|
}
|
|
if (!count) {
|
|
uint64_t retry = restart_timeout_ms(SysGetTick());
|
|
if (retry == SYS_WAIT_TIMEOUT_INFINITE) return NTLSTATUS_SUCCESS;
|
|
if (retry) (void)SysSleep(retry);
|
|
continue;
|
|
}
|
|
uint32_t selected = 0;
|
|
SYS_WAIT_MANY_REQUEST request = {0};
|
|
request.Size = sizeof(request);
|
|
request.Handles = waits;
|
|
request.Count = count;
|
|
request.SignaledIndexOut = &selected;
|
|
uint64_t now = SysGetTick();
|
|
uint64_t readyWait = starting_timeout_ms(now);
|
|
uint64_t retryWait = restart_timeout_ms(now);
|
|
request.TimeoutMilliseconds = readyWait < retryWait ? readyWait : retryWait;
|
|
if (request.TimeoutMilliseconds) {
|
|
status = SysWaitMany(&request);
|
|
if (status != NTLSTATUS_TIMEOUT && !KSUCCESS(status)) return status;
|
|
if (KSUCCESS(status)) {
|
|
INIT_SERVICE* service = &gServices[indices[selected]];
|
|
if (!KSUCCESS(complete_starting_service(service)) &&
|
|
!queue_service_restart(service) && critical_service_affected(indices[selected]))
|
|
return NTLSTATUS_FAILURE;
|
|
}
|
|
}
|
|
status = expire_starting_services();
|
|
if (!KSUCCESS(status)) return status;
|
|
}
|
|
}
|
|
|
|
|
|
// 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);
|
|
uint64_t started = SysGetTick();
|
|
uint64_t duration = ((uint64_t)service->Descriptor->StopTimeoutMilliseconds *
|
|
gTickFrequency + 999u) / 1000u;
|
|
send.TimeoutMilliseconds = service->Descriptor->StopTimeoutMilliseconds;
|
|
(void)SysSendPort(&send);
|
|
uint64_t elapsed = SysGetTick() - started;
|
|
uint64_t remaining = elapsed >= duration ? 0u : duration - elapsed;
|
|
uint64_t remainingMs = gTickFrequency ?
|
|
(remaining / gTickFrequency) * 1000u +
|
|
((remaining % gTickFrequency) * 1000u + gTickFrequency - 1u) / gTickFrequency : 0u;
|
|
KSTATUS status = SysWaitSingle(service->Process, SYS_WAIT_FLAG_NONE, remainingMs);
|
|
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]]);
|
|
}
|
|
|
|
// Quiesces dependent services before the provider generation changes.
|
|
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 the configured session image from the system volume.
|
|
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 = SysSvFromCstr(gServiceConfig.SessionImage);
|
|
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);
|
|
}
|
|
|
|
if (gServiceConfig.StartupCount + 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]);
|
|
|
|
status = SysGetTickFrequency(&gTickFrequency);
|
|
if (!KSUCCESS(status) || gTickFrequency == 0u || gTickFrequency > UINT64_MAX / 600000u) {
|
|
debug_print("[init] FATAL: monotonic clock unavailable for supervision\n");
|
|
stop_all_services();
|
|
exit_now(1);
|
|
}
|
|
status = start_automatic_services(systemVolume, unprivilegedServiceContext);
|
|
if (!KSUCCESS(status)) {
|
|
debug_print("[init] FATAL: automatic service startup failed\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 started\n");
|
|
status = launch_services(true, systemVolume, unprivilegedServiceContext);
|
|
if (!KSUCCESS(status)) {
|
|
stop_all_services();
|
|
exit_now(1);
|
|
}
|
|
|
|
bool restartChild = false;
|
|
for (;;) {
|
|
status = start_pending_restarts(systemVolume, unprivilegedServiceContext);
|
|
if (KSUCCESS(status)) status = launch_services(true, systemVolume, unprivilegedServiceContext);
|
|
if (!KSUCCESS(status) || !KSUCCESS(expire_starting_services())) {
|
|
debug_print("[init] FATAL: delayed service dependency failed\n");
|
|
stop_all_services();
|
|
exit_now(1);
|
|
}
|
|
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].Process == SYS_HANDLE_INVALID) continue;
|
|
if (gServices[i].State != InitServiceReady &&
|
|
gServices[i].State != InitServiceStarting) continue;
|
|
waits[waitCount] = gServices[i].State == InitServiceStarting ?
|
|
gServices[i].ControlEndpoint : 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;
|
|
uint64_t now = SysGetTick();
|
|
uint64_t readyWait = starting_timeout_ms(now);
|
|
uint64_t retryWait = restart_timeout_ms(now);
|
|
wait.TimeoutMilliseconds = readyWait < retryWait ? readyWait : retryWait;
|
|
status = SysWaitMany(&wait);
|
|
if (status == NTLSTATUS_TIMEOUT) continue;
|
|
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];
|
|
if (failed->State == InitServiceStarting) {
|
|
KSTATUS readyStatus = complete_starting_service(failed);
|
|
if (!KSUCCESS(readyStatus) && !queue_service_restart(failed) &&
|
|
critical_service_affected(failedIndex)) {
|
|
debug_print("[init] FATAL: critical service READY failed\n");
|
|
(void)SysKillProcess(child, 1);
|
|
(void)SysWait(child, SYS_WAIT_FLAG_NONE);
|
|
(void)SysCloseHandle(child);
|
|
stop_all_services();
|
|
exit_now(1);
|
|
}
|
|
continue;
|
|
}
|
|
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;
|
|
const INIT_SERVICE_RESTART_POLICY restart = failed->Descriptor->RestartPolicy;
|
|
if ((restart == InitServiceRestartAlways ||
|
|
(restart == InitServiceRestartOnFailure && exitCode != 0)) &&
|
|
queue_service_restart(failed)) continue;
|
|
// Any affected critical service is a fatal dependency loss.
|
|
if (critical_service_affected(failedIndex)) {
|
|
(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);
|
|
}
|