Skip to content

Commit

Permalink
SystemServer+LibCore: Implement socket takeover
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bugaevc authored and awesomekling committed Nov 26, 2019
1 parent 396ad4d commit c9e21b2
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 10 deletions.
74 changes: 64 additions & 10 deletions Libraries/LibCore/CLocalServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,93 @@
#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)
return false;

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;
}

Expand Down
3 changes: 3 additions & 0 deletions Libraries/LibCore/CLocalServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -20,6 +21,8 @@ class CLocalServer : public CObject {
private:
explicit CLocalServer(CObject* parent = nullptr);

void setup_notifier();

int m_fd { -1 };
bool m_listening { false };
RefPtr<CNotifier> m_notifier;
Expand Down
71 changes: 71 additions & 0 deletions Servers/SystemServer/Service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions Servers/SystemServer/Service.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <AK/RefPtr.h>
#include <AK/String.h>
#include <LibCore/CNotifier.h>
#include <LibCore/CObject.h>

class CConfigFile;
Expand Down Expand Up @@ -33,13 +34,18 @@ class Service final : public CObject {
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 };
gid_t m_gid { 0 };

// 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();
};
2 changes: 2 additions & 0 deletions Servers/SystemServer/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down

0 comments on commit c9e21b2

Please sign in to comment.