Initial commit: syslite lightweight init system

  • Systemd-compatible .service unit file parser
  • Service manager (simple/forking/oneshot, restart policies, dependencies)
  • TTY manager with getty respawn (toybox getty/login)
  • PID 1 main loop: VFS mount, hostname, sysctl, SIGCHLD reaping
  • Config via libconfplus (YAML), CLI via libcmdplus

Merge request reports

Loading

data/syslite.yaml

0 → 100644
+8 −0
Changes for data/syslite.yaml: 8 added lines, 0 removed lines.
Original line number Diff line number Diff line
SYSLITE:
  HOSTNAME: ""
  UNIT_DIR: "/etc/syslite/services"
  DEFAULT_TARGET: "multi-user.target"

  TTY:
    COUNT: 3
    COMMAND: ""

src/main.cpp

0 → 100644
+78 −0
Changes for src/main.cpp: 78 added lines, 0 removed lines.
Original line number Diff line number Diff line
#include "syslite.h"

#include <cmdplus/cmdplus.h>

#include <iostream>
#include <csignal>

static syslite::Init *g_init = nullptr;

static void sigTermHandler(int) {
    if (g_init) g_init->shutdown();
}

static void sigIntHandler(int) {
    if (g_init) g_init->shutdown();
}

// Ctrl+Alt+Del sends SIGINT to PID 1 on Linux
static void sigRebootHandler(int) {
    if (g_init) g_init->reboot();
}

int main(int argc, char **argv) {
    cmdplus::CmdController &cmd = cmdplus::CmdController::getInstance();
    cmd.registerCmd("help", 'h', false, "", "Show help");
    cmd.registerCmd("config", 'c', true, "", "Config file path (e.g. yaml:/etc/syslite/syslite.yaml)");

    cmd.parseCmd(argc, argv);

    if (cmd.getCmdbyKey("help") && cmd.getCmdbyKey("help")->getFound()) {
        std::cout << "syslite — lightweight Linux init system" << std::endl;
        std::cout << std::endl;
        cmd.printHelp();
        return 0;
    }

    if (!cmd.checkRequired()) {
        // If running as PID 1, use default config path
        if (getpid() != 1) {
            std::cerr << "Usage: syslite -c yaml:/etc/syslite/syslite.yaml" << std::endl;
            return 1;
        }
    }

    syslite::Init init;
    g_init = &init;

    // Set up signal handlers
    struct sigaction sa{};
    sa.sa_handler = sigTermHandler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sigaction(SIGTERM, &sa, nullptr);

    sa.sa_handler = sigIntHandler;
    sigaction(SIGINT, &sa, nullptr);

    // SIGUSR1 for reboot (alternative to Ctrl+Alt+Del)
    sa.sa_handler = sigRebootHandler;
    sigaction(SIGUSR1, &sa, nullptr);

    // Ignore SIGPIPE
    signal(SIGPIPE, SIG_IGN);

    // Load config
    std::string configPath;
    auto *configCmd = cmd.getCmdbyKey("config");
    if (configCmd && configCmd->getFound()) {
        configPath = configCmd->getValue();
    } else {
        configPath = "yaml:/etc/syslite/syslite.yaml";
    }

    init.loadConfig(configPath);

    // Run the init main loop
    return init.run();
}

src/service.cpp

0 → 100644
+492 −0
Changes for src/service.cpp: 492 added lines, 0 removed lines.
Original line number Diff line number Diff line
#include "service.h"

#include <iostream>
#include <fstream>
#include <filesystem>
#include <algorithm>
#include <cstring>
#include <csignal>

#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>

namespace syslite {

    ServiceManager::ServiceManager() {}
    ServiceManager::~ServiceManager() {}

    int ServiceManager::loadDirectory(const std::string &path) {
        int count = 0;
        if (!std::filesystem::exists(path))
            return 0;

        for (auto &entry : std::filesystem::directory_iterator(path)) {
            if (entry.is_regular_file() || entry.is_symlink()) {
                std::string fname = entry.path().filename().string();
                if (fname.size() > 8 && fname.substr(fname.size() - 8) == ".service") {
                    if (loadUnit(entry.path().string()))
                        ++count;
                }
            }
        }

        std::cout << "[syslite] Loaded " << count << " service(s) from " << path << std::endl;
        return count;
    }

