diff --git a/Base/etc/SystemServer.ini b/Base/etc/SystemServer.ini new file mode 100644 index 0000000000..22bf7be108 --- /dev/null +++ b/Base/etc/SystemServer.ini @@ -0,0 +1,28 @@ +[TTYServer] +# NOTE: We don't start anything on tty0 since that's the "active" TTY while WindowServer is up. +Arguments=tty1 +StdIO=/dev/tty1 +Priority=high + +[ProtocolServer] +Priority=low +User=anon + +[LookupServer] +Priority=low +User=anon + +[WindowServer] +Priority=high +User=anon + +[AudioServer] +Priority=high +User=anon + +[Taskbar] +Priority=high +User=anon + +[Terminal] +User=anon diff --git a/Servers/SystemServer/Makefile b/Servers/SystemServer/Makefile index c49ea284f1..787fcd40a4 100644 --- a/Servers/SystemServer/Makefile +++ b/Servers/SystemServer/Makefile @@ -1,6 +1,7 @@ include ../../Makefile.common SYSTEMSERVER_OBJS = \ + Service.o \ main.o APP = SystemServer diff --git a/Servers/SystemServer/Service.cpp b/Servers/SystemServer/Service.cpp new file mode 100644 index 0000000000..c27e8372e7 --- /dev/null +++ b/Servers/SystemServer/Service.cpp @@ -0,0 +1,138 @@ +#include "Service.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct UidAndGid { + uid_t uid; + gid_t gid; +}; + +static HashMap* s_user_map; + +void Service::resolve_user() +{ + if (s_user_map == nullptr) { + s_user_map = new HashMap; + for (struct passwd* passwd = getpwent(); passwd; passwd = getpwent()) + s_user_map->set(passwd->pw_name, { passwd->pw_uid, passwd->pw_gid }); + endpwent(); + } + + auto user = s_user_map->get(m_user); + if (!user.has_value()) { + dbg() << "Failed to resolve user name " << m_user; + ASSERT_NOT_REACHED(); + } + m_uid = user.value().uid; + m_gid = user.value().gid; +} + +void Service::spawn() +{ + dbg() << "Spawning " << name(); + + m_pid = fork(); + + if (m_pid < 0) { + perror("fork"); + ASSERT_NOT_REACHED(); + } else if (m_pid == 0) { + // We are the child. + + struct sched_param p; + p.sched_priority = m_priority; + int rc = sched_setparam(0, &p); + if (rc < 0) { + perror("sched_setparam"); + ASSERT_NOT_REACHED(); + } + + if (!m_stdio_file_path.is_null()) { + close(0); + int fd = open_with_path_length(m_stdio_file_path.characters(), m_stdio_file_path.length(), O_RDWR, 0); + ASSERT(fd <= 0); + if (fd < 0) { + perror("open"); + ASSERT_NOT_REACHED(); + } + dup2(0, 1); + dup2(0, 2); + } + + if (!m_user.is_null()) { + setuid(m_uid); + setgid(m_gid); + } + + char* argv[m_extra_arguments.size() + 2]; + argv[0] = const_cast(m_executable_path.characters()); + for (int i = 0; i < m_extra_arguments.size(); i++) + argv[i + 1] = const_cast(m_extra_arguments[i].characters()); + argv[m_extra_arguments.size() + 1] = nullptr; + + rc = execv(argv[0], argv); + perror("exec"); + ASSERT_NOT_REACHED(); + } +} + +Service::Service(const CConfigFile& config, const StringView& name) + : CObject(nullptr) +{ + ASSERT(config.has_group(name)); + + set_name(name); + m_executable_path = config.read_entry(name, "Executable", String::format("/bin/%s", this->name().characters())); + m_extra_arguments = config.read_entry(name, "Arguments", "").split(' '); + m_stdio_file_path = config.read_entry(name, "StdIO"); + + String prio = config.read_entry(name, "Priority"); + if (prio == "idle") + m_priority = 0; + else if (prio == "low") + m_priority = 1; + else if (prio == "normal" || prio.is_null()) + m_priority = 2; + else if (prio == "high") + m_priority = 3; + else + ASSERT_NOT_REACHED(); + + m_user = config.read_entry(name, "User"); + if (!m_user.is_null()) + resolve_user(); +} + +void Service::save_to(JsonObject& json) +{ + CObject::save_to(json); + + json.set("executable_path", m_executable_path); + + // FIXME: This crashes Inspector. + /* + JsonArray extra_args; + for (String& arg : m_extra_arguments) + extra_args.append(arg); + json.set("extra_arguments", move(extra_args)); + */ + + json.set("stdio_file_path", m_stdio_file_path); + json.set("priority", m_priority); + json.set("user", m_user); + json.set("uid", m_uid); + json.set("gid", m_gid); + + if (m_pid > 0) + json.set("pid", m_pid); + else + json.set("pid", nullptr); +} diff --git a/Servers/SystemServer/Service.h b/Servers/SystemServer/Service.h new file mode 100644 index 0000000000..06209cbd4d --- /dev/null +++ b/Servers/SystemServer/Service.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +class CConfigFile; + +namespace AK { +class JsonObject; +} + +class Service final : public CObject { + C_OBJECT(Service) + +public: + void spawn(); + + void save_to(AK::JsonObject&) override; + +private: + Service(const CConfigFile&, const StringView& name); + + // Path to the executable. By default this is /bin/{m_name}. + String m_executable_path; + // Extra arguments, starting from argv[1], to pass when exec'ing. + Vector m_extra_arguments; + // File path to open as stdio fds. + String m_stdio_file_path; + int m_priority { 1 }; + // The name of the user we should run this service as. + String m_user; + uid_t m_uid { 0 }; + gid_t m_gid { 0 }; + + // PID of the running instance of this service. + pid_t m_pid { -1 }; + + void resolve_user(); +}; diff --git a/Servers/SystemServer/main.cpp b/Servers/SystemServer/main.cpp index e84fa4ed99..0030180a43 100644 --- a/Servers/SystemServer/main.cpp +++ b/Servers/SystemServer/main.cpp @@ -1,11 +1,11 @@ +#include "Service.h" #include +#include +#include #include #include -#include -#include #include #include -#include #include #include #include @@ -18,52 +18,6 @@ void sigchld_handler(int) dbg() << "reaped pid " << pid; } -void start_process(const String& program, const Vector& arguments, int prio, const char* tty = nullptr) -{ - pid_t pid = 0; - - while (true) { - dbg() << "Forking for " << program << "..."; - int pid = fork(); - if (pid < 0) { - dbg() << "Fork " << program << " failed! " << strerror(errno); - continue; - } else if (pid > 0) { - // parent... - dbg() << "Process " << program << " hopefully started with priority " << prio << "..."; - return; - } - break; - } - - while (true) { - dbg() << "Executing for " << program << "... at prio " << prio; - struct sched_param p; - p.sched_priority = prio; - int ret = sched_setparam(pid, &p); - ASSERT(ret == 0); - - if (tty != nullptr) { - close(0); - ASSERT(open(tty, O_RDWR) == 0); - dup2(0, 1); - dup2(0, 2); - } - - char* progv[256]; - progv[0] = const_cast(program.characters()); - for (int i = 0; i < arguments.size() && i < 254; i++) - progv[i + 1] = const_cast(arguments[i].characters()); - progv[arguments.size() + 1] = nullptr; - ret = execv(progv[0], progv); - if (ret < 0) { - dbg() << "Exec " << progv[0] << " failed! " << strerror(errno); - continue; - } - break; - } -} - static void check_for_test_mode() { auto f = CFile::construct("/proc/cmdline"); @@ -88,35 +42,42 @@ static void check_for_test_mode() } } +static void mount_all_filesystems() +{ + dbg() << "Spawning mount -a to mount all filesystems."; + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + ASSERT_NOT_REACHED(); + } else if (pid == 0) { + execl("/bin/mount", "mount", "-a", nullptr); + perror("exec"); + ASSERT_NOT_REACHED(); + } else { + wait(nullptr); + } +} + int main(int, char**) { - int lowest_prio = sched_get_priority_min(SCHED_OTHER); - int highest_prio = sched_get_priority_max(SCHED_OTHER); - - // Mount the filesystems. - start_process("/bin/mount", { "-a" }, highest_prio); - wait(nullptr); - - // NOTE: We don't start anything on tty0 since that's the "active" TTY while WindowServer is up. - start_process("/bin/TTYServer", { "tty1" }, highest_prio, "/dev/tty1"); - - // Drop privileges. - setgid(100); - setuid(100); + mount_all_filesystems(); signal(SIGCHLD, sigchld_handler); - start_process("/bin/ProtocolServer", {}, lowest_prio); - start_process("/bin/LookupServer", {}, lowest_prio); - start_process("/bin/WindowServer", {}, highest_prio); - start_process("/bin/AudioServer", {}, highest_prio); - start_process("/bin/Taskbar", {}, highest_prio); - start_process("/bin/Terminal", {}, highest_prio - 1); + CEventLoop event_loop; + + // Read our config and instantiate services. + Vector> services; + auto config = CConfigFile::get_for_system("SystemServer"); + for (auto name : config->groups()) + services.append(Service::construct(*config, name)); + + for (auto& service : services) + service->spawn(); // This won't return if we're in test mode. check_for_test_mode(); - while (1) { - sleep(3600); - } + return event_loop.exec(); }