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