    bool ServiceManager::loadUnit(const std::string &path) {
        UnitFile unit;
        if (!parseUnitFile(path, unit)) {
            std::cerr << "[syslite] Failed to parse: " << path << std::endl;
            return false;
        }

        ServiceEntry entry;
        entry.unit = std::move(unit);
        _services[entry.unit.name] = std::move(entry);
        return true;
    }

    // Resolve dependencies and determine start order for a target
    void ServiceManager::startTarget(const std::string &target) {
        // Collect services that are WantedBy this target
        std::vector<std::string> toStart;
        for (auto &[name, entry] : _services) {
            for (auto &wb : entry.unit.wantedBy) {
                if (wb == target) {
                    toStart.push_back(name);
                    break;
                }
            }
        }

        // Simple dependency-based ordering: start services whose After
        // dependencies have already been started first
        std::vector<std::string> started;
        std::vector<std::string> pending = toStart;
        int maxPasses = static_cast<int>(pending.size()) + 1;

        while (!pending.empty() && maxPasses-- > 0) {
            std::vector<std::string> nextPending;
            for (auto &name : pending) {
                auto *entry = findService(name);
                if (!entry) continue;

                // Check if all After deps are either started or not in our set
                bool depsReady = true;
                for (auto &dep : entry->unit.after) {
                    // Strip .service suffix if present
                    std::string depName = dep;
                    if (depName.size() > 8 && depName.substr(depName.size() - 8) == ".service")
                        depName = depName.substr(0, depName.size() - 8);

                    // If this dep is in our start set and not yet started, wait
                    if (std::find(toStart.begin(), toStart.end(), depName) != toStart.end()) {
                        if (std::find(started.begin(), started.end(), depName) == started.end()) {
                            depsReady = false;
                            break;
                        }
                    }
                }

                if (depsReady) {
                    startService(name);
                    started.push_back(name);
                } else {
                    nextPending.push_back(name);
                }
            }
            pending = nextPending;
        }

        // Start any remaining that couldn't resolve deps
        for (auto &name : pending) {
            std::cerr << "[syslite] Starting " << name << " (unresolved deps)" << std::endl;
            startService(name);
        }

        _startOrder = started;
        for (auto &name : pending)
            _startOrder.push_back(name);
    }

    std::vector<std::string> ServiceManager::buildArgv(const std::string &cmdline) {
        std::vector<std::string> args;
        std::string current;
        bool inQuote = false;
        char quoteChar = 0;

        for (size_t i = 0; i < cmdline.size(); ++i) {
            char c = cmdline[i];

            if (inQuote) {
                if (c == quoteChar) {
                    inQuote = false;
                } else {
                    current += c;
                }
            } else if (c == '"' || c == '\'') {
                inQuote = true;
                quoteChar = c;
            } else if (c == ' ' || c == '\t') {
                if (!current.empty()) {
                    args.push_back(current);
                    current.clear();
                }
            } else {
                current += c;
            }
        }
        if (!current.empty())
            args.push_back(current);

        return args;
    }

    void ServiceManager::resolveEnvironment(std::string &cmd, const ServiceEntry &entry) {
        // Replace $MAINPID with actual PID
        std::string pidStr = std::to_string(entry.pid);
        size_t pos;
        while ((pos = cmd.find("$MAINPID")) != std::string::npos)
            cmd.replace(pos, 8, pidStr);
    }

    void ServiceManager::createRuntimeDirectory(const ServiceEntry &entry) {
        if (entry.unit.runtimeDirectory.empty())
            return;

        std::string dir = "/run/" + entry.unit.runtimeDirectory;
        std::filesystem::create_directories(dir);

        if (!entry.unit.user.empty()) {
            struct passwd *pw = getpwnam(entry.unit.user.c_str());
            struct group *gr = entry.unit.group.empty()
                ? nullptr : getgrnam(entry.unit.group.c_str());
            if (pw) {
                chown(dir.c_str(), pw->pw_uid, gr ? gr->gr_gid : pw->pw_gid);
            }
        }
    }

