diff --git a/Userland/Libraries/LibCore/Process.cpp b/Userland/Libraries/LibCore/Process.cpp index d7d8f7dccc8..52267c97146 100644 --- a/Userland/Libraries/LibCore/Process.cpp +++ b/Userland/Libraries/LibCore/Process.cpp @@ -15,8 +15,11 @@ #include #include #include +#include +#include #include #include +#include #include #include @@ -376,4 +379,135 @@ ErrorOr IPCProcess::spawn_and_connect_to_proces return ProcessAndIPCSocket { move(process), move(ipc_socket) }; } +static ErrorOr> get_process_pid(StringView process_name, StringView pid_path) +{ + if (System::stat(pid_path).is_error()) + return OptionalNone {}; + + Optional pid; + { + auto pid_file = File::open(pid_path, File::OpenMode::Read); + if (pid_file.is_error()) { + warnln("Could not open {} PID file '{}': {}", process_name, pid_path, pid_file.error()); + return pid_file.release_error(); + } + + auto contents = pid_file.value()->read_until_eof(); + if (contents.is_error()) { + warnln("Could not read {} PID file '{}': {}", process_name, pid_path, contents.error()); + return contents.release_error(); + } + + pid = StringView { contents.value() }.to_number(); + } + + if (!pid.has_value()) { + warnln("{} PID file '{}' exists, but with an invalid PID", process_name, pid_path); + TRY(System::unlink(pid_path)); + return OptionalNone {}; + } + if (kill(*pid, 0) < 0) { + warnln("{} PID file '{}' exists with PID {}, but process cannot be found", process_name, pid_path, *pid); + TRY(System::unlink(pid_path)); + return OptionalNone {}; + } + + return pid; +} + +// This is heavily based on how SystemServer's Service creates its socket. +static ErrorOr create_ipc_socket(ByteString const& socket_path) +{ + if (!System::stat(socket_path).is_error()) + TRY(System::unlink(socket_path)); + +#ifdef SOCK_NONBLOCK + auto socket_fd = TRY(System::socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); +#else + auto socket_fd = TRY(System::socket(AF_LOCAL, SOCK_STREAM, 0)); + + int option = 1; + TRY(System::ioctl(socket_fd, FIONBIO, &option)); + TRY(System::fcntl(socket_fd, F_SETFD, FD_CLOEXEC)); +#endif + +#if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_GNU_HURD) + TRY(System::fchmod(socket_fd, 0600)); +#endif + + auto socket_address = SocketAddress::local(socket_path); + auto socket_address_un = socket_address.to_sockaddr_un().release_value(); + + TRY(System::bind(socket_fd, reinterpret_cast(&socket_address_un), sizeof(socket_address_un))); + TRY(System::listen(socket_fd, 16)); + + return socket_fd; +} + +struct ProcessPaths { + ByteString socket_path; + ByteString pid_path; +}; +static ErrorOr paths_for_process(StringView process_name) +{ + auto runtime_directory = TRY(StandardPaths::runtime_directory()); + auto socket_path = ByteString::formatted("{}/{}.socket", runtime_directory, process_name); + auto pid_path = ByteString::formatted("{}/{}.pid", runtime_directory, process_name); + + return ProcessPaths { move(socket_path), move(pid_path) }; +} + +ErrorOr IPCProcess::spawn_singleton_and_connect_to_process(ProcessSpawnOptions const& options) +{ + auto [socket_path, pid_path] = TRY(paths_for_process(options.name)); + Process process { -1 }; + + if (auto existing_pid = TRY(get_process_pid(options.name, pid_path)); existing_pid.has_value()) { + process = Process { *existing_pid }; + } else { + auto ipc_fd = TRY(create_ipc_socket(socket_path)); + + sigset_t original_set; + sigset_t setting_set; + sigfillset(&setting_set); + (void)pthread_sigmask(SIG_BLOCK, &setting_set, &original_set); + + // FIXME: Roll this daemon implementation into `Process::disown`. + if (auto pid = TRY(System::fork()); pid == 0) { + (void)pthread_sigmask(SIG_SETMASK, &original_set, nullptr); + TRY(System::setsid()); + TRY(System::signal(SIGCHLD, SIG_IGN)); + + auto& arguments = const_cast&>(options.arguments); + arguments.append("--pid-file"sv); + arguments.append(pid_path); + + auto takeover_string = ByteString::formatted("{}:{}", options.name, TRY(System::dup(ipc_fd))); + TRY(Environment::set("SOCKET_TAKEOVER"sv, takeover_string, Environment::Overwrite::Yes)); + + auto process = TRY(Process::spawn(options)); + { + auto pid_file = TRY(File::open(pid_path, File::OpenMode::Write)); + TRY(pid_file->write_until_depleted(ByteString::number(process.pid()))); + } + + TRY(System::kill(getpid(), SIGTERM)); + } else { + auto wait_err = System::waitpid(pid); + (void)pthread_sigmask(SIG_SETMASK, &original_set, nullptr); + TRY(wait_err); + } + + auto pid = TRY(get_process_pid(options.name, pid_path)); + VERIFY(pid.has_value()); + + process = Process { *pid }; + } + + auto ipc_socket = TRY(LocalSocket::connect(socket_path)); + TRY(ipc_socket->set_blocking(true)); + + return ProcessAndIPCSocket { move(process), move(ipc_socket) }; +} + } diff --git a/Userland/Libraries/LibCore/Process.h b/Userland/Libraries/LibCore/Process.h index f2295964aec..78db8d8dbff 100644 --- a/Userland/Libraries/LibCore/Process.h +++ b/Userland/Libraries/LibCore/Process.h @@ -44,6 +44,8 @@ struct ProcessSpawnOptions { Vector const& file_actions {}; }; +class IPCProcess; + class Process { AK_MAKE_NONCOPYABLE(Process); @@ -98,6 +100,8 @@ public: ErrorOr wait_for_termination(); private: + friend IPCProcess; + Process(pid_t pid) : m_pid(pid) , m_should_disown(true) @@ -125,6 +129,15 @@ public: return ProcessAndIPCClient { move(process), move(client) }; } + template + static ErrorOr> spawn_singleton(ProcessSpawnOptions const& options, ClientArguments&&... client_arguments) + { + auto [process, socket] = TRY(spawn_singleton_and_connect_to_process(options)); + auto client = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) ClientType { move(socket), forward(client_arguments)... })); + + return ProcessAndIPCClient { move(process), move(client) }; + } + pid_t pid() const { return m_process.pid(); } private: @@ -133,6 +146,7 @@ private: NonnullOwnPtr m_ipc_socket; }; static ErrorOr spawn_and_connect_to_process(ProcessSpawnOptions const& options); + static ErrorOr spawn_singleton_and_connect_to_process(ProcessSpawnOptions const& options); Process m_process; };