mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 19:27:44 +00:00
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.
This commit is contained in:
parent
396ad4d6b2
commit
c9e21b2bcc
5 changed files with 146 additions and 10 deletions
|
@ -3,18 +3,67 @@
|
||||||
#include <LibCore/CNotifier.h>
|
#include <LibCore/CNotifier.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
CLocalServer::CLocalServer(CObject* parent)
|
CLocalServer::CLocalServer(CObject* parent)
|
||||||
: CObject(parent)
|
: CObject(parent)
|
||||||
{
|
{
|
||||||
m_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
|
|
||||||
ASSERT(m_fd >= 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CLocalServer::~CLocalServer()
|
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)
|
bool CLocalServer::listen(const String& address)
|
||||||
{
|
{
|
||||||
if (m_listening)
|
if (m_listening)
|
||||||
|
@ -22,20 +71,25 @@ bool CLocalServer::listen(const String& address)
|
||||||
|
|
||||||
int rc;
|
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 socket_address = CSocketAddress::local(address);
|
||||||
auto un = socket_address.to_sockaddr_un();
|
auto un = socket_address.to_sockaddr_un();
|
||||||
rc = ::bind(m_fd, (const sockaddr*)&un, sizeof(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);
|
rc = ::listen(m_fd, 5);
|
||||||
ASSERT(rc == 0);
|
if (rc < 0) {
|
||||||
m_listening = true;
|
perror("listen");
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
m_notifier = CNotifier::construct(m_fd, CNotifier::Event::Read, this);
|
m_listening = true;
|
||||||
m_notifier->on_ready_to_read = [this] {
|
setup_notifier();
|
||||||
if (on_ready_to_accept)
|
|
||||||
on_ready_to_accept();
|
|
||||||
};
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ class CLocalServer : public CObject {
|
||||||
public:
|
public:
|
||||||
virtual ~CLocalServer() override;
|
virtual ~CLocalServer() override;
|
||||||
|
|
||||||
|
bool take_over_from_system_server();
|
||||||
bool is_listening() const { return m_listening; }
|
bool is_listening() const { return m_listening; }
|
||||||
bool listen(const String& address);
|
bool listen(const String& address);
|
||||||
|
|
||||||
|
@ -20,6 +21,8 @@ public:
|
||||||
private:
|
private:
|
||||||
explicit CLocalServer(CObject* parent = nullptr);
|
explicit CLocalServer(CObject* parent = nullptr);
|
||||||
|
|
||||||
|
void setup_notifier();
|
||||||
|
|
||||||
int m_fd { -1 };
|
int m_fd { -1 };
|
||||||
bool m_listening { false };
|
bool m_listening { false };
|
||||||
RefPtr<CNotifier> m_notifier;
|
RefPtr<CNotifier> m_notifier;
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
#include <AK/JsonArray.h>
|
#include <AK/JsonArray.h>
|
||||||
#include <AK/JsonObject.h>
|
#include <AK/JsonObject.h>
|
||||||
#include <LibCore/CConfigFile.h>
|
#include <LibCore/CConfigFile.h>
|
||||||
|
#include <LibCore/CLocalSocket.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <libgen.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -44,6 +46,61 @@ Service* Service::find_by_pid(pid_t pid)
|
||||||
return (*it).value;
|
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()
|
void Service::spawn()
|
||||||
{
|
{
|
||||||
dbg() << "Spawning " << name();
|
dbg() << "Spawning " << name();
|
||||||
|
@ -76,6 +133,14 @@ void Service::spawn()
|
||||||
dup2(0, 2);
|
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()) {
|
if (!m_user.is_null()) {
|
||||||
setuid(m_uid);
|
setuid(m_uid);
|
||||||
setgid(m_gid);
|
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_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");
|
m_user = config.read_entry(name, "User");
|
||||||
if (!m_user.is_null())
|
if (!m_user.is_null())
|
||||||
resolve_user();
|
resolve_user();
|
||||||
|
@ -156,6 +226,7 @@ void Service::save_to(JsonObject& json)
|
||||||
json.set("stdio_file_path", m_stdio_file_path);
|
json.set("stdio_file_path", m_stdio_file_path);
|
||||||
json.set("priority", m_priority);
|
json.set("priority", m_priority);
|
||||||
json.set("keep_alive", m_keep_alive);
|
json.set("keep_alive", m_keep_alive);
|
||||||
|
json.set("socket_path", m_socket_path);
|
||||||
json.set("user", m_user);
|
json.set("user", m_user);
|
||||||
json.set("uid", m_uid);
|
json.set("uid", m_uid);
|
||||||
json.set("gid", m_gid);
|
json.set("gid", m_gid);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <AK/RefPtr.h>
|
#include <AK/RefPtr.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
|
#include <LibCore/CNotifier.h>
|
||||||
#include <LibCore/CObject.h>
|
#include <LibCore/CObject.h>
|
||||||
|
|
||||||
class CConfigFile;
|
class CConfigFile;
|
||||||
|
@ -33,6 +34,8 @@ private:
|
||||||
int m_priority { 1 };
|
int m_priority { 1 };
|
||||||
// Whether we should re-launch it if it exits.
|
// Whether we should re-launch it if it exits.
|
||||||
bool m_keep_alive { false };
|
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.
|
// The name of the user we should run this service as.
|
||||||
String m_user;
|
String m_user;
|
||||||
uid_t m_uid { 0 };
|
uid_t m_uid { 0 };
|
||||||
|
@ -40,6 +43,9 @@ private:
|
||||||
|
|
||||||
// PID of the running instance of this service.
|
// PID of the running instance of this service.
|
||||||
pid_t m_pid { -1 };
|
pid_t m_pid { -1 };
|
||||||
|
// An open fd to the socket.
|
||||||
|
int m_socket_fd { -1 };
|
||||||
|
|
||||||
void resolve_user();
|
void resolve_user();
|
||||||
|
void setup_socket();
|
||||||
};
|
};
|
||||||
|
|
|
@ -89,11 +89,13 @@ int main(int, char**)
|
||||||
CEventLoop event_loop;
|
CEventLoop event_loop;
|
||||||
|
|
||||||
// Read our config and instantiate services.
|
// Read our config and instantiate services.
|
||||||
|
// This takes care of setting up sockets.
|
||||||
Vector<RefPtr<Service>> services;
|
Vector<RefPtr<Service>> services;
|
||||||
auto config = CConfigFile::get_for_system("SystemServer");
|
auto config = CConfigFile::get_for_system("SystemServer");
|
||||||
for (auto name : config->groups())
|
for (auto name : config->groups())
|
||||||
services.append(Service::construct(*config, name));
|
services.append(Service::construct(*config, name));
|
||||||
|
|
||||||
|
// After we've set them all up, spawn them!
|
||||||
for (auto& service : services)
|
for (auto& service : services)
|
||||||
service->spawn();
|
service->spawn();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue