diff --git a/development/lyxsocket/.cvsignore b/development/lyxsocket/.cvsignore new file mode 100644 index 0000000000..82c1e5ff31 --- /dev/null +++ b/development/lyxsocket/.cvsignore @@ -0,0 +1 @@ +lyxclient diff --git a/development/lyxsocket/lyxclient.C b/development/lyxsocket/lyxclient.C new file mode 100644 index 0000000000..0e7364a7be --- /dev/null +++ b/development/lyxsocket/lyxclient.C @@ -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 +#include +#include +#include + + +// getpid(), getppid() +#include +#include + +// select() +#include + +// opendir(), closedir(), readdir() +#include +#include + +// stat() +#include + +// socket(), connect() +#include +#include + +// fcntl() +#include + + +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('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 lyxSockets(string const & dir, string const & pid) +{ + vector 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(timeout); + to.tv_usec = static_cast((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 : +// 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 : 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 const & args); + std::map helper; + std::map isset; + bool parse(int, char * []); + vector nonopt; +}; + +bool CmdLineParser::parse(int argc, char * argv[]) +{ + int opt = 1; + while(opt < argc) { + vector 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 const & arg) +{ + usage(); + exit(0); +} + +string clientName(support::itoa(::getppid()) + ">" + support::itoa(::getpid())); +int n(vector 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 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 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(arg[0]) + ' ' + + static_cast(arg[1]); + return 2; +} + +char * serverAddress; +int a(vector 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 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 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 addrs = support::lyxSockets(cmdline::mainTmp, cmdline::serverPid); + vector::iterator addr = addrs.begin(); + vector::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; +} diff --git a/src/ChangeLog b/src/ChangeLog index 57be76bc3a..f7f6e49167 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,10 @@ +2003-10-13 Joao Luis Meloni Assirati + + * 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 diff --git a/src/Makefile.am b/src/Makefile.am index aba9d4ea53..9a54e1107a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -209,6 +209,8 @@ lyx_SOURCES = \ lyxrow_funcs.h \ lyxserver.C \ lyxserver.h \ + lyxsocket.C \ + lyxsocket.h \ lyxtext.h \ lyxtextclass.C \ lyxtextclass.h \ diff --git a/src/frontends/ChangeLog b/src/frontends/ChangeLog index 963ae54817..cf56889bf2 100644 --- a/src/frontends/ChangeLog +++ b/src/frontends/ChangeLog @@ -1,3 +1,9 @@ +2003-10-13 Joao Luis Meloni Assirati + + * 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 * lyx_gui.h: add and other small fixes to make Lars' diff --git a/src/frontends/gtk/ChangeLog b/src/frontends/gtk/ChangeLog index 30958b4d12..e7af1db63d 100644 --- a/src/frontends/gtk/ChangeLog +++ b/src/frontends/gtk/ChangeLog @@ -1,3 +1,9 @@ +2003-10-13 Angus Leeming + + * 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 Fix doxygen warnings. diff --git a/src/frontends/gtk/lyx_gui.C b/src/frontends/gtk/lyx_gui.C index ce655c1fc0..c70778345a 100644 --- a/src/frontends/gtk/lyx_gui.C +++ b/src/frontends/gtk/lyx_gui.C @@ -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"; diff --git a/src/frontends/lyx_gui.h b/src/frontends/lyx_gui.h index f8be6a290d..1ef368778b 100644 --- a/src/frontends/lyx_gui.h +++ b/src/frontends/lyx_gui.h @@ -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 diff --git a/src/frontends/qt2/ChangeLog b/src/frontends/qt2/ChangeLog index 40bf47a5ae..bc2557df72 100644 --- a/src/frontends/qt2/ChangeLog +++ b/src/frontends/qt2/ChangeLog @@ -1,3 +1,9 @@ +2003-10-13 Angus Leeming + + * 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 * lyx_gui.C (start): adjust for distpatch change diff --git a/src/frontends/qt2/lyx_gui.C b/src/frontends/qt2/lyx_gui.C index 0b1a1fa405..05f02d1ddd 100644 --- a/src/frontends/qt2/lyx_gui.C +++ b/src/frontends/qt2/lyx_gui.C @@ -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) diff --git a/src/frontends/xforms/ChangeLog b/src/frontends/xforms/ChangeLog index 8bca6e674f..d9801ed0d1 100644 --- a/src/frontends/xforms/ChangeLog +++ b/src/frontends/xforms/ChangeLog @@ -1,3 +1,9 @@ +2003-10-13 Joao Luis Meloni Assirati + + * 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 * lyx_gui.C (start): adjust for dispatch change diff --git a/src/frontends/xforms/lyx_gui.C b/src/frontends/xforms/lyx_gui.C index 29e851a86b..bf3bb46e47 100644 --- a/src/frontends/xforms/lyx_gui.C +++ b/src/frontends/xforms/lyx_gui.C @@ -29,12 +29,13 @@ #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 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::const_iterator cit = files.begin(); vector::const_iterator end = files.end(); @@ -313,6 +317,7 @@ void start(string const & batch, vector 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(data); + client->server()->dataCallback(client); +} + +extern "C" +void C_serversocket_callback(int, void * data) +{ + LyXServerSocket * server = static_cast(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() { diff --git a/src/lyx_main.C b/src/lyx_main.C index f9b845e70e..e813c532e5 100644 --- a/src/lyx_main.C +++ b/src/lyx_main.C @@ -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 index 0000000000..b84a51d15c --- /dev/null +++ b/src/lyxsocket.C @@ -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 + +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::const_iterator client = clients.begin(); +// std::set::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 : 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 index 0000000000..cadea4cbe7 --- /dev/null +++ b/src/lyxsocket.h @@ -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 +#include + +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 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 diff --git a/src/support/Makefile.am b/src/support/Makefile.am index 235d127016..575e74faf1 100644 --- a/src/support/Makefile.am +++ b/src/support/Makefile.am @@ -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 index 0000000000..8e28da55f0 --- /dev/null +++ b/src/support/socktools.C @@ -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 + +#include "socktools.h" +#include "debug.h" +#include "lyxlib.h" + +#include +#include +#include +#include + +#include + +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(&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(0), reinterpret_cast(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 index 0000000000..22a542c558 --- /dev/null +++ b/src/support/socktools.h @@ -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 + +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