mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-05-11 17:37:33 +02:00
This commit splits out synchronization primitives from LibThreading into LibSync. This is because LibThreading depends on LibCore, while LibCore needs the synchronization primitives from LibThreading. This worked while they were header only, but when I tried to add an implementation file it ran into the circular dependency. To abstract away the pthread implementation using cpp files is necessary so the synchronization primitives were moved to a separate library.
216 lines
7.9 KiB
C++
216 lines
7.9 KiB
C++
/*
|
|
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibCore/Environment.h>
|
|
#include <LibCore/File.h>
|
|
#include <LibCore/Process.h>
|
|
#include <LibCore/Socket.h>
|
|
#include <LibCore/StandardPaths.h>
|
|
#include <LibCore/System.h>
|
|
#include <LibWebView/Process.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#if defined(AK_OS_MACOS)
|
|
# include <LibIPC/TransportBootstrapMach.h>
|
|
# include <LibWebView/Application.h>
|
|
#endif
|
|
|
|
#if defined(AK_OS_WINDOWS)
|
|
# include <AK/ScopeGuard.h>
|
|
# include <AK/Windows.h>
|
|
#endif
|
|
|
|
namespace WebView {
|
|
|
|
Process::Process(ProcessType type, RefPtr<IPC::ConnectionBase> connection, Core::Process process)
|
|
: m_process(move(process))
|
|
, m_type(type)
|
|
, m_connection(move(connection))
|
|
{
|
|
}
|
|
|
|
Process::~Process()
|
|
{
|
|
if (m_connection)
|
|
m_connection->shutdown();
|
|
}
|
|
|
|
ErrorOr<Process::ProcessAndIPCTransport> Process::spawn_and_connect_to_process(Core::ProcessSpawnOptions const& options, bool capture_output)
|
|
{
|
|
// Set up pipes for stdout/stderr capture if requested
|
|
ProcessOutputCapture output_capture;
|
|
Array<int, 2> stdout_pipe {};
|
|
Array<int, 2> stderr_pipe {};
|
|
|
|
Core::ProcessSpawnOptions spawn_options = options;
|
|
|
|
if (capture_output) {
|
|
stdout_pipe = TRY(Core::System::pipe2(O_CLOEXEC));
|
|
stderr_pipe = TRY(Core::System::pipe2(O_CLOEXEC));
|
|
|
|
// Clear close-on-exec for the write ends so they're inherited by the child
|
|
TRY(Core::System::set_close_on_exec(stdout_pipe[1], false));
|
|
TRY(Core::System::set_close_on_exec(stderr_pipe[1], false));
|
|
|
|
// Add file actions to redirect stdout/stderr in the child
|
|
spawn_options.file_actions.append(Core::FileAction::DupFd { .write_fd = stdout_pipe[1], .fd = STDOUT_FILENO });
|
|
spawn_options.file_actions.append(Core::FileAction::DupFd { .write_fd = stderr_pipe[1], .fd = STDERR_FILENO });
|
|
spawn_options.file_actions.append(Core::FileAction::CloseFile { .fd = stdout_pipe[1] });
|
|
spawn_options.file_actions.append(Core::FileAction::CloseFile { .fd = stderr_pipe[1] });
|
|
}
|
|
|
|
#if defined(AK_OS_MACOS)
|
|
auto port_a_recv = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
|
auto port_a_send = TRY(port_a_recv.insert_right(Core::MachPort::MessageRight::MakeSend));
|
|
auto port_b_recv = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive));
|
|
auto port_b_send = TRY(port_b_recv.insert_right(Core::MachPort::MessageRight::MakeSend));
|
|
|
|
Sync::MutexLocker child_registration_locker(Application::transport_bootstrap_server().child_registration_lock());
|
|
auto process = TRY(Core::Process::spawn(spawn_options));
|
|
|
|
Application::transport_bootstrap_server().register_child_transport(process.pid(), IPC::TransportBootstrapMachPorts { move(port_b_recv), move(port_a_send) });
|
|
|
|
auto transport = make<IPC::Transport>(move(port_a_recv), move(port_b_send));
|
|
#else
|
|
int socket_fds[2] {};
|
|
TRY(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_fds));
|
|
|
|
ArmedScopeGuard guard_fd_0 { [&] { MUST(Core::System::close(socket_fds[0])); } };
|
|
ArmedScopeGuard guard_fd_1 { [&] { MUST(Core::System::close(socket_fds[1])); } };
|
|
|
|
// Note: Core::System::socketpair creates inheritable sockets both on Linux and Windows unless SOCK_CLOEXEC is specified.
|
|
TRY(Core::System::set_close_on_exec(socket_fds[0], true));
|
|
|
|
auto takeover_string = MUST(String::formatted("{}:{}", options.name, socket_fds[1]));
|
|
TRY(Core::Environment::set("SOCKET_TAKEOVER"sv, takeover_string, Core::Environment::Overwrite::Yes));
|
|
|
|
auto process = TRY(Core::Process::spawn(spawn_options));
|
|
|
|
auto ipc_socket = TRY(Core::LocalSocket::adopt_fd(socket_fds[0]));
|
|
guard_fd_0.disarm();
|
|
TRY(ipc_socket->set_blocking(true));
|
|
|
|
auto transport = make<IPC::Transport>(move(ipc_socket));
|
|
#endif
|
|
|
|
if (capture_output) {
|
|
// Close write ends in parent
|
|
MUST(Core::System::close(stdout_pipe[1]));
|
|
MUST(Core::System::close(stderr_pipe[1]));
|
|
|
|
// Wrap read ends in File objects
|
|
output_capture.stdout_file = TRY(Core::File::adopt_fd(stdout_pipe[0], Core::File::OpenMode::Read));
|
|
output_capture.stderr_file = TRY(Core::File::adopt_fd(stderr_pipe[0], Core::File::OpenMode::Read));
|
|
}
|
|
|
|
return ProcessAndIPCTransport { move(process), move(transport), move(output_capture) };
|
|
}
|
|
|
|
ErrorOr<Optional<pid_t>> Process::get_process_pid(StringView process_name, StringView pid_path)
|
|
{
|
|
if (Core::System::stat(pid_path).is_error())
|
|
return OptionalNone {};
|
|
|
|
Optional<pid_t> pid;
|
|
{
|
|
auto pid_file = Core::File::open(pid_path, Core::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<pid_t>();
|
|
}
|
|
|
|
if (!pid.has_value()) {
|
|
warnln("{} PID file '{}' exists, but with an invalid PID", process_name, pid_path);
|
|
TRY(Core::System::unlink(pid_path));
|
|
return OptionalNone {};
|
|
}
|
|
|
|
bool const process_not_found = [&pid]() {
|
|
#if defined(AK_OS_WINDOWS)
|
|
HANDLE process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, *pid);
|
|
if (process_handle == nullptr)
|
|
return true;
|
|
|
|
// FIXME: We should create an RAII wrapper around HANDLE objects.
|
|
ScopeGuard handle_guard = [&process_handle] { CloseHandle(process_handle); };
|
|
DWORD exit_code = 0;
|
|
|
|
if (GetExitCodeProcess(process_handle, &exit_code) == 0)
|
|
return true;
|
|
|
|
return exit_code != STILL_ACTIVE;
|
|
#else
|
|
return kill(*pid, 0) < 0;
|
|
#endif
|
|
}();
|
|
|
|
if (process_not_found) {
|
|
warnln("{} PID file '{}' exists with PID {}, but process cannot be found", process_name, pid_path, *pid);
|
|
TRY(Core::System::unlink(pid_path));
|
|
return OptionalNone {};
|
|
}
|
|
|
|
return pid;
|
|
}
|
|
|
|
// This is heavily based on how SystemServer's Service creates its socket.
|
|
ErrorOr<int> Process::create_ipc_socket(ByteString const& socket_path)
|
|
{
|
|
if (!Core::System::stat(socket_path).is_error())
|
|
TRY(Core::System::unlink(socket_path));
|
|
|
|
#if defined(AK_OS_WINDOWS)
|
|
auto socket_fd = TRY(Core::System::socket(AF_LOCAL, SOCK_STREAM, 0));
|
|
int option = 1;
|
|
TRY(Core::System::ioctl(socket_fd, FIONBIO, &option));
|
|
if (SetHandleInformation(to_handle(socket_fd), HANDLE_FLAG_INHERIT, 0) == 0)
|
|
return Error::from_windows_error();
|
|
#else
|
|
# ifdef SOCK_NONBLOCK
|
|
auto socket_fd = TRY(Core::System::socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0));
|
|
# else
|
|
auto socket_fd = TRY(Core::System::socket(AF_LOCAL, SOCK_STREAM, 0));
|
|
|
|
int option = 1;
|
|
TRY(Core::System::ioctl(socket_fd, FIONBIO, &option));
|
|
TRY(Core::System::fcntl(socket_fd, F_SETFD, FD_CLOEXEC));
|
|
# endif
|
|
|
|
# if !defined(AK_OS_BSD_GENERIC) && !defined(AK_OS_GNU_HURD)
|
|
TRY(Core::System::fchmod(socket_fd, 0600));
|
|
# endif
|
|
#endif
|
|
|
|
auto socket_address = Core::SocketAddress::local(socket_path);
|
|
auto socket_address_un = socket_address.to_sockaddr_un().release_value();
|
|
|
|
TRY(Core::System::bind(socket_fd, reinterpret_cast<sockaddr*>(&socket_address_un), sizeof(socket_address_un)));
|
|
TRY(Core::System::listen(socket_fd, 16));
|
|
|
|
return socket_fd;
|
|
}
|
|
|
|
ErrorOr<Process::ProcessPaths> Process::paths_for_process(StringView process_name)
|
|
{
|
|
auto runtime_directory = TRY(Core::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) };
|
|
}
|
|
|
|
}
|