SystemServer: Add support for accepting socket connections :^)

You can now ask SystemServer to not only listen for connections on the socket,
but to actually accept them, and to spawn an instance of the service for each
client connection. In this case, it's the accepted, not listening, socket that
the service processes will receive using socket takeover.

This mode obviously requires the service to be a multi-instance service.
This commit is contained in:
Sergey Bugaev 2020-06-08 23:50:21 +03:00 committed by Andreas Kling
parent ac4c2f890f
commit 701994bfd1
Notes: sideshowbarker 2024-07-19 05:44:09 +09:00
2 changed files with 35 additions and 10 deletions

View File

@ -158,11 +158,26 @@ void Service::setup_notifier()
m_socket_notifier = Core::Notifier::construct(m_socket_fd, Core::Notifier::Event::Read, this);
m_socket_notifier->on_ready_to_read = [this] {
dbg() << "Ready to read on behalf of " << name();
handle_socket_connection();
};
}
void Service::handle_socket_connection()
{
dbg() << "Ready to read on behalf of " << name();
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();
};
spawn(m_socket_fd);
}
}
void Service::activate()
@ -172,10 +187,10 @@ void Service::activate()
if (m_lazy)
setup_notifier();
else
spawn();
spawn(m_socket_fd);
}
void Service::spawn()
void Service::spawn(int socket_fd)
{
dbg() << "Spawning " << name();
@ -231,11 +246,12 @@ void Service::spawn()
dup2(STDIN_FILENO, STDERR_FILENO);
}
if (!m_socket_path.is_null()) {
ASSERT(m_socket_fd > 2);
dup2(m_socket_fd, 3);
if (socket_fd >= 0) {
ASSERT(!m_socket_path.is_null());
ASSERT(socket_fd > 2);
dup2(socket_fd, 3);
// The new descriptor is !CLOEXEC here.
// This is true even if m_socket_fd == 3.
// This is true even if socket_fd == 3.
setenv("SOCKET_TAKEOVER", "1", true);
}
@ -330,11 +346,14 @@ Service::Service(const Core::ConfigFile& config, const StringView& name)
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);
@ -379,6 +398,7 @@ void Service::save_to(JsonObject& json)
json.set("uid", m_uid);
json.set("gid", m_gid);
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);

View File

@ -47,7 +47,7 @@ public:
private:
Service(const Core::ConfigFile&, const StringView& name);
void spawn();
void spawn(int socket_fd = -1);
// Path to the executable. By default this is /bin/{m_name}.
String m_executable_path;
@ -62,6 +62,10 @@ private:
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.
@ -93,4 +97,5 @@ private:
void resolve_user();
void setup_socket();
void setup_notifier();
void handle_socket_connection();
};