    void ServiceManager::setupChildProcess(const ServiceEntry &entry) {
        // Set working directory
        if (!entry.unit.workingDirectory.empty()) {
            if (chdir(entry.unit.workingDirectory.c_str()) != 0) {
                std::cerr << "[syslite] chdir failed: " << entry.unit.workingDirectory << std::endl;
            }
        }

        // Load environment file
        if (!entry.unit.environmentFile.empty()) {
            std::string envPath = entry.unit.environmentFile;
            // Strip leading '-' (optional file)
            if (!envPath.empty() && envPath[0] == '-')
                envPath = envPath.substr(1);

            std::ifstream envFile(envPath);
            if (envFile.is_open()) {
                std::string line;
                while (std::getline(envFile, line)) {
                    if (line.empty() || line[0] == '#') continue;
                    putenv(strdup(line.c_str()));
                }
            }
        }

        // Set individual environment variables
        for (auto &[key, val] : entry.unit.environment) {
            setenv(key.c_str(), val.c_str(), 1);
        }

        // Drop privileges
        if (!entry.unit.group.empty()) {
            struct group *gr = getgrnam(entry.unit.group.c_str());
            if (gr) setgid(gr->gr_gid);
        }
        if (!entry.unit.user.empty()) {
            struct passwd *pw = getpwnam(entry.unit.user.c_str());
            if (pw) {
                initgroups(entry.unit.user.c_str(), pw->pw_gid);
                setuid(pw->pw_uid);
            }
        }

        // Reset signals
        signal(SIGCHLD, SIG_DFL);
        signal(SIGTERM, SIG_DFL);
        signal(SIGINT, SIG_DFL);

        // New session for the service
        setsid();
    }

    bool ServiceManager::execService(ServiceEntry &entry) {
        if (entry.unit.execStart.empty()) {
            std::cerr << "[syslite] No ExecStart for " << entry.unit.name << std::endl;
            entry.state = ServiceState::Failed;
            return false;
        }

        createRuntimeDirectory(entry);

        // Run ExecStartPre if set
        if (!entry.unit.execStartPre.empty()) {
            std::string preCmd = entry.unit.execStartPre;
            // Strip leading '-' (ignore failures)
            bool ignoreFailure = false;
            if (!preCmd.empty() && preCmd[0] == '-') {
                ignoreFailure = true;
                preCmd = preCmd.substr(1);
            }

            pid_t prePid = fork();
            if (prePid == 0) {
                setupChildProcess(entry);
                auto args = buildArgv(preCmd);
                std::vector<char*> argv;
                for (auto &a : args)
                    argv.push_back(const_cast<char*>(a.c_str()));
                argv.push_back(nullptr);
                execvp(argv[0], argv.data());
                _exit(127);
            } else if (prePid > 0) {
                int preStatus;
                waitpid(prePid, &preStatus, 0);
                if (WIFEXITED(preStatus) && WEXITSTATUS(preStatus) != 0 && !ignoreFailure) {
                    std::cerr << "[syslite] ExecStartPre failed for " << entry.unit.name << std::endl;
                    entry.state = ServiceState::Failed;
                    return false;
                }
            }
        }

        entry.state = ServiceState::Starting;

        pid_t pid = fork();
        if (pid == 0) {
            // Child
            setupChildProcess(entry);

            auto args = buildArgv(entry.unit.execStart);
            std::vector<char*> argv;
            for (auto &a : args)
                argv.push_back(const_cast<char*>(a.c_str()));
            argv.push_back(nullptr);

            execvp(argv[0], argv.data());
            std::cerr << "[syslite] exec failed: " << entry.unit.execStart
                      << ": " << strerror(errno) << std::endl;
            _exit(127);
        }

        if (pid < 0) {
            std::cerr << "[syslite] fork failed for " << entry.unit.name << std::endl;
            entry.state = ServiceState::Failed;
            return false;
        }

        entry.pid = pid;

        if (entry.unit.type == ServiceType::Forking) {
            // Wait for the forking parent to exit, then read PID file
            int status;
            waitpid(pid, &status, 0);

            if (!entry.unit.pidFile.empty()) {
                std::ifstream pf(entry.unit.pidFile);
                if (pf.is_open()) {
                    pid_t realPid;
                    pf >> realPid;
                    if (realPid > 0)
                        entry.pid = realPid;
                }
            } else {
                // No PID file — we can't reliably track the daemon
                entry.pid = -1;
            }
            entry.state = ServiceState::Running;
        }
        else if (entry.unit.type == ServiceType::Oneshot) {
            // Wait for completion
            int status;
            waitpid(pid, &status, 0);
            entry.exitCode = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
            entry.pid = -1;
            entry.state = (entry.exitCode == 0) ? ServiceState::Stopped : ServiceState::Failed;
        }
        else {
            // Simple — process stays running
            entry.state = ServiceState::Running;
        }

        std::cout << "[syslite] Started " << entry.unit.name
                  << " (pid=" << entry.pid << ")" << std::endl;
        return true;
    }

