diff --git a/CMakeLists.txt b/CMakeLists.txt index 49dff5f..a0a758a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,7 @@ else() message(STATUS "idasql: Fetching libxsql from GitHub...") FetchContent_Declare(libxsql GIT_REPOSITORY https://github.com/0xeb/libxsql.git - GIT_TAG v1.0.8 + GIT_TAG v1.0.10 GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(libxsql) diff --git a/src/cli/main.cpp b/src/cli/main.cpp index e7271fd..85a0ff2 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -707,7 +707,58 @@ static std::string build_cli_http_help_text() { return out.str(); } +// CLI --http server, on the shared libxsql thinclient (use_queue=true: queries +// run on this main thread via run_until_stopped, for Hex-Rays thread affinity). static int run_http_mode(idasql::Database& db, int port, const std::string& bind_addr, const std::string& auth_token) { + idasql::IDAHTTPServer server; + idasql::HTTPStatementExecutor exec = + [&db](const std::string& stmt, xsql::ScriptStatementResult& out) { + idasql::QueryResult r = db.query(stmt); + out.columns = r.columns; + out.rows.reserve(r.rows.size()); + for (const auto& row : r.rows) out.rows.push_back(row.values); + out.elapsed_ms = static_cast(r.elapsed_ms); + out.success = r.success; + out.error = r.error; + }; + + int actual_port = server.start(port, exec, bind_addr, /*use_queue=*/true, auth_token); + if (actual_port < 0) { + std::cerr << "Error: Failed to start HTTP server\n"; + return 1; + } + + g_http_stop_requested.store(false); + auto old_handler = std::signal(SIGINT, http_signal_handler); +#ifdef _WIN32 + auto old_break = std::signal(SIGBREAK, http_signal_handler); +#else + auto old_term = std::signal(SIGTERM, http_signal_handler); +#endif + server.set_interrupt_check([]() { return g_http_stop_requested.load(); }); + + std::cout << "IDASQL HTTP server: http://" << (bind_addr.empty() ? "127.0.0.1" : bind_addr) + << ":" << actual_port << "\n"; + std::cout << "Database: " << db.info() << "\n"; + std::cout << "Press Ctrl+C to stop.\n\n"; + std::cout.flush(); + + server.run_until_stopped(); + server.stop(); + + std::signal(SIGINT, old_handler); +#ifdef _WIN32 + std::signal(SIGBREAK, old_break); +#else + std::signal(SIGTERM, old_term); +#endif + std::cout << "\nHTTP server stopped.\n"; + return 0; +} + +// Superseded by the thinclient-based run_http_mode above; retained briefly and +// no longer called (cleanup follow-up). +static int run_http_mode_legacy(idasql::Database& db, int port, const std::string& bind_addr, const std::string& auth_token) { xsql::thinclient::server_config cfg; cfg.port = port; cfg.bind_address = bind_addr.empty() ? "127.0.0.1" : bind_addr; diff --git a/src/common/http_server.cpp b/src/common/http_server.cpp index cbb17ec..140de38 100644 --- a/src/common/http_server.cpp +++ b/src/common/http_server.cpp @@ -94,6 +94,9 @@ int IDAHTTPServer::start(int port, HTTPStatementExecutor executor, bind_addr_ = bind_addr.empty() ? "127.0.0.1" : bind_addr; auto config = make_idasql_config(port, bind_addr_, use_queue); config.statement_executor = std::move(executor); + // Non-queue servers (REPL/plugin background) run the executor on the HTTP + // worker; serialize so the non-concurrency-safe IDA DB handle is safe. + config.serialize_requests = !use_queue; if (!auth_token.empty()) config.auth_token = auth_token; impl_ = std::make_unique(config); return impl_->start(); diff --git a/src/plugin/main.cpp b/src/plugin/main.cpp index 855ad0e..310f42a 100644 --- a/src/plugin/main.cpp +++ b/src/plugin/main.cpp @@ -358,14 +358,26 @@ struct idasql_plugmod_t : public plugmod_t } } - // SQL executor that uses execute_sync for thread safety and returns JSON - idasql::HTTPQueryCallback sql_cb = [this](const std::string& sql) -> std::string { - xsql::ScriptResult result = run_query_script_sync(sql); - return xsql::script_result_to_json(result); - }; + // Single-statement executor: each statement runs on IDA's main thread + // via execute_sync (Hex-Rays thread affinity). The thinclient owns + // multi-statement orchestration, options, and formatting; non-queue + + // serialize_requests keeps requests one-at-a-time. + idasql::HTTPStatementExecutor sql_exec = + [this](const std::string& stmt, xsql::ScriptStatementResult& out) { + query_request_t req(engine_.get(), stmt); + execute_sync(req, MFF_WRITE); + const idasql::QueryResult& r = req.result; + out.columns = r.columns; + out.rows.reserve(r.rows.size()); + for (const auto& row : r.rows) out.rows.push_back(row.values); + out.elapsed_ms = static_cast(r.elapsed_ms); + out.success = r.success; + out.error = r.error; + }; - // Start HTTP server, no queue (plugin mode) - int port = http_server_.start(req_port, sql_cb, addr); + // Start HTTP server, no queue (plugin mode; execute_sync marshals each + // statement to the main thread). + int port = http_server_.start(req_port, sql_exec, addr, /*use_queue=*/false); if (port <= 0) { return "Error: Failed to start HTTP server"; }