]> git.lyx.org Git - features.git/commitdiff
Jo�o Assirati's lyxsocket patch.
authorAngus Leeming <leeming@lyx.org>
Mon, 13 Oct 2003 12:25:11 +0000 (12:25 +0000)
committerAngus Leeming <leeming@lyx.org>
Mon, 13 Oct 2003 12:25:11 +0000 (12:25 +0000)
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@7910 a592a061-630c-0410-9148-cb99ea01b6c8

18 files changed:
development/lyxsocket/.cvsignore [new file with mode: 0644]
development/lyxsocket/lyxclient.C [new file with mode: 0644]
src/ChangeLog
src/Makefile.am
src/frontends/ChangeLog
src/frontends/gtk/ChangeLog
src/frontends/gtk/lyx_gui.C
src/frontends/lyx_gui.h
src/frontends/qt2/ChangeLog
src/frontends/qt2/lyx_gui.C
src/frontends/xforms/ChangeLog
src/frontends/xforms/lyx_gui.C
src/lyx_main.C
src/lyxsocket.C [new file with mode: 0644]
src/lyxsocket.h [new file with mode: 0644]
src/support/Makefile.am
src/support/socktools.C [new file with mode: 0644]
src/support/socktools.h [new file with mode: 0644]

diff --git a/development/lyxsocket/.cvsignore b/development/lyxsocket/.cvsignore
new file mode 100644 (file)
index 0000000..82c1e5f
--- /dev/null
@@ -0,0 +1 @@
+lyxclient
diff --git a/development/lyxsocket/lyxclient.C b/development/lyxsocket/lyxclient.C
new file mode 100644 (file)
index 0000000..0e7364a
--- /dev/null
@@ -0,0 +1,564 @@
+/**
+ * \file lyxclient.C
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author João Luis M. Assirati
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include <string>
+#include <vector>
+#include <map>
+#include <iostream>
+
+
+// getpid(), getppid()
+#include <sys/types.h>
+#include <unistd.h>
+
+// select()
+#include <sys/select.h>
+
+// opendir(), closedir(), readdir()
+#include <sys/types.h>
+#include <dirent.h>
+
+// stat()
+#include <sys/stat.h>
+
+// socket(), connect()
+#include <sys/socket.h>
+#include <sys/un.h>
+
+// fcntl()
+#include <fcntl.h>
+
+
+using std::string;
+using std::vector;
+using std::cout;
+using std::cerr;
+using std::cin;
+using std::endl;
+
+
+namespace support {
+
+string itoa(unsigned int i)
+{
+       string str;
+       if(!i) {
+               str = "0";
+       } else {
+               while((0 < i) && (i <= 9)) {
+                       str = static_cast<char>('0' + i % 10) + str;
+                       i /= 10;
+               }
+       }
+       return str;
+}
+
+
+bool prefixIs(string const & a, string const & pre)
+{
+       char const * p_a = a.c_str();
+       char const * p_pre = pre.c_str();
+       while ((*p_a != '\0') && (*p_pre != '\0') && (*p_a == *p_pre)) {
+               ++p_a;
+               ++p_pre;
+       }
+       if (*p_pre == '\0') return true; 
+       return false;
+}
+
+
+// Parts stolen from lyx::support::DirList()
+// Returns pathnames begining with dir and ending with
+// pathname separator (/ in unix)
+vector<string> lyxSockets(string const & dir, string const & pid)
+{
+       vector<string> dirlist;
+       DIR * dirp = ::opendir(dir.c_str());
+       if (!dirp) {
+               cerr << "lyxclient: Could not read dir " << dir
+                    << ": " << strerror(errno);
+               return dirlist;
+       }
+
+       dirent * dire;
+       while ((dire = ::readdir(dirp))) {
+               string const fil = dire->d_name;
+               if (prefixIs(fil, "lyx_tmpdir" + pid)) {
+                       string lyxsocket = dir + '/' + fil + "/lyxsocket";
+                       struct stat status;
+                       // not checking if it is a socket -- just if it exists
+                       if (!::stat(lyxsocket.c_str(), &status)) {
+                               dirlist.push_back(lyxsocket);
+                       }
+               }
+       }
+
+       ::closedir(dirp);
+       return dirlist;
+}
+
+namespace socktools {
+
+int connect(string const & name)
+{
+       int fd; // File descriptor for the socket
+       sockaddr_un addr; // Structure that hold the socket address
+
+       // char sun_path[108]
+       string::size_type len = name.size();
+       if(len > 107) {
+               cerr << "lyxclient: Socket address '" << name
+                    << "' too long." << endl;
+               return -1;
+       }
+       // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
+       addr.sun_family = AF_UNIX;
+       name.copy(addr.sun_path, 107);
+       addr.sun_path[len] = '\0';
+
+       if((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
+               cerr << "lyxclient: Could not create socket: "
+                    << strerror(errno) << endl;
+               return -1;
+       }
+       if(::connect(fd, (struct sockaddr *)&addr, SUN_LEN(&addr)) == -1) {
+               cerr << "lyxclient: Could not connect to socket " << name
+                    << ": " << strerror(errno) << endl;
+               ::close(fd);
+               return -1;
+        }
+       if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
+               cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
+                    << strerror(errno) << endl;
+               ::close(fd);
+               return -1;
+       }
+       return fd;
+}
+
+} // namespace socktools
+} // namespace support
+
+
+
+// Class IOWatch ------------------------------------------------------------
+class IOWatch 
+{
+public:
+       IOWatch();
+       void clear();
+       void addfd(int);
+       bool wait(double);
+       bool wait();
+       bool isset(int fd);
+                       
+private:
+       fd_set des;
+       fd_set act;
+};
+
+IOWatch::IOWatch() {
+       clear();
+}
+void IOWatch::clear() {
+       FD_ZERO(&des);
+}
+void IOWatch::addfd(int fd) {
+       FD_SET(fd, &des);
+}
+bool IOWatch::wait(double timeout) {
+       timeval to;
+       to.tv_sec = static_cast<long int>(timeout);
+       to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
+       act = des;
+       return select(FD_SETSIZE, &act,
+                     (fd_set *)0, (fd_set *)0, &to);
+}
+bool IOWatch::wait() {
+       act = des;
+       return select(FD_SETSIZE, &act,
+                     (fd_set *)0, (fd_set *)0, (timeval *)0);
+}
+bool IOWatch::isset(int fd) {
+       return FD_ISSET(fd, &act);
+}
+// ~Class IOWatch ------------------------------------------------------------
+
+
+// Class LyXDataSocket -------------------------------------------------------
+// Modified LyXDataSocket class for use with the client
+class LyXDataSocket
+{
+public:
+       LyXDataSocket(string const &);
+       ~LyXDataSocket();
+       // File descriptor of the connection
+       int fd() const;
+       // Connection status
+       bool connected() const;
+       // Line buffered input from the socket  
+       bool readln(string &);
+       // Write the string + '\n' to the socket
+       void writeln(string const &);
+
+private:
+       // File descriptor for the data socket
+       int fd_;
+       // True if the connection is up
+       bool connected_;
+       // buffer for input data
+       string buffer;
+};
+
+LyXDataSocket::LyXDataSocket(string const & address)
+{
+       if ((fd_ = support::socktools::connect(address)) == -1) {
+               connected_ = false;
+       } else {
+               connected_ = true;
+       }
+}
+
+LyXDataSocket::~LyXDataSocket()
+{
+       ::close(fd_);
+}
+
+int LyXDataSocket::fd() const
+{
+       return fd_;
+}
+
+bool LyXDataSocket::connected() const
+{
+       return connected_;
+}
+
+// Returns true if there was a complete line to input
+// A line is of the form <key>:<value>
+//   A line not of this form will not be passed
+// The line read is splitted and stored in 'key' and 'value'
+bool LyXDataSocket::readln(string & line)
+{
+       int const charbuf_size = 100;
+        char charbuf[charbuf_size]; // buffer for the ::read() system call
+       int count;
+       string::size_type pos;
+
+       // read and store characters in buffer
+       while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
+               charbuf[count] = '\0'; // turn it into a c string
+               buffer += charbuf;
+       }
+
+       // Error conditions. The buffer must still be
+       // processed for lines read
+       if (count == 0) { // EOF -- connection closed
+               connected_ = false;
+       } else if ((count == -1) && (errno != EAGAIN)) { // IO error
+               cerr << "lyxclient: IO error." << endl;
+               connected_ = false;
+       }
+
+       // Cut a line from buffer
+       if ((pos = buffer.find('\n')) == string::npos)
+               return false; // No complete line stored
+       line = buffer.substr(0, pos);
+       buffer = buffer.substr(pos + 1);
+       return true;
+}
+
+// Write a line of the form <key>:<value> to the socket
+void LyXDataSocket::writeln(string const & line)
+{
+       string linen(line + '\n');
+       int size = linen.size();
+       int written = ::write(fd_, linen.c_str(), size);
+       if (written < size) { // Allways mean end of connection.
+               if ((written == -1) && (errno == EPIPE)) {
+                       // The program will also receive a SIGPIPE
+                       // that must be catched
+                       cerr << "lyxclient: connection closed while writing."
+                            << endl;
+               } else {
+                       // Anything else, including errno == EAGAIN, must be
+                       // considered IO error. EAGAIN should never happen
+                       // when line is small
+                       cerr << "lyxclient: IO error: " << strerror(errno);
+               }
+               connected_ = false;
+       }
+}
+// ~Class LyXDataSocket -------------------------------------------------------
+
+
+// Class CmdLineParser -------------------------------------------------------
+class CmdLineParser
+{
+public:
+       typedef int (*optfunc)(vector<char *> const & args);
+       std::map<string, optfunc> helper;
+       std::map<string, bool> isset;
+       bool parse(int, char * []);
+       vector<char *> nonopt;
+};
+
+bool CmdLineParser::parse(int argc, char * argv[])
+{
+       int opt = 1;
+       while(opt < argc) {
+               vector<char *> args;
+               if(helper[argv[opt]]) {
+                       isset[argv[opt]] = true;
+                       int arg = opt + 1;
+                       while((arg < argc) && (!helper[argv[arg]])) {
+                               args.push_back(argv[arg]);
+                               ++arg;
+                       }
+                       int taken = helper[argv[opt]](args);
+                       if(taken == -1) return false;
+                       opt += 1 + taken;
+               } else {
+                       if(argv[opt][0] == '-') {
+                               if((argv[opt][1] == '-')
+                                  && (argv[opt][2]== '\0')) {
+                                       ++opt;
+                                       while(opt < argc) {
+                                               nonopt.push_back(argv[opt]);
+                                               ++opt;
+                                       }
+                                       return true;
+                               } else {
+                                       cerr << "lyxclient: unknown option "
+                                            << argv[opt] << endl;
+                                       return false;
+                               }
+                       }
+                       nonopt.push_back(argv[opt]);
+                       ++opt;
+               }
+       }
+       return true;
+}
+// ~Class CmdLineParser -------------------------------------------------------
+
+
+
+namespace cmdline
+{
+void usage() 
+{
+       cerr << "Usage: lyxclient [options]" << endl
+            << "Options are:" << endl
+            << "  -a address    set address of the lyx socket" << endl
+            << "  -t directory  set system temporary directory" << endl
+            << "  -p pid        select a running lyx by pid" << endl
+            << "  -c command    send a single command and quit" << endl
+            << "  -g file row   send a command to go to file and row" << endl
+            << "  -n name       set client name" << endl
+            << "  -h name       display this help end exit" << endl
+            << "If -a is not used, lyxclient will use the arguments of -t and -p to look for" << endl
+            << "a running lyx. If -t is not set, 'directory' defaults to /tmp. If -p is set," << endl
+            << "lyxclient will connect only to a lyx with the specified pid. Options -c and -g" << endl
+            << "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient" << endl
+            << "will read commands from standard input and disconnect when command read is BYE:"
+            << endl;
+}
+
+int h(vector<char *> const & arg)
+{
+       usage();
+       exit(0);
+}
+
+string clientName(support::itoa(::getppid()) + ">" + support::itoa(::getpid()));
+int n(vector<char *> const & arg)
+{
+       if(arg.size() < 1) {
+               cerr << "lyxclient: The option -n requires 1 argument."
+                    << endl;
+               return -1;
+       }
+       clientName = arg[0];
+       return 1;
+}
+
+string singleCommand;
+int c(vector<char *> const & arg)
+{
+       if(arg.size() < 1) {
+               cerr << "lyxclient: The option -c requires 1 argument."
+                    << endl;
+               return -1;
+       }
+       singleCommand = arg[0];
+       return 1;
+}
+
+int g(vector<char *> const & arg)
+{
+       if(arg.size() < 2) {
+               cerr << "lyxclient: The option -g requires 2 arguments."
+                    << endl;
+               return -1;
+       }
+       singleCommand = "LYXCMD:server-goto-file-row "
+               + static_cast<string>(arg[0]) + ' '
+               + static_cast<string>(arg[1]);
+       return 2;
+}
+
+char * serverAddress;
+int a(vector<char *> const & arg)
+{
+       if(arg.size() < 1) {
+               cerr << "lyxclient: The option -a requires 1 argument."
+                    << endl;
+               return -1;
+       }
+       serverAddress = arg[0];
+       return 1;
+}
+
+string mainTmp("/tmp");
+int t(vector<char *> const & arg)
+{
+       if(arg.size() < 1) {
+               cerr << "lyxclient: The option -t requires 1 argument."
+                    << endl;
+               return -1;
+       }
+       mainTmp = arg[0];
+       return 1;
+}
+
+string serverPid; // Init to empty string
+int p(vector<char *> const & arg)
+{
+       if(arg.size() < 1) {
+               cerr << "lyxclient: The option -p requires 1 argument."
+                    << endl;
+               return -1;
+       }
+       serverPid = arg[0];
+       return 1;
+}
+
+} // namespace cmdline
+
+using support::prefixIs;
+
+int main(int argc, char * argv[])
+{
+       CmdLineParser parser;
+       parser.helper["-h"] = cmdline::h;
+       parser.helper["-c"] = cmdline::c;
+       parser.helper["-g"] = cmdline::g;
+       parser.helper["-n"] = cmdline::n;
+       parser.helper["-a"] = cmdline::a;
+       parser.helper["-t"] = cmdline::t;
+       parser.helper["-p"] = cmdline::p;
+       // Command line failure conditions:
+       if((!parser.parse(argc, argv))
+          || (parser.isset["-c"] && parser.isset["-g"])
+          || (parser.isset["-a"] && parser.isset["-p"])) {
+               cmdline::usage();
+               return 1;
+       }
+
+       LyXDataSocket * server;
+
+       if(parser.isset["-a"]) {
+               server = new LyXDataSocket(cmdline::serverAddress);
+               if(!server->connected()) {
+                       cerr << "lyxclient: " << "Could not connect to "
+                            << cmdline::serverAddress << endl;
+                       return 1;
+               }
+       } else {
+               // We have to look for an address.
+               // serverPid can be empty.
+               vector<string> addrs = support::lyxSockets(cmdline::mainTmp, cmdline::serverPid);
+               vector<string>::iterator addr = addrs.begin();
+               vector<string>::iterator end = addrs.end();
+               while (addr < end) {
+                       server = new LyXDataSocket(*addr);
+                       if(server->connected()) break;
+                       cerr << "lyxclient: " << "Could not connect to "
+                            << *addr << endl;
+                       delete server;
+                       ++addr;
+               }
+               if(addr == end) {
+                       cerr << "lyxclient: No suitable server found." << endl;
+                       return 1;
+               }
+               cerr << "lyxclient: " << "Connected to " << *addr << endl;
+       }
+       
+       int const serverfd = server->fd();
+       
+       IOWatch iowatch;
+       iowatch.addfd(serverfd);
+
+       // Used to read from server
+       string answer;
+
+       // Send greeting
+       server->writeln("HELLO:" + cmdline::clientName);
+       // wait at most 2 seconds until server responds
+       iowatch.wait(2.0);
+       if(iowatch.isset(serverfd) && server->readln(answer)) {
+               if(prefixIs(answer, "BYE:")) {
+                       cerr << "lyxclient: Server disconnected." << endl;
+                       cout << answer << endl;
+                       return 1;
+               }
+       } else {
+               cerr << "lyxclient: No answer from server." << endl;
+               return 1;
+       }
+
+       if(parser.isset["-g"] || parser.isset["-c"]) {
+               server->writeln(cmdline::singleCommand);
+               iowatch.wait(2.0);
+               if(iowatch.isset(serverfd) && server->readln(answer)) {
+                       cout << answer;
+                       if(prefixIs(answer, "ERROR:")) return 1;
+                       return 0;
+               } else {
+                       cerr << "lyxclient: No answer from server." << endl;
+                       return 1;
+               }
+       }
+       
+       // Take commands from stdin
+       iowatch.addfd(0); // stdin
+       bool saidbye = false;
+       while((!saidbye) && server->connected()) {
+               iowatch.wait();
+               if(iowatch.isset(0)) {
+                       string command;
+                       cin >> command;
+                       if(command == "BYE:") {
+                               server->writeln("BYE:");
+                               saidbye = true;
+                       } else {
+                               server->writeln("LYXCMD:" + command);
+                       }
+               }
+               if(iowatch.isset(serverfd)) {
+                       while(server->readln(answer))
+                               cout << answer << endl;
+               }
+       }
+
+       return 0;
+}
index 57be76bc3aca5ac0c917200c343557cb892a0919..f7f6e49167ca331e6812f6c5ae09038eebb2b78a 100644 (file)
@@ -1,3 +1,10 @@
+2003-10-13  Joao Luis Meloni Assirati <assirati@fma.if.usp.br>
+
+       * lyxsocket.[Ch]: new files. A simple local socket interface for lyx.
+
+       * Makefile.am: add lyxsocket.[Ch].
+
+       * lyx_main.C (error_handler): handle SIGPIPE.
 
 2003-10-13  André Pönitz  <poenitz@gmx.net>
 
index aba9d4ea5359330c4f980b99f900951e9d3acef2..9a54e1107a12cfc8c9718b5745a3b4833d8d89bb 100644 (file)
@@ -209,6 +209,8 @@ lyx_SOURCES = \
        lyxrow_funcs.h \
        lyxserver.C \
        lyxserver.h \
+       lyxsocket.C \
+       lyxsocket.h \
        lyxtext.h \
        lyxtextclass.C \
        lyxtextclass.h \
index 963ae548174cf18838d842e98327f2cd68071db3..cf56889bf2e6ab2a2f63cce0a7f61170ecce8817 100644 (file)
@@ -1,3 +1,9 @@
+2003-10-13  Joao Luis Meloni Assirati <assirati@fma.if.usp.br>
+
+       * lyx_gui.h (set_datasocket_callback, set_serversocket_callback,
+       remove_datasocket_callback, remove_serversocket_callback):
+       new function declarations, implemented in the various frontends.
+
 2003-10-07  Martin Vermeer  <martin.vermeer@hut.fi>
 
        * lyx_gui.h: add <string> and other small fixes to make Lars'
index 30958b4d1257153ab87e234a66358d74c38b2dc2..e7af1db63d2deec4deb9ce8a448f02bed6269732 100644 (file)
@@ -1,3 +1,9 @@
+2003-10-13  Angus Leeming  <leeming@lyx.org>
+
+       * lyx_gui.C (set_datasocket_callback, set_serversocket_callback,
+       remove_datasocket_callback, remove_serversocket_callback):
+       placeholder functions, enabling the frontend to be linked.
+
 2003-10-08  Angus Leeming  <leeming@lyx.org>
 
        Fix doxygen warnings.
index ce655c1fc0990ecee837452092ad504c4b69f8e9..c70778345a96012bee14f817322bf7ccf791b4fc 100644 (file)
@@ -435,6 +435,22 @@ void lyx_gui::remove_read_callback(int fd)
 }
 
 
+void set_datasocket_callback(LyXDataSocket * /* p */)
+{}
+
+
+void remove_datasocket_callback(LyXDataSocket * /* p */)
+{}
+
+
+void set_serversocket_callback(LyXServerSocket * /* p */)
+{}
+
+
+void remove_serversocket_callback(LyXServerSocket * /* p */)
+{}
+
+
 string const lyx_gui::roman_font_name()
 {
        return "times";
index f8be6a290d0143d01b11bab62de938e6b2a5ca09..1ef368778b61201739b969e6e70cbefd16960e4a 100644 (file)
@@ -22,6 +22,8 @@ class Dialogs;
 class LColor_color;
 class LyXFont;
 class LyXComm;
+class LyXDataSocket;
+class LyXServerSocket;
 class FuncRequest;
 
 /// GUI interaction
@@ -94,12 +96,16 @@ bool font_available(LyXFont const & font);
  * add a callback for I/O read notification
  */
 void set_read_callback(int fd, LyXComm * comm);
+void set_datasocket_callback(LyXDataSocket *);
+void set_serversocket_callback(LyXServerSocket *);
 
 /**
  * remove a I/O read callback
  * @param fd file descriptor
  */
 void remove_read_callback(int fd);
+void remove_datasocket_callback(LyXDataSocket *);
+void remove_serversocket_callback(LyXServerSocket *);
 
 } // namespace lyx_gui
 
