openmsx-control-socket.cc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. /**
  2. * Example implementation for bidirectional communication with openMSX.
  3. *
  4. * requires: libxml2
  5. * compile:
  6. * *nix: g++ `xml2-config --cflags` `xml2-config --libs` openmsx-control-socket.cc
  7. * win32: g++ `xml2-config --cflags` `xml2-config --libs` openmsx-control-socket.cc -lwsock32
  8. */
  9. #include <string>
  10. #include <deque>
  11. #include <vector>
  12. #include <iostream>
  13. #include <unistd.h>
  14. #include <sys/types.h>
  15. #include <sys/select.h>
  16. #include <sys/stat.h>
  17. #include <libxml/parser.h>
  18. #include <unistd.h>
  19. #include <dirent.h>
  20. #ifdef _WIN32
  21. #include <fstream>
  22. #include <winsock2.h>
  23. #else
  24. #include <sys/socket.h>
  25. #include <netinet/in.h>
  26. #include <sys/un.h>
  27. #include <pwd.h>
  28. #endif
  29. using std::cout;
  30. using std::endl;
  31. using std::deque;
  32. using std::string;
  33. using std::vector;
  34. class ReadDir
  35. {
  36. public:
  37. ReadDir(const std::string& directory) {
  38. dir = opendir(directory.c_str());
  39. }
  40. ~ReadDir() {
  41. if (dir) {
  42. closedir(dir);
  43. }
  44. }
  45. dirent* getEntry() {
  46. if (!dir) {
  47. return 0;
  48. }
  49. return readdir(dir);
  50. }
  51. private:
  52. DIR* dir;
  53. };
  54. static string getTempDir()
  55. {
  56. const char* result = NULL;
  57. if (!result) result = getenv("TMPDIR");
  58. if (!result) result = getenv("TMP");
  59. if (!result) result = getenv("TEMP");
  60. if (!result) {
  61. #ifdef _WIN32
  62. result = "C:/WINDOWS/TEMP";
  63. #else
  64. result = "/tmp";
  65. #endif
  66. }
  67. return result;
  68. }
  69. static string getUserName()
  70. {
  71. #ifdef _WIN32
  72. return "default";
  73. #else
  74. struct passwd* pw = getpwuid(getuid());
  75. return pw->pw_name ? pw->pw_name : "";
  76. #endif
  77. }
  78. class OpenMSXComm
  79. {
  80. public:
  81. // main loop
  82. void start(int sd);
  83. // send a command to openmsx
  84. void sendCommand(const string& command);
  85. private:
  86. // XML parsing call-back functions
  87. static void cb_start_element(OpenMSXComm* comm, const xmlChar* name,
  88. const xmlChar** attrs);
  89. static void cb_end_element(OpenMSXComm* comm, const xmlChar* name);
  90. static void cb_text(OpenMSXComm* comm, const xmlChar* chars, int len);
  91. void parseReply(const char** attrs);
  92. void parseLog(const char** attrs);
  93. void parseUpdate(const char** attrs);
  94. void doReply();
  95. void doLog();
  96. void doUpdate();
  97. // commands being executed
  98. deque<string> commandStack;
  99. // XML parsing
  100. enum State {
  101. START,
  102. TAG_OPENMSX,
  103. TAG_REPLY,
  104. TAG_LOG,
  105. TAG_UPDATE,
  106. } state;
  107. unsigned unknownLevel;
  108. string content;
  109. xmlSAXHandler sax_handler;
  110. xmlParserCtxt* parser_context;
  111. enum ReplyStatus {
  112. REPLY_UNKNOWN,
  113. REPLY_OK,
  114. REPLY_NOK
  115. } replyStatus;
  116. enum LogLevel {
  117. LOG_UNKNOWN,
  118. LOG_INFO,
  119. LOG_WARNING
  120. } logLevel;
  121. string updateType;
  122. string updateName;
  123. // communication with openmsx process
  124. int sd;
  125. };
  126. void OpenMSXComm::cb_start_element(OpenMSXComm* comm, const xmlChar* name,
  127. const xmlChar** attrs)
  128. {
  129. if (comm->unknownLevel) {
  130. ++(comm->unknownLevel);
  131. return;
  132. }
  133. switch (comm->state) {
  134. case START:
  135. if (strcmp((const char*)name, "openmsx-output") == 0) {
  136. comm->state = TAG_OPENMSX;
  137. } else {
  138. ++(comm->unknownLevel);
  139. }
  140. break;
  141. case TAG_OPENMSX:
  142. if (strcmp((const char*)name, "reply") == 0) {
  143. comm->state = TAG_REPLY;
  144. comm->parseReply((const char**)attrs);
  145. } else if (strcmp((const char*)name, "log") == 0) {
  146. comm->state = TAG_LOG;
  147. comm->parseLog((const char**)attrs);
  148. } else if (strcmp((const char*)name, "update") == 0) {
  149. comm->state = TAG_UPDATE;
  150. comm->parseUpdate((const char**)attrs);
  151. } else {
  152. ++(comm->unknownLevel);
  153. }
  154. break;
  155. default:
  156. ++(comm->unknownLevel);
  157. break;
  158. }
  159. comm->content.clear();
  160. }
  161. void OpenMSXComm::parseReply(const char** attrs)
  162. {
  163. replyStatus = REPLY_UNKNOWN;
  164. if (attrs) {
  165. for ( ; *attrs; attrs += 2) {
  166. if (strcmp(attrs[0], "result") == 0) {
  167. if (strcmp(attrs[1], "ok") == 0) {
  168. replyStatus = REPLY_OK;
  169. } else if (strcmp(attrs[1], "nok") == 0) {
  170. replyStatus = REPLY_NOK;
  171. }
  172. }
  173. }
  174. }
  175. }
  176. void OpenMSXComm::parseLog(const char** attrs)
  177. {
  178. logLevel = LOG_UNKNOWN;
  179. if (attrs) {
  180. for ( ; *attrs; attrs += 2) {
  181. if (strcmp(attrs[0], "level") == 0) {
  182. if (strcmp(attrs[1], "info") == 0) {
  183. logLevel = LOG_INFO;
  184. } else if (strcmp(attrs[1], "warning") == 0) {
  185. logLevel = LOG_WARNING;
  186. }
  187. }
  188. }
  189. }
  190. }
  191. void OpenMSXComm::parseUpdate(const char** attrs)
  192. {
  193. updateType = "unknown";
  194. if (attrs) {
  195. for ( ; *attrs; attrs += 2) {
  196. if (strcmp(attrs[0], "type") == 0) {
  197. updateType = attrs[1];
  198. } else if (strcmp(attrs[0], "name") == 0) {
  199. updateName = attrs[1];
  200. }
  201. }
  202. }
  203. }
  204. void OpenMSXComm::cb_end_element(OpenMSXComm* comm, const xmlChar* name)
  205. {
  206. if (comm->unknownLevel) {
  207. --(comm->unknownLevel);
  208. return;
  209. }
  210. switch (comm->state) {
  211. case TAG_OPENMSX:
  212. comm->state = START;
  213. break;
  214. case TAG_REPLY:
  215. comm->doReply();
  216. comm->state = TAG_OPENMSX;
  217. break;
  218. case TAG_LOG:
  219. comm->doLog();
  220. comm->state = TAG_OPENMSX;
  221. break;
  222. case TAG_UPDATE:
  223. comm->doUpdate();
  224. comm->state = TAG_OPENMSX;
  225. break;
  226. default:
  227. break;
  228. }
  229. }
  230. void OpenMSXComm::doReply()
  231. {
  232. switch (replyStatus) {
  233. case REPLY_OK:
  234. cout << "OK: ";
  235. break;
  236. case REPLY_NOK:
  237. cout << "ERR: ";
  238. break;
  239. }
  240. cout << commandStack.front() << endl;
  241. commandStack.pop_front();
  242. if (!content.empty()) {
  243. cout << content << endl;
  244. }
  245. }
  246. void OpenMSXComm::doLog()
  247. {
  248. switch (logLevel) {
  249. case LOG_INFO:
  250. cout << "INFO: ";
  251. break;
  252. case LOG_WARNING:
  253. cout << "WARNING: ";
  254. break;
  255. }
  256. cout << content << endl;
  257. }
  258. void OpenMSXComm::doUpdate()
  259. {
  260. cout << "UPDATE: " << updateType << " " << updateName << " " << content << endl;
  261. }
  262. void OpenMSXComm::cb_text(OpenMSXComm* comm, const xmlChar* chars, int len)
  263. {
  264. switch (comm->state) {
  265. case TAG_REPLY:
  266. case TAG_LOG:
  267. case TAG_UPDATE:
  268. comm->content.append((const char*)chars, len);
  269. break;
  270. default:
  271. break;
  272. }
  273. }
  274. void OpenMSXComm::sendCommand(const string& command)
  275. {
  276. write(sd, "<command>", 9);
  277. write(sd, command.c_str(), command.length());
  278. write(sd, "</command>", 10);
  279. commandStack.push_back(command);
  280. }
  281. void OpenMSXComm::start(int sd_)
  282. {
  283. sd = sd_;
  284. // init XML parser
  285. state = START;
  286. unknownLevel = 0;
  287. memset(&sax_handler, 0, sizeof(sax_handler));
  288. sax_handler.startElement = (startElementSAXFunc)cb_start_element;
  289. sax_handler.endElement = (endElementSAXFunc) cb_end_element;
  290. sax_handler.characters = (charactersSAXFunc) cb_text;
  291. parser_context = xmlCreatePushParserCtxt(&sax_handler, this, 0, 0, 0);
  292. write(sd, "<openmsx-control>", 17);
  293. // event loop
  294. string command; // (partial) input from STDIN
  295. while (true) {
  296. char buf[4096];
  297. fd_set rdfs;
  298. FD_ZERO(&rdfs);
  299. FD_SET(sd, &rdfs);
  300. FD_SET(STDIN_FILENO, &rdfs);
  301. select(sd + 1, &rdfs, NULL, NULL, NULL);
  302. if (FD_ISSET(sd, &rdfs)) {
  303. // data available from openMSX
  304. ssize_t size = read(sd, buf, 4096);
  305. if (size == 0) {
  306. // openmsx process died
  307. break;
  308. }
  309. xmlParseChunk(parser_context, buf, size, 0);
  310. }
  311. if (FD_ISSET(STDIN_FILENO, &rdfs)) {
  312. // data available from STDIN
  313. ssize_t size = read(STDIN_FILENO, buf, 4096);
  314. char* oldpos = buf;
  315. while (true) {
  316. char* pos = (char*)memchr(oldpos, '\n', size);
  317. if (pos) {
  318. unsigned num = pos - oldpos;
  319. command.append(oldpos, num);
  320. sendCommand(command);
  321. command.clear();
  322. oldpos = pos + 1;
  323. size -= num + 1;
  324. } else {
  325. command.append(pos, size);
  326. break;
  327. }
  328. }
  329. }
  330. }
  331. // cleanup
  332. xmlFreeParserCtxt(parser_context);
  333. }
  334. static bool checkSocketDir(const string& dir)
  335. {
  336. struct stat st;
  337. if (stat(dir.c_str(), &st)) {
  338. // cannot stat
  339. return false;
  340. }
  341. if (!S_ISDIR(st.st_mode)) {
  342. // not a directory
  343. return false;
  344. }
  345. #ifndef _WIN32
  346. // only do permission and owner checks on *nix
  347. if ((st.st_mode & 0777) != 0700) {
  348. // wrong permissions
  349. return false;
  350. }
  351. if (st.st_uid != getuid()) {
  352. // wrong uid
  353. return false;
  354. }
  355. #endif
  356. return true;
  357. }
  358. static bool checkSocket(const string& socket)
  359. {
  360. string dir = socket.substr(0, socket.find_last_of('/'));
  361. string name = socket.substr(socket.find_last_of('/') + 1);
  362. if (name.substr(0, 7) != "socket.") {
  363. // wrong name
  364. return false;
  365. }
  366. struct stat st;
  367. if (stat(socket.c_str(), &st)) {
  368. // cannot stat
  369. return false;
  370. }
  371. #ifdef _WIN32
  372. if (!S_ISREG(st.st_mode)) {
  373. // not a regular file
  374. return false;
  375. }
  376. #else
  377. if (!S_ISSOCK(st.st_mode)) {
  378. // not a socket
  379. return false;
  380. }
  381. #endif
  382. #ifndef _WIN32
  383. // only do permission and owner checks on *nix
  384. if ((st.st_mode & 0777) != 0600) {
  385. // check will be different on win32 (!= 777) thus actually useless
  386. // wrong permissions
  387. return false;
  388. }
  389. if (st.st_uid != getuid()) {
  390. // does this work on win32? is this check meaningful?
  391. // wrong uid
  392. return false;
  393. }
  394. #endif
  395. return true;
  396. }
  397. static void deleteSocket(const string& socket)
  398. {
  399. unlink(socket.c_str()); // ignore errors
  400. string dir = socket.substr(0, socket.find_last_of('/'));
  401. rmdir(dir.c_str()); // ignore errors
  402. }
  403. static int openSocket(const string& socketName)
  404. {
  405. if (!checkSocket(socketName)) {
  406. return -1;
  407. }
  408. #ifdef _WIN32
  409. int port = -1;
  410. std::ifstream in(socketName.c_str());
  411. in >> port;
  412. if (port == -1) {
  413. return -1;
  414. }
  415. int sd = socket(AF_INET, SOCK_STREAM, 0);
  416. if (sd == -1) {
  417. return -1;
  418. }
  419. sockaddr_in addr;
  420. memset((char*)&addr, 0, sizeof(addr));
  421. addr.sin_family = AF_INET;
  422. addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  423. addr.sin_port = htons(port);
  424. #else
  425. int sd = socket(AF_UNIX, SOCK_STREAM, 0);
  426. if (sd == -1) {
  427. return -1;
  428. }
  429. sockaddr_un addr;
  430. addr.sun_family = AF_UNIX;
  431. strcpy(addr.sun_path, socketName.c_str());
  432. #endif
  433. if (connect(sd, (sockaddr*)&addr, sizeof(addr)) == -1) {
  434. // It appears to be a socket but we cannot connect to it.
  435. // Must be a stale socket. Try to clean it up.
  436. deleteSocket(socketName);
  437. close(sd);
  438. return -1;
  439. }
  440. return sd;
  441. }
  442. void collectServers(vector<string>& servers)
  443. {
  444. string dir = getTempDir() + "/openmsx-" + getUserName();
  445. if (!checkSocketDir(dir)) {
  446. return;
  447. }
  448. ReadDir readDir(dir);
  449. while (dirent* entry = readDir.getEntry()) {
  450. string socketName = dir + '/' + entry->d_name;
  451. int sd = openSocket(socketName);
  452. if (sd != -1) {
  453. close(sd);
  454. servers.push_back(socketName);
  455. }
  456. }
  457. }
  458. int main()
  459. {
  460. #ifdef _WIN32
  461. WSAData wsaData;
  462. WSAStartup(MAKEWORD(1, 1), &wsaData);
  463. #endif
  464. vector<string> servers;
  465. collectServers(servers);
  466. if (servers.empty()) {
  467. cout << "No running openmsx found." << endl;
  468. return 0;
  469. }
  470. // TODO let the user pick one if there is more than 1
  471. int sd = openSocket(servers.front());
  472. OpenMSXComm comm;
  473. comm.start(sd);
  474. return 0;
  475. }