diff --git a/apps/init.cpp b/apps/init.cpp index 3d7f4dc5..45917b06 100644 --- a/apps/init.cpp +++ b/apps/init.cpp @@ -1,13 +1,23 @@ +#include +#include +#include +#include +#include + +#include #include #include #include #include +#include #include #include #include #include #include +FILE* g_init_log; + #define xmknod(path, mode, maj, min) \ if (mknod(path, mode, makedev(maj, min)) < 0) exit(255); @@ -23,6 +33,170 @@ static void populate_devfs() xmknod("/dev/fb0", 0222, 3, 0); } +struct Service +{ + String name; + String command; + bool restart { false }; + String environment; + Option pid {}; +}; + +Vector g_services; + +static Result service_child(const Service& service) +{ + auto args = TRY(service.command.split(" \n")); + + if (service.environment.is_empty()) { TRY(os::Process::exec(args[0].view(), args.slice(), false)); } + else + { + auto env = TRY(service.environment.split(",\n")); + TRY(os::Process::exec(args[0].view(), args.slice(), env.slice(), false)); + } + + return {}; +} + +static Result try_start_service(Service& service) +{ + pid_t pid = TRY(os::Process::fork()); + if (pid == 0) + { + auto rc = service_child(service); + if (rc.has_error()) + { + fprintf(g_init_log, "[child %d] failed to start service %s due to error: %s\n", getpid(), + service.name.chars(), rc.error_string()); + } + fclose(g_init_log); + exit(127); + } + + fprintf(g_init_log, "[init] created new child process %d for service %s\n", pid, service.name.chars()); + + service.pid = pid; + + return {}; +} + +static void start_service(Service& service) +{ + auto rc = try_start_service(service); + if (rc.has_error()) + { + fprintf(g_init_log, "[init] failed to start service %s due to error: %s\n", service.name.chars(), + rc.error_string()); + } +} + +static Result load_service(StringView path) +{ + fprintf(g_init_log, "[init] reading service file: %s\n", path.chars()); + + auto file = TRY(os::File::open(path, os::File::ReadOnly)); + + Service service; + + while (true) + { + auto line = TRY(file->read_line(false)); + if (line.is_empty()) break; + + auto parts = TRY(line.split_once('=')); + if (parts.size() < 2 || parts[0].is_empty() || parts[1].is_empty()) + { + fprintf(g_init_log, "[init] file contains invalid line, aborting: '%s'\n", line.chars()); + return {}; + } + + if (parts[0].view() == "Name") + { + service.name = move(parts[1]); + continue; + } + + if (parts[0].view() == "Command") + { + service.command = move(parts[1]); + continue; + } + + if (parts[0].view() == "Restart") + { + if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1) + { + service.restart = true; + continue; + } + service.restart = false; + continue; + } + + if (parts[0].view() == "Environment") + { + service.environment = move(parts[1]); + continue; + } + + fprintf(g_init_log, "[init] skipping unknown entry name %s\n", parts[0].chars()); + continue; + } + + if (service.name.is_empty()) + { + fprintf(g_init_log, "[init] service file is missing 'Name' entry, aborting!\n"); + return {}; + } + + if (service.command.is_empty()) + { + fprintf(g_init_log, "[init] service file is missing 'Command' entry, aborting!\n"); + return {}; + } + + fprintf(g_init_log, "[init] loaded service %s into memory\n", service.name.chars()); + + TRY(g_services.try_append(move(service))); + + return {}; +} + +static Result load_services() +{ + DIR* dp = opendir("/etc/init"); + if (!dp) + { + fprintf(g_init_log, "[init] cannot open service directory: %s\n", strerror(errno)); + return {}; + } + + dirent* ent; + while ((ent = readdir(dp))) + { + if ("."_sv == ent->d_name || ".."_sv == ent->d_name) continue; + + auto service_path = TRY(PathParser::join("/etc/init"_sv, ent->d_name)); + TRY(load_service(service_path.view())); + } + + closedir(dp); + + return {}; +} + +static Result start_services() +{ + TRY(load_services()); + for (auto& service : g_services) + { + fprintf(g_init_log, "[init] starting service %s\n", service.name.chars()); + start_service(service); + } + + return {}; +} + int main() { if (getpid() != 1) @@ -39,16 +213,32 @@ int main() stdout = fopen("/dev/console", "w"); stderr = fopen("/dev/console", "w"); - pid_t ret = fork(); + g_init_log = fopen("/init.log", "w+"); + fcntl(fileno(g_init_log), F_SETFD, FD_CLOEXEC); - if (ret == 0) + start_services(); + + while (1) { - char* argv[] = { "/bin/sh", NULL }; - char* envp[] = { "PATH=/bin:/sbin", NULL }; - execve(argv[0], argv, envp); - perror("execv"); - return 1; - } + int status; + pid_t child = wait(&status); - while (1) wait(NULL); + for (auto& service : g_services) + { + if (service.pid.has_value() && service.pid.value() == child) + { + fprintf(g_init_log, "[init] service %s exited with status %d\n", service.name.chars(), + WEXITSTATUS(status)); + + if (service.restart) + { + fprintf(g_init_log, "[init] restarting service %s\n", service.name.chars()); + + start_service(service); + } + + break; + } + } + } } diff --git a/initrd/etc/init/99-shell b/initrd/etc/init/99-shell new file mode 100644 index 00000000..2f6fb30e --- /dev/null +++ b/initrd/etc/init/99-shell @@ -0,0 +1,4 @@ +Name=shell +Command=/bin/sh +Restart=true +Environment=PATH=/bin:/sbin