index 40bf47a5aed1917264285b41ab9a7787c689b709..bc2557df72c1ce77473e4cf13a13cd0cbdf7382e 100644 (file)
@@ -1,3 +1,9 @@
+2003-10-13  Angus Leeming  <leeming@lyx.org>
+
+       * lyx_gui.C (set_datasocket_callback, set_serversocket_callback,
+       remove_datasocket_callback, remove_serversocket_callback):
+       placeholder functions, enabling the frontend to be linked.
+
 2003-10-13  Lars Gullik Bjønnes  <larsbj@gullik.net>
 
        * lyx_gui.C (start): adjust for distpatch change
index 0b1a1fa4051dde39d998ff2c1e3b42e9fc4edb90..05f02d1dddd100d653aa60e57757ded6c496282a 100644 (file)
@@ -236,6 +236,22 @@ void remove_read_callback(int fd)
 }
 
 
+void set_datasocket_callback(LyXDataSocket * /* p */)
+{}
+
+
+void remove_datasocket_callback(LyXDataSocket * /* p */)
+{}
+
+
+void set_serversocket_callback(LyXServerSocket * /* p */)
+{}
+
+
+void remove_serversocket_callback(LyXServerSocket * /* p */)
+{}
+
+
 string const roman_font_name()
 {
        if (!use_gui)
index 8bca6e674f2a798f3c38c984dee5861990d69b53..d9801ed0d1015eaeb75a66cf8aaf16a5cea5c03a 100644 (file)
@@ -1,3 +1,9 @@
+2003-10-13  Joao Luis Meloni Assirati <assirati@fma.if.usp.br>
+
+       * lyx_gui.C (set_datasocket_callback, set_serversocket_callback,
+       remove_datasocket_callback, remove_serversocket_callback):
+       functions to inform the core when the socket has changed state.
+
 2003-10-13  Lars Gullik Bjønnes  <larsbj@gullik.net>
 
        * lyx_gui.C (start): adjust for dispatch change
index 29e851a86beb1ff10c9fbde5c9ff703c11ec8842..bf3bb46e479c8b2dfa8a82f910a9c1d175c037b9 100644 (file)
 #include "lyxfunc.h"
 #include "lyxrc.h"
 #include "lyxserver.h"
+#include "lyxsocket.h"
 
 #include "graphics/LoaderQueue.h"
 
+#include "support/filetools.h"
 #include "support/lyxlib.h"
 #include "support/os.h"
-#include "support/filetools.h"
 #include "support/path_defines.h"
 
 #include "lyx_forms.h"
@@ -69,6 +70,7 @@ extern BufferList bufferlist;
 
 // FIXME: wrong place !
 LyXServer * lyxserver;
+LyXServerSocket * lyxsocket;
 
 namespace {
 
@@ -289,6 +291,8 @@ void start(string const & batch, vector<string> const & files)
        // FIXME: some code below needs moving
 
        lyxserver = new LyXServer(&view.getLyXFunc(), lyxrc.lyxpipes);
+       lyxsocket = new LyXServerSocket(&view.getLyXFunc(),
+                         os::slashify_path(os::getTmpDir() + "/lyxsocket"));
 
        vector<string>::const_iterator cit = files.begin();
        vector<string>::const_iterator end = files.end();
@@ -313,6 +317,7 @@ void start(string const & batch, vector<string> const & files)
        }
 
        // FIXME: breaks emergencyCleanup
+       delete lyxsocket;
        delete lyxserver;
 }
 
