Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ Options:
--ipc-path <path> IPC path (default: /tmp/coretrace_ipc).
--serve-host <host> HTTP server host when --ipc=serve.
--serve-port <port> HTTP server port when --ipc=serve.
--shutdown-token <tok> Token required for POST /shutdown (server mode).
--shutdown-timeout-ms <ms> Graceful shutdown timeout in ms (0 = wait indefinitely).
--async Enables asynchronous execution.

Examples:
Expand All @@ -79,7 +81,7 @@ Description:
Start the HTTP server:

```bash
./ctrace --ipc serve --serve-host 127.0.0.1 --serve-port 8080
./ctrace --ipc serve --serve-host 127.0.0.1 --serve-port 8080 --shutdown-token mytoken
```

Send a request:
Expand Down Expand Up @@ -114,28 +116,45 @@ Response notes:
- `result.outputs` groups tool output by tool name.
- Each output entry has `stream` and `message`. If a tool emits JSON, `message` is returned as a JSON object.

Shutdown the server (HTTP request):

```bash
curl -i -X POST http://127.0.0.1:8080/shutdown \
-H "Authorization: Bearer mytoken"
```

Alternative header:

```bash
curl -i -X POST http://127.0.0.1:8080/shutdown \
-H "X-Admin-Token: mytoken"
```

The server responds with `202 Accepted` and stops accepting new requests while allowing in-flight requests to finish
(up to `--shutdown-timeout-ms` if configured).

### Mangle/Demangle API

```c++
bool hasMangled = ctrace_tools::isMangled(entry_points);
bool hasMangled = ctrace_tools::mangle::isMangled(entry_points);

std::cout << "Is mangled : " << hasMangled << std::endl;
if (hasMangled)
std::cout << abi::__cxa_demangle(entry_points.c_str(), 0, 0, &status) << std::endl;

std::vector<std::string> params1 = {};
std::string mangled1 = ctrace_tools::mangleFunction("", "single_compute()", params1);
std::string mangled1 = ctrace_tools::mangle::mangleFunction("", "single_compute()", params1);
std::cout << "Mangled single_compute(): " << mangled1 << "\n";
std::cout << abi::__cxa_demangle(mangled1.c_str(), 0, 0, &status) << std::endl;

// Example 2 : with namespace
std::vector<std::string> params2 = {"std::string", "int"};
std::string mangled2 = ctrace_tools::mangleFunction("math", "compute", params2);
std::string mangled2 = ctrace_tools::mangle::mangleFunction("math", "compute", params2);
std::cout << "Mangled math::compute(std::string, int): " << mangled2 << "\n";

// Example 3 : without parameters with namespace
std::vector<std::string> params3;
std::string mangled3 = ctrace_tools::mangleFunction("utils", "init", params3);
std::string mangled3 = ctrace_tools::mangle::mangleFunction("utils", "init", params3);
std::cout << "Mangled utils::init(): " << mangled3 << "\n";

```
Expand Down
16 changes: 16 additions & 0 deletions include/Config/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ static void printHelp(void)
--ipc <method> Specifies the IPC method to use (e.g., fifo, socket).
--ipc-path <path> Specifies the IPC path (default: /tmp/coretrace_ipc).
--async Enables asynchronous execution.
--shutdown-token <tok> Token required for POST /shutdown (server mode).
--shutdown-timeout-ms <ms> Graceful shutdown timeout in ms (0 = wait indefinitely).

Examples:
ctrace --input main.cpp,util.cpp --static --invoke=cppcheck,flawfinder
Expand Down Expand Up @@ -82,6 +84,8 @@ namespace ctrace
std::string ipcPath = "/tmp/coretrace_ipc"; ///< Path for IPC communication.
std::string serverHost = "127.0.0.1"; ///< Host for server IPC (if applicable).
int serverPort = 8080; ///< Port for server IPC (if applicable).
std::string shutdownToken; ///< Token required for POST /shutdown.
int shutdownTimeoutMs = 0; ///< Shutdown timeout in milliseconds (0 = wait indefinitely).