    bool ServiceManager::startService(const std::string &name) {
        auto it = _services.find(name);
        if (it == _services.end()) {
            std::cerr << "[syslite] Unknown service: " << name << std::endl;
            return false;
        }

        auto &entry = it->second;
        if (entry.state == ServiceState::Running) {
            return true;
        }

        // Start required dependencies first
        for (auto &req : entry.unit.requires) {
            std::string depName = req;
            if (depName.size() > 8 && depName.substr(depName.size() - 8) == ".service")
                depName = depName.substr(0, depName.size() - 8);
            startService(depName);
        }

        std::cout << "[syslite] Starting " << name << "..." << std::endl;
        return execService(entry);
    }

    bool ServiceManager::stopService(const std::string &name) {
        auto it = _services.find(name);
        if (it == _services.end())
            return false;

        auto &entry = it->second;
        if (entry.state != ServiceState::Running)
            return true;

        entry.state = ServiceState::Stopping;
        std::cout << "[syslite] Stopping " << name << "..." << std::endl;

        // ExecStop if defined
        if (!entry.unit.execStop.empty() && entry.pid > 0) {
            std::string stopCmd = entry.unit.execStop;
            resolveEnvironment(stopCmd, entry);

            pid_t stopPid = fork();
            if (stopPid == 0) {
                auto args = buildArgv(stopCmd);
                std::vector<char*> argv;
                for (auto &a : args)
                    argv.push_back(const_cast<char*>(a.c_str()));
                argv.push_back(nullptr);
                execvp(argv[0], argv.data());
                _exit(127);
            } else if (stopPid > 0) {
                int status;
                waitpid(stopPid, &status, 0);
            }
        } else if (entry.pid > 0) {
            // Default: send SIGTERM
            kill(entry.pid, SIGTERM);
        }

        // Wait for process to exit with timeout
        if (entry.pid > 0) {
            int timeoutMs = entry.unit.timeoutStopSec * 1000;
            int waited = 0;
            int step = 100; // 100ms

            while (waited < timeoutMs) {
                int status;
                pid_t ret = waitpid(entry.pid, &status, WNOHANG);
                if (ret > 0) {
                    entry.pid = -1;
                    entry.state = ServiceState::Stopped;
                    std::cout << "[syslite] Stopped " << name << std::endl;
                    return true;
                }
                if (ret < 0) break; // already gone
                usleep(step * 1000);
                waited += step;
            }

            // Timeout: SIGKILL
            if (entry.pid > 0) {
                std::cerr << "[syslite] " << name << " did not stop, sending SIGKILL" << std::endl;
                kill(entry.pid, SIGKILL);
                waitpid(entry.pid, nullptr, 0);
            }
        }

        entry.pid = -1;
        entry.state = ServiceState::Stopped;
        return true;
    }

    void ServiceManager::stopAll() {
        // Stop in reverse start order
        std::vector<std::string> order = _startOrder;
        std::reverse(order.begin(), order.end());

        // Also stop anything not in the start order
        for (auto &[name, entry] : _services) {
            if (entry.state == ServiceState::Running) {
                if (std::find(order.begin(), order.end(), name) == order.end())
                    order.push_back(name);
            }
        }

        for (auto &name : order) {
            stopService(name);
        }
    }

