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 <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 using std::string;
23 
24 
25 namespace openmsx {
26 
27 static string getUserName()
28 {
29 #if defined(_WIN32)
30  return "default";
31 #else
32  struct passwd* pw = getpwuid(getuid());
33  return pw->pw_name ? pw->pw_name : string{};
34 #endif
35 }
36 
37 static bool checkSocketDir(const string& dir)
38 {
39  struct stat st;
40  if (stat(dir.c_str(), &st)) {
41  // cannot stat
42  return false;
43  }
44  if (!S_ISDIR(st.st_mode)) {
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 static bool checkSocket(const string& socket)
63 {
64  std::string_view name = FileOperations::getFilename(socket);
65  if (!StringOp::startsWith(name, "socket.")) {
66  return false; // wrong name
67  }
68 
69  struct stat st;
70  if (stat(socket.c_str(), &st)) {
71  // cannot stat
72  return false;
73  }
74 #ifdef _WIN32
75  if (!S_ISREG(st.st_mode)) {
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 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 (int n = 0; n < RANGE; ++n) {
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, reinterpret_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 
125 SOCKET CliServer::createSocket()
126 {
127  string dir = strCat(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, reinterpret_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 
185 void CliServer::exitAcceptLoop()
186 {
187  sock_close(listenSock);
188  poller.abort();
189 }
190 
191 static void deleteSocket(const string& socket)
192 {
193  FileOperations::unlink(socket); // ignore errors
194  string 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  , listenSock(OPENMSX_INVALID_SOCKET)
206 {
207  sock_startup();
208  try {
209  listenSock = createSocket();
210  thread = std::thread([this]() { mainLoop(); });
211  } catch (MSXException& e) {
212  cliComm.printWarning(e.getMessage());
213  }
214 }
215 
217 {
218  if (listenSock != OPENMSX_INVALID_SOCKET) {
219  exitAcceptLoop();
220  thread.join();
221  }
222 
223  deleteSocket(socketName);
224  sock_cleanup();
225 }
226 
227 void CliServer::mainLoop()
228 {
229 #ifndef _WIN32
230  // Set socket to non-blocking to make sure accept() doesn't hang when
231  // a connection attempt is dropped between poll() and accept().
232  fcntl(listenSock, F_SETFL, O_NONBLOCK);
233 #endif
234  while (true) {
235  // wait for incoming connection
236  // Note: On Windows, closing the socket is sufficient to exit the
237  // accept() call.
238 #ifndef _WIN32
239  if (poller.poll(listenSock)) {
240  break;
241  }
242 #endif
243  SOCKET sd = accept(listenSock, nullptr, nullptr);
244  if (poller.aborted()) {
245  if (sd != OPENMSX_INVALID_SOCKET) {
246  sock_close(sd);
247  }
248  break;
249  }
250  if (sd == OPENMSX_INVALID_SOCKET) {
251  if (errno == one_of(EAGAIN, EWOULDBLOCK)) {
252  continue;
253  } else {
254  break;
255  }
256  }
257 #ifndef _WIN32
258  // The BSD/OSX sockets implementation inherits O_NONBLOCK, while Linux
259  // does not. To be on the safe side, we explicitly reset file flags.
260  fcntl(sd, F_SETFL, 0);
261 #endif
262  cliComm.addListener(std::make_unique<SocketConnection>(
263  commandController, eventDistributor, sd));
264  }
265 }
266 
267 } // namespace openmsx
one_of.hh
openmsx::sock_startup
void sock_startup()
Definition: Socket.cc:27
StringOp::startsWith
bool startsWith(string_view total, string_view part)
Definition: StringOp.cc:71
openmsx::SOCKET
int SOCKET
Definition: Socket.hh:22
openmsx::CommandController
Definition: CommandController.hh:17
openmsx::OPENMSX_INVALID_SOCKET
constexpr int OPENMSX_INVALID_SOCKET
Definition: Socket.hh:20
statp.hh
openmsx::Poller::aborted
bool aborted()
Returns true iff abort() was called.
Definition: Poller.hh:28
random.hh
openmsx::Poller::poll
bool poll(int fd)
Waits for an event to occur on the given file descriptor.
Definition: Poller.cc:43
openmsx::FileOperations::unlink
int unlink(const std::string &path)
Call unlink() in a platform-independent manner.
Definition: FileOperations.cc:282
MSXException.hh
openmsx::sock_cleanup
void sock_cleanup()
Definition: Socket.cc:42
openmsx::MSXException
Definition: MSXException.hh:9
GlobalCliComm.hh
openmsx::FileOperations::openofstream
void openofstream(std::ofstream &stream, const std::string &filename)
Open an ofstream in a platform-independent manner.
Definition: FileOperations.cc:367
openmsx::EventDistributor
Definition: EventDistributor.hh:16
random_int
int random_int(int from, int thru)
Return a random integer in the range [from, thru] (note: closed interval).
Definition: random.hh:38
openmsx::GlobalCliComm
Definition: GlobalCliComm.hh:15
openmsx::CliComm::printWarning
void printWarning(std::string_view message)
Definition: CliComm.cc:10
one_of
Definition: one_of.hh:7
openmsx::CliServer::CliServer
CliServer(CommandController &commandController, EventDistributor &eventDistributor, GlobalCliComm &cliComm)
Definition: CliServer.cc:199
openmsx::FileOperations::getTempDir
string getTempDir()
Get the name of the temp directory on the system.
Definition: FileOperations.cc:762
CliServer.hh
FileOperations.hh
StringOp.hh
openmsx::FileOperations::rmdir
int rmdir(const std::string &path)
Call rmdir() in a platform-independent manner.
Definition: FileOperations.cc:291
openmsx::SOCKET_ERROR
constexpr int SOCKET_ERROR
Definition: Socket.hh:21
openmsx::CliServer::~CliServer
~CliServer()
Definition: CliServer.cc:216
CliConnection.hh
openmsx::sock_close
void sock_close(SOCKET sd)
Definition: Socket.cc:52
openmsx::FileOperations::mkdir
void mkdir(const string &path, mode_t mode)
Create the specified directory.
Definition: FileOperations.cc:220
openmsx::MSXException::getMessage
const std::string & getMessage() const &
Definition: MSXException.hh:23
openmsx::Poller::abort
void abort()
Aborts a poll in progress and any future poll attempts.
Definition: Poller.cc:31
strCat
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
openmsx::GlobalCliComm::addListener
void addListener(std::unique_ptr< CliListener > listener)
Definition: GlobalCliComm.cc:19
openmsx::sock_error
std::string sock_error()
Definition: Socket.cc:9
openmsx::FileOperations::getFilename
string_view getFilename(string_view path)
Returns the file portion of a path name.
Definition: FileOperations.cc:390