@@ -382,6 +387,20 @@ void C_read_callback(int, void * data)
        comm->read_ready();
 }
 
+extern "C"
+void C_datasocket_callback(int, void * data)
+{
+       LyXDataSocket * client = static_cast<LyXDataSocket *>(data);
+       client->server()->dataCallback(client);
+}
+
+extern "C"
+void C_serversocket_callback(int, void * data)
+{
+       LyXServerSocket * server = static_cast<LyXServerSocket *>(data);
+       server->serverCallback();
+}
+
 }
 
 void set_read_callback(int fd, LyXComm * comm)
@@ -389,12 +408,30 @@ void set_read_callback(int fd, LyXComm * comm)
        fl_add_io_callback(fd, FL_READ, C_read_callback, comm);
 }
 
-
 void remove_read_callback(int fd)
 {
        fl_remove_io_callback(fd, FL_READ, C_read_callback);
 }
 
+void set_datasocket_callback(LyXDataSocket * p)
+{
+       fl_add_io_callback(p->fd(), FL_READ, C_datasocket_callback, p);
+}
+
+void remove_datasocket_callback(LyXDataSocket * p)
+{
+       fl_remove_io_callback(p->fd(), FL_READ, C_datasocket_callback);
+}
+
+void set_serversocket_callback(LyXServerSocket * p)
+{
+       fl_add_io_callback(p->fd(), FL_READ, C_serversocket_callback, p);
+}
+
+void remove_serversocket_callback(LyXServerSocket * p)
+{
+       fl_remove_io_callback(p->fd(), FL_READ, C_serversocket_callback);
+}
 
 string const roman_font_name()
 {
index f9b845e70e8852e5461f0d74c17a96600f44d387..e813c532e5c8f9a089f16a74fcaf5b674841a647 100644 (file)
@@ -214,6 +214,13 @@ static void error_handler(int err_sig)
        case SIGTERM:
                // no comments
                break;
+       case SIGPIPE:
+               // This will be received if lyx tries to write to a socket
+               // whose reading end was closed. It can safely be ignored,
+               // as in this case the ::write() system call will return -1
+               // and errno will be set to EPIPE
+               return;
+               //break;
        }
 
        // Deinstall the signal handlers
