#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include FILE* g_init_log; struct Service { String name; String command; bool restart { false }; String environment; String standard_output; String standard_error; String standard_input; bool wait { false }; Option pid {}; }; Vector g_services; static Result service_child(const Service& service, SharedPtr output, SharedPtr error, SharedPtr input) { auto args = TRY(service.command.split(" \n")); if (output) dup2(output->fd(), STDOUT_FILENO); if (error) dup2(error->fd(), STDERR_FILENO); if (input) dup2(input->fd(), STDIN_FILENO); 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) { SharedPtr new_stdout = {}; SharedPtr new_stderr = {}; SharedPtr new_stdin = {}; if (!service.standard_output.is_empty()) { new_stdout = TRY(os::File::open_or_create(service.standard_output.view(), os::File::Append, 0600)); new_stdout->set_close_on_exec(); } if (!service.standard_error.is_empty()) { new_stderr = TRY(os::File::open_or_create(service.standard_error.view(), os::File::Append, 0600)); new_stderr->set_close_on_exec(); } if (!service.standard_input.is_empty()) { new_stdin = TRY(os::File::open(service.standard_input.view(), os::File::ReadOnly)); new_stdin->set_close_on_exec(); } pid_t pid = TRY(os::Process::fork()); if (pid == 0) { auto rc = service_child(service, new_stdout, new_stderr, new_stdin); 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()); if (service.wait) { fprintf(g_init_log, "[init] waiting for child process %d to finish\n", pid); int status; TRY(os::Process::wait(pid, &status)); fprintf(g_init_log, "[init] child process %d exited with code %d\n", pid, WEXITSTATUS(status)); } else 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(const os::Path& path) { fprintf(g_init_log, "[init] reading service file: %s\n", path.name().chars()); auto file = TRY(os::File::open(path, os::File::ReadOnly)); Service service; while (true) { auto line = TRY(file->read_line()); if (line.is_empty()) break; line.trim("\n"); if (line.is_empty()) continue; 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") { if (!service.command.is_empty()) { fprintf(g_init_log, "[init] 'Command' cannot be specified after 'Script' has already been set! (%s)\n", line.chars()); return {}; } service.command = move(parts[1]); continue; } if (parts[0].view() == "Script") { if (!service.command.is_empty()) { fprintf(g_init_log, "[init] 'Script' cannot be specified after 'Command' has already been set! (%s)\n", line.chars()); return {}; } service.command = TRY(String::format("/bin/sh -- %s"_sv, parts[1].chars())); 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; } if (parts[0].view() == "StandardOutput") { service.standard_output = move(parts[1]); continue; } if (parts[0].view() == "StandardError") { service.standard_error = move(parts[1]); continue; } if (parts[0].view() == "StandardInput") { service.standard_input = move(parts[1]); continue; } if (parts[0].view() == "Wait") { if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1) { service.wait = true; continue; } service.wait = false; continue; } fprintf(g_init_log, "[init] skipping unknown entry name %s\n", parts[0].chars()); } 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' or 'Script' 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() { auto dir = TRY(os::Directory::open("/etc/init")); auto services = TRY(dir->list_names(os::Directory::Filter::ParentAndBase)); sort(services.begin(), services.end(), String::compare); for (const auto& entry : services) TRY(load_service({ dir->fd(), entry.view() })); 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 {}; } static Result set_hostname() { auto file = TRY(os::File::open("/etc/hostname", os::File::ReadOnly)); auto hostname = TRY(file->read_line()); hostname.trim("\n"); if (sethostname(hostname.chars(), hostname.length()) < 0) return {}; fprintf(g_init_log, "[init] successfully set system hostname to '%s'\n", hostname.chars()); return {}; } static void mount_devfs() { if (mkdir("/dev", 0755) < 0 && errno != EEXIST) exit(255); if (mount("/dev", "devfs") < 0) exit(255); } int main() { if (getpid() != 1) { fprintf(stderr, "error: init not running as PID 1.\n"); return 1; } mount_devfs(); // Before this point, we don't even have an stdin, stdout and stderr. Set it up now so that child processes (and us) // can print stuff. stdin = fopen("/dev/console", "r"); stdout = fopen("/dev/console", "w"); stderr = fopen("/dev/console", "w"); umask(022); g_init_log = fopen("/init.log", "w+"); fcntl(fileno(g_init_log), F_SETFD, FD_CLOEXEC); set_hostname(); start_services(); while (1) { int status; auto rc = os::Process::wait(os::Process::ANY_CHILD, &status); if (rc.has_error()) continue; pid_t child = rc.release_value(); 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; } } } }