240 lines
6.7 KiB
C++
240 lines
6.7 KiB
C++
/*
|
|
* main.cpp
|
|
* Init system for MontaukOS (PID 0)
|
|
* Discovers and starts system services from 0:/config/init.toml.
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include <montauk/syscall.h>
|
|
#include <montauk/config.h>
|
|
#include <libc/stdio.h>
|
|
|
|
// ---- ANSI color codes ----
|
|
|
|
#define C_RESET "\033[0m"
|
|
#define C_BOLD "\033[1m"
|
|
#define C_DIM "\033[2m"
|
|
#define C_RED "\033[31m"
|
|
#define C_GREEN "\033[32m"
|
|
#define C_YELLOW "\033[33m"
|
|
#define C_BLUE "\033[34m"
|
|
#define C_CYAN "\033[36m"
|
|
#define C_WHITE "\033[37m"
|
|
|
|
// ---- Process scan ----
|
|
|
|
static bool system_already_initialized() {
|
|
/* A valid init will always be PID 0 */
|
|
|
|
if (montauk::getpid() != 0) return true;
|
|
return false;
|
|
}
|
|
|
|
// ---- Logging ----
|
|
|
|
static void log_timestamp(char* buf, int size) {
|
|
montauk::abi::DateTime dt;
|
|
montauk::gettime(&dt);
|
|
snprintf(buf, size, "%02d:%02d:%02d", dt.Hour, dt.Minute, dt.Second);
|
|
}
|
|
|
|
enum LogLevel { LOG_OK, LOG_INFO, LOG_WARN, LOG_ERR };
|
|
|
|
static void log(LogLevel level, const char* msg) {
|
|
char line[512];
|
|
char ts[16];
|
|
log_timestamp(ts, sizeof(ts));
|
|
|
|
const char* tag;
|
|
const char* color;
|
|
switch (level) {
|
|
case LOG_OK: tag = "OK:"; color = C_GREEN; break;
|
|
case LOG_INFO: tag = "INFO:"; color = C_CYAN; break;
|
|
case LOG_WARN: tag = "WARN:"; color = C_YELLOW; break;
|
|
case LOG_ERR: tag = "FAIL:"; color = C_RED; break;
|
|
}
|
|
|
|
snprintf(line, sizeof(line),
|
|
C_DIM "%s" C_RESET " %s%s" C_RESET " %s",
|
|
ts, color, tag, msg);
|
|
montauk::write_log("init", line);
|
|
}
|
|
|
|
static void log_ok(const char* msg) { log(LOG_OK, msg); }
|
|
static void log_info(const char* msg) { log(LOG_INFO, msg); }
|
|
static void log_warn(const char* msg) { log(LOG_WARN, msg); }
|
|
static void log_err(const char* msg) { log(LOG_ERR, msg); }
|
|
|
|
// ---- Service runner ----
|
|
|
|
static bool service_installed(const char* path) {
|
|
int fd = montauk::open(path);
|
|
if (fd < 0) return false;
|
|
montauk::close(fd);
|
|
return true;
|
|
}
|
|
|
|
static bool run_service(const char* path, const char* name, bool wait = true) {
|
|
char msg[128];
|
|
|
|
snprintf(msg, sizeof(msg), "Starting %s", name);
|
|
log_info(msg);
|
|
|
|
int pid = montauk::spawn(path);
|
|
if (pid < 0) {
|
|
snprintf(msg, sizeof(msg), "Failed to start %s", name);
|
|
log_err(msg);
|
|
return false;
|
|
}
|
|
|
|
if (wait) {
|
|
montauk::waitpid(pid);
|
|
snprintf(msg, sizeof(msg), "%s finished (pid %d)", name, pid);
|
|
log_ok(msg);
|
|
} else {
|
|
snprintf(msg, sizeof(msg), "%s spawned in background (pid %d)", name, pid);
|
|
log_ok(msg);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ---- Service configuration ----
|
|
|
|
static constexpr const char* SERVICE_PREFIX = "services.";
|
|
|
|
static bool is_service_table(const montauk::toml::Value* value) {
|
|
if (!value || value->type != montauk::toml::Type::Table || !value->key)
|
|
return false;
|
|
if (!montauk::starts_with(value->key, SERVICE_PREFIX))
|
|
return false;
|
|
|
|
// Only [services.<id>] declares a service. Nested tables belong to that
|
|
// service and must not be treated as additional services.
|
|
const char* id = value->key + montauk::slen(SERVICE_PREFIX);
|
|
if (!*id) return false;
|
|
while (*id) {
|
|
if (*id == '.') return false;
|
|
id++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void service_key(char* out, int out_size, const char* id, const char* field) {
|
|
snprintf(out, out_size, "%s%s.%s", SERVICE_PREFIX, id, field);
|
|
}
|
|
|
|
static montauk::toml::Value* find_service(const montauk::toml::Doc& doc, const char* id) {
|
|
char table_key[256];
|
|
snprintf(table_key, sizeof(table_key), "%s%s", SERVICE_PREFIX, id);
|
|
return doc.get_table(table_key);
|
|
}
|
|
|
|
static bool run_configured_service(const montauk::toml::Doc& doc,
|
|
montauk::toml::Value* service,
|
|
int fallback_depth = 0) {
|
|
const char* id = service->key + montauk::slen(SERVICE_PREFIX);
|
|
char key[256];
|
|
char msg[192];
|
|
|
|
service_key(key, sizeof(key), id, "name");
|
|
const char* name = doc.get_string(key, id);
|
|
|
|
service_key(key, sizeof(key), id, "enabled");
|
|
if (!doc.get_bool(key, true)) {
|
|
snprintf(msg, sizeof(msg), "%s disabled, skipping", name);
|
|
log_info(msg);
|
|
return true;
|
|
}
|
|
|
|
service_key(key, sizeof(key), id, "path");
|
|
const char* path = doc.get_string(key, "");
|
|
if (!path[0]) {
|
|
snprintf(msg, sizeof(msg), "Service %s has no path, skipping", id);
|
|
log_err(msg);
|
|
return false;
|
|
}
|
|
|
|
service_key(key, sizeof(key), id, "optional");
|
|
bool optional = doc.get_bool(key, false);
|
|
if (optional && !service_installed(path)) {
|
|
snprintf(msg, sizeof(msg), "%s not installed, skipping", name);
|
|
log_info(msg);
|
|
return true;
|
|
}
|
|
|
|
service_key(key, sizeof(key), id, "wait");
|
|
bool wait = doc.get_bool(key, true);
|
|
if (run_service(path, name, wait)) return true;
|
|
|
|
service_key(key, sizeof(key), id, "fallback");
|
|
const char* fallback = doc.get_string(key, "");
|
|
if (!fallback[0]) return false;
|
|
|
|
if (fallback_depth >= 8) {
|
|
log_err("Service fallback chain is too deep");
|
|
return false;
|
|
}
|
|
|
|
auto* fallback_service = find_service(doc, fallback);
|
|
if (!fallback_service) {
|
|
snprintf(msg, sizeof(msg), "Fallback service %s is not configured", fallback);
|
|
log_err(msg);
|
|
return false;
|
|
}
|
|
|
|
snprintf(msg, sizeof(msg), "%s failed, falling back to %s", name, fallback);
|
|
log_warn(msg);
|
|
return run_configured_service(doc, fallback_service, fallback_depth + 1);
|
|
}
|
|
|
|
const char* getReleaseString(char* buffer, int n) {
|
|
montauk::abi::SysInfo info;
|
|
montauk::get_info(&info);
|
|
|
|
snprintf(buffer, n, "The Montauk Operating System, version %s (build %u)",
|
|
info.osVersion, info.buildNumber);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
// ---- Main ----
|
|
|
|
extern "C" void _start() {
|
|
if (system_already_initialized()) {
|
|
montauk::exit(1);
|
|
}
|
|
|
|
char releaseString[128] = "";
|
|
getReleaseString(releaseString, 128);
|
|
|
|
log_info(releaseString);
|
|
|
|
auto config = montauk::config::load("init");
|
|
int discovered = 0;
|
|
|
|
// Table order is file order, making the config itself the startup order.
|
|
for (int i = 0; i < config.entries.count; i++) {
|
|
auto* service = config.entries.items[i];
|
|
if (!is_service_table(service)) continue;
|
|
discovered++;
|
|
|
|
const char* id = service->key + montauk::slen(SERVICE_PREFIX);
|
|
char key[256];
|
|
service_key(key, sizeof(key), id, "fallback_only");
|
|
if (config.get_bool(key, false)) continue;
|
|
|
|
run_configured_service(config, service);
|
|
}
|
|
|
|
if (discovered == 0)
|
|
log_err("No services found in 0:/config/init.toml");
|
|
|
|
config.destroy();
|
|
|
|
log_warn("All services exited");
|
|
|
|
for (;;) {
|
|
montauk::yield();
|
|
}
|
|
}
|