    void ServiceManager::handleChild(pid_t pid, int status) {
        for (auto &[name, entry] : _services) {
            if (entry.pid == pid) {
                entry.exitCode = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
                entry.pid = -1;

                if (entry.state == ServiceState::Stopping) {
                    entry.state = ServiceState::Stopped;
                } else if (entry.state == ServiceState::Running) {
                    if (entry.exitCode == 0) {
                        entry.state = ServiceState::Stopped;
                        std::cout << "[syslite] " << name << " exited normally" << std::endl;
                    } else {
                        entry.state = ServiceState::Failed;
                        std::cerr << "[syslite] " << name << " exited with code "
                                  << entry.exitCode << std::endl;
                    }
                }
                return;
            }
        }
    }

    void ServiceManager::checkRestarts() {
        for (auto &[name, entry] : _services) {
            bool shouldRestart = false;

            if (entry.unit.restart == RestartPolicy::Always &&
                (entry.state == ServiceState::Stopped || entry.state == ServiceState::Failed)) {
                shouldRestart = true;
            }
            else if (entry.unit.restart == RestartPolicy::OnFailure &&
                     entry.state == ServiceState::Failed) {
                shouldRestart = true;
            }

            if (shouldRestart && entry.pid <= 0) {
                ++entry.restartCount;
                std::cout << "[syslite] Restarting " << name
                          << " (attempt " << entry.restartCount << ")" << std::endl;
                execService(entry);
            }
        }
    }

    ServiceEntry *ServiceManager::findService(const std::string &name) {
        auto it = _services.find(name);
        if (it == _services.end()) return nullptr;
        return &it->second;
    }

}

src/service.h

0 → 100644
+74 −0
Changes for src/service.h: 74 added lines, 0 removed lines.
Original line number Diff line number Diff line
#pragma once

#include "unitfile.h"

#include <string>
#include <vector>
#include <map>
#include <sys/types.h>

namespace syslite {

    enum class ServiceState {
        Stopped,
        Starting,
        Running,
        Stopping,
        Failed
    };

    struct ServiceEntry {
        UnitFile      unit;
        ServiceState  state  = ServiceState::Stopped;
        pid_t         pid    = -1;
        int           exitCode = 0;
        int           restartCount = 0;
    };

    class ServiceManager {
    public:
        ServiceManager();
        ~ServiceManager();

        // Scan a directory for .service files and load them
        int loadDirectory(const std::string &path);

        // Load a single .service file
        bool loadUnit(const std::string &path);

        // Start a service by name
        bool startService(const std::string &name);

        // Stop a service by name
        bool stopService(const std::string &name);

        // Start all services wanted by a given target (e.g. "multi-user.target")
        void startTarget(const std::string &target);

        // Stop all running services (reverse order)
        void stopAll();

        // Handle a terminated child process (call from SIGCHLD handler)
        void handleChild(pid_t pid, int status);

        // Check if any service needs restart and do it
        void checkRestarts();

        // Get service entry by name
        ServiceEntry *findService(const std::string &name);

        // Get all services
        const std::map<std::string, ServiceEntry> &services() const { return _services; }

    private:
        bool execService(ServiceEntry &entry);
        void setupChildProcess(const ServiceEntry &entry);
        void createRuntimeDirectory(const ServiceEntry &entry);
        std::vector<std::string> buildArgv(const std::string &cmdline);
        void resolveEnvironment(std::string &cmd, const ServiceEntry &entry);

        std::map<std::string, ServiceEntry> _services;
        std::vector<std::string> _startOrder; // topological order
    };

}

src/syslite.cpp

0 → 100644
+286 −0
Changes for src/syslite.cpp: 286 added lines, 0 removed lines.
Original line number Diff line number Diff line
#include "syslite.h"

#include <confplus/conf.h>
#include <confplus/exception.h>

#include <iostream>
#include <fstream>
#include <filesystem>
#include <cstring>
#include <csignal>

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/reboot.h>

namespace syslite {

    std::atomic<bool> Init::running{true};
    std::atomic<bool> Init::doReboot{false};

    Init::Init()
        : _unitDir("/etc/syslite/services")
        , _defaultTarget("multi-user.target")
        , _numTtys(3)
    {}

    Init::~Init() {}

