summaryrefslogtreecommitdiff
path: root/Userland/Services/SystemServer
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2021-01-12 12:23:01 +0100
committerAndreas Kling <kling@serenityos.org>2021-01-12 12:23:01 +0100
commitc7ac7e6eaff862c1ea4f99e0c6ce75e095106d9c (patch)
treece2a3fef96f0b8eebe907743f1e03739457c11a6 /Userland/Services/SystemServer
parent4055b0329117c1a280080bbd638eb48bafe29638 (diff)
downloadserenity-c7ac7e6eaff862c1ea4f99e0c6ce75e095106d9c.zip
Services: Move to Userland/Services/
Diffstat (limited to 'Userland/Services/SystemServer')
-rw-r--r--Userland/Services/SystemServer/CMakeLists.txt7
-rw-r--r--Userland/Services/SystemServer/Service.cpp376
-rw-r--r--Userland/Services/SystemServer/Service.h102
-rw-r--r--Userland/Services/SystemServer/main.cpp242
4 files changed, 727 insertions, 0 deletions
diff --git a/Userland/Services/SystemServer/CMakeLists.txt b/Userland/Services/SystemServer/CMakeLists.txt
new file mode 100644
index 0000000000..fda6eb1bcc
--- /dev/null
+++ b/Userland/Services/SystemServer/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(SOURCES
+ main.cpp
+ Service.cpp
+)
+
+serenity_bin(SystemServer)
+target_link_libraries(SystemServer LibCore)
diff --git a/Userland/Services/SystemServer/Service.cpp b/Userland/Services/SystemServer/Service.cpp
new file mode 100644
index 0000000000..99b01b5ab6
--- /dev/null
+++ b/Userland/Services/SystemServer/Service.cpp
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Service.h"
+#include <AK/HashMap.h>
+#include <AK/JsonArray.h>
+#include <AK/JsonObject.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/File.h>
+#include <LibCore/Socket.h>
+#include <grp.h>
+#include <libgen.h>
+#include <pwd.h>
+#include <sched.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+static HashMap<pid_t, Service*> s_service_map;
+
+Service* Service::find_by_pid(pid_t pid)
+{
+ auto it = s_service_map.find(pid);
+ if (it == s_service_map.end())
+ return nullptr;
+ return (*it).value;
+}
+
+void Service::setup_socket()
+{
+ ASSERT(!m_socket_path.is_null());
+ ASSERT(m_socket_fd == -1);
+
+ auto ok = Core::File::ensure_parent_directories(m_socket_path);
+ ASSERT(ok);
+
+ // Note: we use SOCK_CLOEXEC here to make sure we don't leak every socket to
+ // all the clients. We'll make the one we do need to pass down !CLOEXEC later
+ // after forking off the process.
+ m_socket_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+ if (m_socket_fd < 0) {
+ perror("socket");
+ ASSERT_NOT_REACHED();
+ }
+
+ if (m_account.has_value()) {
+ auto& account = m_account.value();
+ if (fchown(m_socket_fd, account.uid(), account.gid()) < 0) {
+ perror("fchown");
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ if (fchmod(m_socket_fd, m_socket_permissions) < 0) {
+ perror("fchmod");
+ ASSERT_NOT_REACHED();
+ }
+
+ auto socket_address = Core::SocketAddress::local(m_socket_path);
+ auto un_optional = socket_address.to_sockaddr_un();
+ if (!un_optional.has_value()) {
+ dbg() << "Socket name " << m_socket_path << " is too long. BUG! This should have failed earlier!";
+ ASSERT_NOT_REACHED();
+ }
+ auto un = un_optional.value();
+ int rc = bind(m_socket_fd, (const sockaddr*)&un, sizeof(un));
+ if (rc < 0) {
+ perror("bind");
+ ASSERT_NOT_REACHED();
+ }
+
+ rc = listen(m_socket_fd, 16);
+ if (rc < 0) {
+ perror("listen");
+ ASSERT_NOT_REACHED();
+ }
+}
+
+void Service::setup_notifier()
+{
+ ASSERT(m_lazy);
+ ASSERT(m_socket_fd >= 0);
+ ASSERT(!m_socket_notifier);
+
+ m_socket_notifier = Core::Notifier::construct(m_socket_fd, Core::Notifier::Event::Read, this);
+ m_socket_notifier->on_ready_to_read = [this] {
+ handle_socket_connection();
+ };
+}
+
+void Service::handle_socket_connection()
+{
+#ifdef SERVICE_DEBUG
+ dbg() << "Ready to read on behalf of " << name();
+#endif
+ if (m_accept_socket_connections) {
+ int accepted_fd = accept(m_socket_fd, nullptr, nullptr);
+ if (accepted_fd < 0) {
+ perror("accept");
+ return;
+ }
+ spawn(accepted_fd);
+ close(accepted_fd);
+ } else {
+ remove_child(*m_socket_notifier);
+ m_socket_notifier = nullptr;
+ spawn(m_socket_fd);
+ }
+}
+
+void Service::activate()
+{
+ ASSERT(m_pid < 0);
+
+ if (m_lazy)
+ setup_notifier();
+ else
+ spawn(m_socket_fd);
+}
+
+void Service::spawn(int socket_fd)
+{
+#ifdef SERVICE_DEBUG
+ dbg() << "Spawning " << name();
+#endif
+
+ m_run_timer.start();
+ pid_t pid = fork();
+
+ if (pid < 0) {
+ perror("fork");
+ dbg() << "Failed to spawn " << name() << ". Sucks, dude :(";
+ } else if (pid == 0) {
+ // We are the child.
+
+ if (!m_working_directory.is_null()) {
+ if (chdir(m_working_directory.characters()) < 0) {
+ perror("chdir");
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ 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(STDIN_FILENO);
+ 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(STDIN_FILENO, STDOUT_FILENO);
+ dup2(STDIN_FILENO, STDERR_FILENO);
+
+ if (isatty(STDIN_FILENO)) {
+ ioctl(STDIN_FILENO, TIOCSCTTY);
+ }
+ } else {
+ if (isatty(STDIN_FILENO)) {
+ ioctl(STDIN_FILENO, TIOCNOTTY);
+ }
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+
+ int fd = open("/dev/null", O_RDWR);
+ ASSERT(fd == STDIN_FILENO);
+ dup2(STDIN_FILENO, STDOUT_FILENO);
+ dup2(STDIN_FILENO, STDERR_FILENO);
+ }
+
+ if (socket_fd >= 0) {
+ ASSERT(!m_socket_path.is_null());
+ ASSERT(socket_fd > 3);
+ dup2(socket_fd, 3);
+ // The new descriptor is !CLOEXEC here.
+ setenv("SOCKET_TAKEOVER", "1", true);
+ }
+
+ if (m_account.has_value()) {
+ auto& account = m_account.value();
+ if (setgid(account.gid()) < 0 || setgroups(account.extra_gids().size(), account.extra_gids().data()) < 0 || setuid(account.uid()) < 0) {
+ dbgln("Failed to drop privileges (GID={}, UID={})\n", account.gid(), account.uid());
+ exit(1);
+ }
+ setenv("HOME", account.home_directory().characters(), true);
+ }
+
+ for (String& env : m_environment)
+ putenv(const_cast<char*>(env.characters()));
+
+ char* argv[m_extra_arguments.size() + 2];
+ argv[0] = const_cast<char*>(m_executable_path.characters());
+ for (size_t i = 0; i < m_extra_arguments.size(); i++)
+ argv[i + 1] = const_cast<char*>(m_extra_arguments[i].characters());
+ argv[m_extra_arguments.size() + 1] = nullptr;
+
+ rc = execv(argv[0], argv);
+ perror("exec");
+ ASSERT_NOT_REACHED();
+ } else if (!m_multi_instance) {
+ // We are the parent.
+ m_pid = pid;
+ s_service_map.set(pid, this);
+ }
+}
+
+void Service::did_exit(int exit_code)
+{
+ ASSERT(m_pid > 0);
+ ASSERT(!m_multi_instance);
+
+ dbg() << "Service " << name() << " has exited with exit code " << exit_code;
+
+ s_service_map.remove(m_pid);
+ m_pid = -1;
+
+ if (!m_keep_alive)
+ return;
+
+ int run_time_in_msec = m_run_timer.elapsed();
+ bool exited_successfully = exit_code == 0;
+
+ if (!exited_successfully && run_time_in_msec < 1000) {
+ switch (m_restart_attempts) {
+ case 0:
+ dbgln("Trying again");
+ break;
+ case 1:
+ dbgln("Third time's a charm?");
+ break;
+ default:
+ dbg() << "Giving up on " << name() << ". Good luck!";
+ return;
+ }
+ m_restart_attempts++;
+ }
+
+ activate();
+}
+
+Service::Service(const Core::ConfigFile& config, const StringView& name)
+ : Core::Object(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 == "low")
+ m_priority = 10;
+ else if (prio == "normal" || prio.is_null())
+ m_priority = 30;
+ else if (prio == "high")
+ m_priority = 50;
+ else
+ ASSERT_NOT_REACHED();
+
+ m_keep_alive = config.read_bool_entry(name, "KeepAlive");
+ m_lazy = config.read_bool_entry(name, "Lazy");
+
+ m_user = config.read_entry(name, "User");
+ if (!m_user.is_null()) {
+ auto result = Core::Account::from_name(m_user.characters());
+ if (result.is_error())
+ warnln("Failed to resolve user {}: {}", m_user, result.error());
+ else
+ m_account = result.value();
+ }
+
+ m_working_directory = config.read_entry(name, "WorkingDirectory");
+ m_environment = config.read_entry(name, "Environment").split(' ');
+ m_boot_modes = config.read_entry(name, "BootModes", "graphical").split(',');
+ m_multi_instance = config.read_bool_entry(name, "MultiInstance");
+ m_accept_socket_connections = config.read_bool_entry(name, "AcceptSocketConnections");
+
+ m_socket_path = config.read_entry(name, "Socket");
+
+ // Lazy requires Socket.
+ ASSERT(!m_lazy || !m_socket_path.is_null());
+ // AcceptSocketConnections always requires Socket, Lazy, and MultiInstance.
+ ASSERT(!m_accept_socket_connections || (!m_socket_path.is_null() && m_lazy && m_multi_instance));
+ // MultiInstance doesn't work with KeepAlive.
+ ASSERT(!m_multi_instance || !m_keep_alive);
+ // Socket path (plus NUL) must fit into the structs sent to the Kernel.
+ ASSERT(m_socket_path.length() < UNIX_PATH_MAX);
+
+ if (!m_socket_path.is_null() && is_enabled()) {
+ auto socket_permissions_string = config.read_entry(name, "SocketPermissions", "0600");
+ m_socket_permissions = strtol(socket_permissions_string.characters(), nullptr, 8) & 04777;
+ setup_socket();
+ }
+}
+
+void Service::save_to(JsonObject& json)
+{
+ Core::Object::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));
+
+ JsonArray boot_modes;
+ for (String& mode : m_boot_modes)
+ boot_modes.append(mode);
+ json.set("boot_modes", boot_modes);
+
+ JsonArray environment;
+ for (String& env : m_environment)
+ boot_modes.append(env);
+ json.set("environment", environment);
+ */
+
+ json.set("stdio_file_path", m_stdio_file_path);
+ json.set("priority", m_priority);
+ json.set("keep_alive", m_keep_alive);
+ json.set("socket_path", m_socket_path);
+ json.set("socket_permissions", m_socket_permissions);
+ json.set("lazy", m_lazy);
+ json.set("user", m_user);
+ json.set("multi_instance", m_multi_instance);
+ json.set("accept_socket_connections", m_accept_socket_connections);
+
+ if (m_pid > 0)
+ json.set("pid", m_pid);
+ else
+ json.set("pid", nullptr);
+
+ json.set("restart_attempts", m_restart_attempts);
+ json.set("working_directory", m_working_directory);
+}
+
+bool Service::is_enabled() const
+{
+ extern String g_boot_mode;
+ return m_boot_modes.contains_slow(g_boot_mode);
+}
diff --git a/Userland/Services/SystemServer/Service.h b/Userland/Services/SystemServer/Service.h
new file mode 100644
index 0000000000..23014825cf
--- /dev/null
+++ b/Userland/Services/SystemServer/Service.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/RefPtr.h>
+#include <AK/String.h>
+#include <LibCore/Account.h>
+#include <LibCore/ElapsedTimer.h>
+#include <LibCore/Notifier.h>
+#include <LibCore/Object.h>
+
+class Service final : public Core::Object {
+ C_OBJECT(Service)
+
+public:
+ bool is_enabled() const;
+ void activate();
+ void did_exit(int exit_code);
+
+ static Service* find_by_pid(pid_t);
+
+ // FIXME: Port to Core::Property
+ void save_to(AK::JsonObject&);
+
+private:
+ Service(const Core::ConfigFile&, const StringView& name);
+
+ void spawn(int socket_fd = -1);
+
+ // 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<String> m_extra_arguments;
+ // File path to open as stdio fds.
+ String m_stdio_file_path;
+ int m_priority { 1 };
+ // Whether we should re-launch it if it exits.
+ bool m_keep_alive { false };
+ // Path to the socket to create and listen on on behalf of this service.
+ String m_socket_path;
+ // File system permissions for the socket.
+ mode_t m_socket_permissions { 0 };
+ // Whether we should accept connections on the socket and pass the accepted
+ // (and not listening) socket to the service. This requires a multi-instance
+ // service.
+ bool m_accept_socket_connections { false };
+ // Whether we should only spawn this service once somebody connects to the socket.
+ bool m_lazy;
+ // The name of the user we should run this service as.
+ String m_user;
+ // The working directory in which to spawn the service.
+ String m_working_directory;
+ // Boot modes to run this service in. By default, this is the graphical mode.
+ Vector<String> m_boot_modes;
+ // Whether several instances of this service can run at once.
+ bool m_multi_instance { false };
+ // Environment variables to pass to the service.
+ Vector<String> m_environment;
+
+ // The resolved user account to run this service as.
+ Optional<Core::Account> m_account;
+
+ // For single-instance services, PID of the running instance of this service.
+ pid_t m_pid { -1 };
+ // An open fd to the socket.
+ int m_socket_fd { -1 };
+ RefPtr<Core::Notifier> m_socket_notifier;
+
+ // Timer since we last spawned the service.
+ Core::ElapsedTimer m_run_timer;
+ // How many times we have tried to restart this service, only counting those
+ // times where it has exited unsuccessfully and too quickly.
+ int m_restart_attempts { 0 };
+
+ void setup_socket();
+ void setup_notifier();
+ void handle_socket_connection();
+};
diff --git a/Userland/Services/SystemServer/main.cpp b/Userland/Services/SystemServer/main.cpp
new file mode 100644
index 0000000000..6409b0eda6
--- /dev/null
+++ b/Userland/Services/SystemServer/main.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Service.h"
+#include <AK/Assertions.h>
+#include <AK/ByteBuffer.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/Event.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/File.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+String g_boot_mode = "graphical";
+
+static void sigchld_handler(int)
+{
+ for (;;) {
+ int status = 0;
+ pid_t pid = waitpid(-1, &status, WNOHANG);
+ if (pid < 0) {
+ perror("waitpid");
+ break;
+ }
+ if (pid == 0)
+ break;
+
+#ifdef SYSTEMSERVER_DEBUG
+ dbg() << "Reaped child with pid " << pid << ", exit status " << status;
+#endif
+
+ Service* service = Service::find_by_pid(pid);
+ if (service == nullptr) {
+ // This can happen for multi-instance services.
+ continue;
+ }
+
+ service->did_exit(status);
+ }
+}
+
+static void parse_boot_mode()
+{
+ auto f = Core::File::construct("/proc/cmdline");
+ if (!f->open(Core::IODevice::ReadOnly)) {
+ dbg() << "Failed to read command line: " << f->error_string();
+ return;
+ }
+ const String cmdline = String::copy(f->read_all(), Chomp);
+ dbg() << "Read command line: " << cmdline;
+
+ for (auto& part : cmdline.split_view(' ')) {
+ auto pair = part.split_view('=', 2);
+ if (pair.size() == 2 && pair[0] == "boot_mode")
+ g_boot_mode = pair[1];
+ }
+ dbg() << "Booting in " << g_boot_mode << " mode";
+}
+
+static void prepare_devfs()
+{
+ // FIXME: Find a better way to all of this stuff, without hardcoding all of this!
+
+ int rc = mount(-1, "/dev", "dev", 0);
+ if (rc != 0) {
+ ASSERT_NOT_REACHED();
+ }
+
+ rc = mkdir("/dev/pts", 0755);
+ if (rc != 0) {
+ ASSERT_NOT_REACHED();
+ }
+
+ rc = mount(-1, "/dev/pts", "devpts", 0);
+ if (rc != 0) {
+ ASSERT_NOT_REACHED();
+ }
+
+ rc = symlink("/dev/random", "/dev/urandom");
+ if (rc < 0) {
+ ASSERT_NOT_REACHED();
+ }
+
+ // FIXME: Find a better way to chown without hardcoding the gid!
+ // This will fail with ENOENT in text mode.
+ rc = chown("/dev/fb0", 0, 3);
+ if (rc < 0 && errno != ENOENT) {
+ ASSERT_NOT_REACHED();
+ }
+
+ // FIXME: Find a better way to chown without hardcoding the gid!
+ rc = chown("/dev/keyboard", 0, 3);
+ if (rc < 0) {
+ ASSERT_NOT_REACHED();
+ }
+
+ // FIXME: Find a better way to chown without hardcoding the gid!
+ rc = chown("/dev/mouse", 0, 3);
+ if (rc < 0) {
+ ASSERT_NOT_REACHED();
+ }
+
+ for (size_t index = 0; index < 4; index++) {
+ // FIXME: Find a better way to chown without hardcoding the gid!
+ rc = chown(String::formatted("/dev/tty{}", index).characters(), 0, 2);
+ if (rc < 0) {
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ for (size_t index = 0; index < 4; index++) {
+ // FIXME: Find a better way to chown without hardcoding the gid!
+ rc = chown(String::formatted("/dev/ttyS{}", index).characters(), 0, 2);
+ if (rc < 0) {
+ ASSERT_NOT_REACHED();
+ }
+ }
+
+ // FIXME: Find a better way to chown without hardcoding the gid!
+ rc = chown("/dev/audio", 0, 4);
+ if (rc < 0) {
+ ASSERT_NOT_REACHED();
+ }
+
+ rc = symlink("/proc/self/fd/0", "/dev/stdin");
+ if (rc < 0) {
+ ASSERT_NOT_REACHED();
+ }
+ rc = symlink("/proc/self/fd/1", "/dev/stdout");
+ if (rc < 0) {
+ ASSERT_NOT_REACHED();
+ }
+ rc = symlink("/proc/self/fd/2", "/dev/stderr");
+ if (rc < 0) {
+ ASSERT_NOT_REACHED();
+ }
+}
+
+static void mount_all_filesystems()
+{
+ dbgln("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);
+ }
+}
+
+static void create_tmp_rpc_directory()
+{
+ dbgln("Creating /tmp/rpc directory");
+ auto old_umask = umask(0);
+ auto rc = mkdir("/tmp/rpc", 01777);
+ if (rc < 0) {
+ perror("mkdir(/tmp/rpc)");
+ ASSERT_NOT_REACHED();
+ }
+ umask(old_umask);
+}
+
+static void create_tmp_coredump_directory()
+{
+ dbgln("Creating /tmp/coredump directory");
+ auto old_umask = umask(0);
+ auto rc = mkdir("/tmp/coredump", 0755);
+ if (rc < 0) {
+ perror("mkdir(/tmp/coredump)");
+ ASSERT_NOT_REACHED();
+ }
+ umask(old_umask);
+}
+
+int main(int, char**)
+{
+ prepare_devfs();
+
+ if (pledge("stdio proc exec tty accept unix rpath wpath cpath chown fattr id sigaction", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ mount_all_filesystems();
+ create_tmp_rpc_directory();
+ create_tmp_coredump_directory();
+ parse_boot_mode();
+
+ Core::EventLoop event_loop;
+
+ event_loop.register_signal(SIGCHLD, sigchld_handler);
+
+ // Read our config and instantiate services.
+ // This takes care of setting up sockets.
+ NonnullRefPtrVector<Service> services;
+ auto config = Core::ConfigFile::get_for_system("SystemServer");
+ for (auto name : config->groups()) {
+ auto service = Service::construct(*config, name);
+ if (service->is_enabled())
+ services.append(service);
+ }
+
+ // After we've set them all up, activate them!
+ dbg() << "Activating " << services.size() << " services...";
+ for (auto& service : services)
+ service.activate();
+
+ return event_loop.exec();
+}