@@ -222,6 +229,7 @@ static void error_handler(int err_sig)
        signal(SIGFPE, SIG_DFL);
        signal(SIGSEGV, SIG_DFL);
        signal(SIGTERM, SIG_DFL);
+       signal(SIGPIPE, SIG_DFL);
 
        LyX::emergencyCleanup();
 
@@ -250,6 +258,7 @@ void LyX::init(bool gui)
        signal(SIGSEGV, error_handler);
        signal(SIGINT, error_handler);
        signal(SIGTERM, error_handler);
+       signal(SIGPIPE, error_handler);
 
        bool const explicit_userdir = setLyxPaths();
 
diff --git a/src/lyxsocket.C b/src/lyxsocket.C
new file mode 100644 (file)
index 0000000..b84a51d
--- /dev/null
@@ -0,0 +1,252 @@
+/**
+ * \file lyxsocket.C
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Lars Gullik Bjønnes
+ * \author Jean-Marc Lasgouttes
+ * \author Angus Leeming
+ * \author John Levon
+ * \author João Luis M. Assirati
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include "lyxsocket.h"
+
+#include "debug.h"
+#include "funcrequest.h"
+#include "LyXAction.h"
+#include "lyxfunc.h"
+
+#include "frontends/lyx_gui.h"
+
+#include "support/lyxlib.h"
+#include "support/socktools.h"
+
+#include <iostream>
+
+using std::endl;
+using std::string;
+
+
+// Address is the unix address for the socket.
+// MAX_CLIENTS is the maximum number of clients
+// that can connect at the same time.
+LyXServerSocket::LyXServerSocket(LyXFunc * f, string const & addr)
+       : func(f),
+         fd_(lyx::support::socktools::listen(addr, MAX_CLIENTS)),
+         address_(addr)
+{
+       if (fd_ == -1) {
+               lyxerr << "lyx: Disabling LyX socket." << endl;
+               return;
+       }
+       lyx_gui::set_serversocket_callback(this);
+       lyxerr[Debug::LYXSERVER] << "lyx: New server socket "
+                                << fd_ << ' ' << address_ << endl;
+}
+
+
+// Close the socket and remove the address of the filesystem.
+LyXServerSocket::~LyXServerSocket()
+{
+       ::close(fd_);
+       lyx::support::unlink(address_);
+       while (!clients.empty()) close(*clients.rbegin());
+       lyxerr[Debug::LYXSERVER] << "lyx: Server socket quitting" << endl;
+}
+
+
+int LyXServerSocket::fd() const
+{
+       return fd_;
+}
+
+
+string const & LyXServerSocket::address() const
+{
+       return address_;
+}
+
+
+// Creates a new LyXDataSocket and checks to see if the connection
+// is OK and if the number of clients does not exceed MAX_CLIENTS
+void LyXServerSocket::serverCallback()
+{
+       LyXDataSocket * client = new LyXDataSocket(this);
+       if (client->connected()) {
+               if (clients.size() == MAX_CLIENTS) {
+                       client->writeln("BYE:Too many clients connected");
+               } else {
+                       clients.insert(client);
+                       lyx_gui::set_datasocket_callback(client);
+                       return;
+               }
+       }
+       delete client;
+}
+
+
+// Reads and processes input from client and check
+// if the connection has been closed
+void LyXServerSocket::dataCallback(LyXDataSocket * client)
+{
+       string line;
+       string::size_type pos;
+       bool saidbye = false;
+       while ((!saidbye) && client->readln(line)) {
+               // The protocol must be programmed here
+               // Split the key and the data
+               if ((pos = line.find(':')) == string::npos) {
+                       client->writeln("ERROR:" + line + ":malformed message");
+                       continue;
+               }
+
+               string const key = line.substr(0, pos);
+               if (key == "LYXCMD") {
+                       string const cmd = line.substr(pos + 1);
+                       func->dispatch(lyxaction.lookupFunc(cmd));
+                       string const rval = func->getMessage();
+                       if (func->errorStat()) {
+                               client->writeln("ERROR:" + cmd + ':' + rval);
+                       } else {
+                               client->writeln("INFO:" + cmd + ':' + rval);
+                       }
+               } else if (key == "HELLO") {
+                       // no use for client name!
+                       client->writeln("HELLO:");
+               } else if (key == "BYE") {
+                       saidbye = true;
+               } else {
+                       client->writeln("ERROR:unknown key " + key);
+               }
+       }
+
+       if (saidbye || (!client->connected())) {
+               close(client);
+       }
+}
+
+
+// Removes client callback and deletes client object
+void LyXServerSocket::close(LyXDataSocket * client)
+{
+       lyx_gui::remove_datasocket_callback(client);
+       clients.erase(client);
+       delete client;
+}
+
+// Debug
+// void LyXServerSocket::dump() const
+// {
+//     lyxerr << "LyXServerSocket debug dump.\n"
+//          << "fd = " << fd_ << ", address = " << address_ << ".\n"
+//          << "Clients: " << clients.size() << ".\n";
+//     if (!clients.empty()) {
+//             std::set<LyXDataSocket *>::const_iterator client = clients.begin();
+//             std::set<LyXDataSocket *>::const_iterator end = clients.end();
+//             for (; client != end; ++client)
+//                     lyxerr << "fd = " << (*client)->fd() << "\n";
+//     }
+// }
+
+
+LyXDataSocket::LyXDataSocket(LyXServerSocket * serv)
+       :server_(serv),
+        fd_(lyx::support::socktools::accept(serv->fd()))
+{
+       if (fd_ == -1) {
+               connected_ = false;
+       } else {
+               lyxerr[Debug::LYXSERVER] << "lyx: New data socket " << fd_ << endl;
+               connected_ = true;
+       }
+}
+
+
+LyXDataSocket::~LyXDataSocket()
+{
+       ::close(fd_);
+       lyxerr[Debug::LYXSERVER] << "lyx: Data socket " << fd_ << " quitting." << endl;
+}
+
+
+LyXServerSocket * LyXDataSocket::server() const
+{
+       return server_;
+}
+
+
+int LyXDataSocket::fd() const
+{
+       return fd_;
+}
+
+
+bool LyXDataSocket::connected() const
+{
+       return connected_;
+}
+
+
+// Returns true if there was a complete line to input
+bool LyXDataSocket::readln(string & line)
+{
+       int const charbuf_size = 100;
+        char charbuf[charbuf_size]; // buffer for the ::read() system call
+       int count;
+       string::size_type pos;
+
+       // read and store characters in buffer
+       while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
+               charbuf[count] = '\0'; // turn it into a c string
+               buffer += charbuf;
+       }
+
+       // Error conditions. The buffer must still be
+       // processed for lines read
+       if (count == 0) { // EOF -- connection closed
+               lyxerr[Debug::LYXSERVER] << "lyx: Data socket " << fd_
+                                        << ": connection closed." << endl;
+               connected_ = false;
+       } else if ((count == -1) && (errno != EAGAIN)) { // IO error
+               lyxerr << "lyx: Data socket " << fd_
+                      << ": IO error." << endl;
+               connected_ = false;
+       }
+
+       // Cut a line from buffer
+       if ((pos = buffer.find('\n')) == string::npos) {
+               lyxerr[Debug::LYXSERVER] << "lyx: Data socket " << fd_
+                                        << ": line not completed." << endl;
+               return false; // No complete line stored
+       }
+       line = buffer.substr(0, pos);
+       buffer = buffer.substr(pos + 1);
+       return true;
+}
+
+
+// Write a line of the form <key>:<value> to the socket
+void LyXDataSocket::writeln(string const & line)
+{
+       string linen(line + '\n');
+       int size = linen.size();
+       int written = ::write(fd_, linen.c_str(), size);
+       if (written < size) { // Allways mean end of connection.
+               if ((written == -1) && (errno == EPIPE)) {
+                       // The program will also receive a SIGPIPE
+                       // that must be catched
+                       lyxerr << "lyx: Data socket " << fd_
+                              << " connection closed while writing." << endl;
+               } else {
+                       // Anything else, including errno == EAGAIN, must be
+                       // considered IO error. EAGAIN should never happen
+                       // when line is small
+                       lyxerr << "lyx: Data socket " << fd_
+                            << " IO error: " << strerror(errno);
+               }
+               connected_ = false;
+       }
+}
diff --git a/src/lyxsocket.h b/src/lyxsocket.h
new file mode 100644 (file)
index 0000000..cadea4c
--- /dev/null
@@ -0,0 +1,96 @@
+// -*- C++ -*-
+/**
+ * \file lyxsocket.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Lars Gullik Bjønnes
+ * \author Jean-Marc Lasgouttes
+ * \author João Luis M. Assirati
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef LYXSOCKET_H
+#define LYXSOCKET_H
+
+#include "support/socktools.h"
+#include "lyxfunc.h"
+
+#include <string>
+#include <set>
+
+class LyXServerSocket;
+class LyXDataSocket;
+
+/** Sockets can be in two states: listening and connected.
+ *  Connected sockets are used to transfer data, and will therefore
+ *  be called Data Sockets. Listening sockets are used to create
+ *  Data Sockets when clients connect, and therefore will be called
+ * Server Sockets.
+
+ * This class encapsulates local (unix) server socket operations and
+ * manages LyXDataSockets objects that are created when clients connect.
+ */
+class LyXServerSocket
+{
+public:
+       LyXServerSocket(LyXFunc *, std::string const &);
+       ~LyXServerSocket();
+       /// File descriptor of the socket
+       int fd() const;
+       /// Address of the local socket
+       std::string const & address() const;
+       /// To be called when there is activity in the server socket
+       void serverCallback();
+       /// To be called when there is activity in the data socket
+       void dataCallback(LyXDataSocket *);
+
+private:
+       /// Close the connection to the argument client
+       void close(LyXDataSocket *);
+
+       LyXFunc * func;
+       /// File descriptor for the server socket
+       int fd_;
+       /// Stores the socket filename
+       std::string address_;
+       /// Maximum number of simultaneous clients
+       enum {
+                MAX_CLIENTS = 10
+        };
+       /// All connections
+       std::set<LyXDataSocket *> clients;
+};
+
+
+/** This class encapsulates data socket operations.
+ *  It provides read and write IO operations on the socket.
+ */
+class LyXDataSocket
+{
+public:
+       LyXDataSocket(LyXServerSocket *);
+       ~LyXDataSocket();
+       /// The object that allocated us
+       LyXServerSocket * server() const;
+       /// File descriptor of the connection
+       int fd() const;
+       /// Connection status
+       bool connected() const;
+       /// Line buffered input from the socket 
+       bool readln(std::string &);
+       /// Write the string + '\n' to the socket
+       void writeln(std::string const &);
+
+private:
+       LyXServerSocket * server_;
+       /// File descriptor for the data socket
+       int fd_;
+       /// True if the connection is up
+       bool connected_;
+       /// buffer for input data
+       std::string buffer;
+};
+
+#endif // LYXSOCKET_H
index 235d127016d21fe89a34c808b45b033ee38ae0fd..575e74faf1c05cf54e8085011cb415b37d7aa539 100644 (file)
@@ -65,6 +65,8 @@ libsupport_la_SOURCES = \
        rmdir.C \
        snprintf.h \
        snprintf.c \
