openMSX
CliServer.cc
Go to the documentation of this file.
1#include "CliServer.hh"
2#include "GlobalCliComm.hh"
3#include "CliConnection.hh"
4#include "FileOperations.hh"
5#include "MSXException.hh"
6#include "one_of.hh"
7#include "random.hh"
8#include "xrange.hh"
9#include <memory>
10#include <string>
11
12#ifdef _WIN32
13#include <fstream>
14#include <ctime>
15#else
16#include <pwd.h>
17#include <unistd.h>
18#include <fcntl.h>
19#endif
20
21namespace openmsx {
22
23[[nodiscard]] static std::string getUserName()
24{
25#if defined(_WIN32)
26 return "default";
27#else
28 struct passwd* pw = getpwuid(getuid());
29 return pw->pw_name ? pw->pw_name : std::string{};
30#endif
31}
32
33[[nodiscard]] static bool checkSocketDir(zstring_view dir)
34{
35 auto st = FileOperations::getStat(dir);
36 if (!st) {
37 // error during stat()
38 return false;
39 }
41 // not a directory
42 return false;
43 }
44#ifndef _WIN32
45 // only do permission and owner checks on *nix
46 if ((st->st_mode & 0777) != 0700) {
47 // wrong permissions
48 return false;
49 }
50 if (st->st_uid != getuid()) {
51 // wrong uid
52 return false;
53 }
54#endif
55 return true;
56}
57
58[[nodiscard]] static bool checkSocket(zstring_view socket)
59{
60 std::string_view name = FileOperations::getFilename(socket);
61 if (!name.starts_with("socket.")) {
62 return false; // wrong name
63 }
64
65 auto st = FileOperations::getStat(socket);
66 if (!st) {
67 // error during stat()
68 return false;
69 }
70#ifdef _WIN32
72 // not a regular file
73 return false;
74 }
75#else
76 if (!S_ISSOCK(st->st_mode)) {
77 // not a socket
78 return false;
79 }
80#endif
81#ifndef _WIN32
82 // only do permission and owner checks on *nix
83 if ((st->st_mode & 0777) != 0600) {
84 // check will be different on win32 (!= 777) thus actually useless
85 // wrong permissions
86 return false;
87 }
88 if (st->st_uid != getuid()) {
89 // does this work on win32? is this check meaningful?
90 // wrong uid
91 return false;
92 }
93#endif
94 return true;
95}
96
97#ifdef _WIN32
98[[nodiscard]] static int openPort(SOCKET listenSock)
99{
100 const int BASE = 9938;
101 const int RANGE = 64;
102
103 int first = random_int(0, RANGE - 1); // [0, RANGE)
104
105 for (auto n : xrange(RANGE)) {
106 int port = BASE + ((first + n) % RANGE);
107 sockaddr_in server_address;
108 memset(&server_address, 0, sizeof(server_address));
109 server_address.sin_family = AF_INET;
110 server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
111 server_address.sin_port = htons(port);
112 if (bind(listenSock, reinterpret_cast<sockaddr*>(&server_address),
113 sizeof(server_address)) != -1) {
114 return port;
115 }
116 }
117 throw MSXException("Couldn't open socket.");
118}
119#endif
120
121SOCKET CliServer::createSocket()
122{
123 auto dir = tmpStrCat(FileOperations::getTempDir(), "/openmsx-", getUserName());
124 FileOperations::mkdir(dir, 0700);
125 if (!checkSocketDir(dir)) {
126 throw MSXException("Couldn't create socket directory.");
127 }
128 socketName = strCat(dir, "/socket.", int(getpid()));
129
130#ifdef _WIN32
131 SOCKET sd = socket(AF_INET, SOCK_STREAM, 0);
132 if (sd == OPENMSX_INVALID_SOCKET) {
133 throw MSXException(sock_error());
134 }
135 int portNumber = openPort(sd);
136
137 // write port number to file
138 FileOperations::unlink(socketName); // ignore error
139 std::ofstream out;
140 FileOperations::openOfStream(out, socketName);
141 out << portNumber << '\n';
142 if (!out.good()) {
143 sock_close(sd);
144 throw MSXException("Couldn't write socket port file.");
145 }
146
147#else
148 SOCKET sd = socket(AF_UNIX, SOCK_STREAM, 0);
149 if (sd == OPENMSX_INVALID_SOCKET) {
150 throw MSXException(sock_error());
151 }
152
153 FileOperations::unlink(socketName); // ignore error
154
155 sockaddr_un addr;
156 strncpy(addr.sun_path, socketName.c_str(), sizeof(addr.sun_path) - 1);
157 addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
158 addr.sun_family = AF_UNIX;
159
160 if (bind(sd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
161 sock_close(sd);
162 throw MSXException("Couldn't bind socket.");
163 }
164 if (chmod(socketName.c_str(), 0600) == -1) {
165 sock_close(sd);
166 throw MSXException("Couldn't set socket permissions.");
167 }
168
169#endif
170 if (!checkSocket(socketName)) {
171 sock_close(sd);
172 throw MSXException("Opened socket fails sanity check.");
173 }
174 if (listen(sd, SOMAXCONN) == SOCKET_ERROR) {
175 sock_close(sd);
176 throw MSXException("Couldn't listen to socket: ", sock_error());
177 }
178 return sd;
179}
180
181void CliServer::exitAcceptLoop()
182{
183 sock_close(listenSock);
184 poller.abort();
185}
186
187static void deleteSocket(const std::string& socket)
188{
189 FileOperations::unlink(socket); // ignore errors
190 auto dir = socket.substr(0, socket.find_last_of('/'));
191 FileOperations::rmdir(dir); // ignore errors
192}
193
194
196 EventDistributor& eventDistributor_,
197 GlobalCliComm& cliComm_)
198 : commandController(commandController_)
199 , eventDistributor(eventDistributor_)
200 , cliComm(cliComm_)
201 , listenSock(OPENMSX_INVALID_SOCKET)
202{
203 try {
204 listenSock = createSocket();
205 thread = std::thread([this]() { mainLoop(); });
206 } catch (MSXException& e) {
207 cliComm.printWarning(e.getMessage());
208 }
209}
210
212{
213 if (listenSock != OPENMSX_INVALID_SOCKET) {
214 exitAcceptLoop();
215 thread.join();
216 }
217
218 deleteSocket(socketName);
219}
220
221void CliServer::mainLoop()
222{
223#ifndef _WIN32
224 // Set socket to non-blocking to make sure accept() doesn't hang when
225 // a connection attempt is dropped between poll() and accept().
226 fcntl(listenSock, F_SETFL, O_NONBLOCK);
227#endif
228 while (true) {
229 // wait for incoming connection
230 // Note: On Windows, closing the socket is sufficient to exit the
231 // accept() call.
232#ifndef _WIN32
233 if (poller.poll(listenSock)) {
234 break;
235 }
236#endif
237 SOCKET sd = accept(listenSock, nullptr, nullptr);
238 if (poller.aborted()) {
239 if (sd != OPENMSX_INVALID_SOCKET) {
240 sock_close(sd);
241 }
242 break;
243 }
244 if (sd == OPENMSX_INVALID_SOCKET) {
245 if (errno == one_of(EAGAIN, EWOULDBLOCK)) {
246 continue;
247 } else {
248 break;
249 }
250 }
251#ifndef _WIN32
252 // The BSD/OSX sockets implementation inherits O_NONBLOCK, while Linux
253 // does not. To be on the safe side, we explicitly reset file flags.
254 fcntl(sd, F_SETFL, 0);
255#endif
256 cliComm.addListener(std::make_unique<SocketConnection>(
257 commandController, eventDistributor, sd));
258 }
259}
260
261} // namespace openmsx
void printWarning(std::string_view message)
Definition CliComm.cc:10
CliServer(CommandController &commandController, EventDistributor &eventDistributor, GlobalCliComm &cliComm)
Definition CliServer.cc:195
CliListener * addListener(std::unique_ptr< CliListener > listener)
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
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr zstring_view substr(size_type pos) const
void mkdir(zstring_view path, mode_t mode)
Create the specified directory.
bool isRegularFile(const Stat &st)
int rmdir(zstring_view path)
Call rmdir() in a platform-independent manner.
string getTempDir()
Get the name of the temp directory on the system.
bool isDirectory(const Stat &st)
void openOfStream(std::ofstream &stream, zstring_view filename)
Open an ofstream in a platform-independent manner.
string_view getFilename(string_view path)
Returns the file portion of a path name.
std::optional< Stat > getStat(zstring_view filename)
Call stat() and return the stat structure.
int unlink(zstring_view path)
Call unlink() in a platform-independent manner.
This file implemented 3 utility functions:
Definition Autofire.cc:9
constexpr int OPENMSX_INVALID_SOCKET
Definition Socket.hh:23
constexpr int SOCKET_ERROR
Definition Socket.hh:24
void sock_close(SOCKET sd)
Definition Socket.cc:52
std::string sock_error()
Definition Socket.cc:9
int SOCKET
Definition Socket.hh:25
int random_int(int from, int thru)
Return a random integer in the range [from, thru] (note: closed interval).
Definition random.hh:38
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
constexpr auto xrange(T e)
Definition xrange.hh:132