diff options
author | Sergey Bugaev <bugaevc@gmail.com> | 2019-11-26 19:27:21 +0300 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-11-26 19:58:25 +0100 |
commit | c9e21b2bcc2ae063306fd4f5639285a08c9e6802 (patch) | |
tree | 70ea3c6de5a05aad95705daf3e02df4120e5bca4 | |
parent | 396ad4d6b257c8a3afb143954606aca4ecb57e74 (diff) | |
download | serenity-c9e21b2bcc2ae063306fd4f5639285a08c9e6802.zip |
SystemServer+LibCore: Implement socket takeover
SystemServer can now create sockets on behalf of services before spawning any
of them, and pass the open socket fd as fd 3. CLocalServer gains a method to
complete the takeover and listen on the passed fd.
This is not used by any services at the moment.
-rw-r--r-- | Libraries/LibCore/CLocalServer.cpp | 74 | ||||
-rw-r--r-- | Libraries/LibCore/CLocalServer.h | 3 | ||||
-rw-r--r-- | Servers/SystemServer/Service.cpp | 71 | ||||
-rw-r--r-- | Servers/SystemServer/Service.h | 6 | ||||
-rw-r--r-- | Servers/SystemServer/main.cpp | 2 |
5 files changed, 146 insertions, 10 deletions
diff --git a/Libraries/LibCore/CLocalServer.cpp b/Libraries/LibCore/CLocalServer.cpp index b68c5381f1..87f1c96df4 100644 --- a/Libraries/LibCore/CLocalServer.cpp +++ b/Libraries/LibCore/CLocalServer.cpp @@ -3,18 +3,67 @@ #include <LibCore/CNotifier.h> #include <stdio.h> #include <sys/socket.h> +#include <sys/stat.h> +#include <unistd.h> CLocalServer::CLocalServer(CObject* parent) : CObject(parent) { - m_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - ASSERT(m_fd >= 0); } CLocalServer::~CLocalServer() { } +bool CLocalServer::take_over_from_system_server() +{ + if (m_listening) + return false; + + constexpr auto socket_takeover = "SOCKET_TAKEOVER"; + + if (getenv(socket_takeover)) { + dbg() << "Taking the socket over from SystemServer"; + + // Sanity check: it has to be a socket. + struct stat stat; + int rc = fstat(3, &stat); + if (rc == 0 && S_ISSOCK(stat.st_mode)) { + // The SystemServer has passed us the socket as fd 3, + // so use that instead of creating our own. + m_fd = 3; + // It had to be !CLOEXEC for obvious reasons, but we + // don't need it to be !CLOEXEC anymore, so set the + // CLOEXEC flag now. + fcntl(m_fd, F_SETFD, FD_CLOEXEC); + // We wouldn't want our children to think we're passing + // them a socket either, so unset the env variable. + unsetenv(socket_takeover); + + m_listening = true; + setup_notifier(); + return true; + } else { + if (rc != 0) + perror("fstat"); + dbg() << "It's not a socket, what the heck??"; + } + } + + dbg() << "Failed to take the socket over from SystemServer"; + + return false; +} + +void CLocalServer::setup_notifier() +{ + m_notifier = CNotifier::construct(m_fd, CNotifier::Event::Read, this); + m_notifier->on_ready_to_read = [this] { + if (on_ready_to_accept) + on_ready_to_accept(); + }; +} + bool CLocalServer::listen(const String& address) { if (m_listening) @@ -22,20 +71,25 @@ bool CLocalServer::listen(const String& address) int rc; + m_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + ASSERT(m_fd >= 0); + auto socket_address = CSocketAddress::local(address); auto un = socket_address.to_sockaddr_un(); rc = ::bind(m_fd, (const sockaddr*)&un, sizeof(un)); - ASSERT(rc == 0); + if (rc < 0) { + perror("bind"); + ASSERT_NOT_REACHED(); + } rc = ::listen(m_fd, 5); - ASSERT(rc == 0); - m_listening = true; + if (rc < 0) { + perror("listen"); + ASSERT_NOT_REACHED(); + } - m_notifier = CNotifier::construct(m_fd, CNotifier::Event::Read, this); - m_notifier->on_ready_to_read = [this] { - if (on_ready_to_accept) - on_ready_to_accept(); - }; + m_listening = true; + setup_notifier(); return true; } diff --git a/Libraries/LibCore/CLocalServer.h b/Libraries/LibCore/CLocalServer.h index 1e512728d8..ffbaaa171c 100644 --- a/Libraries/LibCore/CLocalServer.h +++ b/Libraries/LibCore/CLocalServer.h @@ -10,6 +10,7 @@ class CLocalServer : public CObject { public: virtual ~CLocalServer() override; + bool take_over_from_system_server(); bool is_listening() const { return m_listening; } bool listen(const String& address); @@ -20,6 +21,8 @@ public: private: explicit CLocalServer(CObject* parent = nullptr); + void setup_notifier(); + int m_fd { -1 }; bool m_listening { false }; RefPtr<CNotifier> m_notifier; diff --git a/Servers/SystemServer/Service.cpp b/Servers/SystemServer/Service.cpp index b955e1efd5..d1ecbf17c8 100644 --- a/Servers/SystemServer/Service.cpp +++ b/Servers/SystemServer/Service.cpp @@ -3,7 +3,9 @@ #include <AK/JsonArray.h> #include <AK/JsonObject.h> #include <LibCore/CConfigFile.h> +#include <LibCore/CLocalSocket.h> #include <fcntl.h> +#include <libgen.h> #include <pwd.h> #include <sched.h> #include <stdio.h> @@ -44,6 +46,61 @@ Service* Service::find_by_pid(pid_t pid) return (*it).value; } +static int ensure_parent_directories(const char* path) +{ + ASSERT(path[0] == '/'); + + char* parent_buffer = strdup(path); + const char* parent = dirname(parent_buffer); + + int rc = 0; + while (true) { + int rc = mkdir(parent, 0755); + + if (rc == 0) + break; + + if (errno != ENOENT) + break; + + ensure_parent_directories(parent); + }; + + free(parent_buffer); + return rc; +} + +void Service::setup_socket() +{ + ASSERT(!m_socket_path.is_null()); + ASSERT(m_socket_fd == -1); + + ensure_parent_directories(m_socket_path.characters()); + + // 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(); + } + + auto socket_address = CSocketAddress::local(m_socket_path); + auto un = socket_address.to_sockaddr_un(); + int rc = bind(m_socket_fd, (const sockaddr*)&un, sizeof(un)); + if (rc < 0) { + perror("bind"); + ASSERT_NOT_REACHED(); + } + + rc = listen(m_socket_fd, 5); + if (rc < 0) { + perror("listen"); + ASSERT_NOT_REACHED(); + } +} + void Service::spawn() { dbg() << "Spawning " << name(); @@ -76,6 +133,14 @@ void Service::spawn() dup2(0, 2); } + if (!m_socket_path.is_null()) { + ASSERT(m_socket_fd > 2); + dup2(m_socket_fd, 3); + // The new descriptor is !CLOEXEC here. + // This is true even if m_socket_fd == 3. + setenv("SOCKET_TAKEOVER", "1", true); + } + if (!m_user.is_null()) { setuid(m_uid); setgid(m_gid); @@ -134,6 +199,11 @@ Service::Service(const CConfigFile& config, const StringView& name) m_keep_alive = config.read_bool_entry(name, "KeepAlive"); + m_socket_path = config.read_entry(name, "Socket"); + if (!m_socket_path.is_null()) { + setup_socket(); + } + m_user = config.read_entry(name, "User"); if (!m_user.is_null()) resolve_user(); @@ -156,6 +226,7 @@ void Service::save_to(JsonObject& json) 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("user", m_user); json.set("uid", m_uid); json.set("gid", m_gid); diff --git a/Servers/SystemServer/Service.h b/Servers/SystemServer/Service.h index d685ff4c37..4518d292de 100644 --- a/Servers/SystemServer/Service.h +++ b/Servers/SystemServer/Service.h @@ -2,6 +2,7 @@ #include <AK/RefPtr.h> #include <AK/String.h> +#include <LibCore/CNotifier.h> #include <LibCore/CObject.h> class CConfigFile; @@ -33,6 +34,8 @@ private: 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; // The name of the user we should run this service as. String m_user; uid_t m_uid { 0 }; @@ -40,6 +43,9 @@ private: // PID of the running instance of this service. pid_t m_pid { -1 }; + // An open fd to the socket. + int m_socket_fd { -1 }; void resolve_user(); + void setup_socket(); }; diff --git a/Servers/SystemServer/main.cpp b/Servers/SystemServer/main.cpp index 603fdd4553..d8eb333702 100644 --- a/Servers/SystemServer/main.cpp +++ b/Servers/SystemServer/main.cpp @@ -89,11 +89,13 @@ int main(int, char**) CEventLoop event_loop; // Read our config and instantiate services. + // This takes care of setting up sockets. Vector<RefPtr<Service>> services; auto config = CConfigFile::get_for_system("SystemServer"); for (auto name : config->groups()) services.append(Service::construct(*config, name)); + // After we've set them all up, spawn them! for (auto& service : services) service->spawn(); |