replace nob with ms' build, move disk creation to build.bat, major comment cleanup, remove old and stupid tests, overhaul interrupt management with per-CPU vector allocation, affinity migration and safe teardown, add proper IOAPIC routing and PCI MSI/MSI-X/INTx support, migrate drivers to the new interrupt API, integrate ACPICA with a proper OS services layer, move ACPI firmware events, power button handling and S5 shutdown into acpi.sys, add parallel driver startup and atomic publication, replace PnP arrays with dynamic lists, redesign service startup again to be asynchronous, add delayed startup mode for services, allow omitting keys in services.ini, fix acpi ec deferred work handling, fix kernel threads having garbage exit code.
This commit is contained in:
1 parent
d36f19509c
commit
36eef8f58d
696 files changed
+147281
-7530
No files matched your search
+328
-102
@@ -21,6 +21,7 @@
|
||||
typedef enum INIT_SERVICE_STATE {
|
||||
InitServiceStopped = 0,
|
||||
InitServiceStarting,
|
||||
InitServiceRestartPending,
|
||||
InitServiceReady,
|
||||
InitServiceStopping,
|
||||
InitServiceFailed,
|
||||
@@ -31,10 +32,16 @@ typedef struct INIT_SERVICE {
|
||||
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.
|
||||
@@ -82,13 +89,12 @@ static void forget_service_grants(const INIT_SERVICE_DESCRIPTOR* provider) {
|
||||
}
|
||||
}
|
||||
|
||||
// Keeps the named READY exports for subsequent dependent services. Ownership of
|
||||
// an export handle passes to the catalog only when retainHandles is true.
|
||||
// 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,
|
||||
bool retainHandles) {
|
||||
if (!provider || !message || count != message->ExportCount) return NTLSTATUS_INVALID_PARAMETER;
|
||||
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) {
|
||||
@@ -100,16 +106,17 @@ static KSTATUS register_ready_exports(const INIT_SERVICE_DESCRIPTOR* provider,
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -296,10 +303,12 @@ static KSTATUS start_service_process(SYS_HANDLE systemVolume,
|
||||
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 != 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);
|
||||
@@ -341,7 +350,23 @@ static KSTATUS start_service_process(SYS_HANDLE systemVolume,
|
||||
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;
|
||||
@@ -364,7 +389,7 @@ static KSTATUS start_service_process(SYS_HANDLE systemVolume,
|
||||
}
|
||||
|
||||
// Waits for one service READY notification and receives its service-defined export handles.
|
||||
static KSTATUS wait_service_ready(INIT_SERVICE* service,
|
||||
static KSTATUS wait_service_ready(INIT_SERVICE* service, uint64_t timeout,
|
||||
SYS_HANDLE* exportsOut,
|
||||
uint32_t exportCapacity,
|
||||
uint32_t* exportCountOut,
|
||||
@@ -392,7 +417,7 @@ static KSTATUS wait_service_ready(INIT_SERVICE* service,
|
||||
receive.HandlesOut = exportsOut;
|
||||
receive.HandleCapacity = exportCapacity;
|
||||
receive.InfoOut = &info;
|
||||
receive.TimeoutMilliseconds = service->Descriptor->ReadyTimeoutMilliseconds;
|
||||
receive.TimeoutMilliseconds = timeout;
|
||||
KSTATUS status = SysReceivePort(&receive);
|
||||
if (!KSUCCESS(status)) return status;
|
||||
|
||||
@@ -415,23 +440,13 @@ static KSTATUS wait_service_ready(INIT_SERVICE* service,
|
||||
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;
|
||||
}
|
||||
|
||||
// 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, handles, SYS_PORT_MAX_TRANSFER_HANDLES, &count, &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);
|
||||
@@ -450,7 +465,7 @@ static KSTATUS start_managed_service(SYS_HANDLE systemVolume,
|
||||
status = NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
if (KSUCCESS(status)) {
|
||||
status = register_ready_exports(descriptor, &ready, handles, count, true);
|
||||
status = register_ready_exports(descriptor, &ready, handles, count);
|
||||
if (KSUCCESS(status))
|
||||
for (uint32_t i = 0; i < count; ++i) handles[i] = SYS_HANDLE_INVALID;
|
||||
}
|
||||
@@ -469,6 +484,225 @@ static KSTATUS start_managed_service(SYS_HANDLE systemVolume,
|
||||
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;
|
||||
@@ -482,10 +716,17 @@ static void stop_service(INIT_SERVICE* service) {
|
||||
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);
|
||||
KSTATUS status = SysWaitSingle(service->Process, SYS_WAIT_FLAG_NONE,
|
||||
service->Descriptor->StopTimeoutMilliseconds);
|
||||
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);
|
||||
@@ -501,17 +742,7 @@ static void stop_all_services(void) {
|
||||
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.
|
||||
// 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];
|
||||
@@ -520,19 +751,7 @@ static void stop_service_dependents(uint16_t providerIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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));
|
||||
@@ -627,47 +846,32 @@ void _start(void) {
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
SYS_STRING_VIEW childPath = SysGetPeb()->CommandLine;
|
||||
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);
|
||||
}
|
||||
|
||||
uint32_t autoCount = gServiceConfig.StartupCount;
|
||||
if (autoCount + 2u > SYS_WAIT_MANY_MAX_HANDLES) {
|
||||
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]);
|
||||
|
||||
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) {
|
||||
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);
|
||||
@@ -678,10 +882,22 @@ void _start(void) {
|
||||
stop_all_services();
|
||||
exit_now(1);
|
||||
}
|
||||
debug_print("[init] session process started\n");
|
||||
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;
|
||||
@@ -690,9 +906,11 @@ void _start(void) {
|
||||
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;
|
||||
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;
|
||||
@@ -701,8 +919,12 @@ void _start(void) {
|
||||
wait.Handles = waits;
|
||||
wait.Count = waitCount;
|
||||
wait.SignaledIndexOut = &signaled;
|
||||
wait.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE;
|
||||
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);
|
||||
@@ -742,6 +964,19 @@ void _start(void) {
|
||||
}
|
||||
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: ");
|
||||
@@ -752,21 +987,12 @@ void _start(void) {
|
||||
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");
|
||||
}
|
||||
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.
|
||||
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) {
|
||||
if (critical_service_affected(failedIndex)) {
|
||||
(void)SysKillProcess(child, 1);
|
||||
(void)SysWait(child, SYS_WAIT_FLAG_NONE);
|
||||
(void)SysCloseHandle(child);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
!include $(BASEDIR)\makefile.def
|
||||
@@ -1,19 +1,26 @@
|
||||
// Service restart budget: three automatic attempts in any sliding five-minute interval.
|
||||
// A per-service sliding restart budget with explicit count and time window.
|
||||
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define INIT_RESTART_MAX_ATTEMPTS 16u
|
||||
|
||||
typedef struct INIT_RESTART_BUDGET {
|
||||
uint64_t Attempts[3];
|
||||
uint64_t Attempts[INIT_RESTART_MAX_ATTEMPTS];
|
||||
uint32_t Count;
|
||||
} INIT_RESTART_BUDGET;
|
||||
|
||||
static inline bool InitConsumeRestartBudget(INIT_RESTART_BUDGET* budget,
|
||||
uint64_t now, uint64_t frequency) {
|
||||
if (!budget || !frequency || budget->Count > 3u ||
|
||||
frequency > UINT64_MAX / 300u) return false;
|
||||
uint64_t window = frequency * 300u;
|
||||
uint64_t now, uint64_t frequency,
|
||||
uint32_t limit, uint32_t windowMilliseconds) {
|
||||
if (!budget || !frequency || !limit || limit > INIT_RESTART_MAX_ATTEMPTS ||
|
||||
!windowMilliseconds || budget->Count > INIT_RESTART_MAX_ATTEMPTS ||
|
||||
frequency > UINT64_MAX / windowMilliseconds)
|
||||
return false;
|
||||
uint64_t window = frequency * windowMilliseconds;
|
||||
window = window / 1000u + (window % 1000u != 0u);
|
||||
if (!window) window = 1;
|
||||
uint32_t keep = 0;
|
||||
for (uint32_t i = 0; i < budget->Count; ++i) {
|
||||
uint64_t started = budget->Attempts[i];
|
||||
@@ -21,7 +28,7 @@ static inline bool InitConsumeRestartBudget(INIT_RESTART_BUDGET* budget,
|
||||
budget->Attempts[keep++] = started;
|
||||
}
|
||||
budget->Count = keep;
|
||||
if (keep == 3u) return false;
|
||||
if (keep >= limit) return false;
|
||||
budget->Attempts[budget->Count++] = now;
|
||||
return true;
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string.h>
|
||||
#include <ntlite/status.h>
|
||||
|
||||
#define INIT_SERVICE_KEY_NAME 0x00000001u
|
||||
#define INIT_SERVICE_KEY_IMAGE 0x00000002u
|
||||
#define INIT_SERVICE_KEY_START 0x00000004u
|
||||
#define INIT_SERVICE_KEY_CRITICAL 0x00000008u
|
||||
@@ -16,18 +17,25 @@
|
||||
#define INIT_SERVICE_KEY_REQUIRES 0x00000080u
|
||||
#define INIT_SERVICE_KEY_GRANTS 0x00000100u
|
||||
#define INIT_SERVICE_KEY_EXPORTS 0x00000200u
|
||||
#define INIT_SERVICE_REQUIRED_KEYS 0x000003FEu
|
||||
#define INIT_SERVICE_KEY_AFTER 0x00000400u
|
||||
#define INIT_SERVICE_KEY_PRIVILEGES 0x00000800u
|
||||
#define INIT_SERVICE_KEY_RESTART_LIMIT 0x00001000u
|
||||
#define INIT_SERVICE_KEY_RESTART_WINDOW 0x00002000u
|
||||
#define INIT_SERVICE_KEY_RESTART_DELAY 0x00004000u
|
||||
|
||||
typedef struct INIT_PARSED_SERVICE {
|
||||
INIT_SERVICE_DESCRIPTOR Descriptor;
|
||||
char DependencyIds[INIT_SERVICE_DEPENDENCY_MAX][INIT_SERVICE_ID_MAX + 1u];
|
||||
uint32_t DependencyCount;
|
||||
char AfterIds[INIT_SERVICE_DEPENDENCY_MAX][INIT_SERVICE_ID_MAX + 1u];
|
||||
uint32_t AfterCount;
|
||||
uint32_t PresentKeys;
|
||||
uint32_t SectionLine;
|
||||
} INIT_PARSED_SERVICE;
|
||||
|
||||
static char gServicesIniBuffer[INIT_SERVICES_MAX_FILE_BYTES + 1u];
|
||||
static INIT_PARSED_SERVICE gParsedServices[INIT_SERVICE_MAX_COUNT];
|
||||
static char gSessionImage[SYS_PATH_MAX];
|
||||
|
||||
// Returns whether one byte is horizontal configuration whitespace.
|
||||
static bool is_space(char c) {
|
||||
@@ -129,29 +137,66 @@ static int32_t find_parsed_service(const INIT_PARSED_SERVICE* services,
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parses the comma-separated hard dependency list for one service.
|
||||
static bool parse_requires(char* value, INIT_PARSED_SERVICE* service) {
|
||||
service->DependencyCount = 0;
|
||||
// Parses distinct service IDs for one dependency edge type.
|
||||
static bool parse_edges(char* value,
|
||||
char ids[INIT_SERVICE_DEPENDENCY_MAX][INIT_SERVICE_ID_MAX + 1u],
|
||||
uint32_t* countOut) {
|
||||
*countOut = 0;
|
||||
if (!*value) return true;
|
||||
|
||||
char* cursor = value;
|
||||
for (;;) {
|
||||
char* comma = strchr(cursor, ',');
|
||||
if (comma) *comma = '\0';
|
||||
char* id = trim_left(cursor);
|
||||
trim_right(id);
|
||||
if (!validate_service_id(id) || service->DependencyCount >= INIT_SERVICE_DEPENDENCY_MAX)
|
||||
if (!validate_service_id(id) || *countOut >= INIT_SERVICE_DEPENDENCY_MAX)
|
||||
return false;
|
||||
|
||||
for (uint32_t i = 0; i < service->DependencyCount; ++i)
|
||||
if (strcmp(service->DependencyIds[i], id) == 0) return false;
|
||||
if (!copy_bounded(service->DependencyIds[service->DependencyCount],
|
||||
sizeof(service->DependencyIds[0]), id))
|
||||
return false;
|
||||
++service->DependencyCount;
|
||||
|
||||
for (uint32_t i = 0; i < *countOut; ++i)
|
||||
if (strcmp(ids[i], id) == 0) return false;
|
||||
if (!copy_bounded(ids[*countOut], sizeof(ids[0]), id)) return false;
|
||||
++*countOut;
|
||||
if (!comma) break;
|
||||
cursor = comma + 1;
|
||||
cursor = comma + 1u;
|
||||
if (!*cursor) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_privileges(char* value, SYS_PRIVILEGES* privilegesOut) {
|
||||
static const struct { const char* Name; SYS_PRIVILEGES Bit; } names[] = {
|
||||
{ "process-open-any", SYS_PRIV_PROCESS_OPEN_ANY },
|
||||
{ "process-enumerate", SYS_PRIV_PROCESS_ENUMERATE },
|
||||
{ "thread-enumerate-any", SYS_PRIV_THREAD_ENUMERATE_ANY },
|
||||
{ "kernel-diagnostics", SYS_PRIV_KERNEL_DIAGNOSTICS },
|
||||
{ "system-power", SYS_PRIV_SYSTEM_POWER },
|
||||
{ "audio-access", SYS_PRIV_AUDIO_ACCESS },
|
||||
{ "audio-master-control", SYS_PRIV_AUDIO_MASTER_CONTROL },
|
||||
{ "console-access", SYS_PRIV_CONSOLE_ACCESS },
|
||||
{ "display-access", SYS_PRIV_DISPLAY_ACCESS },
|
||||
{ "debug-output", SYS_PRIV_DEBUG_OUTPUT },
|
||||
{ "raw-storage", SYS_PRIV_RAW_STORAGE },
|
||||
{ "filesystem-provider", SYS_PRIV_FILESYSTEM_PROVIDER },
|
||||
{ "network-access", SYS_PRIV_NETWORK_ACCESS }
|
||||
};
|
||||
*privilegesOut = 0;
|
||||
if (!*value) return true;
|
||||
char* cursor = value;
|
||||
for (;;) {
|
||||
char* comma = strchr(cursor, ',');
|
||||
if (comma) *comma = '\0';
|
||||
char* name = trim_left(cursor);
|
||||
trim_right(name);
|
||||
bool found = false;
|
||||
for (uint32_t i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
|
||||
if (strcmp(names[i].Name, name) != 0) continue;
|
||||
if (*privilegesOut & names[i].Bit) return false;
|
||||
*privilegesOut |= names[i].Bit;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found) return false;
|
||||
if (!comma) break;
|
||||
cursor = comma + 1u;
|
||||
if (!*cursor) return false;
|
||||
}
|
||||
return true;
|
||||
@@ -215,7 +260,11 @@ static bool parse_exports(char* value, INIT_PARSED_SERVICE* service) {
|
||||
static bool parse_service_key(INIT_PARSED_SERVICE* service, char* key, char* value) {
|
||||
uint32_t bit = 0;
|
||||
|
||||
if (strcmp(key, "image") == 0) {
|
||||
if (strcmp(key, "name") == 0) {
|
||||
bit = INIT_SERVICE_KEY_NAME;
|
||||
if (!*value || !copy_bounded(service->Descriptor.Name, sizeof(service->Descriptor.Name), value))
|
||||
return false;
|
||||
} else if (strcmp(key, "image") == 0) {
|
||||
bit = INIT_SERVICE_KEY_IMAGE;
|
||||
if (!validate_image_path(value) ||
|
||||
!copy_bounded(service->Descriptor.ImagePath, sizeof(service->Descriptor.ImagePath), value))
|
||||
@@ -223,6 +272,7 @@ static bool parse_service_key(INIT_PARSED_SERVICE* service, char* key, char* val
|
||||
} else if (strcmp(key, "start") == 0) {
|
||||
bit = INIT_SERVICE_KEY_START;
|
||||
if (strcmp(value, "auto") == 0) service->Descriptor.StartPolicy = InitServiceStartAuto;
|
||||
else if (strcmp(value, "delayed") == 0) service->Descriptor.StartPolicy = InitServiceStartDelayed;
|
||||
else if (strcmp(value, "disabled") == 0) service->Descriptor.StartPolicy = InitServiceStartDisabled;
|
||||
else return false;
|
||||
} else if (strcmp(key, "critical") == 0) {
|
||||
@@ -232,7 +282,17 @@ static bool parse_service_key(INIT_PARSED_SERVICE* service, char* key, char* val
|
||||
bit = INIT_SERVICE_KEY_RESTART;
|
||||
if (strcmp(value, "never") == 0) service->Descriptor.RestartPolicy = InitServiceRestartNever;
|
||||
else if (strcmp(value, "on-failure") == 0) service->Descriptor.RestartPolicy = InitServiceRestartOnFailure;
|
||||
else if (strcmp(value, "always") == 0) service->Descriptor.RestartPolicy = InitServiceRestartAlways;
|
||||
else return false;
|
||||
} else if (strcmp(key, "restart_limit") == 0) {
|
||||
bit = INIT_SERVICE_KEY_RESTART_LIMIT;
|
||||
if (!parse_u32(value, 1u, 16u, &service->Descriptor.RestartLimit)) return false;
|
||||
} else if (strcmp(key, "restart_window_ms") == 0) {
|
||||
bit = INIT_SERVICE_KEY_RESTART_WINDOW;
|
||||
if (!parse_u32(value, 1u, 3600000u, &service->Descriptor.RestartWindowMilliseconds)) return false;
|
||||
} else if (strcmp(key, "restart_delay_ms") == 0) {
|
||||
bit = INIT_SERVICE_KEY_RESTART_DELAY;
|
||||
if (!parse_u32(value, 0u, 600000u, &service->Descriptor.RestartDelayMilliseconds)) return false;
|
||||
} else if (strcmp(key, "ready_timeout_ms") == 0) {
|
||||
bit = INIT_SERVICE_KEY_READY_TIMEOUT;
|
||||
if (!parse_u32(value, 1u, 600000u, &service->Descriptor.ReadyTimeoutMilliseconds)) return false;
|
||||
@@ -241,7 +301,13 @@ static bool parse_service_key(INIT_PARSED_SERVICE* service, char* key, char* val
|
||||
if (!parse_u32(value, 1u, 600000u, &service->Descriptor.StopTimeoutMilliseconds)) return false;
|
||||
} else if (strcmp(key, "requires") == 0) {
|
||||
bit = INIT_SERVICE_KEY_REQUIRES;
|
||||
if (!parse_requires(value, service)) return false;
|
||||
if (!parse_edges(value, service->DependencyIds, &service->DependencyCount)) return false;
|
||||
} else if (strcmp(key, "after") == 0) {
|
||||
bit = INIT_SERVICE_KEY_AFTER;
|
||||
if (!parse_edges(value, service->AfterIds, &service->AfterCount)) return false;
|
||||
} else if (strcmp(key, "privileges") == 0) {
|
||||
bit = INIT_SERVICE_KEY_PRIVILEGES;
|
||||
if (!parse_privileges(value, &service->Descriptor.Privileges)) return false;
|
||||
} else if (strcmp(key, "grants") == 0) {
|
||||
bit = INIT_SERVICE_KEY_GRANTS;
|
||||
if (!parse_grants(value, service)) return false;
|
||||
@@ -261,6 +327,9 @@ static bool parse_service_key(INIT_PARSED_SERVICE* service, char* key, char* val
|
||||
static KSTATUS parse_services_ini(char* buffer, uint32_t* serviceCountOut, uint32_t* errorLineOut) {
|
||||
uint32_t serviceCount = 0;
|
||||
INIT_PARSED_SERVICE* current = NULL;
|
||||
bool inSession = false;
|
||||
bool sessionSeen = false;
|
||||
bool sessionImageSeen = false;
|
||||
uint32_t lineNumber = 0;
|
||||
char* line = buffer;
|
||||
|
||||
@@ -279,14 +348,22 @@ static KSTATUS parse_services_ini(char* buffer, uint32_t* serviceCountOut, uint3
|
||||
// Blank and whole-line comments are the only ignored line forms.
|
||||
} else if (*text == '[') {
|
||||
size_t length = strlen(text);
|
||||
if (length < 11u || text[length - 1u] != ']' || strchr(text + 1, ']') != &text[length - 1u]) {
|
||||
if (length < 3u || text[length - 1u] != ']' || strchr(text + 1, ']') != &text[length - 1u]) {
|
||||
valid = false;
|
||||
} else {
|
||||
text[length - 1u] = '\0';
|
||||
const char prefix[] = "service.";
|
||||
if (strncmp(text + 1, prefix, sizeof(prefix) - 1u) != 0) {
|
||||
if (strcmp(text + 1, "session") == 0) {
|
||||
if (sessionSeen) valid = false;
|
||||
else {
|
||||
sessionSeen = true;
|
||||
inSession = true;
|
||||
current = NULL;
|
||||
}
|
||||
} else if (strncmp(text + 1, prefix, sizeof(prefix) - 1u) != 0) {
|
||||
valid = false;
|
||||
} else {
|
||||
inSession = false;
|
||||
char* id = text + 1u + sizeof(prefix) - 1u;
|
||||
if (!validate_service_id(id) || serviceCount >= INIT_SERVICE_MAX_COUNT ||
|
||||
find_parsed_service(gParsedServices, serviceCount, id) >= 0) {
|
||||
@@ -295,13 +372,20 @@ static KSTATUS parse_services_ini(char* buffer, uint32_t* serviceCountOut, uint3
|
||||
current = &gParsedServices[serviceCount++];
|
||||
memset(current, 0, sizeof(*current));
|
||||
current->SectionLine = lineNumber;
|
||||
if (!copy_bounded(current->Descriptor.Id, sizeof(current->Descriptor.Id), id))
|
||||
current->Descriptor.StartPolicy = InitServiceStartAuto;
|
||||
current->Descriptor.RestartPolicy = InitServiceRestartNever;
|
||||
current->Descriptor.ReadyTimeoutMilliseconds = 5000u;
|
||||
current->Descriptor.StopTimeoutMilliseconds = 5000u;
|
||||
current->Descriptor.RestartLimit = 3u;
|
||||
current->Descriptor.RestartWindowMilliseconds = 300000u;
|
||||
if (!copy_bounded(current->Descriptor.Id, sizeof(current->Descriptor.Id), id) ||
|
||||
!copy_bounded(current->Descriptor.Name, sizeof(current->Descriptor.Name), id))
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!current) {
|
||||
if (!current && !inSession) {
|
||||
valid = false;
|
||||
} else {
|
||||
char* equals = strchr(text, '=');
|
||||
@@ -313,7 +397,14 @@ static KSTATUS parse_services_ini(char* buffer, uint32_t* serviceCountOut, uint3
|
||||
char* value = trim_left(equals + 1);
|
||||
trim_right(key);
|
||||
trim_right(value);
|
||||
if (!*key || !parse_service_key(current, key, value)) valid = false;
|
||||
if (inSession) {
|
||||
if (strcmp(key, "image") != 0 || sessionImageSeen ||
|
||||
!validate_image_path(value) ||
|
||||
!copy_bounded(gSessionImage, sizeof(gSessionImage), value)) valid = false;
|
||||
else sessionImageSeen = true;
|
||||
} else if (!*key || !parse_service_key(current, key, value)) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,7 +417,7 @@ static KSTATUS parse_services_ini(char* buffer, uint32_t* serviceCountOut, uint3
|
||||
line = saved ? end + 1u : end;
|
||||
}
|
||||
|
||||
if (serviceCount == 0)
|
||||
if (serviceCount == 0 || !sessionImageSeen)
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
*serviceCountOut = serviceCount;
|
||||
return NTLSTATUS_SUCCESS;
|
||||
@@ -337,6 +428,7 @@ static KSTATUS validate_services(uint32_t serviceCount,
|
||||
INIT_SERVICE_CONFIG* configOut,
|
||||
uint32_t* errorLineOut) {
|
||||
memset(configOut, 0, sizeof(*configOut));
|
||||
memcpy(configOut->SessionImage, gSessionImage, strlen(gSessionImage) + 1u);
|
||||
|
||||
static const char* const bootGrants[] = {
|
||||
NTL_BOOT_CAP_NAMESPACE_ROOT, NTL_BOOT_CAP_VOLUME_MANAGER, NTL_BOOT_CAP_INPUT_MANAGER,
|
||||
@@ -369,7 +461,7 @@ static KSTATUS validate_services(uint32_t serviceCount,
|
||||
}
|
||||
bool declaredDependency = false;
|
||||
if (provider >= 0 && (uint32_t)provider != i &&
|
||||
gParsedServices[provider].Descriptor.StartPolicy == InitServiceStartAuto) {
|
||||
gParsedServices[provider].Descriptor.StartPolicy != InitServiceStartDisabled) {
|
||||
for (uint32_t d = 0; d < gParsedServices[i].DependencyCount; ++d)
|
||||
if (strcmp(gParsedServices[i].DependencyIds[d],
|
||||
gParsedServices[provider].Descriptor.Id) == 0)
|
||||
@@ -390,9 +482,29 @@ static KSTATUS validate_services(uint32_t serviceCount,
|
||||
}
|
||||
}
|
||||
|
||||
// Exactly one critical automatic service owns the primordial namespace authority.
|
||||
uint32_t namespaceProviders = 0;
|
||||
for (uint32_t i = 0; i < serviceCount; ++i) {
|
||||
const INIT_SERVICE_DESCRIPTOR* descriptor = &gParsedServices[i].Descriptor;
|
||||
bool namespaceRoot = false;
|
||||
bool registration = false;
|
||||
for (uint32_t g = 0; g < descriptor->GrantCount; ++g) {
|
||||
if (strcmp(descriptor->Grants[g], NTL_BOOT_CAP_NAMESPACE_ROOT) == 0) namespaceRoot = true;
|
||||
if (strcmp(descriptor->Grants[g], NTL_BOOT_CAP_RESOURCE_REGISTRATION) == 0) registration = true;
|
||||
}
|
||||
if (!namespaceRoot && !registration) continue;
|
||||
if (!namespaceRoot || !registration || !descriptor->Critical ||
|
||||
descriptor->StartPolicy != InitServiceStartAuto) {
|
||||
if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
++namespaceProviders;
|
||||
}
|
||||
if (namespaceProviders != 1u) return NTLSTATUS_INVALID_PARAMETER;
|
||||
|
||||
for (uint32_t i = 0; i < serviceCount; ++i) {
|
||||
INIT_PARSED_SERVICE* parsed = &gParsedServices[i];
|
||||
if (parsed->PresentKeys != INIT_SERVICE_REQUIRED_KEYS) {
|
||||
if ((parsed->PresentKeys & INIT_SERVICE_KEY_IMAGE) == 0u) {
|
||||
if (errorLineOut) *errorLineOut = parsed->SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
@@ -415,14 +527,43 @@ static KSTATUS validate_services(uint32_t serviceCount,
|
||||
}
|
||||
descriptor->Dependencies[d] = (uint16_t)dependency;
|
||||
}
|
||||
descriptor->AfterCount = parsed->AfterCount;
|
||||
for (uint32_t d = 0; d < parsed->AfterCount; ++d) {
|
||||
int32_t dependency = find_parsed_service(gParsedServices, serviceCount, parsed->AfterIds[d]);
|
||||
if (dependency < 0 || (uint32_t)dependency == i) {
|
||||
if (errorLineOut) *errorLineOut = parsed->SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
for (uint32_t r = 0; r < descriptor->DependencyCount; ++r) {
|
||||
if (descriptor->Dependencies[r] == (uint16_t)dependency) {
|
||||
if (errorLineOut) *errorLineOut = parsed->SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
descriptor->After[d] = (uint16_t)dependency;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < serviceCount; ++i) {
|
||||
INIT_SERVICE_DESCRIPTOR* descriptor = &configOut->Services[i];
|
||||
if (descriptor->StartPolicy != InitServiceStartAuto) continue;
|
||||
if (descriptor->Critical && descriptor->StartPolicy != InitServiceStartAuto) {
|
||||
if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
if (descriptor->StartPolicy == InitServiceStartDisabled) continue;
|
||||
for (uint32_t d = 0; d < descriptor->DependencyCount; ++d) {
|
||||
INIT_SERVICE_DESCRIPTOR* dependency = &configOut->Services[descriptor->Dependencies[d]];
|
||||
if (dependency->StartPolicy == InitServiceStartDisabled) {
|
||||
if (dependency->StartPolicy == InitServiceStartDisabled ||
|
||||
(descriptor->StartPolicy == InitServiceStartAuto &&
|
||||
dependency->StartPolicy != InitServiceStartAuto)) {
|
||||
if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
for (uint32_t d = 0; d < descriptor->AfterCount; ++d) {
|
||||
INIT_SERVICE_DESCRIPTOR* dependency = &configOut->Services[descriptor->After[d]];
|
||||
if (descriptor->StartPolicy == InitServiceStartAuto &&
|
||||
dependency->StartPolicy == InitServiceStartDelayed) {
|
||||
if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine;
|
||||
return NTLSTATUS_INVALID_PARAMETER;
|
||||
}
|
||||
@@ -432,7 +573,7 @@ static KSTATUS validate_services(uint32_t serviceCount,
|
||||
uint32_t indegree[INIT_SERVICE_MAX_COUNT] = {0};
|
||||
bool emitted[INIT_SERVICE_MAX_COUNT] = {0};
|
||||
for (uint32_t i = 0; i < serviceCount; ++i)
|
||||
indegree[i] = configOut->Services[i].DependencyCount;
|
||||
indegree[i] = configOut->Services[i].DependencyCount + configOut->Services[i].AfterCount;
|
||||
|
||||
uint32_t emittedCount = 0;
|
||||
while (emittedCount < serviceCount) {
|
||||
@@ -456,19 +597,17 @@ static KSTATUS validate_services(uint32_t serviceCount,
|
||||
}
|
||||
|
||||
emitted[next] = true;
|
||||
if (configOut->Services[next].StartPolicy == InitServiceStartAuto)
|
||||
if (configOut->Services[next].StartPolicy != InitServiceStartDisabled)
|
||||
configOut->StartupOrder[configOut->StartupCount++] = (uint16_t)next;
|
||||
++emittedCount;
|
||||
|
||||
for (uint32_t i = 0; i < serviceCount; ++i) {
|
||||
if (emitted[i]) continue;
|
||||
INIT_SERVICE_DESCRIPTOR* descriptor = &configOut->Services[i];
|
||||
for (uint32_t d = 0; d < descriptor->DependencyCount; ++d) {
|
||||
if (descriptor->Dependencies[d] == (uint16_t)next) {
|
||||
--indegree[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (uint32_t d = 0; d < descriptor->DependencyCount; ++d)
|
||||
if (descriptor->Dependencies[d] == (uint16_t)next) --indegree[i];
|
||||
for (uint32_t d = 0; d < descriptor->AfterCount; ++d)
|
||||
if (descriptor->After[d] == (uint16_t)next) --indegree[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,6 +671,7 @@ KSTATUS InitLoadServiceConfig(SYS_HANDLE systemVolume,
|
||||
if (errorLineOut) *errorLineOut = 0;
|
||||
|
||||
memset(gParsedServices, 0, sizeof(gParsedServices));
|
||||
memset(gSessionImage, 0, sizeof(gSessionImage));
|
||||
size_t size = 0;
|
||||
KSTATUS status = read_services_ini(systemVolume, &size);
|
||||
if (!KSUCCESS(status)) return status;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <stdint.h>
|
||||
#include <ntlite/syscall.h>
|
||||
#include <ntlite/service_control.h>
|
||||
#include <ntlite/security.h>
|
||||
|
||||
#define INIT_SERVICES_PATH "system/services.ini"
|
||||
#define INIT_SERVICES_MAX_FILE_BYTES (64u * 1024u)
|
||||
@@ -18,24 +19,33 @@
|
||||
|
||||
typedef enum INIT_SERVICE_START_POLICY {
|
||||
InitServiceStartAuto = 0,
|
||||
InitServiceStartDelayed,
|
||||
InitServiceStartDisabled,
|
||||
} INIT_SERVICE_START_POLICY;
|
||||
|
||||
typedef enum INIT_SERVICE_RESTART_POLICY {
|
||||
InitServiceRestartNever = 0,
|
||||
InitServiceRestartOnFailure,
|
||||
InitServiceRestartAlways,
|
||||
} INIT_SERVICE_RESTART_POLICY;
|
||||
|
||||
typedef struct INIT_SERVICE_DESCRIPTOR {
|
||||
char Id[INIT_SERVICE_ID_MAX + 1u];
|
||||
char Name[64];
|
||||
char ImagePath[SYS_PATH_MAX];
|
||||
INIT_SERVICE_START_POLICY StartPolicy;
|
||||
bool Critical;
|
||||
INIT_SERVICE_RESTART_POLICY RestartPolicy;
|
||||
uint32_t ReadyTimeoutMilliseconds;
|
||||
uint32_t StopTimeoutMilliseconds;
|
||||
uint32_t RestartLimit;
|
||||
uint32_t RestartWindowMilliseconds;
|
||||
uint32_t RestartDelayMilliseconds;
|
||||
SYS_PRIVILEGES Privileges;
|
||||
uint32_t DependencyCount;
|
||||
uint16_t Dependencies[INIT_SERVICE_DEPENDENCY_MAX];
|
||||
uint32_t AfterCount;
|
||||
uint16_t After[INIT_SERVICE_DEPENDENCY_MAX];
|
||||
uint32_t GrantCount;
|
||||
char Grants[INIT_SERVICE_GRANT_MAX][INIT_SERVICE_GRANT_NAME_MAX + 1u];
|
||||
uint32_t ExportCount;
|
||||
@@ -43,6 +53,7 @@ typedef struct INIT_SERVICE_DESCRIPTOR {
|
||||
} INIT_SERVICE_DESCRIPTOR;
|
||||
|
||||
typedef struct INIT_SERVICE_CONFIG {
|
||||
char SessionImage[SYS_PATH_MAX];
|
||||
INIT_SERVICE_DESCRIPTOR Services[INIT_SERVICE_MAX_COUNT];
|
||||
uint16_t StartupOrder[INIT_SERVICE_MAX_COUNT];
|
||||
uint32_t ServiceCount;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
TARGETNAME=init
|
||||
TARGETPATH=obj
|
||||
TARGETTYPE=PROGRAM
|
||||
FLAVOR=USER
|
||||
TARGETLIBS="$(BASEDIR)\bin\syscall_s.lib" "$(BASEDIR)\bin\libc.lib" "$(BASEDIR)\bin\native_runtime.lib"
|
||||
EXTRA_LFLAGS=/ENTRY:_start
|
||||
SOURCES=@/code/user/system/init/init.c @/code/user/system/init/service_config.c
|
||||
Reference in new issue
Block a user