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