diff --git a/AK/Debug.h.in b/AK/Debug.h.in index c96874d4b3..936ec445da 100644 --- a/AK/Debug.h.in +++ b/AK/Debug.h.in @@ -386,6 +386,10 @@ #cmakedefine01 SQL_DEBUG #endif +#ifndef SQLSERVER_DEBUG +#cmakedefine01 SQLSERVER_DEBUG +#endif + #ifndef SYNTAX_HIGHLIGHTING_DEBUG #cmakedefine01 SYNTAX_HIGHLIGHTING_DEBUG #endif diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index b9c8c70658..9cb06a02b2 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -159,6 +159,7 @@ set(SOCKET_DEBUG ON) set(SOLITAIRE_DEBUG ON) set(SPAM_DEBUG ON) set(SQL_DEBUG ON) +set(SQLSERVER_DEBUG ON) set(STORAGE_DEVICE_DEBUG ON) set(SYNTAX_HIGHLIGHTING_DEBUG ON) set(SYSCALL_1_DEBUG ON) diff --git a/Tests/LibSQL/CMakeLists.txt b/Tests/LibSQL/CMakeLists.txt index f002a1e864..787696fb33 100644 --- a/Tests/LibSQL/CMakeLists.txt +++ b/Tests/LibSQL/CMakeLists.txt @@ -1,5 +1,5 @@ file(GLOB TEST_SOURCES CONFIGURE_DEPENDS "*.cpp") foreach(source ${TEST_SOURCES}) - serenity_test(${source} LibSQL LIBS LibSQL) + serenity_test(${source} LibSQL LIBS LibSQL LibIPC) endforeach() diff --git a/Userland/Libraries/LibSQL/CMakeLists.txt b/Userland/Libraries/LibSQL/CMakeLists.txt index 3e7f033067..a5ba1f6b69 100644 --- a/Userland/Libraries/LibSQL/CMakeLists.txt +++ b/Userland/Libraries/LibSQL/CMakeLists.txt @@ -14,10 +14,16 @@ set(SOURCES Key.cpp Meta.cpp Row.cpp + SQLClient.cpp TreeNode.cpp Tuple.cpp Value.cpp ) +set(GENERATED_SOURCES + ../../Services/SQLServer/SQLClientEndpoint.h + ../../Services/SQLServer/SQLServerEndpoint.h + ) + serenity_lib(LibSQL sql) target_link_libraries(LibSQL LibCore LibSyntax) diff --git a/Userland/Libraries/LibSQL/SQLClient.cpp b/Userland/Libraries/LibSQL/SQLClient.cpp new file mode 100644 index 0000000000..8114ef8075 --- /dev/null +++ b/Userland/Libraries/LibSQL/SQLClient.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace SQL { + +SQLClient::~SQLClient() +{ +} + +void SQLClient::connected(int connection_id) +{ + if (on_connected) + on_connected(connection_id); +} + +void SQLClient::disconnected(int connection_id) +{ + if (on_disconnected) + on_disconnected(connection_id); +} + +void SQLClient::connection_error(int connection_id, int code, String const& message) +{ + if (on_connection_error) + on_connection_error(connection_id, code, message); + else + warnln("Connection error for connection_id {}: {} ({})", connection_id, message, code); +} + +void SQLClient::execution_error(int statement_id, int code, String const& message) +{ + if (on_execution_error) + on_execution_error(statement_id, code, message); + else + warnln("Execution error for statement_id {}: {} ({})", statement_id, message, code); +} + +void SQLClient::execution_success(int statement_id, bool has_results, int created, int updated, int deleted) +{ + if (on_execution_success) + on_execution_success(statement_id, has_results, created, updated, deleted); + else + outln("{} row(s) created, {} updated, {} deleted", created, updated, deleted); +} + +void SQLClient::next_result(int statement_id, Vector const& row) +{ + if (on_next_result) { + on_next_result(statement_id, row); + return; + } + bool first = true; + for (auto& column : row) { + if (!first) + out(", "); + out("\"{}\"", column); + first = false; + } + outln(); +} + +void SQLClient::results_exhausted(int statement_id, int total_rows) +{ + if (on_results_exhausted) + on_results_exhausted(statement_id, total_rows); + else + outln("{} total row(s)", total_rows); +} + +} diff --git a/Userland/Libraries/LibSQL/SQLClient.h b/Userland/Libraries/LibSQL/SQLClient.h new file mode 100644 index 0000000000..d473c8f65d --- /dev/null +++ b/Userland/Libraries/LibSQL/SQLClient.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace SQL { + +class SQLClient + : public IPC::ServerConnection + , public SQLClientEndpoint { + C_OBJECT(SQLClient); + virtual ~SQLClient(); + + Function on_connected; + Function on_disconnected; + Function on_connection_error; + Function on_execution_error; + Function on_execution_success; + Function const&)> on_next_result; + Function on_results_exhausted; + +private: + SQLClient() + : IPC::ServerConnection(*this, "/tmp/portal/sql") + { + } + + virtual void connected(int connection_id) override; + virtual void connection_error(int connection_id, int code, String const& message) override; + virtual void execution_success(int statement_id, bool has_results, int created, int updated, int deleted) override; + virtual void next_result(int statement_id, Vector const&) override; + virtual void results_exhausted(int statement_id, int total_rows) override; + virtual void execution_error(int statement_id, int code, String const& message) override; + virtual void disconnected(int connection_id) override; +}; + +} diff --git a/Userland/Libraries/LibSQL/SQLResult.h b/Userland/Libraries/LibSQL/SQLResult.h index 0c83b19b40..f2871270f7 100644 --- a/Userland/Libraries/LibSQL/SQLResult.h +++ b/Userland/Libraries/LibSQL/SQLResult.h @@ -40,17 +40,18 @@ constexpr char const* command_tag(SQLCommand command) } } -#define ENUMERATE_SQL_ERRORS(S) \ - S(NoError, "No error") \ - S(DatabaseUnavailable, "Database Unavailable") \ - S(StatementUnavailable, "Statement with id {} Unavailable") \ - S(SyntaxError, "Syntax Error") \ - S(DatabaseDoesNotExist, "Database {} does not exist") \ - S(SchemaDoesNotExist, "Schema {} does not exist") \ - S(SchemaExists, "Schema {} already exist") \ - S(TableDoesNotExist, "Table {} does not exist") \ - S(TableExists, "Table {} already exist") \ - S(InvalidType, "Invalid type {}") +#define ENUMERATE_SQL_ERRORS(S) \ + S(NoError, "No error") \ + S(DatabaseUnavailable, "Database Unavailable") \ + S(StatementUnavailable, "Statement with id '{}' Unavailable") \ + S(SyntaxError, "Syntax Error") \ + S(DatabaseDoesNotExist, "Database '{}' does not exist") \ + S(SchemaDoesNotExist, "Schema '{}' does not exist") \ + S(SchemaExists, "Schema '{}' already exist") \ + S(TableDoesNotExist, "Table '{}' does not exist") \ + S(TableExists, "Table '{}' already exist") \ + S(InvalidType, "Invalid type '{}'") \ + S(InvalidDatabaseName, "Invalid database name '{}'") enum class SQLErrorCode { #undef __ENUMERATE_SQL_ERROR @@ -120,6 +121,7 @@ private: , m_update_count(update_count) , m_insert_count(insert_count) , m_delete_count(delete_count) + , m_has_results(command == SQLCommand::Select) { } diff --git a/Userland/Services/CMakeLists.txt b/Userland/Services/CMakeLists.txt index 3feb4fe174..93e3b60610 100644 --- a/Userland/Services/CMakeLists.txt +++ b/Userland/Services/CMakeLists.txt @@ -12,6 +12,7 @@ add_subdirectory(LaunchServer) add_subdirectory(LookupServer) add_subdirectory(NotificationServer) add_subdirectory(RequestServer) +add_subdirectory(SQLServer) add_subdirectory(SystemServer) add_subdirectory(Taskbar) add_subdirectory(TelnetServer) diff --git a/Userland/Services/SQLServer/CMakeLists.txt b/Userland/Services/SQLServer/CMakeLists.txt new file mode 100644 index 0000000000..940de361e6 --- /dev/null +++ b/Userland/Services/SQLServer/CMakeLists.txt @@ -0,0 +1,20 @@ +serenity_component( + SQLServer + REQUIRED + TARGETS SQLServer +) + +compile_ipc(SQLServer.ipc SQLServerEndpoint.h) +compile_ipc(SQLClient.ipc SQLClientEndpoint.h) + +set(SOURCES + ClientConnection.cpp + DatabaseConnection.cpp + main.cpp + SQLClientEndpoint.h + SQLServerEndpoint.h + SQLStatement.cpp + ) + +serenity_bin(SQLServer) +target_link_libraries(SQLServer LibCore LibIPC LibSQL) diff --git a/Userland/Services/SQLServer/ClientConnection.cpp b/Userland/Services/SQLServer/ClientConnection.cpp new file mode 100644 index 0000000000..d75bccf246 --- /dev/null +++ b/Userland/Services/SQLServer/ClientConnection.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace SQLServer { + +static HashMap> s_connections; + +RefPtr ClientConnection::client_connection_for(int client_id) +{ + if (s_connections.contains(client_id)) + return *s_connections.get(client_id).value(); + dbgln_if(SQLSERVER_DEBUG, "Invalid client_id {}", client_id); + return nullptr; +} + +ClientConnection::ClientConnection(AK::NonnullRefPtr socket, int client_id) + : IPC::ClientConnection(*this, move(socket), client_id) +{ + s_connections.set(client_id, *this); +} + +ClientConnection::~ClientConnection() +{ +} + +void ClientConnection::die() +{ + s_connections.remove(client_id()); +} + +Messages::SQLServer::ConnectResponse ClientConnection::connect(String const& database_name) +{ + dbgln_if(SQLSERVER_DEBUG, "ClientConnection::connect(database_name: {})", database_name); + auto database_connection = DatabaseConnection::construct(database_name, client_id()); + return { database_connection->connection_id() }; +} + +void ClientConnection::disconnect(int connection_id) +{ + dbgln_if(SQLSERVER_DEBUG, "ClientConnection::disconnect(connection_id: {})", connection_id); + auto database_connection = DatabaseConnection::connection_for(connection_id); + if (database_connection) + database_connection->disconnect(); + else + dbgln("Database connection has disappeared"); +} + +Messages::SQLServer::SqlStatementResponse ClientConnection::sql_statement(int connection_id, String const& sql) +{ + dbgln_if(SQLSERVER_DEBUG, "ClientConnection::sql_statement(connection_id: {}, sql: '{}')", connection_id, sql); + auto database_connection = DatabaseConnection::connection_for(connection_id); + if (database_connection) { + auto statement_id = database_connection->sql_statement(sql); + dbgln_if(SQLSERVER_DEBUG, "ClientConnection::sql_statement -> statement_id = {}", statement_id); + return { statement_id }; + } else { + dbgln("Database connection has disappeared"); + return { -1 }; + } +} + +void ClientConnection::statement_execute(int statement_id) +{ + dbgln_if(SQLSERVER_DEBUG, "ClientConnection::statement_execute_query(statement_id: {})", statement_id); + auto statement = SQLStatement::statement_for(statement_id); + if (statement && statement->connection()->client_id() == client_id()) { + statement->execute(); + } else { + dbgln_if(SQLSERVER_DEBUG, "Statement has disappeared"); + async_execution_error(statement_id, (int)SQL::SQLErrorCode::StatementUnavailable, String::formatted("{}", statement_id)); + } +} + +} diff --git a/Userland/Services/SQLServer/ClientConnection.h b/Userland/Services/SQLServer/ClientConnection.h new file mode 100644 index 0000000000..96af97fe70 --- /dev/null +++ b/Userland/Services/SQLServer/ClientConnection.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace SQLServer { + +class ClientConnection final + : public IPC::ClientConnection { + C_OBJECT(ClientConnection); + +public: + explicit ClientConnection(NonnullRefPtr, int client_id); + virtual ~ClientConnection() override; + + virtual void die() override; + + static RefPtr client_connection_for(int client_id); + +private: + virtual Messages::SQLServer::ConnectResponse connect(String const&) override; + virtual Messages::SQLServer::SqlStatementResponse sql_statement(int, String const&) override; + virtual void statement_execute(int) override; + virtual void disconnect(int) override; +}; + +} diff --git a/Userland/Services/SQLServer/DatabaseConnection.cpp b/Userland/Services/SQLServer/DatabaseConnection.cpp new file mode 100644 index 0000000000..e1ffebeec9 --- /dev/null +++ b/Userland/Services/SQLServer/DatabaseConnection.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace SQLServer { + +static HashMap> s_connections; + +RefPtr DatabaseConnection::connection_for(int connection_id) +{ + if (s_connections.contains(connection_id)) + return *s_connections.get(connection_id).value(); + dbgln_if(SQLSERVER_DEBUG, "Invalid connection_id {}", connection_id); + return nullptr; +} + +static int s_next_connection_id = 0; + +DatabaseConnection::DatabaseConnection(String database_name, int client_id) + : Object() + , m_database_name(move(database_name)) + , m_connection_id(s_next_connection_id++) + , m_client_id(client_id) +{ + LexicalPath path(database_name); + if (path.title() != database_name) { + auto client_connection = ClientConnection::client_connection_for(m_client_id); + client_connection->async_connection_error(m_connection_id, (int)SQL::SQLErrorCode::InvalidDatabaseName, m_database_name); + return; + } + + dbgln_if(SQLSERVER_DEBUG, "DatabaseConnection {} initiating connection with database '{}'", connection_id(), m_database_name); + s_connections.set(m_connection_id, *this); + deferred_invoke([&](Object&) { + m_database = SQL::Database::construct(String::formatted("/home/anon/sql/{}.db", m_database_name)); + m_accept_statements = true; + auto client_connection = ClientConnection::client_connection_for(client_id); + if (client_connection) + client_connection->async_connected(m_connection_id); + else + warnln("Cannot notify client of database connection. Client disconnected"); + }); +} + +void DatabaseConnection::disconnect() +{ + dbgln_if(SQLSERVER_DEBUG, "DatabaseConnection::disconnect(connection_id {}, database '{}'", connection_id(), m_database_name); + m_accept_statements = false; + deferred_invoke([&](Object&) { + m_database = nullptr; + s_connections.remove(m_connection_id); + auto client_connection = ClientConnection::client_connection_for(client_id()); + if (client_connection) + client_connection->async_disconnected(m_connection_id); + else + warnln("Cannot notify client of database disconnection. Client disconnected"); + }); +} + +int DatabaseConnection::sql_statement(String const& sql) +{ + dbgln_if(SQLSERVER_DEBUG, "DatabaseConnection::sql_statement(connection_id {}, database '{}', sql '{}'", connection_id(), m_database_name, sql); + auto client_connection = ClientConnection::client_connection_for(client_id()); + if (!client_connection) { + warnln("Cannot notify client of database disconnection. Client disconnected"); + return -1; + } + if (!m_accept_statements) { + client_connection->async_execution_error(-1, (int)SQL::SQLErrorCode::DatabaseUnavailable, m_database_name); + return -1; + } + auto statement = SQLStatement::construct(*this, sql); + return statement->statement_id(); +} + +} diff --git a/Userland/Services/SQLServer/DatabaseConnection.h b/Userland/Services/SQLServer/DatabaseConnection.h new file mode 100644 index 0000000000..a7fc7c4551 --- /dev/null +++ b/Userland/Services/SQLServer/DatabaseConnection.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace SQLServer { + +class DatabaseConnection final : public Core::Object { + C_OBJECT(DatabaseConnection) + ~DatabaseConnection() override = default; + + static RefPtr connection_for(int connection_id); + int connection_id() const { return m_connection_id; } + int client_id() const { return m_client_id; } + RefPtr database() { return m_database; } + void disconnect(); + int sql_statement(String const& sql); + +private: + DatabaseConnection(String database_name, int client_id); + + RefPtr m_database { nullptr }; + String m_database_name; + int m_connection_id; + int m_client_id; + bool m_accept_statements { false }; +}; + +} diff --git a/Userland/Services/SQLServer/Forward.h b/Userland/Services/SQLServer/Forward.h new file mode 100644 index 0000000000..06ffa54c4b --- /dev/null +++ b/Userland/Services/SQLServer/Forward.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +namespace SQLServer { +class ClientConnection; +class DatabaseConnection; +class SQLStatement; +} diff --git a/Userland/Services/SQLServer/SQLClient.ipc b/Userland/Services/SQLServer/SQLClient.ipc new file mode 100644 index 0000000000..960ed8e907 --- /dev/null +++ b/Userland/Services/SQLServer/SQLClient.ipc @@ -0,0 +1,10 @@ +endpoint SQLClient +{ + connected(int connection_id) =| + connection_error(int connection_id, int code, String message) =| + execution_success(int statement_id, bool has_results, int created, int updated, int deleted) =| + next_result(int statement_id, Vector row) =| + results_exhausted(int statement_id, int total_rows) =| + execution_error(int statement_id, int code, String message) =| + disconnected(int connection_id) =| +} diff --git a/Userland/Services/SQLServer/SQLServer.ipc b/Userland/Services/SQLServer/SQLServer.ipc new file mode 100644 index 0000000000..351f4228d4 --- /dev/null +++ b/Userland/Services/SQLServer/SQLServer.ipc @@ -0,0 +1,7 @@ +endpoint SQLServer [magic=5432] +{ + connect(String name) => (int connection_id) + sql_statement(int connection_id, String statement) => (int statement_id) + statement_execute(int statement_id) =| + disconnect(int connection_id) =| +} diff --git a/Userland/Services/SQLServer/SQLStatement.cpp b/Userland/Services/SQLServer/SQLStatement.cpp new file mode 100644 index 0000000000..d8610d9480 --- /dev/null +++ b/Userland/Services/SQLServer/SQLStatement.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace SQLServer { + +static HashMap> s_statements; + +RefPtr SQLStatement::statement_for(int statement_id) +{ + if (s_statements.contains(statement_id)) + return *s_statements.get(statement_id).value(); + dbgln_if(SQLSERVER_DEBUG, "Invalid statement_id {}", statement_id); + return nullptr; +} + +static int s_next_statement_id = 0; + +SQLStatement::SQLStatement(DatabaseConnection& connection, String sql) + : Core::Object(&connection) + , m_statement_id(s_next_statement_id++) + , m_sql(move(sql)) +{ + dbgln_if(SQLSERVER_DEBUG, "SQLStatement({}, {})", connection.connection_id(), sql); + s_statements.set(m_statement_id, *this); +} + +void SQLStatement::report_error(SQL::SQLError error) +{ + dbgln_if(SQLSERVER_DEBUG, "SQLStatement::report_error(statement_id {}, error {}", statement_id(), error.to_string()); + auto client_connection = ClientConnection::client_connection_for(connection()->client_id()); + m_statement = nullptr; + m_result = nullptr; + remove_from_parent(); + s_statements.remove(statement_id()); + if (!client_connection) { + warnln("Cannot return execution error. Client disconnected"); + warnln("SQLStatement::report_error(statement_id {}, error {}", statement_id(), error.to_string()); + m_result = nullptr; + return; + } + client_connection->async_execution_error(statement_id(), (int)error.code, error.to_string()); + m_result = nullptr; +} + +void SQLStatement::execute() +{ + dbgln_if(SQLSERVER_DEBUG, "SQLStatement::execute(statement_id {}", statement_id()); + auto client_connection = ClientConnection::client_connection_for(connection()->client_id()); + if (!client_connection) { + warnln("Cannot yield next result. Client disconnected"); + return; + } + + deferred_invoke([&](Object&) { + auto maybe_error = parse(); + if (maybe_error.has_value()) { + report_error(maybe_error.value()); + return; + } + m_result = m_statement->execute(*connection()->database()); + if (m_result->error().code != SQL::SQLErrorCode::NoError) { + report_error(m_result->error()); + return; + } + auto client_connection = ClientConnection::client_connection_for(connection()->client_id()); + if (!client_connection) { + warnln("Cannot return statement execution results. Client disconnected"); + return; + } + client_connection->async_execution_success(statement_id(), m_result->has_results(), m_result->updated(), m_result->inserted(), m_result->deleted()); + if (m_result->has_results()) { + m_index = 0; + next(); + } + }); +} + +Optional SQLStatement::parse() +{ + auto parser = SQL::AST::Parser(SQL::AST::Lexer(m_sql)); + m_statement = parser.next_statement(); + if (parser.has_errors()) { + return SQL::SQLError { SQL::SQLErrorCode::SyntaxError, parser.errors()[0].to_string() }; + } + return {}; +} + +void SQLStatement::next() +{ + VERIFY(m_result->has_results()); + auto client_connection = ClientConnection::client_connection_for(connection()->client_id()); + if (!client_connection) { + warnln("Cannot yield next result. Client disconnected"); + return; + } + if (m_index < m_result->results().size()) { + auto& tuple = m_result->results()[m_index++]; + client_connection->async_next_result(statement_id(), tuple.to_string_vector()); + deferred_invoke([&](Object&) { + next(); + }); + } else { + client_connection->async_results_exhausted(statement_id(), (int)m_index); + } +} + +} diff --git a/Userland/Services/SQLServer/SQLStatement.h b/Userland/Services/SQLServer/SQLStatement.h new file mode 100644 index 0000000000..433a5f60b3 --- /dev/null +++ b/Userland/Services/SQLServer/SQLStatement.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021, Jan de Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace SQLServer { + +class SQLStatement final : public Core::Object { + C_OBJECT(SQLStatement) + ~SQLStatement() override = default; + + static RefPtr statement_for(int statement_id); + int statement_id() const { return m_statement_id; } + String const& sql() const { return m_sql; } + DatabaseConnection* connection() { return dynamic_cast(parent()); } + void execute(); + +private: + SQLStatement(DatabaseConnection&, String sql); + Optional parse(); + void next(); + void report_error(SQL::SQLError); + + int m_statement_id; + String m_sql; + size_t m_index { 0 }; + RefPtr m_statement { nullptr }; + RefPtr m_result { nullptr }; +}; + +} diff --git a/Userland/Services/SQLServer/main.cpp b/Userland/Services/SQLServer/main.cpp new file mode 100644 index 0000000000..ac7c4f6610 --- /dev/null +++ b/Userland/Services/SQLServer/main.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) +{ + if (pledge("stdio accept unix rpath wpath cpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + struct stat statbuf; + if (stat("/home/anon/sql", &statbuf) != 0) { + if (errno != ENOENT) { + perror("stat"); + return 1; + } + if (mkdir("/home/anon/sql", 0700) != 0) { + perror("mkdir"); + return 1; + } + } + + if (unveil("/home/anon/sql", "rwc") < 0) { + perror("unveil"); + return 1; + } + if (unveil(nullptr, nullptr) < 0) { + perror("unveil"); + return 1; + } + + Core::EventLoop event_loop; + auto server = Core::LocalServer::construct(); + bool ok = server->take_over_from_system_server(); + VERIFY(ok); + + server->on_ready_to_accept = [&] { + auto client_socket = server->accept(); + if (!client_socket) { + dbgln("SQLServer: accept failed."); + return; + } + static int s_next_client_id = 0; + int client_id = ++s_next_client_id; + IPC::new_client_connection(client_socket.release_nonnull(), client_id); + }; + + return event_loop.exec(); +}