openMSX
CliConnection.cc
Go to the documentation of this file.
1// TODO:
2// - To avoid any possible conflicts, anything called from "run" should be
3// locked.
4// - Maybe document for each method whether it is called from the listener
5// thread or from the main thread?
6// - Unsubscribe at CliComm after stream is closed.
7
8#include "CliConnection.hh"
9#include "EventDistributor.hh"
10#include "Event.hh"
11#include "CommandController.hh"
12#include "CommandException.hh"
13#include "TclObject.hh"
14#include "TemporaryString.hh"
15#include "XMLEscape.hh"
16#include "cstdiop.hh"
17#include "ranges.hh"
18#include "unistdp.hh"
19#include <array>
20#include <cassert>
21#include <iostream>
22
23#ifdef _WIN32
26#endif
27
28namespace openmsx {
29
30// class CliConnection
31
33 EventDistributor& eventDistributor_)
34 : parser([this](const std::string& cmd) { execute(cmd); })
35 , commandController(commandController_)
36 , eventDistributor(eventDistributor_)
37{
38 ranges::fill(updateEnabled, false);
39
40 eventDistributor.registerEventListener(EventType::CLICOMMAND, *this);
41}
42
47
48void CliConnection::log(CliComm::LogLevel level, std::string_view message, float fraction) noexcept
49{
50 auto levelStr = CliComm::getLevelStrings();
51 std::string fullMessage{message};
52 if (level == CliComm::PROGRESS && fraction >= 0.0f) {
53 strAppend(fullMessage, "... ", int(100.0f * fraction), '%');
54 }
55 output(tmpStrCat("<log level=\"", levelStr[level], "\">",
56 XMLEscape(fullMessage), "</log>\n"));
57}
58
59void CliConnection::update(CliComm::UpdateType type, std::string_view machine,
60 std::string_view name, std::string_view value) noexcept
61{
62 if (!getUpdateEnable(type)) return;
63
64 auto updateStr = CliComm::getUpdateStrings();
65 auto tmp = strCat("<update type=\"", updateStr[type], '\"');
66 if (!machine.empty()) {
67 strAppend(tmp, " machine=\"", machine, '\"');
68 }
69 if (!name.empty()) {
70 strAppend(tmp, " name=\"", XMLEscape(name), '\"');
71 }
72 strAppend(tmp, '>', XMLEscape(value), "</update>\n");
73
74 output(tmp);
75}
76
78{
79 output("<openmsx-output>\n");
80}
81
83{
84 thread = std::thread([this]() { run(); });
85}
86
88{
89 output("</openmsx-output>\n");
90 close();
91
92 poller.abort();
93 // Thread might not be running if start() was never called.
94 if (thread.joinable()) {
95 thread.join();
96 }
97}
98
99void CliConnection::execute(const std::string& command)
100{
101 eventDistributor.distributeEvent(CliCommandEvent(command, this));
102}
103
104static TemporaryString reply(std::string_view message, bool status)
105{
106 return tmpStrCat("<reply result=\"", (status ? "ok" : "nok"), "\">",
107 XMLEscape(message), "</reply>\n");
108}
109
110int CliConnection::signalEvent(const Event& event)
111{
112 assert(getType(event) == EventType::CLICOMMAND);
113 const auto& commandEvent = get_event<CliCommandEvent>(event);
114 if (commandEvent.getId() == this) {
115 try {
116 auto result = commandController.executeCommand(
117 commandEvent.getCommand(), this).getString();
118 output(reply(result, true));
119 } catch (CommandException& e) {
120 std::string result = std::move(e).getMessage() + '\n';
121 output(reply(result, false));
122 }
123 }
124 return 0;
125}
126
127
128// class StdioConnection
129
130static constexpr int BUF_SIZE = 4096;
132 EventDistributor& eventDistributor_)
133 : CliConnection(commandController_, eventDistributor_)
134{
135 startOutput();
136}
137
142
143void StdioConnection::run()
144{
145 // runs in helper thread
146 while (true) {
147#ifdef _WIN32
148 if (poller.aborted()) break;
149#else
150 if (poller.poll(STDIN_FILENO)) break;
151#endif
152 std::array<char, BUF_SIZE> buf;
153 auto n = read(STDIN_FILENO, buf.data(), sizeof(buf));
154 if (n > 0) {
155 parser.parse(subspan(buf, 0, n));
156 } else if (n < 0) {
157 break;
158 }
159 }
160}
161
162void StdioConnection::output(std::string_view message)
163{
164 std::cout << message << std::flush;
165}
166
167void StdioConnection::close()
168{
169 // don't close stdin/out/err
170}
171
172
173#ifdef _WIN32
174// class PipeConnection
175
176// INVALID_HANDLE_VALUE is #defined as (HANDLE)(-1)
177// but that gives a old-style-cast warning
178static const HANDLE OPENMSX_INVALID_HANDLE_VALUE = reinterpret_cast<HANDLE>(-1);
179
180PipeConnection::PipeConnection(CommandController& commandController_,
181 EventDistributor& eventDistributor_,
182 std::string_view name)
183 : CliConnection(commandController_, eventDistributor_)
184{
185 auto pipeName = strCat("\\\\.\\pipe\\", name);
186 pipeHandle = CreateFileA(pipeName.c_str(), GENERIC_READ, 0, nullptr,
187 OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
188 if (pipeHandle == OPENMSX_INVALID_HANDLE_VALUE) {
189 throw FatalError("Error reopening pipefile '", pipeName, "': error ",
190 unsigned(GetLastError()));
191 }
192
193 shutdownEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
194 if (!shutdownEvent) {
195 throw FatalError("Error creating shutdown event: ", GetLastError());
196 }
197
198 startOutput();
199}
200
201PipeConnection::~PipeConnection()
202{
203 end();
204
205 assert(pipeHandle == OPENMSX_INVALID_HANDLE_VALUE);
206 CloseHandle(shutdownEvent);
207}
208
209static void InitOverlapped(LPOVERLAPPED overlapped)
210{
211 ZeroMemory(overlapped, sizeof(*overlapped));
212 overlapped->hEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
213 if (!overlapped->hEvent) {
214 throw FatalError("Error creating overlapped event: ", GetLastError());
215 }
216}
217
218static void ClearOverlapped(LPOVERLAPPED overlapped)
219{
220 if (overlapped->hEvent) {
221 CloseHandle(overlapped->hEvent);
222 overlapped->hEvent = nullptr;
223 }
224}
225
226void PipeConnection::run()
227{
228 // runs in helper thread
229 OVERLAPPED overlapped;
230 InitOverlapped(&overlapped);
231 HANDLE waitHandles[2] = { shutdownEvent, overlapped.hEvent };
232
233 while (pipeHandle != OPENMSX_INVALID_HANDLE_VALUE) {
234 char buf[BUF_SIZE];
235 if (!ReadFile(pipeHandle, buf, BUF_SIZE, nullptr, &overlapped) &&
236 GetLastError() != ERROR_IO_PENDING) {
237 break; // Pipe broke
238 }
239 DWORD wait = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
240 if (wait == WAIT_OBJECT_0 + 1) {
241 DWORD bytesRead;
242 if (!GetOverlappedResult(pipeHandle, &overlapped, &bytesRead, TRUE)) {
243 break; // Pipe broke
244 }
245 parser.parse(std::span{buf, bytesRead});
246 } else if (wait == WAIT_OBJECT_0) {
247 break; // Shutdown
248 } else {
249 throw FatalError(
250 "WaitForMultipleObjects returned unexpectedly: ", wait);
251 }
252 }
253
254 ClearOverlapped(&overlapped);
255
256 // We own the pipe handle, so close it here
257 CloseHandle(pipeHandle);
258 pipeHandle = OPENMSX_INVALID_HANDLE_VALUE;
259}
260
261void PipeConnection::output(std::string_view message)
262{
263 if (pipeHandle != OPENMSX_INVALID_HANDLE_VALUE) {
264 std::cout << message << std::flush;
265 }
266}
267
268void PipeConnection::close()
269{
270 SetEvent(shutdownEvent);
271}
272#endif // _WIN32
273
274
275// class SocketConnection
276
278 EventDistributor& eventDistributor_,
279 SOCKET sd_)
280 : CliConnection(commandController_, eventDistributor_)
281 , sd(sd_)
282{
283}
284
289
290void SocketConnection::run()
291{
292 // runs in helper thread
293#ifdef _WIN32
294 bool ok;
295 {
296 std::lock_guard<std::mutex> lock(sdMutex);
297 // Authenticate and authorize the caller
298 SocketStreamWrapper stream(sd);
299 SspiNegotiateServer server(stream);
300 ok = server.Authenticate() && server.Authorize();
301 }
302 if (!ok) {
303 closeSocket();
304 return;
305 }
306#endif
307 // Start output element
308 established = true; // TODO needs locking?
309 startOutput();
310
311 // TODO is locking correct?
312 // No need to lock in this thread because we don't write to 'sd'
313 // and 'sd' only gets written to in this thread.
314 while (true) {
315 if (sd == OPENMSX_INVALID_SOCKET) return;
316#ifndef _WIN32
317 if (poller.poll(sd)) {
318 break;
319 }
320#endif
321 std::array<char, BUF_SIZE> buf;
322 auto n = sock_recv(sd, buf.data(), sizeof(buf));
323 if (n > 0) {
324 parser.parse(subspan(buf, 0, n));
325 } else if (n < 0) {
326 break;
327 }
328 }
329 closeSocket();
330}
331
332void SocketConnection::output(std::string_view message_)
333{
334 if (!established) { // TODO needs locking?
335 // Connection isn't authorized yet (and opening tag is not
336 // yet send). Ignore log and update messages for now.
337 return;
338 }
339 // std::span message = message_; // error with clang-15/libc++
340 std::span message{message_.begin(), message_.end()};
341 while (!message.empty()) {
342 ptrdiff_t bytesSend;
343 {
344 std::lock_guard<std::mutex> lock(sdMutex);
345 if (sd == OPENMSX_INVALID_SOCKET) return;
346 bytesSend = sock_send(sd, message.data(), message.size());
347 }
348 if (bytesSend > 0) {
349 message = message.subspan(bytesSend);
350 } else {
351 // Note: On Windows we rely on closing the socket to
352 // wake up the worker thread, on other platforms
353 // we rely on Poller.
354 closeSocket();
355 poller.abort();
356 break;
357 }
358 }
359}
360
361void SocketConnection::closeSocket()
362{
363 std::lock_guard<std::mutex> lock(sdMutex);
364 if (sd != OPENMSX_INVALID_SOCKET) {
365 SOCKET _sd = sd;
367 sock_close(_sd);
368 }
369}
370
371void SocketConnection::close()
372{
373 closeSocket();
374}
375
376} // namespace openmsx
void XMLEscape(std::string_view s, Output output)
Definition XMLEscape.hh:22
void parse(std::span< const char > buf)
TemporaryString.
static std::span< const std::string_view, NUM_LEVELS > getLevelStrings()
Definition CliComm.hh:93
static std::span< const std::string_view, NUM_UPDATES > getUpdateStrings()
Definition CliComm.hh:99
Command received on CliComm connection.
Definition Event.hh:346
void end()
End this connection by sending the closing tag and then closing the stream.
virtual void close()=0
Close the connection.
void start()
Starts the helper thread.
AdhocCliCommParser parser
void startOutput()
Send opening XML tag, should be called exactly once by a subclass shortly after opening a connection.
virtual void output(std::string_view message)=0
CliConnection(CommandController &commandController, EventDistributor &eventDistributor)
virtual TclObject executeCommand(zstring_view command, CliConnection *connection=nullptr)=0
Execute the given command.
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
void distributeEvent(Event &&event)
Schedule the given event for delivery.
bool poll(int fd)
Waits for an event to occur on the given file descriptor.
Definition Poller.cc:43
bool aborted()
Returns true iff abort() was called.
Definition Poller.hh:32
void abort()
Aborts a poll in progress and any future poll attempts.
Definition Poller.cc:31
SocketConnection(CommandController &commandController, EventDistributor &eventDistributor, SOCKET sd)
void output(std::string_view message) override
void output(std::string_view message) override
StdioConnection(CommandController &commandController, EventDistributor &eventDistributor)
zstring_view getString() const
Definition TclObject.cc:142
This file implemented 3 utility functions:
Definition Autofire.cc:9
constexpr int OPENMSX_INVALID_SOCKET
Definition Socket.hh:23
ptrdiff_t sock_send(SOCKET sd, const char *buf, size_t count)
Definition Socket.cc:85
void sock_close(SOCKET sd)
Definition Socket.cc:52
EventType getType(const Event &event)
Definition Event.hh:526
std::variant< KeyUpEvent, KeyDownEvent, MouseMotionEvent, MouseButtonUpEvent, MouseButtonDownEvent, MouseWheelEvent, JoystickAxisMotionEvent, JoystickHatEvent, JoystickButtonUpEvent, JoystickButtonDownEvent, OsdControlReleaseEvent, OsdControlPressEvent, WindowEvent, TextEvent, FileDropEvent, QuitEvent, FinishFrameEvent, CliCommandEvent, GroupEvent, BootEvent, FrameDrawnEvent, BreakEvent, SwitchRendererEvent, TakeReverseSnapshotEvent, AfterTimedEvent, MachineLoadedEvent, MachineActivatedEvent, MachineDeactivatedEvent, MidiInReaderEvent, MidiInWindowsEvent, MidiInCoreMidiEvent, MidiInCoreMidiVirtualEvent, MidiInALSAEvent, Rs232TesterEvent, Rs232NetEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
Definition Event.hh:454
int SOCKET
Definition Socket.hh:25
ptrdiff_t sock_recv(SOCKET sd, char *buf, size_t count)
Definition Socket.cc:62
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
STL namespace.
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition ranges.hh:471
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
constexpr auto end(const zstring_view &x)