| 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();
 
-   }
 
- }
 
 
  |