477 lines
19 KiB
C
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);
|
|
}
|