std::vector<std::string> specificTools; ///< List of specific tools to invoke.

Expand Down Expand Up @@ -228,6 +232,18 @@ namespace ctrace
config.global.serverPort = std::stoi(value);
std::cout << "[DEBUG] Server port set to " << config.global.serverPort << std::endl;
};
commands["--shutdown-token"] = [this](const std::string& value)
{
config.global.shutdownToken = value;
};
commands["--shutdown-timeout-ms"] = [this](const std::string& value)
{
config.global.shutdownTimeoutMs = std::stoi(value);
if (config.global.shutdownTimeoutMs < 0)
{
config.global.shutdownTimeoutMs = 0;
}
};
// commands["--output"] = [this](const std::string& value) {
// if (!config.files.empty()) config.files.back().output_file = value;
// };
Expand Down
213 changes: 210 additions & 3 deletions include/Process/Ipc/HttpServer.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// server.cpp
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <initializer_list>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
Expand Down Expand Up @@ -494,16 +498,26 @@ class ApiHandler
class HttpServer
{
public:
HttpServer(ApiHandler& apiHandler, ILogger& logger)
: apiHandler_(apiHandler), logger_(logger)
HttpServer(ApiHandler& apiHandler, ILogger& logger, const ctrace::GlobalConfig& config)
: apiHandler_(apiHandler),
logger_(logger),
shutdown_token_(config.shutdownToken),
shutdown_timeout_(std::chrono::milliseconds(config.shutdownTimeoutMs))
{}

void run(const std::string& host, int port)
{
// CORS
server_.Options("/api", [this](const httplib::Request&, httplib::Response& res) {
set_cors(res);
res.status = 200;
if (is_shutting_down())
{
res.status = 503;
}
else
{
res.status = 200;
}
});

// Endpoint principal
Expand All @@ -512,14 +526,61 @@ class HttpServer
handle_post_api(req, res);
});

// Shutdown endpoint
server_.Options("/shutdown", [this](const httplib::Request&, httplib::Response& res) {
set_cors(res);
if (is_shutting_down())
{
res.status = 503;
}
else
{
res.status = 200;
}
});

server_.Post("/shutdown", [this](const httplib::Request& req, httplib::Response& res) {
set_cors(res);
handle_post_shutdown(req, res);
});

logger_.info("[SERVER] Listening on http://" + host + ":" + std::to_string(port));
server_.listen(host.c_str(), port);
finalize_shutdown();
}

private:
struct InFlightGuard
{
explicit InFlightGuard(HttpServer& server) : server_(server)
{
server_.begin_request();
}

~InFlightGuard()
{
server_.end_request();
}

HttpServer& server_;
};

httplib::Server server_;
ApiHandler& apiHandler_;
ILogger& logger_;
std::atomic<bool> shutting_down_{false};
std::atomic<bool> shutdown_requested_{false};
std::atomic<int> in_flight_{0};
std::mutex shutdown_mutex_;
std::condition_variable shutdown_cv_;
std::thread shutdown_thread_;
std::string shutdown_token_;
std::chrono::milliseconds shutdown_timeout_{0};

bool is_shutting_down() const
{
return shutting_down_.load(std::memory_order_acquire);
}

static void set_cors(httplib::Response& res)
{
Expand All @@ -528,8 +589,116 @@ class HttpServer
res.set_header("Access-Control-Allow-Headers", "Content-Type");
}

void begin_request()
{
in_flight_.fetch_add(1, std::memory_order_relaxed);
}

void end_request()
{
const int remaining = in_flight_.fetch_sub(1, std::memory_order_acq_rel) - 1;
if (remaining == 0)
{
shutdown_cv_.notify_all();
}
}

static void flush_logs()
{
std::cout << std::flush;
std::cerr << std::flush;
}

bool is_authorized_shutdown(const httplib::Request& req) const
{
if (shutdown_token_.empty())
{
return false;
}

const std::string bearer = req.get_header_value("Authorization");
if (!bearer.empty())
{
const std::string prefix = "Bearer ";
if (bearer.rfind(prefix, 0) == 0)
{
return bearer.substr(prefix.size()) == shutdown_token_;
}
return bearer == shutdown_token_;
}

const std::string admin_token = req.get_header_value("X-Admin-Token");
return !admin_token.empty() && admin_token == shutdown_token_;
}

void initiate_shutdown()
{
bool expected = false;
if (!shutdown_requested_.compare_exchange_strong(expected, true))
{
return;
}

shutting_down_.store(true, std::memory_order_release);

shutdown_thread_ = std::thread([this]() {
logger_.info("[SERVER] Shutdown requested. Stopping listener...");
server_.stop();
wait_for_inflight_or_timeout();
});
}

void wait_for_inflight_or_timeout()
{
std::unique_lock<std::mutex> lock(shutdown_mutex_);
const auto done = [this]() {
return in_flight_.load(std::memory_order_acquire) == 0;
};

if (shutdown_timeout_.count() > 0)
{
if (!shutdown_cv_.wait_for(lock, shutdown_timeout_, done))
{
logger_.error("[SERVER] Shutdown timeout exceeded. Forcing exit.");
}
}
else
{
shutdown_cv_.wait(lock, done);
}
}

void finalize_shutdown()
{
if (shutdown_thread_.joinable())
{
shutdown_thread_.join();
}
if (shutdown_requested_.load(std::memory_order_acquire))
{
logger_.info("[SERVER] Shutdown complete.");
}
flush_logs();
}

void handle_post_api(const httplib::Request& req, httplib::Response& res)
{
if (is_shutting_down())
{
json err;
err["proto"] = "coretrace-1.0";
err["type"] = "response";
err["status"] = "error";
err["error"] = {
{"code", "ServerShuttingDown"},
{"message", "Server is shutting down."}
};
res.status = 503;
res.set_content(err.dump(), "application/json");
return;
}

InFlightGuard guard(*this);
try {
json request = json::parse(req.body);
json response = apiHandler_.handle_request(request);
Expand All @@ -552,4 +721,42 @@ class HttpServer
res.set_content(err.dump(), "application/json");
}
}

void handle_post_shutdown(const httplib::Request& req, httplib::Response& res)
{
if (!is_authorized_shutdown(req))
{
json err;
err["status"] = "error";
err["error"] = {
{"code", "Unauthorized"},
{"message", shutdown_token_.empty()
? "Shutdown token not configured."
: "Invalid shutdown token."}
};
res.status = 403;
res.set_content(err.dump(), "application/json");
return;
}

if (shutdown_requested_.load(std::memory_order_acquire))
{
json ok;
ok["status"] = "accepted";
ok["message"] = "Shutdown already in progress.";
ok["timeout_ms"] = shutdown_timeout_.count();
res.status = 202;
res.set_content(ok.dump(), "application/json");
return;
}

json ok;
ok["status"] = "accepted";
ok["message"] = "Shutdown initiated.";
ok["timeout_ms"] = shutdown_timeout_.count();
res.status = 202;
res.set_content(ok.dump(), "application/json");

initiate_shutdown();
}
};
4 changes: 2 additions & 2 deletions include/Process/Tools/AnalysisTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ class EntryPoint
EntryPoint(const std::string& entryPointName, const std::vector<std::string>& paramTypes)
: name(entryPointName), paramTypes(paramTypes)
{
m_isMangled = ctrace_tools::isMangled(name);
m_isMangled = ctrace_tools::mangle::isMangled(name);

if (m_isMangled)
{
mangledName = name;
}
else
{
mangledName = ctrace_tools::mangleFunction("", name, paramTypes);
mangledName = ctrace_tools::mangle::mangleFunction("", name, paramTypes);
}
}
~EntryPoint() = default;
Expand Down
2 changes: 1 addition & 1 deletion include/ctrace_tools/mangle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <cxxabi.h>
#include <memory>

namespace ctrace_tools
namespace ctrace_tools::mangle
{
/**
* @brief Concept to define types that can be converted to `std::string_view`.
Expand Down
Loading
Loading