summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Bugaev <bugaevc@gmail.com>2019-11-26 19:27:21 +0300
committerAndreas Kling <awesomekling@gmail.com>2019-11-26 19:58:25 +0100
commitc9e21b2bcc2ae063306fd4f5639285a08c9e6802 (patch)
tree70ea3c6de5a05aad95705daf3e02df4120e5bca4
parent396ad4d6b257c8a3afb143954606aca4ecb57e74 (diff)
downloadserenity-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.cpp74
-rw-r--r--Libraries/LibCore/CLocalServer.h3
-rw-r--r--Servers/SystemServer/Service.cpp71
-rw-r--r--Servers/SystemServer/Service.h6
-rw-r--r--Servers/SystemServer/main.cpp2
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();