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