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 "statp.hh"
9 #include "xrange.hh"
10 #include <memory>
11 #include <string>
12 
13 #ifdef _WIN32
14 #include <fstream>
15 #include <ctime>
16 #else
17 #include <pwd.h>
18 #include <unistd.h>
19 #include <fcntl.h>
20 #endif
21 
22 namespace openmsx {
23 
24 [[nodiscard]] static std::string getUserName()
25 {
26 #if defined(_WIN32)
27  return "default";
28 #else
29  struct passwd* pw = getpwuid(getuid());
30  return pw->pw_name ? pw->pw_name : std::string{};
31 #endif
32 }
33 
34 [[nodiscard]] static bool checkSocketDir(zstring_view dir)
35 {
36  struct stat st;
37  if (stat(dir.c_str(), &st)) {
38  // cannot stat
39  return false;
40  }
41  if (!S_ISDIR(st.st_mode)) {
42  // not a directory
43  return false;
44  }
45 #ifndef _WIN32
46  // only do permission and owner checks on *nix
47  if ((st.st_mode & 0777) != 0700) {
48  // wrong permissions
49  return false;
50  }
51  if (st.st_uid != getuid()) {
52  // wrong uid
53  return false;
54  }
55 #endif
56  return true;
57 }
58 
59 [[nodiscard]] static bool checkSocket(zstring_view socket)
60 {
61  std::string_view name = FileOperations::getFilename(socket);
62  if (!name.starts_with("socket.")) {
63  return false; // wrong name
64  }
65 
66  struct stat st;
67  if (stat(socket.c_str(), &st)) {
68  // cannot stat
69  return false;
70  }
71 #ifdef _WIN32
72  if (!S_ISREG(st.st_mode)) {
73  // not a regular file
74  return false;
75  }
76 #else
77  if (!S_ISSOCK(st.st_mode)) {
78  // not a socket
79  return false;
80  }
81 #endif
82 #ifndef _WIN32
83  // only do permission and owner checks on *nix
84  if ((st.st_mode & 0777) != 0600) {
85  // check will be different on win32 (!= 777) thus actually useless
86  // wrong permissions
87  return false;
88  }
89  if (st.st_uid != getuid()) {
90  // does this work on win32? is this check meaningful?
91  // wrong uid
92  return false;
93  }
94 #endif
95  return true;
96 }
97 
98 #ifdef _WIN32
99 [[nodiscard]] static int openPort(SOCKET listenSock)
100 {
101  const int BASE = 9938;
102  const int RANGE = 64;
103 
104  int first = random_int(0, RANGE - 1); // [0, RANGE)
105 
106  for (auto n : xrange(RANGE)) {
107  int port = BASE + ((first + n) % RANGE);
108  sockaddr_in server_address;
109  memset(&server_address, 0, sizeof(server_address));
110  server_address.sin_family = AF_INET;
111  server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
112  server_address.sin_port = htons(port);
113  if (bind(listenSock, reinterpret_cast<sockaddr*>(&server_address),
114  sizeof(server_address)) != -1) {
115  return port;
116  }
117  }
118  throw MSXException("Couldn't open socket.");
119 }
120 #endif
121 
122 SOCKET CliServer::createSocket()
123 {
124  auto dir = tmpStrCat(FileOperations::getTempDir(), "/openmsx-", getUserName());
125  FileOperations::mkdir(dir, 0700);
126  if (!checkSocketDir(dir)) {
127  throw MSXException("Couldn't create socket directory.");
128  }
129  socketName = strCat(dir, "/socket.", int(getpid()));
130 
131 #ifdef _WIN32
132  SOCKET sd = socket(AF_INET, SOCK_STREAM, 0);
133  if (sd == OPENMSX_INVALID_SOCKET) {
134  throw MSXException(sock_error());
135  }
136  int portNumber = openPort(sd);
137 
138  // write port number to file
139  FileOperations::unlink(socketName); // ignore error
140  std::ofstream out;
141  FileOperations::openofstream(out, socketName);
142  out << portNumber << '\n';
143  if (!out.good()) {
144  sock_close(sd);
145  throw MSXException("Couldn't write socket port file.");
146  }
147 
148 #else
149  SOCKET sd = socket(AF_UNIX, SOCK_STREAM, 0);
150  if (sd == OPENMSX_INVALID_SOCKET) {
151  throw MSXException(sock_error());
152  }
153 
154  FileOperations::unlink(socketName); // ignore error
155 
156  sockaddr_un addr;
157  strncpy(addr.sun_path, socketName.c_str(), sizeof(addr.sun_path) - 1);
158  addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
159  addr.sun_family = AF_UNIX;
160 
161  if (bind(sd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
162  sock_close(sd);
163  throw MSXException("Couldn't bind socket.");
164  }
165  if (chmod(socketName.c_str(), 0600) == -1) {
166  sock_close(sd);
167  throw MSXException("Couldn't set socket permissions.");
168  }
169 
170 #endif
171  if (!checkSocket(socketName)) {
172  sock_close(sd);
173  throw MSXException("Opened socket fails sanity check.");
174  }
175  if (listen(sd, SOMAXCONN) == SOCKET_ERROR) {
176  sock_close(sd);
177  throw MSXException("Couldn't listen to socket: ", sock_error());
178  }
179  return sd;
180 }
181 
182 void CliServer::exitAcceptLoop()
183 {
184  sock_close(listenSock);
185  poller.abort();
186 }
187 
188 static void deleteSocket(const std::string& socket)
189 {
190  FileOperations::unlink(socket); // ignore errors
191  auto dir = socket.substr(0, socket.find_last_of('/'));
192  FileOperations::rmdir(dir); // ignore errors
193 }
194 
195 
197  EventDistributor& eventDistributor_,
198  GlobalCliComm& cliComm_)
199  : commandController(commandController_)
200  , eventDistributor(eventDistributor_)
201  , cliComm(cliComm_)
202  , listenSock(OPENMSX_INVALID_SOCKET)
203 {
204  sock_startup();
205  try {
206  listenSock = createSocket();
207  thread = std::thread([this]() { mainLoop(); });
208  } catch (MSXException& e) {
209  cliComm.printWarning(e.getMessage());
210  }
211 }
212 
214 {
215  if (listenSock != OPENMSX_INVALID_SOCKET) {
216  exitAcceptLoop();
217  thread.join();
218  }
219 
220  deleteSocket(socketName);
221  sock_cleanup();
222 }
223 
224 void 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
Definition: one_of.hh:7
void printWarning(std::string_view message)
Definition: CliComm.cc:10
CliServer(CommandController &commandController, EventDistributor &eventDistributor, GlobalCliComm &cliComm)
Definition: CliServer.cc:196
void 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:28
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.
Definition: zstring_view.hh:22
constexpr const char * c_str() const
Definition: zstring_view.hh:49
constexpr zstring_view substr(size_type pos) const
Definition: zstring_view.hh:55
constexpr double e
Definition: Math.hh:18
void mkdir(zstring_view path, mode_t mode)
Create the specified directory.
void openofstream(std::ofstream &stream, zstring_view filename)
Open an ofstream in a platform-independent manner.
int rmdir(zstring_view path)
Call rmdir() in a platform-independent manner.
string getTempDir()
Get the name of the temp directory on the system.
string_view getFilename(string_view path)
Returns the file portion of a path name.
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:20
constexpr int SOCKET_ERROR
Definition: Socket.hh:21
void sock_close(SOCKET sd)
Definition: Socket.cc:52
void sock_startup()
Definition: Socket.cc:27
std::string sock_error()
Definition: Socket.cc:9
int SOCKET
Definition: Socket.hh:22
void sock_cleanup()
Definition: Socket.cc:42
int random_int(int from, int thru)
Return a random integer in the range [from, thru] (note: closed interval).
Definition: random.hh:38
TemporaryString tmpStrCat(Ts &&... ts)
Definition: strCat.hh:617
std::string strCat(Ts &&...ts)
Definition: strCat.hh:549
constexpr auto xrange(T e)
Definition: xrange.hh:133