Files
nt-lite/code/user/system/init/service_config.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

477 lines
19 KiB
C

// Init service-configuration parser. Malformed or ambiguous service policy is rejected at boot.
#include "service_config.h"
#include <stddef.h>
#include <string.h>
#include <ntlite/status.h>
#define INIT_SERVICE_KEY_TYPE 0x00000001u
#define INIT_SERVICE_KEY_IMAGE 0x00000002u
#define INIT_SERVICE_KEY_START 0x00000004u
#define INIT_SERVICE_KEY_CRITICAL 0x00000008u
#define INIT_SERVICE_KEY_RESTART 0x00000010u
#define INIT_SERVICE_KEY_READY_TIMEOUT 0x00000020u
#define INIT_SERVICE_KEY_STOP_TIMEOUT 0x00000040u
#define INIT_SERVICE_KEY_REQUIRES 0x00000080u
#define INIT_SERVICE_REQUIRED_KEYS 0x000000FFu
typedef struct INIT_PARSED_SERVICE {
INIT_SERVICE_DESCRIPTOR Descriptor;
char DependencyIds[INIT_SERVICE_DEPENDENCY_MAX][INIT_SERVICE_ID_MAX + 1u];
uint32_t DependencyCount;
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];
// Returns whether one byte is horizontal configuration whitespace.
static bool is_space(char c) {
return c == ' ' || c == '\t' || c == '\r';
}
// Trims leading horizontal whitespace in place by returning the first content byte.
static char* trim_left(char* text) {
while (*text && is_space(*text)) ++text;
return text;
}
// Trims trailing horizontal whitespace from one mutable string.
static void trim_right(char* text) {
char* end = text + strlen(text);
while (end > text && is_space(end[-1])) *--end = '\0';
}
// Returns whether one service identifier byte belongs to the deliberately small grammar.
static bool is_service_id_char(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.';
}
// Validates a case-sensitive service identifier used by sections and dependencies.
static bool validate_service_id(const char* id) {
size_t length = id ? strlen(id) : 0;
if (length == 0 || length > INIT_SERVICE_ID_MAX) return false;
for (size_t i = 0; i < length; ++i)
if (!is_service_id_char(id[i])) return false;
return true;
}
// Validates the capability-relative native image path accepted by the process loader.
static bool validate_image_path(const char* path) {
size_t length = path ? strlen(path) : 0;
if (length == 0 || length >= SYS_PATH_MAX || path[0] == '/' || path[length - 1u] == '/')
return false;
const char* component = path;
for (size_t i = 0; i <= length; ++i) {
char c = path[i];
if (c == '\\' || c == ':') return false;
if (c != '/' && c != '\0') continue;
size_t componentLength = (size_t)(&path[i] - component);
if (componentLength == 0 ||
(componentLength == 1 && component[0] == '.') ||
(componentLength == 2 && component[0] == '.' && component[1] == '.'))
return false;
component = &path[i + 1u];
}
return true;
}
// Copies one bounded configuration string into fixed init-owned storage.
static bool copy_bounded(char* destination, size_t capacity, const char* source) {
size_t length = source ? strlen(source) : 0;
if (!destination || capacity == 0 || length >= capacity) return false;
memcpy(destination, source, length + 1u);
return true;
}
// Parses one strict lower-case boolean.
static bool parse_bool(const char* text, bool* valueOut) {
if (!text || !valueOut) return false;
if (strcmp(text, "true") == 0) {
*valueOut = true;
return true;
}
if (strcmp(text, "false") == 0) {
*valueOut = false;
return true;
}
return false;
}
// Parses one bounded unsigned decimal integer without signs or suffixes.
static bool parse_u32(const char* text, uint32_t minimum, uint32_t maximum, uint32_t* valueOut) {
if (!text || !*text || !valueOut) return false;
uint64_t value = 0;
for (const char* p = text; *p; ++p) {
if (*p < '0' || *p > '9') return false;
value = value * 10u + (uint32_t)(*p - '0');
if (value > maximum) return false;
}
if (value < minimum) return false;
*valueOut = (uint32_t)value;
return true;
}
// Looks up a parsed service by its case-sensitive identifier.
static int32_t find_parsed_service(const INIT_PARSED_SERVICE* services,
uint32_t serviceCount,
const char* id) {
for (uint32_t i = 0; i < serviceCount; ++i)
if (strcmp(services[i].Descriptor.Id, id) == 0) return (int32_t)i;
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;
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)
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;
if (!comma) break;
cursor = comma + 1;
if (!*cursor) return false;
}
return true;
}
// Applies one known key to the current service and rejects duplicates or invalid values.
static bool parse_service_key(INIT_PARSED_SERVICE* service, char* key, char* value) {
uint32_t bit = 0;
if (strcmp(key, "type") == 0) {
bit = INIT_SERVICE_KEY_TYPE;
if (strcmp(value, "resource") == 0) service->Descriptor.Type = InitServiceTypeResource;
else if (strcmp(value, "dwm") == 0) service->Descriptor.Type = InitServiceTypeDwm;
else if (strcmp(value, "generic") == 0) service->Descriptor.Type = InitServiceTypeGeneric;
else 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))
return false;
} else if (strcmp(key, "start") == 0) {
bit = INIT_SERVICE_KEY_START;
if (strcmp(value, "auto") == 0) service->Descriptor.StartPolicy = InitServiceStartAuto;
else if (strcmp(value, "disabled") == 0) service->Descriptor.StartPolicy = InitServiceStartDisabled;
else return false;
} else if (strcmp(key, "critical") == 0) {
bit = INIT_SERVICE_KEY_CRITICAL;
if (!parse_bool(value, &service->Descriptor.Critical)) return false;
} else if (strcmp(key, "restart") == 0) {
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 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;
} else if (strcmp(key, "stop_timeout_ms") == 0) {
bit = INIT_SERVICE_KEY_STOP_TIMEOUT;
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;
} else {
return false;
}
if (service->PresentKeys & bit) return false;
service->PresentKeys |= bit;
return true;
}
// Parses the strict services.ini grammar into temporary fixed storage.
static KSTATUS parse_services_ini(char* buffer, uint32_t* serviceCountOut, uint32_t* errorLineOut) {
uint32_t serviceCount = 0;
INIT_PARSED_SERVICE* current = NULL;
uint32_t lineNumber = 0;
char* line = buffer;
while (*line) {
++lineNumber;
char* end = strchr(line, '\n');
if (!end) end = line + strlen(line);
char saved = *end;
*end = '\0';
char* text = trim_left(line);
trim_right(text);
bool valid = true;
if (!*text || *text == ';' || *text == '#') {
// 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]) {
valid = false;
} else {
text[length - 1u] = '\0';
const char prefix[] = "service.";
if (strncmp(text + 1, prefix, sizeof(prefix) - 1u) != 0) {
valid = false;
} else {
char* id = text + 1u + sizeof(prefix) - 1u;
if (!validate_service_id(id) || serviceCount >= INIT_SERVICE_MAX_COUNT ||
find_parsed_service(gParsedServices, serviceCount, id) >= 0) {
valid = false;
} else {
current = &gParsedServices[serviceCount++];
memset(current, 0, sizeof(*current));
current->SectionLine = lineNumber;
if (!copy_bounded(current->Descriptor.Id, sizeof(current->Descriptor.Id), id))
valid = false;
}
}
}
} else {
if (!current) {
valid = false;
} else {
char* equals = strchr(text, '=');
if (!equals || strchr(equals + 1, '=')) {
valid = false;
} else {
*equals = '\0';
char* key = text;
char* value = trim_left(equals + 1);
trim_right(key);
trim_right(value);
if (!*key || !parse_service_key(current, key, value)) valid = false;
}
}
}
*end = saved;
if (!valid) {
if (errorLineOut) *errorLineOut = lineNumber;
return NTLSTATUS_INVALID_PARAMETER;
}
line = saved ? end + 1u : end;
}
if (serviceCount == 0) return NTLSTATUS_INVALID_PARAMETER;
*serviceCountOut = serviceCount;
return NTLSTATUS_SUCCESS;
}
// Resolves dependency identifiers, validates invariants, and computes dependency-first order.
static KSTATUS validate_services(uint32_t serviceCount,
INIT_SERVICE_CONFIG* configOut,
uint32_t* errorLineOut) {
uint32_t resourceCount = 0;
uint16_t resourceIndex = 0;
uint32_t dwmCount = 0;
uint16_t dwmIndex = UINT16_MAX;
memset(configOut, 0, sizeof(*configOut));
configOut->ResourceServiceIndex = UINT16_MAX;
configOut->DwmServiceIndex = UINT16_MAX;
for (uint32_t i = 0; i < serviceCount; ++i) {
INIT_PARSED_SERVICE* parsed = &gParsedServices[i];
if (parsed->PresentKeys != INIT_SERVICE_REQUIRED_KEYS) {
if (errorLineOut) *errorLineOut = parsed->SectionLine;
return NTLSTATUS_INVALID_PARAMETER;
}
INIT_SERVICE_DESCRIPTOR* descriptor = &configOut->Services[i];
*descriptor = parsed->Descriptor;
descriptor->DependencyCount = parsed->DependencyCount;
for (uint32_t d = 0; d < parsed->DependencyCount; ++d) {
int32_t dependency = find_parsed_service(gParsedServices, serviceCount, parsed->DependencyIds[d]);
if (dependency < 0 || (uint32_t)dependency == i) {
if (errorLineOut) *errorLineOut = parsed->SectionLine;
return NTLSTATUS_INVALID_PARAMETER;
}
descriptor->Dependencies[d] = (uint16_t)dependency;
}
if (descriptor->Type == InitServiceTypeResource) {
++resourceCount;
resourceIndex = (uint16_t)i;
} else if (descriptor->Type == InitServiceTypeDwm) {
++dwmCount;
dwmIndex = (uint16_t)i;
}
}
if (resourceCount != 1u || dwmCount > 1u) return NTLSTATUS_INVALID_PARAMETER;
INIT_SERVICE_DESCRIPTOR* resource = &configOut->Services[resourceIndex];
if (resource->StartPolicy != InitServiceStartAuto || !resource->Critical ||
resource->RestartPolicy != InitServiceRestartNever || resource->DependencyCount != 0u) {
if (errorLineOut) *errorLineOut = gParsedServices[resourceIndex].SectionLine;
return NTLSTATUS_INVALID_PARAMETER;
}
if (dwmCount == 1u) {
INIT_SERVICE_DESCRIPTOR* dwm = &configOut->Services[dwmIndex];
bool requiresResource = false;
for (uint32_t d = 0; d < dwm->DependencyCount; ++d)
if (dwm->Dependencies[d] == resourceIndex) requiresResource = true;
if (dwm->StartPolicy == InitServiceStartAuto &&
(dwm->Critical || dwm->RestartPolicy != InitServiceRestartNever || !requiresResource)) {
if (errorLineOut) *errorLineOut = gParsedServices[dwmIndex].SectionLine;
return NTLSTATUS_INVALID_PARAMETER;
}
}
// The generic adapter publishes READY listeners through RSSRV, so automatic generic services require it.
for (uint32_t i = 0; i < serviceCount; ++i) {
INIT_SERVICE_DESCRIPTOR* generic = &configOut->Services[i];
if (generic->Type != InitServiceTypeGeneric || generic->StartPolicy != InitServiceStartAuto) continue;
bool requiresResource = false;
for (uint32_t d = 0; d < generic->DependencyCount; ++d)
if (generic->Dependencies[d] == resourceIndex) requiresResource = true;
if (generic->Critical || generic->RestartPolicy != InitServiceRestartNever || !requiresResource) {
if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine;
return NTLSTATUS_INVALID_PARAMETER;
}
}
for (uint32_t i = 0; i < serviceCount; ++i) {
INIT_SERVICE_DESCRIPTOR* descriptor = &configOut->Services[i];
if (descriptor->StartPolicy != InitServiceStartAuto) continue;
for (uint32_t d = 0; d < descriptor->DependencyCount; ++d) {
INIT_SERVICE_DESCRIPTOR* dependency = &configOut->Services[descriptor->Dependencies[d]];
if (dependency->StartPolicy == InitServiceStartDisabled) {
if (errorLineOut) *errorLineOut = gParsedServices[i].SectionLine;
return NTLSTATUS_INVALID_PARAMETER;
}
}
}
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;
uint32_t emittedCount = 0;
while (emittedCount < serviceCount) {
int32_t next = -1;
for (uint32_t i = 0; i < serviceCount; ++i) {
if (!emitted[i] && indegree[i] == 0u) {
next = (int32_t)i;
break;
}
}
if (next < 0) {
if (errorLineOut) {
for (uint32_t i = 0; i < serviceCount; ++i) {
if (!emitted[i]) {
*errorLineOut = gParsedServices[i].SectionLine;
break;
}
}
}
return NTLSTATUS_DEADLOCK;
}
emitted[next] = true;
if (configOut->Services[next].StartPolicy == InitServiceStartAuto)
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;
}
}
}
}
configOut->ServiceCount = serviceCount;
configOut->ResourceServiceIndex = resourceIndex;
configOut->DwmServiceIndex = dwmIndex;
return NTLSTATUS_SUCCESS;
}
// Reads the mandatory services.ini file from the bootstrap system volume.
static KSTATUS read_services_ini(SYS_HANDLE systemVolume, size_t* sizeOut) {
SYS_HANDLE file = SYS_HANDLE_INVALID;
SYS_OPEN_FILE_AT_REQUEST open = SysMakeOpenFileAtRequest(systemVolume,
SYS_SV_LITERAL(INIT_SERVICES_PATH),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&file);
KSTATUS status = SysOpenFileAt(&open);
if (!KSUCCESS(status)) return status;
uint64_t fileSize = 0;
status = SysGetFileSize(file, &fileSize);
if (!KSUCCESS(status)) {
(void)SysCloseHandle(file);
return status;
}
if (fileSize == 0u || fileSize > INIT_SERVICES_MAX_FILE_BYTES) {
(void)SysCloseHandle(file);
return fileSize > INIT_SERVICES_MAX_FILE_BYTES ? NTLSTATUS_LIMIT_REACHED : NTLSTATUS_INVALID_PARAMETER;
}
size_t total = 0;
while (total < (size_t)fileSize) {
size_t transferred = 0;
SYS_FILE_IO_REQUEST read = SysMakeFileIoRequest(file,
gServicesIniBuffer + total,
(size_t)fileSize - total,
&transferred);
status = SysReadFile(&read);
if (!KSUCCESS(status) || transferred == 0u) {
(void)SysCloseHandle(file);
return KSUCCESS(status) ? NTLSTATUS_FAILURE : status;
}
total += transferred;
}
(void)SysCloseHandle(file);
for (size_t i = 0; i < total; ++i)
if (gServicesIniBuffer[i] == '\0') return NTLSTATUS_INVALID_PARAMETER;
gServicesIniBuffer[total] = '\0';
*sizeOut = total;
return NTLSTATUS_SUCCESS;
}
// Loads, strictly parses, and validates services.ini beneath the bootstrap system volume.
KSTATUS InitLoadServiceConfig(SYS_HANDLE systemVolume,
INIT_SERVICE_CONFIG* configOut,
uint32_t* errorLineOut) {
if (systemVolume == SYS_HANDLE_INVALID || !configOut) return NTLSTATUS_INVALID_PARAMETER;
if (errorLineOut) *errorLineOut = 0;
memset(gParsedServices, 0, sizeof(gParsedServices));
size_t size = 0;
KSTATUS status = read_services_ini(systemVolume, &size);
if (!KSUCCESS(status)) return status;
(void)size;
uint32_t serviceCount = 0;
status = parse_services_ini(gServicesIniBuffer, &serviceCount, errorLineOut);
if (!KSUCCESS(status)) return status;
return validate_services(serviceCount, configOut, errorLineOut);
}