    bool Init::loadConfig(const std::string &configPath) {
        try {
            confplus::Config conf(configPath);

            // /SYSLITE/HOSTNAME
            auto *key = conf.getKey("/SYSLITE/HOSTNAME");
            if (key)
                _hostname = conf.getValue(key, 0);

            // /SYSLITE/UNIT_DIR — where .service files live
            key = conf.getKey("/SYSLITE/UNIT_DIR");
            if (key)
                _unitDir = conf.getValue(key, 0);

            // /SYSLITE/DEFAULT_TARGET
            key = conf.getKey("/SYSLITE/DEFAULT_TARGET");
            if (key)
                _defaultTarget = conf.getValue(key, 0);

            // /SYSLITE/TTY/COUNT — number of TTYs to spawn
            key = conf.getKey("/SYSLITE/TTY/COUNT");
            if (key)
                _numTtys = conf.getIntValue(key, 0);

            // /SYSLITE/TTY/COMMAND — custom getty command (optional)
            std::string ttyCmd;
            key = conf.getKey("/SYSLITE/TTY/COMMAND");
            if (key)
                ttyCmd = conf.getValue(key, 0);

            // Register TTYs
            for (int i = 1; i <= _numTtys; ++i) {
                std::string dev = "/dev/tty" + std::to_string(i);
                _ttys.addTty(dev, ttyCmd);
            }

            // Also check for extra service directories
            // /SYSLITE/EXTRA_UNIT_DIRS/[0..n]
            key = conf.getKey("/SYSLITE/EXTRA_UNIT_DIRS");
            if (key) {
                for (size_t i = 0; i < key->getElements(); ++i) {
                    std::string dir = conf.getValue(key, i);
                    if (!dir.empty())
                        _services.loadDirectory(dir);
                }
            }

        } catch (confplus::ConfException &e) {
            std::cerr << "[syslite] Config error: " << e.what() << std::endl;
            // Use defaults — config is optional
        } catch (...) {
            std::cerr << "[syslite] Failed to load config, using defaults" << std::endl;
        }

        // If no TTYs configured via config, set defaults
        if (_numTtys > 0 && _ttys.ttyCount() == 0) {
            for (int i = 1; i <= _numTtys; ++i) {
                _ttys.addTty("/dev/tty" + std::to_string(i));
            }
        }

        return true;
    }

    void Init::mountVirtualFilesystems() {
        // Mount /proc if not already mounted
        if (!std::filesystem::exists("/proc/self")) {
            std::cout << "[syslite] Mounting /proc" << std::endl;
            mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, nullptr);
        }

        // Mount /sys
        if (!std::filesystem::exists("/sys/kernel")) {
            std::cout << "[syslite] Mounting /sys" << std::endl;
            mount("sysfs", "/sys", "sysfs", MS_NOSUID | MS_NODEV | MS_NOEXEC, nullptr);
        }

        // Mount /dev if not already a devtmpfs
        struct ::stat st;
        if (::stat("/dev/null", &st) != 0) {
            std::cout << "[syslite] Mounting /dev" << std::endl;
            mount("devtmpfs", "/dev", "devtmpfs", MS_NOSUID, "mode=0755");
        }

        // Mount /dev/pts
        if (!std::filesystem::exists("/dev/pts/ptmx")) {
            std::cout << "[syslite] Mounting /dev/pts" << std::endl;
            std::filesystem::create_directories("/dev/pts");
            mount("devpts", "/dev/pts", "devpts", MS_NOSUID | MS_NOEXEC,
                  "mode=0620,gid=5");
        }

        // Mount /dev/shm
        if (!std::filesystem::is_directory("/dev/shm")) {
            std::filesystem::create_directories("/dev/shm");
            mount("tmpfs", "/dev/shm", "tmpfs", MS_NOSUID | MS_NODEV, "mode=1777");
        }

        // Mount /run
        if (!std::filesystem::exists("/run/lock")) {
            std::cout << "[syslite] Mounting /run" << std::endl;
            mount("tmpfs", "/run", "tmpfs", MS_NOSUID | MS_NODEV, "mode=0755");
            std::filesystem::create_directories("/run/lock");
        }

