--- /dev/null
+/**
+ * \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;
+}
+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>
lyxrow_funcs.h \
lyxserver.C \
lyxserver.h \
+ lyxsocket.C \
+ lyxsocket.h \
lyxtext.h \
lyxtextclass.C \
lyxtextclass.h \
+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'
+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.
}
+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";
class LColor_color;
class LyXFont;
class LyXComm;
+class LyXDataSocket;
+class LyXServerSocket;
class FuncRequest;
/// GUI interaction
* 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
+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
}
+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)
+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
#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"
// FIXME: wrong place !
LyXServer * lyxserver;
+LyXServerSocket * lyxsocket;
namespace {
// 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();
}
// FIXME: breaks emergencyCleanup
+ delete lyxsocket;
delete lyxserver;
}
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)
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()
{
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
signal(SIGFPE, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGTERM, SIG_DFL);
+ signal(SIGPIPE, SIG_DFL);
LyX::emergencyCleanup();
signal(SIGSEGV, error_handler);
signal(SIGINT, error_handler);
signal(SIGTERM, error_handler);
+ signal(SIGPIPE, error_handler);
bool const explicit_userdir = setLyxPaths();
--- /dev/null
+/**
+ * \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;
+ }
+}
--- /dev/null
+// -*- 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
rmdir.C \
snprintf.h \
snprintf.c \
+ socktools.C \
+ socktools.h \
sstream.h \
std_istream.h \
std_ostream.h \
--- /dev/null
+/**
+ * \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
--- /dev/null
+// -*- 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