+       socktools.C \
+       socktools.h \
        sstream.h \
        std_istream.h \
        std_ostream.h \
diff --git a/src/support/socktools.C b/src/support/socktools.C
new file mode 100644 (file)
index 0000000..8e28da5
--- /dev/null
@@ -0,0 +1,128 @@
+/**
+ * \file socktools.C
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author João Luis M. Assirati
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include <config.h>
+
+#include "socktools.h"
+#include "debug.h"
+#include "lyxlib.h"
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <cerrno>
+
+using std::endl;
+using std::strerror;
+
+using std::string;
+
+
+namespace lyx {
+namespace support {
+namespace socktools {
+
+// Returns a local socket already in the "listen" state (or -1 in case
+// of error). The first argument is the socket address, the second
+// is the length of the queue for connections. If successful, a socket
+// special file 'name' will be created in the filesystem.
+int listen(string const & name, int queue)
+{
+       int fd; // File descriptor for the socket
+       sockaddr_un addr; // Structure that hold the socket address
+
+       // We use 'name' to fill 'addr'
+       string::size_type len = name.size();
+       // the field sun_path in sockaddr_un is a char[108]
+       if (len > 107) {
+               lyxerr << "lyx: Socket address '" << name << "' too long."
+                      << endl;
+               return -1;
+       }
+       // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
+       addr.sun_family = AF_UNIX;
+       name.copy(addr.sun_path, 107);
+       addr.sun_path[len] = '\0';
+
+       // This creates a file descriptor for the socket
+       // Synonims for PF_UNIX are PF_LOCAL and PF_FILE
+       // For local sockets, the protocol is always 0
+       // socket() returns -1 in case of error
+       if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
+               lyxerr << "lyx: Could not create socket descriptor: "
+                      << strerror(errno) << endl;
+               return -1;
+       }
+
+       // Set NONBLOCK mode for the file descriptor
+       if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
+               lyxerr << "lyx: Could not set NONBLOCK mode for socket descriptor: "
+                    << strerror(errno) << endl;
+               ::close(fd);
+               return -1;
+       }
+
+       // bind() gives the local address 'name' for 'fd', also creating
+       // the socket special file in the filesystem. bind() returns -1
+       // in case of error
+       if ((::bind (fd, reinterpret_cast<sockaddr *>(&addr), SUN_LEN(&addr))) == -1) {
+               lyxerr << "lyx: Could not bind address '" << name
+                      << "' to socket descriptor: " << strerror(errno) << endl;
+               ::close(fd);
+               lyx::support::unlink(name);
+               return -1;
+       }
+
+       // Puts the socket in listen state, that is, ready to accept
+       // connections. The second parameter of listen() defines the
+       // maximum length the queue of pending connections may grow to.
+       // It is not a restriction on the number of connections the socket
+       // can accept. Returns -1 in case of error
+       if (::listen (fd, queue) == -1) {
+               lyxerr << "lyx: Could not put socket in 'listen' state: "
+                      << strerror(errno) << endl;
+               ::close(fd);
+               lyx::support::unlink(name);
+               return -1;
+       }
+
+       return fd;
+}
+
+// Returns a file descriptor for a new connection from the socket
+// descriptor 'sd' (or -1 in case of error)
+int accept(int sd)
+{
+       int fd;
+
+       // Returns the new file descriptor or -1 in case of error
+       // Using null pointers for the second and third arguments
+       // dismiss all information about the connecting client
+       if ((fd = accept(sd, reinterpret_cast<sockaddr *>(0), reinterpret_cast<socklen_t *>(0))) == -1) {
+               lyxerr << "lyx: Could not accept connection: "
+                      << strerror(errno) << endl;
+               return -1;
+       }
+
+       // Sets NONBLOCK mode for the file descriptor
+       if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
+               lyxerr << "lyx: Could not set NONBLOCK mode for connection: "
+                      << strerror(errno) << endl;
+               ::close(fd);
+               return -1;
+       }
+       return fd;
+}
+
+} // namespace socktools
+} // namespace support
+} // namespace lyx
diff --git a/src/support/socktools.h b/src/support/socktools.h
new file mode 100644 (file)
index 0000000..22a542c
--- /dev/null
@@ -0,0 +1,28 @@
+// -*- C++ -*-
+/**
+ * \file socktools.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author João Luis M. Assirati
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef SOCKTOOLS_H
+#define SOCKTOOLS_H
+
+#include <string>
+
+namespace lyx {
+namespace support {
+namespace socktools {
+
+int listen(std::string const &, int);
+int accept(int);
+
+} // namespace socktools
+} // namespace support
+} // namespace lyx
+
+#endif // NOT SOCKTOOLS_H