        // Mount /tmp
        mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV, "mode=1777");
    }

    void Init::setHostname() {
        if (_hostname.empty()) {
            // Read from /etc/hostname
            std::ifstream f("/etc/hostname");
            if (f.is_open()) {
                std::getline(f, _hostname);
                // Trim whitespace
                while (!_hostname.empty() &&
                       (_hostname.back() == '\n' || _hostname.back() == '\r' ||
                        _hostname.back() == ' '))
                    _hostname.pop_back();
            }
        }

        if (!_hostname.empty()) {
            sethostname(_hostname.c_str(), _hostname.size());
            std::cout << "[syslite] Hostname: " << _hostname << std::endl;
        }
    }

    void Init::runSysctl() {
        // Apply /etc/sysctl.conf if it exists
        if (std::filesystem::exists("/etc/sysctl.conf")) {
            std::ifstream f("/etc/sysctl.conf");
            std::string line;
            while (std::getline(f, line)) {
                // Skip comments and empty lines
                if (line.empty() || line[0] == '#' || line[0] == ';')
                    continue;

                auto eq = line.find('=');
                if (eq == std::string::npos) continue;

                std::string key = line.substr(0, eq);
                std::string val = line.substr(eq + 1);

                // Trim
                while (!key.empty() && key.back() == ' ') key.pop_back();
                while (!val.empty() && val.front() == ' ') val = val.substr(1);

                // Convert dots to slashes for /proc/sys path
                std::string path = "/proc/sys/";
                for (char c : key) {
                    path += (c == '.') ? '/' : c;
                }

                std::ofstream out(path);
                if (out.is_open()) {
                    out << val;
                }
            }
        }
    }

    void Init::shutdown() {
        running.store(false);
        doReboot.store(false);
    }

    void Init::reboot() {
        running.store(false);
        doReboot.store(true);
    }

    int Init::run() {
        bool isPid1 = (getpid() == 1);

        if (isPid1) {
            std::cout << std::endl;
            std::cout << "  ╔═══════════════════════════════════════╗" << std::endl;
            std::cout << "  ║        syslite init system v0.1       ║" << std::endl;
            std::cout << "  ╚═══════════════════════════════════════╝" << std::endl;
            std::cout << std::endl;

            mountVirtualFilesystems();
            setHostname();
            runSysctl();
        }

        // Load service unit files
        _services.loadDirectory(_unitDir);

        // Also load from standard systemd paths for compatibility
        if (_unitDir != "/etc/systemd/system")
            _services.loadDirectory("/etc/systemd/system");
        if (_unitDir != "/lib/systemd/system")
            _services.loadDirectory("/lib/systemd/system");

        // Start default target
        std::cout << "[syslite] Starting target: " << _defaultTarget << std::endl;
        _services.startTarget(_defaultTarget);

        // Spawn TTYs
        if (isPid1) {
            _ttys.startAll();
        }

        // Main loop: reap children, handle restarts
        while (running.load()) {
            int status;
            pid_t pid = waitpid(-1, &status, WNOHANG);

            if (pid > 0) {
                // Check if it's a getty
                if (!_ttys.handleChild(pid)) {
                    // Must be a service
                    _services.handleChild(pid, status);
                }
            }

            // Check for service restarts
            _services.checkRestarts();

            // Sleep briefly to avoid busy loop
            usleep(200000); // 200ms
        }

        // Shutdown sequence
        std::cout << std::endl;
        std::cout << "[syslite] Shutting down..." << std::endl;

        _ttys.stopAll();
        _services.stopAll();

        if (isPid1) {
            // Sync filesystems
            sync();

            // Unmount /tmp, /run, /dev/shm, /dev/pts
            umount2("/tmp", MNT_DETACH);
            umount2("/run", MNT_DETACH);
            umount2("/dev/shm", MNT_DETACH);
            umount2("/dev/pts", MNT_DETACH);

            if (doReboot.load()) {
                std::cout << "[syslite] Rebooting..." << std::endl;
                ::reboot(RB_AUTOBOOT);
            } else {
                std::cout << "[syslite] Power off..." << std::endl;
                ::reboot(RB_POWER_OFF);
            }
        }

        return 0;
    }

}
Loading
Loading