diff --git a/Meta/gn/secondary/Userland/Libraries/LibCore/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibCore/BUILD.gn index 7c0b6653740..fafbe0033e8 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibCore/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibCore/BUILD.gn @@ -121,6 +121,12 @@ source_set("sources") { if (current_os == "mac") { sources += [ "MachPort.cpp" ] } + if (current_os != "serenity") { + sources += [ + "SingletonProcess.cpp", + "SingletonProcess.h", + ] + } if (current_os == "serenity") { sources += [ "Platform/ProcessStatisticsSerenity.cpp" ] diff --git a/Userland/Libraries/LibCore/CMakeLists.txt b/Userland/Libraries/LibCore/CMakeLists.txt index 3ee499028a6..5e3661c98b0 100644 --- a/Userland/Libraries/LibCore/CMakeLists.txt +++ b/Userland/Libraries/LibCore/CMakeLists.txt @@ -81,6 +81,10 @@ else() ) endif() +if (NOT SERENITYOS) + list(APPEND SOURCES SingletonProcess.cpp) +endif() + if (APPLE OR CMAKE_SYSTEM_NAME STREQUAL "GNU") list(APPEND SOURCES MachPort.cpp) endif() diff --git a/Userland/Libraries/LibCore/SingletonProcess.cpp b/Userland/Libraries/LibCore/SingletonProcess.cpp new file mode 100644 index 00000000000..06a7fdd80cb --- /dev/null +++ b/Userland/Libraries/LibCore/SingletonProcess.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace Core::Detail { + +static ErrorOr should_launch_process(StringView process_name, ByteString const& pid_path) +{ + if (Core::System::stat(pid_path).is_error()) + return true; + + Optional 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(); + } + + if (!pid.has_value()) { + warnln("{} PID file '{}' exists, but with an invalid PID", process_name, pid_path); + TRY(Core::System::unlink(pid_path)); + return true; + } + if (kill(*pid, 0) < 0) { + warnln("{} PID file '{}' exists with PID {}, but process cannot be found", process_name, pid_path, *pid); + TRY(Core::System::unlink(pid_path)); + return true; + } + + return false; +} + +// This is heavily based on how SystemServer's Service creates its socket. +static ErrorOr create_ipc_socket(ByteString const& socket_path) +{ + if (!Core::System::stat(socket_path).is_error()) + TRY(Core::System::unlink(socket_path)); + +#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 + + 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(&socket_address_un), sizeof(socket_address_un))); + TRY(Core::System::listen(socket_fd, 16)); + + return socket_fd; +} + +static ErrorOr launch_process(StringView process_name, ByteString const& socket_path, ByteString const& pid_path, ReadonlySpan candidate_process_paths, ReadonlySpan command_line_arguments) +{ + auto ipc_fd_or_error = create_ipc_socket(socket_path); + if (ipc_fd_or_error.is_error()) { + warnln("Failed to create an IPC socket for {} at {}: {}", process_name, socket_path, ipc_fd_or_error.error()); + return ipc_fd_or_error.release_error(); + } + + sigset_t original_set; + sigset_t setting_set; + sigfillset(&setting_set); + (void)pthread_sigmask(SIG_BLOCK, &setting_set, &original_set); + + auto ipc_fd = ipc_fd_or_error.value(); + auto pid = TRY(Core::System::fork()); + + if (pid == 0) { + (void)pthread_sigmask(SIG_SETMASK, &original_set, nullptr); + TRY(Core::System::setsid()); + TRY(Core::System::signal(SIGCHLD, SIG_IGN)); + + pid = TRY(Core::System::fork()); + + if (pid != 0) { + auto pid_file = TRY(Core::File::open(pid_path, Core::File::OpenMode::Write)); + TRY(pid_file->write_until_depleted(ByteString::number(pid))); + + TRY(Core::System::kill(getpid(), SIGTERM)); + } + + ipc_fd = TRY(Core::System::dup(ipc_fd)); + + auto takeover_string = ByteString::formatted("{}:{}", process_name, ipc_fd); + TRY(Core::Environment::set("SOCKET_TAKEOVER"sv, takeover_string, Core::Environment::Overwrite::Yes)); + + ErrorOr result; + + Vector arguments { + ""sv, // placeholder for the candidate path + "--pid-file"sv, + pid_path, + }; + + for (auto const& argument : command_line_arguments) + arguments.append(argument); + + for (auto const& process_path : candidate_process_paths) { + arguments[0] = process_path; + + result = Core::System::exec(arguments[0], arguments, Core::System::SearchInPath::Yes); + if (!result.is_error()) + break; + } + + if (result.is_error()) { + warnln("Could not launch any of {}: {}", candidate_process_paths, result.error()); + TRY(Core::System::unlink(pid_path)); + } + + VERIFY_NOT_REACHED(); + } + + VERIFY(pid > 0); + + auto wait_err = Core::System::waitpid(pid); + (void)pthread_sigmask(SIG_SETMASK, &original_set, nullptr); + TRY(wait_err); + + return {}; +} + +struct ProcessPaths { + ByteString socket_path; + ByteString pid_path; +}; +static ErrorOr 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) }; +} + +ErrorOr> launch_and_connect_to_process(StringView process_name, ReadonlySpan candidate_process_paths, ReadonlySpan command_line_arguments) +{ + auto [socket_path, pid_path] = TRY(paths_for_process(process_name)); + + if (TRY(Detail::should_launch_process(process_name, pid_path))) + TRY(Detail::launch_process(process_name, socket_path, pid_path, candidate_process_paths, command_line_arguments)); + + auto socket = TRY(Core::LocalSocket::connect(socket_path)); + TRY(socket->set_blocking(true)); + + return socket; +} + +} diff --git a/Userland/Libraries/LibCore/SingletonProcess.h b/Userland/Libraries/LibCore/SingletonProcess.h new file mode 100644 index 00000000000..0d3d2a0d714 --- /dev/null +++ b/Userland/Libraries/LibCore/SingletonProcess.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#if defined(AK_OS_SERENITY) +# error "Singleton process utilities are not to be used on SerenityOS" +#endif + +namespace Core { + +namespace Detail { +ErrorOr> launch_and_connect_to_process(StringView process_name, ReadonlySpan candidate_process_paths, ReadonlySpan command_line_arguments); +} + +template +ErrorOr> launch_singleton_process(StringView process_name, ReadonlySpan candidate_process_paths, ReadonlySpan command_line_arguments = {}) +{ + auto socket = TRY(Detail::launch_and_connect_to_process(process_name, candidate_process_paths, command_line_arguments)); + return adopt_nonnull_ref_or_enomem(new (nothrow) ClientType { move(socket) }); +} + +} diff --git a/Userland/Libraries/LibSQL/SQLClient.cpp b/Userland/Libraries/LibSQL/SQLClient.cpp index aa099c43c55..89f8be349b1 100644 --- a/Userland/Libraries/LibSQL/SQLClient.cpp +++ b/Userland/Libraries/LibSQL/SQLClient.cpp @@ -10,161 +10,8 @@ #include #include -#if !defined(AK_OS_SERENITY) -# include -# include -# include -# include -# include -# include -# include -#endif - namespace SQL { -#if !defined(AK_OS_SERENITY) - -// This is heavily based on how SystemServer's Service creates its socket. -static ErrorOr create_database_socket(ByteString const& socket_path) -{ - if (FileSystem::exists(socket_path)) - TRY(Core::System::unlink(socket_path)); - -# 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 - - 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(&socket_address_un), sizeof(socket_address_un))); - TRY(Core::System::listen(socket_fd, 16)); - - return socket_fd; -} - -static ErrorOr launch_server(ByteString const& socket_path, ByteString const& pid_path, Vector candidate_server_paths) -{ - auto server_fd_or_error = create_database_socket(socket_path); - if (server_fd_or_error.is_error()) { - warnln("Failed to create a database socket at {}: {}", socket_path, server_fd_or_error.error()); - return server_fd_or_error.release_error(); - } - auto server_fd = server_fd_or_error.value(); - sigset_t original_set; - sigset_t setting_set; - sigfillset(&setting_set); - (void)pthread_sigmask(SIG_BLOCK, &setting_set, &original_set); - auto server_pid = TRY(Core::System::fork()); - - if (server_pid == 0) { - (void)pthread_sigmask(SIG_SETMASK, &original_set, nullptr); - TRY(Core::System::setsid()); - TRY(Core::System::signal(SIGCHLD, SIG_IGN)); - server_pid = TRY(Core::System::fork()); - - if (server_pid != 0) { - auto server_pid_file = TRY(Core::File::open(pid_path, Core::File::OpenMode::Write)); - TRY(server_pid_file->write_until_depleted(ByteString::number(server_pid))); - - TRY(Core::System::kill(getpid(), SIGTERM)); - } - - server_fd = TRY(Core::System::dup(server_fd)); - - auto takeover_string = ByteString::formatted("SQLServer:{}", server_fd); - TRY(Core::Environment::set("SOCKET_TAKEOVER"sv, takeover_string, Core::Environment::Overwrite::Yes)); - - ErrorOr result; - for (auto const& server_path : candidate_server_paths) { - auto arguments = Array { - server_path.view(), - "--pid-file"sv, - pid_path, - }; - result = Core::System::exec(arguments[0], arguments, Core::System::SearchInPath::Yes); - if (!result.is_error()) - break; - } - if (result.is_error()) { - warnln("Could not launch any of {}: {}", candidate_server_paths, result.error()); - TRY(Core::System::unlink(pid_path)); - } - - VERIFY_NOT_REACHED(); - } - VERIFY(server_pid > 0); - - auto wait_err = Core::System::waitpid(server_pid); - (void)pthread_sigmask(SIG_SETMASK, &original_set, nullptr); - if (wait_err.is_error()) - return wait_err.release_error(); - return {}; -} - -static ErrorOr should_launch_server(ByteString const& pid_path) -{ - if (!FileSystem::exists(pid_path)) - return true; - - Optional pid; - { - auto server_pid_file = Core::File::open(pid_path, Core::File::OpenMode::Read); - if (server_pid_file.is_error()) { - warnln("Could not open SQLServer PID file '{}': {}", pid_path, server_pid_file.error()); - return server_pid_file.release_error(); - } - - auto contents = server_pid_file.value()->read_until_eof(); - if (contents.is_error()) { - warnln("Could not read SQLServer PID file '{}': {}", pid_path, contents.error()); - return contents.release_error(); - } - - pid = StringView { contents.value() }.to_number(); - } - - if (!pid.has_value()) { - warnln("SQLServer PID file '{}' exists, but with an invalid PID", pid_path); - TRY(Core::System::unlink(pid_path)); - return true; - } - if (kill(*pid, 0) < 0) { - warnln("SQLServer PID file '{}' exists with PID {}, but process cannot be found", pid_path, *pid); - TRY(Core::System::unlink(pid_path)); - return true; - } - - return false; -} - -ErrorOr> SQLClient::launch_server_and_create_client(Vector candidate_server_paths) -{ - auto runtime_directory = TRY(Core::StandardPaths::runtime_directory()); - auto socket_path = ByteString::formatted("{}/SQLServer.socket", runtime_directory); - auto pid_path = ByteString::formatted("{}/SQLServer.pid", runtime_directory); - - if (TRY(should_launch_server(pid_path))) - TRY(launch_server(socket_path, pid_path, move(candidate_server_paths))); - - auto socket = TRY(Core::LocalSocket::connect(move(socket_path))); - TRY(socket->set_blocking(true)); - - return adopt_nonnull_ref_or_enomem(new (nothrow) SQLClient(move(socket))); -} - -#endif - void SQLClient::execution_success(u64 statement_id, u64 execution_id, Vector const& column_names, bool has_results, size_t created, size_t updated, size_t deleted) { if (!on_execution_success) { diff --git a/Userland/Libraries/LibSQL/SQLClient.h b/Userland/Libraries/LibSQL/SQLClient.h index 02cd3d76e0c..7b215cca853 100644 --- a/Userland/Libraries/LibSQL/SQLClient.h +++ b/Userland/Libraries/LibSQL/SQLClient.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include #include @@ -54,9 +53,10 @@ class SQLClient IPC_CLIENT_CONNECTION(SQLClient, "/tmp/session/%sid/portal/sql"sv) public: -#if !defined(AK_OS_SERENITY) - static ErrorOr> launch_server_and_create_client(Vector candidate_server_paths); -#endif + explicit SQLClient(NonnullOwnPtr socket) + : IPC::ConnectionToServer(*this, move(socket)) + { + } virtual ~SQLClient() = default; @@ -66,11 +66,6 @@ public: Function on_results_exhausted; private: - explicit SQLClient(NonnullOwnPtr socket) - : IPC::ConnectionToServer(*this, move(socket)) - { - } - virtual void execution_success(u64 statement_id, u64 execution_id, Vector const& column_names, bool has_results, size_t created, size_t updated, size_t deleted) override; virtual void execution_error(u64 statement_id, u64 execution_id, SQLErrorCode const& code, ByteString const& message) override; virtual void next_result(u64 statement_id, u64 execution_id, Vector const&) override; diff --git a/Userland/Libraries/LibWebView/Database.cpp b/Userland/Libraries/LibWebView/Database.cpp index 3e2f3cf0f7b..c92d499aa8b 100644 --- a/Userland/Libraries/LibWebView/Database.cpp +++ b/Userland/Libraries/LibWebView/Database.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,6 +7,10 @@ #include #include +#if !defined(AK_OS_SERENITY) +# include +#endif + namespace WebView { static constexpr auto database_name = "Browser"sv; @@ -19,9 +23,9 @@ ErrorOr> Database::create() #if !defined(AK_OS_SERENITY) -ErrorOr> Database::create(Vector candidate_sql_server_paths) +ErrorOr> Database::create(ReadonlySpan candidate_sql_server_paths) { - auto sql_client = TRY(SQL::SQLClient::launch_server_and_create_client(move(candidate_sql_server_paths))); + auto sql_client = TRY(Core::launch_singleton_process("SQLServer"sv, candidate_sql_server_paths)); return create(move(sql_client)); } diff --git a/Userland/Libraries/LibWebView/Database.h b/Userland/Libraries/LibWebView/Database.h index bc7ed7d79cb..e26cbf8d9fa 100644 --- a/Userland/Libraries/LibWebView/Database.h +++ b/Userland/Libraries/LibWebView/Database.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * Copyright (c) 2023, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause @@ -30,7 +30,7 @@ class Database : public RefCounted { public: static ErrorOr> create(); #if !defined(AK_OS_SERENITY) - static ErrorOr> create(Vector candidate_sql_server_paths); + static ErrorOr> create(ReadonlySpan candidate_sql_server_paths); #endif ErrorOr prepare_statement(StringView statement); diff --git a/Userland/Utilities/sql.cpp b/Userland/Utilities/sql.cpp index 431cc3c75ba..c53c2747898 100644 --- a/Userland/Utilities/sql.cpp +++ b/Userland/Utilities/sql.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Tim Flynn + * Copyright (c) 2021-2024, Tim Flynn * Copyright (c) 2022, Alex Major * * SPDX-License-Identifier: BSD-2-Clause @@ -19,6 +19,10 @@ #include #include +#if !defined(AK_OS_SERENITY) +# include +#endif + class SQLRepl { public: explicit SQLRepl(Core::EventLoop& loop, ByteString const& database_name, NonnullRefPtr sql_client) @@ -360,7 +364,7 @@ ErrorOr serenity_main(Main::Arguments arguments) auto sql_client = TRY(SQL::SQLClient::try_create()); #else VERIFY(!sql_server_path.is_empty()); - auto sql_client = TRY(SQL::SQLClient::launch_server_and_create_client({ sql_server_path })); + auto sql_client = TRY(Core::launch_singleton_process("SQLServer"sv, { { sql_server_path } })); #endif SQLRepl repl(loop, database_name, move(sql_client));