/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmServerConnection.h" #include "cmServerDictionary.h" #include "cmFileMonitor.h" #include "cmServer.h" #include namespace { struct write_req_t { uv_write_t req; uv_buf_t buf; }; void on_alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { (void)(handle); char* rawBuffer = new char[suggested_size]; *buf = uv_buf_init(rawBuffer, static_cast(suggested_size)); } void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { auto conn = reinterpret_cast(stream->data); if (nread >= 0) { conn->ReadData(std::string(buf->base, buf->base + nread)); } else { conn->TriggerShutdown(); } delete[](buf->base); } void on_write(uv_write_t* req, int status) { (void)(status); auto conn = reinterpret_cast(req->data); // Free req and buffer write_req_t* wr = reinterpret_cast(req); delete[](wr->buf.base); delete wr; conn->ProcessNextRequest(); } void on_new_connection(uv_stream_t* stream, int status) { (void)(status); auto conn = reinterpret_cast(stream->data); conn->Connect(stream); } void on_signal(uv_signal_t* signal, int signum) { auto conn = reinterpret_cast(signal->data); (void)(signum); conn->TriggerShutdown(); } void on_signal_close(uv_handle_t* handle) { delete reinterpret_cast(handle); } void on_pipe_close(uv_handle_t* handle) { delete reinterpret_cast(handle); } void on_tty_close(uv_handle_t* handle) { delete reinterpret_cast(handle); } } // namespace class LoopGuard { public: LoopGuard(cmServerConnection* connection) : Connection(connection) { this->Connection->mLoop = uv_default_loop(); if (!this->Connection->mLoop) { return; } this->Connection->mFileMonitor = new cmFileMonitor(this->Connection->mLoop); } ~LoopGuard() { if (!this->Connection->mLoop) { return; } if (this->Connection->mFileMonitor) { delete this->Connection->mFileMonitor; } uv_loop_close(this->Connection->mLoop); this->Connection->mLoop = nullptr; } private: cmServerConnection* Connection; }; cmServerConnection::cmServerConnection() { } cmServerConnection::~cmServerConnection() { } void cmServerConnection::SetServer(cmServer* s) { this->Server = s; } bool cmServerConnection::ProcessEvents(std::string* errorMessage) { assert(this->Server); errorMessage->clear(); this->RawReadBuffer.clear(); this->RequestBuffer.clear(); LoopGuard guard(this); (void)(guard); if (!this->mLoop) { *errorMessage = "Internal Error: Failed to create event loop."; return false; } this->SIGINTHandler = new uv_signal_t; uv_signal_init(this->mLoop, this->SIGINTHandler); this->SIGINTHandler->data = static_cast(this); uv_signal_start(this->SIGINTHandler, &on_signal, SIGINT); this->SIGHUPHandler = new uv_signal_t; uv_signal_init(this->mLoop, this->SIGHUPHandler); this->SIGHUPHandler->data = static_cast(this); uv_signal_start(this->SIGHUPHandler, &on_signal, SIGHUP); if (!DoSetup(errorMessage)) { return false; } if (uv_run(this->mLoop, UV_RUN_DEFAULT) != 0) { *errorMessage = "Internal Error: Event loop stopped in unclean state."; return false; } // These need to be cleaned up by now: assert(!this->ReadStream); assert(!this->WriteStream); this->RawReadBuffer.clear(); this->RequestBuffer.clear(); return true; } void cmServerConnection::ReadData(const std::string& data) { this->RawReadBuffer += data; for (;;) { auto needle = this->RawReadBuffer.find('\n'); if (needle == std::string::npos) { return; } std::string line = this->RawReadBuffer.substr(0, needle); const auto ls = line.size(); if (ls > 1 && line.at(ls - 1) == '\r') { line.erase(ls - 1, 1); } this->RawReadBuffer.erase(this->RawReadBuffer.begin(), this->RawReadBuffer.begin() + static_cast(needle) + 1); if (line == kSTART_MAGIC) { this->RequestBuffer.clear(); continue; } if (line == kEND_MAGIC) { this->Server->QueueRequest(this->RequestBuffer); this->RequestBuffer.clear(); } else { this->RequestBuffer += line; this->RequestBuffer += "\n"; } } } void cmServerConnection::TriggerShutdown() { this->FileMonitor()->StopMonitoring(); uv_signal_stop(this->SIGINTHandler); uv_signal_stop(this->SIGHUPHandler); uv_close(reinterpret_cast(this->SIGINTHandler), &on_signal_close); // delete handle uv_close(reinterpret_cast(this->SIGHUPHandler), &on_signal_close); // delete handle this->SIGINTHandler = nullptr; this->SIGHUPHandler = nullptr; this->TearDown(); } void cmServerConnection::WriteData(const std::string& data) { assert(this->WriteStream); auto ds = data.size(); write_req_t* req = new write_req_t; req->req.data = this; req->buf = uv_buf_init(new char[ds], static_cast(ds)); memcpy(req->buf.base, data.c_str(), ds); uv_write(reinterpret_cast(req), static_cast(this->WriteStream), &req->buf, 1, on_write); } void cmServerConnection::ProcessNextRequest() { Server->PopOne(); } void cmServerConnection::SendGreetings() { Server->PrintHello(); } cmServerStdIoConnection::cmServerStdIoConnection() { this->Input.tty = nullptr; this->Output.tty = nullptr; } bool cmServerStdIoConnection::DoSetup(std::string* errorMessage) { (void)(errorMessage); if (uv_guess_handle(1) == UV_TTY) { usesTty = true; this->Input.tty = new uv_tty_t; uv_tty_init(this->Loop(), this->Input.tty, 0, 1); uv_tty_set_mode(this->Input.tty, UV_TTY_MODE_NORMAL); Input.tty->data = this; this->ReadStream = reinterpret_cast(this->Input.tty); this->Output.tty = new uv_tty_t; uv_tty_init(this->Loop(), this->Output.tty, 1, 0); uv_tty_set_mode(this->Output.tty, UV_TTY_MODE_NORMAL); Output.tty->data = this; this->WriteStream = reinterpret_cast(this->Output.tty); } else { usesTty = false; this->Input.pipe = new uv_pipe_t; uv_pipe_init(this->Loop(), this->Input.pipe, 0); uv_pipe_open(this->Input.pipe, 0); Input.pipe->data = this; this->ReadStream = reinterpret_cast(this->Input.pipe); this->Output.pipe = new uv_pipe_t; uv_pipe_init(this->Loop(), this->Output.pipe, 0); uv_pipe_open(this->Output.pipe, 1); Output.pipe->data = this; this->WriteStream = reinterpret_cast(this->Output.pipe); } SendGreetings(); uv_read_start(this->ReadStream, on_alloc_buffer, on_read); return true; } void cmServerStdIoConnection::TearDown() { if (usesTty) { uv_close(reinterpret_cast(this->Input.tty), &on_tty_close); uv_close(reinterpret_cast(this->Output.tty), &on_tty_close); this->Input.tty = nullptr; this->Output.tty = nullptr; } else { uv_close(reinterpret_cast(this->Input.pipe), &on_pipe_close); uv_close(reinterpret_cast(this->Output.pipe), &on_pipe_close); this->Input.pipe = nullptr; this->Input.pipe = nullptr; } this->ReadStream = nullptr; this->WriteStream = nullptr; } cmServerPipeConnection::cmServerPipeConnection(const std::string& name) : PipeName(name) { } bool cmServerPipeConnection::DoSetup(std::string* errorMessage) { this->ServerPipe = new uv_pipe_t; uv_pipe_init(this->Loop(), this->ServerPipe, 0); this->ServerPipe->data = this; int r; if ((r = uv_pipe_bind(this->ServerPipe, this->PipeName.c_str())) != 0) { *errorMessage = std::string("Internal Error with ") + this->PipeName + ": " + uv_err_name(r); return false; } auto serverStream = reinterpret_cast(&this->ServerPipe); serverStream->data = this; if ((r = uv_listen(serverStream, 1, on_new_connection)) != 0) { *errorMessage = std::string("Internal Error with ") + this->PipeName + ": " + uv_err_name(r); return false; } return true; } void cmServerPipeConnection::TearDown() { if (this->ClientPipe) { uv_close(reinterpret_cast(this->ClientPipe), &on_pipe_close); this->WriteStream->data = nullptr; } uv_close(reinterpret_cast(&this->ServerPipe), &on_pipe_close); this->ClientPipe = nullptr; this->ServerPipe = nullptr; this->WriteStream = nullptr; this->ReadStream = nullptr; } void cmServerPipeConnection::Connect(uv_stream_t* server) { if (this->ClientPipe) { // Accept and close all pipes but the first: uv_pipe_t* rejectPipe = new uv_pipe_t; uv_pipe_init(this->Loop(), rejectPipe, 0); auto rejecter = reinterpret_cast(rejectPipe); uv_accept(server, rejecter); uv_close(reinterpret_cast(rejecter), &on_pipe_close); return; } this->ClientPipe = new uv_pipe_t; uv_pipe_init(this->Loop(), this->ClientPipe, 0); this->ClientPipe->data = this; auto client = reinterpret_cast(&this->ClientPipe); if (uv_accept(server, client) != 0) { uv_close(reinterpret_cast(client), nullptr); return; } this->ReadStream = client; this->WriteStream = client; uv_read_start(this->ReadStream, on_alloc_buffer, on_read); this->SendGreetings(); }