mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 04:27:43 +00:00
SystemServer+LibCore: Allow service to request multiple sockets
SystemServer only allowed a single socket to be created for a service before this. Now, SystemServer will allow any amount of sockets. The sockets can be defined like so: [SomeService] Socket=/tmp/portal/socket1,/tmp/portal/socket2,/tmp/portal/socket3 SocketPermissions=660,600 The last item in SocketPermissions is applied to the remainder of the sockets in the Socket= line, so multiple sockets can have the same permissions without having to repeat them. Defining multiple sockets is not allowed for socket-activated services at the moment, and wouldn't make much sense anyway. This patch also makes socket takeovers more robust by removing the assumption that the socket will always be passed in fd 3. Now, the SOCKET_TAKEOVER environment variable carries information about which endpoint corresponds to which socket, like so: SOCKET_TAKEOVER=/tmp/portal/socket1:3 /tmp/portal/socket2:4 and LocalServer/LocalService will parse this automatically and select the correct one. The old behavior of getting the default socket is preserved so long as the service only requests a single socket in SystemServer.ini.
This commit is contained in:
parent
4316e817db
commit
5b95850e28
6 changed files with 178 additions and 65 deletions
|
@ -29,6 +29,7 @@
|
|||
#include <AK/HashMap.h>
|
||||
#include <AK/JsonArray.h>
|
||||
#include <AK/JsonObject.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/ConfigFile.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/Socket.h>
|
||||
|
@ -51,63 +52,69 @@ Service* Service::find_by_pid(pid_t pid)
|
|||
return (*it).value;
|
||||
}
|
||||
|
||||
void Service::setup_socket()
|
||||
void Service::setup_socket(SocketDescriptor& socket)
|
||||
{
|
||||
VERIFY(!m_socket_path.is_null());
|
||||
VERIFY(m_socket_fd == -1);
|
||||
VERIFY(socket.fd == -1);
|
||||
|
||||
auto ok = Core::File::ensure_parent_directories(m_socket_path);
|
||||
auto ok = Core::File::ensure_parent_directories(socket.path);
|
||||
VERIFY(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) {
|
||||
int socket_fd = ::socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
|
||||
if (socket_fd < 0) {
|
||||
perror("socket");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
socket.fd = socket_fd;
|
||||
|
||||
if (m_account.has_value()) {
|
||||
auto& account = m_account.value();
|
||||
if (fchown(m_socket_fd, account.uid(), account.gid()) < 0) {
|
||||
if (fchown(socket_fd, account.uid(), account.gid()) < 0) {
|
||||
perror("fchown");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
if (fchmod(m_socket_fd, m_socket_permissions) < 0) {
|
||||
if (fchmod(socket_fd, socket.permissions) < 0) {
|
||||
perror("fchmod");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
auto socket_address = Core::SocketAddress::local(m_socket_path);
|
||||
auto socket_address = Core::SocketAddress::local(socket.path);
|
||||
auto un_optional = socket_address.to_sockaddr_un();
|
||||
if (!un_optional.has_value()) {
|
||||
dbgln("Socket name {} is too long. BUG! This should have failed earlier!", m_socket_path);
|
||||
dbgln("Socket name {} is too long. BUG! This should have failed earlier!", socket.path);
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
auto un = un_optional.value();
|
||||
int rc = bind(m_socket_fd, (const sockaddr*)&un, sizeof(un));
|
||||
int rc = bind(socket_fd, (const sockaddr*)&un, sizeof(un));
|
||||
if (rc < 0) {
|
||||
perror("bind");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
rc = listen(m_socket_fd, 16);
|
||||
rc = listen(socket_fd, 16);
|
||||
if (rc < 0) {
|
||||
perror("listen");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
void Service::setup_sockets()
|
||||
{
|
||||
for (SocketDescriptor& socket : m_sockets)
|
||||
setup_socket(socket);
|
||||
}
|
||||
|
||||
void Service::setup_notifier()
|
||||
{
|
||||
VERIFY(m_lazy);
|
||||
VERIFY(m_socket_fd >= 0);
|
||||
VERIFY(m_sockets.size() == 1);
|
||||
VERIFY(!m_socket_notifier);
|
||||
|
||||
m_socket_notifier = Core::Notifier::construct(m_socket_fd, Core::Notifier::Event::Read, this);
|
||||
m_socket_notifier = Core::Notifier::construct(m_sockets[0].fd, Core::Notifier::Event::Read, this);
|
||||
m_socket_notifier->on_ready_to_read = [this] {
|
||||
handle_socket_connection();
|
||||
};
|
||||
|
@ -115,10 +122,13 @@ void Service::setup_notifier()
|
|||
|
||||
void Service::handle_socket_connection()
|
||||
{
|
||||
VERIFY(m_sockets.size() == 1);
|
||||
dbgln_if(SERVICE_DEBUG, "Ready to read on behalf of {}", name());
|
||||
|
||||
int socket_fd = m_sockets[0].fd;
|
||||
|
||||
if (m_accept_socket_connections) {
|
||||
int accepted_fd = accept(m_socket_fd, nullptr, nullptr);
|
||||
int accepted_fd = accept(socket_fd, nullptr, nullptr);
|
||||
if (accepted_fd < 0) {
|
||||
perror("accept");
|
||||
return;
|
||||
|
@ -128,7 +138,7 @@ void Service::handle_socket_connection()
|
|||
} else {
|
||||
remove_child(*m_socket_notifier);
|
||||
m_socket_notifier = nullptr;
|
||||
spawn(m_socket_fd);
|
||||
spawn(socket_fd);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +149,7 @@ void Service::activate()
|
|||
if (m_lazy)
|
||||
setup_notifier();
|
||||
else
|
||||
spawn(m_socket_fd);
|
||||
spawn();
|
||||
}
|
||||
|
||||
void Service::spawn(int socket_fd)
|
||||
|
@ -198,12 +208,31 @@ void Service::spawn(int socket_fd)
|
|||
dup2(STDIN_FILENO, STDERR_FILENO);
|
||||
}
|
||||
|
||||
StringBuilder builder;
|
||||
|
||||
if (socket_fd >= 0) {
|
||||
VERIFY(!m_socket_path.is_null());
|
||||
VERIFY(socket_fd > 3);
|
||||
dup2(socket_fd, 3);
|
||||
// We were spawned by socket activation. We currently only support
|
||||
// single sockets for socket activation, so make sure that's the case.
|
||||
VERIFY(m_sockets.size() == 1);
|
||||
|
||||
int fd = dup(socket_fd);
|
||||
builder.appendf("%s:%d", m_sockets[0].path.characters(), fd);
|
||||
} else {
|
||||
// We were spawned as a regular process, so dup every socket for this
|
||||
// service and let the service know via SOCKET_TAKEOVER.
|
||||
for (unsigned i = 0; i < m_sockets.size(); i++) {
|
||||
SocketDescriptor& socket = m_sockets.at(i);
|
||||
|
||||
int new_fd = dup(socket.fd);
|
||||
if (i != 0)
|
||||
builder.append(" ");
|
||||
builder.appendf("%s:%d", socket.path.characters(), new_fd);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_sockets.is_empty()) {
|
||||
// The new descriptor is !CLOEXEC here.
|
||||
setenv("SOCKET_TAKEOVER", "1", true);
|
||||
setenv("SOCKET_TAKEOVER", builder.to_string().characters(), true);
|
||||
}
|
||||
|
||||
if (m_account.has_value()) {
|
||||
|
@ -306,22 +335,39 @@ Service::Service(const Core::ConfigFile& config, const StringView& name)
|
|||
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");
|
||||
String socket_entry = config.read_entry(name, "Socket");
|
||||
String socket_permissions_entry = config.read_entry(name, "SocketPermissions", "0600");
|
||||
|
||||
// Lazy requires Socket.
|
||||
VERIFY(!m_lazy || !m_socket_path.is_null());
|
||||
// AcceptSocketConnections always requires Socket, Lazy, and MultiInstance.
|
||||
VERIFY(!m_accept_socket_connections || (!m_socket_path.is_null() && m_lazy && m_multi_instance));
|
||||
if (!socket_entry.is_null()) {
|
||||
Vector<String> socket_paths = socket_entry.split(',');
|
||||
Vector<String> socket_perms = socket_permissions_entry.split(',');
|
||||
m_sockets.ensure_capacity(socket_paths.size());
|
||||
|
||||
// Need i here to iterate along with all other vectors.
|
||||
for (unsigned i = 0; i < socket_paths.size(); i++) {
|
||||
String& path = socket_paths.at(i);
|
||||
|
||||
// Socket path (plus NUL) must fit into the structs sent to the Kernel.
|
||||
VERIFY(path.length() < UNIX_PATH_MAX);
|
||||
|
||||
// This is done so that the last permission repeats for every other
|
||||
// socket. So you can define a single permission, and have it
|
||||
// be applied for every socket.
|
||||
mode_t permissions = strtol(socket_perms.at(min(socket_perms.size() - 1, (long unsigned)i)).characters(), nullptr, 8) & 0777;
|
||||
|
||||
m_sockets.empend(path, -1, permissions);
|
||||
}
|
||||
}
|
||||
|
||||
// Lazy requires Socket, but only one.
|
||||
VERIFY(!m_lazy || m_sockets.size() == 1);
|
||||
// AcceptSocketConnections always requires Socket (single), Lazy, and MultiInstance.
|
||||
VERIFY(!m_accept_socket_connections || (m_sockets.size() == 1 && m_lazy && m_multi_instance));
|
||||
// MultiInstance doesn't work with KeepAlive.
|
||||
VERIFY(!m_multi_instance || !m_keep_alive);
|
||||
// Socket path (plus NUL) must fit into the structs sent to the Kernel.
|
||||
VERIFY(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) & 0777;
|
||||
setup_socket();
|
||||
}
|
||||
if (is_enabled())
|
||||
setup_sockets();
|
||||
}
|
||||
|
||||
void Service::save_to(JsonObject& json)
|
||||
|
@ -346,13 +392,20 @@ void Service::save_to(JsonObject& json)
|
|||
for (String& env : m_environment)
|
||||
boot_modes.append(env);
|
||||
json.set("environment", environment);
|
||||
|
||||
JsonArray sockets;
|
||||
for (SocketDescriptor &socket : m_sockets) {
|
||||
JsonObject socket_obj;
|
||||
socket_obj.set("path", socket.path);
|
||||
socket_obj.set("permissions", socket.permissions);
|
||||
sockets.append(socket);
|
||||
}
|
||||
json.set("sockets", sockets);
|
||||
*/
|
||||
|
||||
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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue