123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file Copyright.txt or https://cmake.org/licensing for details. */
- #include "cmServer.h"
- #include "cmAlgorithms.h"
- #include "cmConnection.h"
- #include "cmFileMonitor.h"
- #include "cmServerDictionary.h"
- #include "cmServerProtocol.h"
- #include "cmSystemTools.h"
- #include "cm_jsoncpp_reader.h"
- #include "cm_jsoncpp_writer.h"
- #include "cmake.h"
- #include "cmsys/FStream.hxx"
- #include <algorithm>
- #include <cassert>
- #include <cstdint>
- #include <iostream>
- #include <memory>
- #include <mutex>
- #include <utility>
- void on_signal(uv_signal_t* signal, int signum)
- {
- auto conn = static_cast<cmServerBase*>(signal->data);
- conn->OnSignal(signum);
- }
- static void on_walk_to_shutdown(uv_handle_t* handle, void* arg)
- {
- (void)arg;
- assert(uv_is_closing(handle));
- if (!uv_is_closing(handle)) {
- uv_close(handle, &cmEventBasedConnection::on_close);
- }
- }
- class cmServer::DebugInfo
- {
- public:
- DebugInfo()
- : StartTime(uv_hrtime())
- {
- }
- bool PrintStatistics = false;
- std::string OutputFile;
- uint64_t StartTime;
- };
- cmServer::cmServer(cmConnection* conn, bool supportExperimental)
- : cmServerBase(conn)
- , SupportExperimental(supportExperimental)
- {
- // Register supported protocols:
- this->RegisterProtocol(new cmServerProtocol1);
- }
- cmServer::~cmServer()
- {
- Close();
- for (cmServerProtocol* p : this->SupportedProtocols) {
- delete p;
- }
- }
- void cmServer::ProcessRequest(cmConnection* connection,
- const std::string& input)
- {
- Json::Reader reader;
- Json::Value value;
- if (!reader.parse(input, value)) {
- this->WriteParseError(connection, "Failed to parse JSON input.");
- return;
- }
- std::unique_ptr<DebugInfo> debug;
- Json::Value debugValue = value["debug"];
- if (!debugValue.isNull()) {
- debug = cm::make_unique<DebugInfo>();
- debug->OutputFile = debugValue["dumpToFile"].asString();
- debug->PrintStatistics = debugValue["showStats"].asBool();
- }
- const cmServerRequest request(this, connection, value[kTYPE_KEY].asString(),
- value[kCOOKIE_KEY].asString(), value);
- if (request.Type.empty()) {
- cmServerResponse response(request);
- response.SetError("No type given in request.");
- this->WriteResponse(connection, response, nullptr);
- return;
- }
- cmSystemTools::SetMessageCallback(reportMessage,
- const_cast<cmServerRequest*>(&request));
- if (this->Protocol) {
- this->Protocol->CMakeInstance()->SetProgressCallback(
- reportProgress, const_cast<cmServerRequest*>(&request));
- this->WriteResponse(connection, this->Protocol->Process(request),
- debug.get());
- } else {
- this->WriteResponse(connection, this->SetProtocolVersion(request),
- debug.get());
- }
- }
- void cmServer::RegisterProtocol(cmServerProtocol* protocol)
- {
- if (protocol->IsExperimental() && !this->SupportExperimental) {
- delete protocol;
- return;
- }
- auto version = protocol->ProtocolVersion();
- assert(version.first >= 0);
- assert(version.second >= 0);
- auto it = std::find_if(this->SupportedProtocols.begin(),
- this->SupportedProtocols.end(),
- [version](cmServerProtocol* p) {
- return p->ProtocolVersion() == version;
- });
- if (it == this->SupportedProtocols.end()) {
- this->SupportedProtocols.push_back(protocol);
- }
- }
- void cmServer::PrintHello(cmConnection* connection) const
- {
- Json::Value hello = Json::objectValue;
- hello[kTYPE_KEY] = "hello";
- Json::Value& protocolVersions = hello[kSUPPORTED_PROTOCOL_VERSIONS] =
- Json::arrayValue;
- for (auto const& proto : this->SupportedProtocols) {
- auto version = proto->ProtocolVersion();
- Json::Value tmp = Json::objectValue;
- tmp[kMAJOR_KEY] = version.first;
- tmp[kMINOR_KEY] = version.second;
- if (proto->IsExperimental()) {
- tmp[kIS_EXPERIMENTAL_KEY] = true;
- }
- protocolVersions.append(tmp);
- }
- this->WriteJsonObject(connection, hello, nullptr);
- }
- void cmServer::reportProgress(const char* msg, float progress, void* data)
- {
- const cmServerRequest* request = static_cast<const cmServerRequest*>(data);
- assert(request);
- if (progress < 0.0f || progress > 1.0f) {
- request->ReportMessage(msg, "");
- } else {
- request->ReportProgress(0, static_cast<int>(progress * 1000), 1000, msg);
- }
- }
- void cmServer::reportMessage(const char* msg, const char* title,
- bool& /* cancel */, void* data)
- {
- const cmServerRequest* request = static_cast<const cmServerRequest*>(data);
- assert(request);
- assert(msg);
- std::string titleString;
- if (title) {
- titleString = title;
- }
- request->ReportMessage(std::string(msg), titleString);
- }
- cmServerResponse cmServer::SetProtocolVersion(const cmServerRequest& request)
- {
- if (request.Type != kHANDSHAKE_TYPE) {
- return request.ReportError("Waiting for type \"" + kHANDSHAKE_TYPE +
- "\".");
- }
- Json::Value requestedProtocolVersion = request.Data[kPROTOCOL_VERSION_KEY];
- if (requestedProtocolVersion.isNull()) {
- return request.ReportError("\"" + kPROTOCOL_VERSION_KEY +
- "\" is required for \"" + kHANDSHAKE_TYPE +
- "\".");
- }
- if (!requestedProtocolVersion.isObject()) {
- return request.ReportError("\"" + kPROTOCOL_VERSION_KEY +
- "\" must be a JSON object.");
- }
- Json::Value majorValue = requestedProtocolVersion[kMAJOR_KEY];
- if (!majorValue.isInt()) {
- return request.ReportError("\"" + kMAJOR_KEY +
- "\" must be set and an integer.");
- }
- Json::Value minorValue = requestedProtocolVersion[kMINOR_KEY];
- if (!minorValue.isNull() && !minorValue.isInt()) {
- return request.ReportError("\"" + kMINOR_KEY +
- "\" must be unset or an integer.");
- }
- const int major = majorValue.asInt();
- const int minor = minorValue.isNull() ? -1 : minorValue.asInt();
- if (major < 0) {
- return request.ReportError("\"" + kMAJOR_KEY + "\" must be >= 0.");
- }
- if (!minorValue.isNull() && minor < 0) {
- return request.ReportError("\"" + kMINOR_KEY +
- "\" must be >= 0 when set.");
- }
- this->Protocol =
- this->FindMatchingProtocol(this->SupportedProtocols, major, minor);
- if (!this->Protocol) {
- return request.ReportError("Protocol version not supported.");
- }
- std::string errorMessage;
- if (!this->Protocol->Activate(this, request, &errorMessage)) {
- this->Protocol = nullptr;
- return request.ReportError("Failed to activate protocol version: " +
- errorMessage);
- }
- return request.Reply(Json::objectValue);
- }
- bool cmServer::Serve(std::string* errorMessage)
- {
- if (this->SupportedProtocols.empty()) {
- *errorMessage =
- "No protocol versions defined. Maybe you need --experimental?";
- return false;
- }
- assert(!this->Protocol);
- return cmServerBase::Serve(errorMessage);
- }
- cmFileMonitor* cmServer::FileMonitor() const
- {
- return fileMonitor.get();
- }
- void cmServer::WriteJsonObject(const Json::Value& jsonValue,
- const DebugInfo* debug) const
- {
- cm::shared_lock<cm::shared_mutex> lock(ConnectionsMutex);
- for (auto& connection : this->Connections) {
- WriteJsonObject(connection.get(), jsonValue, debug);
- }
- }
- void cmServer::WriteJsonObject(cmConnection* connection,
- const Json::Value& jsonValue,
- const DebugInfo* debug) const
- {
- Json::FastWriter writer;
- auto beforeJson = uv_hrtime();
- std::string result = writer.write(jsonValue);
- if (debug) {
- Json::Value copy = jsonValue;
- if (debug->PrintStatistics) {
- Json::Value stats = Json::objectValue;
- auto endTime = uv_hrtime();
- stats["jsonSerialization"] = double(endTime - beforeJson) / 1000000.0;
- stats["totalTime"] = double(endTime - debug->StartTime) / 1000000.0;
- stats["size"] = static_cast<int>(result.size());
- if (!debug->OutputFile.empty()) {
- stats["dumpFile"] = debug->OutputFile;
- }
- copy["zzzDebug"] = stats;
- result = writer.write(copy); // Update result to include debug info
- }
- if (!debug->OutputFile.empty()) {
- cmsys::ofstream myfile(debug->OutputFile.c_str());
- myfile << result;
- }
- }
- connection->WriteData(result);
- }
- cmServerProtocol* cmServer::FindMatchingProtocol(
- const std::vector<cmServerProtocol*>& protocols, int major, int minor)
- {
- cmServerProtocol* bestMatch = nullptr;
- for (auto protocol : protocols) {
- auto version = protocol->ProtocolVersion();
- if (major != version.first) {
- continue;
- }
- if (minor == version.second) {
- return protocol;
- }
- if (!bestMatch || bestMatch->ProtocolVersion().second < version.second) {
- bestMatch = protocol;
- }
- }
- return minor < 0 ? bestMatch : nullptr;
- }
- void cmServer::WriteProgress(const cmServerRequest& request, int min,
- int current, int max,
- const std::string& message) const
- {
- assert(min <= current && current <= max);
- assert(message.length() != 0);
- Json::Value obj = Json::objectValue;
- obj[kTYPE_KEY] = kPROGRESS_TYPE;
- obj[kREPLY_TO_KEY] = request.Type;
- obj[kCOOKIE_KEY] = request.Cookie;
- obj[kPROGRESS_MESSAGE_KEY] = message;
- obj[kPROGRESS_MINIMUM_KEY] = min;
- obj[kPROGRESS_MAXIMUM_KEY] = max;
- obj[kPROGRESS_CURRENT_KEY] = current;
- this->WriteJsonObject(request.Connection, obj, nullptr);
- }
- void cmServer::WriteMessage(const cmServerRequest& request,
- const std::string& message,
- const std::string& title) const
- {
- if (message.empty()) {
- return;
- }
- Json::Value obj = Json::objectValue;
- obj[kTYPE_KEY] = kMESSAGE_TYPE;
- obj[kREPLY_TO_KEY] = request.Type;
- obj[kCOOKIE_KEY] = request.Cookie;
- obj[kMESSAGE_KEY] = message;
- if (!title.empty()) {
- obj[kTITLE_KEY] = title;
- }
- WriteJsonObject(request.Connection, obj, nullptr);
- }
- void cmServer::WriteParseError(cmConnection* connection,
- const std::string& message) const
- {
- Json::Value obj = Json::objectValue;
- obj[kTYPE_KEY] = kERROR_TYPE;
- obj[kERROR_MESSAGE_KEY] = message;
- obj[kREPLY_TO_KEY] = "";
- obj[kCOOKIE_KEY] = "";
- this->WriteJsonObject(connection, obj, nullptr);
- }
- void cmServer::WriteSignal(const std::string& name,
- const Json::Value& data) const
- {
- assert(data.isObject());
- Json::Value obj = data;
- obj[kTYPE_KEY] = kSIGNAL_TYPE;
- obj[kREPLY_TO_KEY] = "";
- obj[kCOOKIE_KEY] = "";
- obj[kNAME_KEY] = name;
- WriteJsonObject(obj, nullptr);
- }
- void cmServer::WriteResponse(cmConnection* connection,
- const cmServerResponse& response,
- const DebugInfo* debug) const
- {
- assert(response.IsComplete());
- Json::Value obj = response.Data();
- obj[kCOOKIE_KEY] = response.Cookie;
- obj[kTYPE_KEY] = response.IsError() ? kERROR_TYPE : kREPLY_TYPE;
- obj[kREPLY_TO_KEY] = response.Type;
- if (response.IsError()) {
- obj[kERROR_MESSAGE_KEY] = response.ErrorMessage();
- }
- this->WriteJsonObject(connection, obj, debug);
- }
- void cmServer::OnConnected(cmConnection* connection)
- {
- PrintHello(connection);
- }
- void cmServer::OnServeStart()
- {
- cmServerBase::OnServeStart();
- fileMonitor = std::make_shared<cmFileMonitor>(GetLoop());
- }
- void cmServer::StartShutDown()
- {
- if (fileMonitor) {
- fileMonitor->StopMonitoring();
- fileMonitor.reset();
- }
- cmServerBase::StartShutDown();
- }
- static void __start_thread(void* arg)
- {
- auto server = static_cast<cmServerBase*>(arg);
- std::string error;
- bool success = server->Serve(&error);
- if (!success || error.empty() == false) {
- std::cerr << "Error during serve: " << error << std::endl;
- }
- }
- bool cmServerBase::StartServeThread()
- {
- ServeThreadRunning = true;
- uv_thread_create(&ServeThread, __start_thread, this);
- return true;
- }
- static void __shutdownThread(uv_async_t* arg)
- {
- auto server = static_cast<cmServerBase*>(arg->data);
- server->StartShutDown();
- }
- bool cmServerBase::Serve(std::string* errorMessage)
- {
- #ifndef NDEBUG
- uv_thread_t blank_thread_t = {};
- assert(uv_thread_equal(&blank_thread_t, &ServeThreadId));
- ServeThreadId = uv_thread_self();
- #endif
- errorMessage->clear();
- ShutdownSignal.init(Loop, __shutdownThread, this);
- SIGINTHandler.init(Loop, this);
- SIGHUPHandler.init(Loop, this);
- SIGINTHandler.start(&on_signal, SIGINT);
- SIGHUPHandler.start(&on_signal, SIGHUP);
- OnServeStart();
- {
- cm::shared_lock<cm::shared_mutex> lock(ConnectionsMutex);
- for (auto& connection : Connections) {
- if (!connection->OnServeStart(errorMessage)) {
- return false;
- }
- }
- }
- if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
- // It is important we don't ever let the event loop exit with open handles
- // at best this is a memory leak, but it can also introduce race conditions
- // which can hang the program.
- assert(false && "Event loop stopped in unclean state.");
- *errorMessage = "Internal Error: Event loop stopped in unclean state.";
- return false;
- }
- return true;
- }
- void cmServerBase::OnConnected(cmConnection*)
- {
- }
- void cmServerBase::OnServeStart()
- {
- }
- void cmServerBase::StartShutDown()
- {
- ShutdownSignal.reset();
- SIGINTHandler.reset();
- SIGHUPHandler.reset();
- {
- std::unique_lock<cm::shared_mutex> lock(ConnectionsMutex);
- for (auto& connection : Connections) {
- connection->OnConnectionShuttingDown();
- }
- Connections.clear();
- }
- uv_walk(&Loop, on_walk_to_shutdown, nullptr);
- }
- bool cmServerBase::OnSignal(int signum)
- {
- (void)signum;
- StartShutDown();
- return true;
- }
- cmServerBase::cmServerBase(cmConnection* connection)
- {
- auto err = uv_loop_init(&Loop);
- (void)err;
- Loop.data = this;
- assert(err == 0);
- AddNewConnection(connection);
- }
- void cmServerBase::Close()
- {
- if (Loop.data) {
- if (ServeThreadRunning) {
- this->ShutdownSignal.send();
- uv_thread_join(&ServeThread);
- }
- uv_loop_close(&Loop);
- Loop.data = nullptr;
- }
- }
- cmServerBase::~cmServerBase()
- {
- Close();
- }
- void cmServerBase::AddNewConnection(cmConnection* ownedConnection)
- {
- {
- std::unique_lock<cm::shared_mutex> lock(ConnectionsMutex);
- Connections.emplace_back(ownedConnection);
- }
- ownedConnection->SetServer(this);
- }
- uv_loop_t* cmServerBase::GetLoop()
- {
- return &Loop;
- }
- void cmServerBase::OnDisconnect(cmConnection* pConnection)
- {
- auto pred = [pConnection](const std::unique_ptr<cmConnection>& m) {
- return m.get() == pConnection;
- };
- {
- std::unique_lock<cm::shared_mutex> lock(ConnectionsMutex);
- Connections.erase(
- std::remove_if(Connections.begin(), Connections.end(), pred),
- Connections.end());
- }
- if (Connections.empty()) {
- this->ShutdownSignal.send();
- }
- }
|