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 std::string fullMessage{message};
51 if (level == CliComm::LogLevel::PROGRESS && fraction >= 0.0f) {
52 strAppend(fullMessage, "... ", int(100.0f * fraction), '%');
53 }
54 output(tmpStrCat("<log level=\"", toString(level), "\">",
55 XMLEscape(fullMessage), "</log>\n"));
56}
57
58void CliConnection::update(CliComm::UpdateType type, std::string_view machine,
59 std::string_view name, std::string_view value) noexcept
60{
61 if (!getUpdateEnable(type)) return;
62
63 auto tmp = strCat("<update type=\"", toString(type), '\"');
64 if (!machine.empty()) {
65 strAppend(tmp, " machine=\"", machine, '\"');
66 }
67 if (!name.empty()) {
68 strAppend(tmp, " name=\"", XMLEscape(name), '\"');
69 }
70 strAppend(tmp, '>', XMLEscape(value), "</update>\n");
71
72 output(tmp);
73}
74
76{
77 output("<openmsx-output>\n");
78}
79
81{
82 thread = std::thread([this]() { run(); });
83}
84
86{
87 output("</openmsx-output>\n");
88 close();
89
90 poller.abort();
91 // Thread might not be running if start() was never called.
92 if (thread.joinable()) {
93 thread.join();
94 }
95}
96
97void CliConnection::execute(const std::string& command)
98{
99 eventDistributor.distributeEvent(CliCommandEvent(command, this));
100}
101
102static TemporaryString reply(std::string_view message, bool status)
103{
104 return tmpStrCat("<reply result=\"", (status ? "ok" : "nok"), "\">",
105 XMLEscape(message), "</reply>\n");
106}
107
108bool CliConnection::signalEvent(const Event& event)
109{
110 assert(getType(event) == EventType::CLICOMMAND);
111 if (const auto& commandEvent = get_event<CliCommandEvent>(event);
112 commandEvent.getId() == this) {
113 try {
114 auto result = commandController.executeCommand(
115 commandEvent.getCommand(), this).getString();
116 output(reply(result, true));
117 } catch (CommandException& e) {
118 std::string result = std::move(e).getMessage() + '\n';
119 output(reply(result, false));
120 }
121 }
122 return false;
123}
124
125
126// class StdioConnection
127
128static constexpr int BUF_SIZE = 4096;
130 EventDistributor& eventDistributor_)
131 : CliConnection(commandController_, eventDistributor_)
132{
133 startOutput();
134}
135
140
141void StdioConnection::run()
142{
143 // runs in helper thread
144 while (true) {
145#ifdef _WIN32
146 if (poller.aborted()) break;
147#else
148 if (poller.poll(STDIN_FILENO)) break;
149#endif
150 std::array<char, BUF_SIZE> buf;
151 auto n = read(STDIN_FILENO, buf.data(), sizeof(buf));
152 if (n > 0) {
153 parser.parse(subspan(buf, 0, n));
154 } else if (n < 0) {
155 break;
156 }
157 }
158}
159
160void StdioConnection::output(std::string_view message)
161{
162 std::cout << message << std::flush;
163}
164
165void StdioConnection::close()
166{
167 // don't close stdin/out/err
168}
169
170
171#ifdef _WIN32
172// class PipeConnection
173
174// INVALID_HANDLE_VALUE is #defined as (HANDLE)(-1)
175// but that gives a old-style-cast warning
176static const HANDLE OPENMSX_INVALID_HANDLE_VALUE = reinterpret_cast<HANDLE>(-1);
177
178PipeConnection::PipeConnection(CommandController& commandController_,
179 EventDistributor& eventDistributor_,
180 std::string_view name)
181 : CliConnection(commandController_, eventDistributor_)
182{
183 auto pipeName = strCat("\\\\.\\pipe\\", name);
184 pipeHandle = CreateFileA(pipeName.c_str(), GENERIC_READ, 0, nullptr,
185 OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
186 if (pipeHandle == OPENMSX_INVALID_HANDLE_VALUE) {
187 throw FatalError("Error reopening pipefile '", pipeName, "': error ",
188 unsigned(GetLastError()));
189 }
190
191 shutdownEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
192 if (!shutdownEvent) {
193 throw FatalError("Error creating shutdown event: ", GetLastError());
194 }
195
196 startOutput();
197}
198
199PipeConnection::~PipeConnection()
200{
201 end();
202
203 assert(pipeHandle == OPENMSX_INVALID_HANDLE_VALUE);
204 CloseHandle(shutdownEvent);
205}
206
207static void InitOverlapped(LPOVERLAPPED overlapped)
208{
209 ZeroMemory(overlapped, sizeof(*overlapped));
210 overlapped->hEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
211 if (!overlapped->hEvent) {
212 throw FatalError("Error creating overlapped event: ", GetLastError());
213 }
214}
215
216static void ClearOverlapped(LPOVERLAPPED overlapped)
217{
218 if (overlapped->hEvent) {
219 CloseHandle(overlapped->hEvent);
220 overlapped->hEvent = nullptr;
221 }
222}
223
224void PipeConnection::run()
225{
226 // runs in helper thread
227 OVERLAPPED overlapped;
228 InitOverlapped(&overlapped);
229 HANDLE waitHandles[2] = { shutdownEvent, overlapped.hEvent };
230
231 while (pipeHandle != OPENMSX_INVALID_HANDLE_VALUE) {
232 char buf[BUF_SIZE];
233 if (!ReadFile(pipeHandle, buf, BUF_SIZE, nullptr, &overlapped) &&
234 GetLastError() != ERROR_IO_PENDING) {
235 break; // Pipe broke
236 }
237 DWORD wait = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
238 if (wait == WAIT_OBJECT_0 + 1) {
239 DWORD bytesRead;
240 if (!GetOverlappedResult(pipeHandle, &overlapped, &bytesRead, TRUE)) {
241 break; // Pipe broke
242 }
243 parser.parse(std::span{buf, bytesRead});
244 } else if (wait == WAIT_OBJECT_0) {
245 break; // Shutdown
246 } else {
247 throw FatalError(
248 "WaitForMultipleObjects returned unexpectedly: ", wait);
249 }
250 }
251
252 ClearOverlapped(&overlapped);
253
254 // We own the pipe handle, so close it here
255 CloseHandle(pipeHandle);
256 pipeHandle = OPENMSX_INVALID_HANDLE_VALUE;
257}
258
259void PipeConnection::output(std::string_view message)
260{
261 if (pipeHandle != OPENMSX_INVALID_HANDLE_VALUE) {
262 std::cout << message << std::flush;
263 }
264}
265
266void PipeConnection::close()
267{
268 SetEvent(shutdownEvent);
269}
270#endif // _WIN32
271
272
273// class SocketConnection
274
276 EventDistributor& eventDistributor_,
277 SOCKET sd_)
278 : CliConnection(commandController_, eventDistributor_)
279 , sd(sd_)
280{
281}
282
287
288void SocketConnection::run()
289{
290 // runs in helper thread
291#ifdef _WIN32
292 bool ok;
293 {
294 std::scoped_lock lock(sdMutex);
295 // Authenticate and authorize the caller
296 SocketStreamWrapper stream(sd);
297 SspiNegotiateServer server(stream);
298 ok = server.Authenticate() && server.Authorize();
299 }
300 if (!ok) {
301 closeSocket();
302 return;
303 }
304#endif
305 // Start output element
306 established = true; // TODO needs locking?
307 startOutput();
308
309 // TODO is locking correct?
310 // No need to lock in this thread because we don't write to 'sd'
311 // and 'sd' only gets written to in this thread.
312 while (true) {
313 if (sd == OPENMSX_INVALID_SOCKET) return;
314#ifndef _WIN32
315 if (poller.poll(sd)) {
316 break;
317 }
318#endif
319 std::array<char, BUF_SIZE> buf;
320 auto n = sock_recv(sd, buf.data(), sizeof(buf));
321 if (n > 0) {
322 parser.parse(subspan(buf, 0, n));
323 } else if (n < 0) {
324 break;
325 }
326 }
327 closeSocket();
328}
329
330void SocketConnection::output(std::string_view message_)
331{
332 if (!established) { // TODO needs locking?
333 // Connection isn't authorized yet (and opening tag is not
334 // yet send). Ignore log and update messages for now.
335 return;
336 }
337 // std::span message = message_; // error with clang-15/libc++
338 std::span message{message_.begin(), message_.end()};
339 while (!message.empty()) {
340 ptrdiff_t bytesSend;
341 {
342 std::scoped_lock lock(sdMutex);
343 if (sd == OPENMSX_INVALID_SOCKET) return;
344 bytesSend = sock_send(sd, message.data(), message.size());
345 }
346 if (bytesSend > 0) {
347 message = message.subspan(bytesSend);
348 } else {
349 // Note: On Windows we rely on closing the socket to
350 // wake up the worker thread, on other platforms
351 // we rely on Poller.
352 closeSocket();
353 poller.abort();
354 break;
355 }
356 }
357}
358
359void SocketConnection::closeSocket()
360{
361 std::scoped_lock lock(sdMutex);
362 if (sd != OPENMSX_INVALID_SOCKET) {
363 SOCKET _sd = sd;
365 sock_close(_sd);
366 }
367}
368
369void SocketConnection::close()
370{
371 closeSocket();
372}
373
374} // namespace openmsx
void XMLEscape(std::string_view s, Output output)
Definition XMLEscape.hh:22
void parse(std::span< const char > buf)
TemporaryString.
Command received on CliComm connection.
Definition Event.hh:336
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:42
bool aborted() const
Returns true iff abort() was called.
Definition Poller.hh:33
void abort()
Aborts a poll in progress and any future poll attempts.
Definition Poller.cc:30
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:141
This file implemented 3 utility functions:
Definition Autofire.cc:11
constexpr int OPENMSX_INVALID_SOCKET
Definition Socket.hh:23
ptrdiff_t sock_send(SOCKET sd, const char *buf, size_t count)
Definition Socket.cc:88
void sock_close(SOCKET sd)
Definition Socket.cc:55
EventType getType(const Event &event)
Definition Event.hh:517
std::string toString(const BooleanInput &input)
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:445
int SOCKET
Definition Socket.hh:25
ptrdiff_t sock_recv(SOCKET sd, char *buf, size_t count)
Definition Socket.cc:65
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:315
STL namespace.
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition ranges.hh:481
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)