Hello,

I wrote a simple local socket interface for lyx that compiles and runs
well in Linux with gcc 3.3 (Debian unstable) with the purpose of getting
inverse dvi search working, for witch I will send a patch in my next
e-mail.

It consists of two classes:

  LyXDataSocket - this class has a socket in the connected state and two
  functions readln() and writeln() for line buffered IO.

  LyXServerSocket - this class has a socket in the listening state. Each
  time a new connection arrives, it allocates a new LyXDataSocket.

These are intended to substitute the lyxserver based on pipes, but can
coexist with it. Up to now, the socket server is used only by the xforms
frontend. Of course, to substitute the pipe lixserver, it has to be ported
to qt and also run on all the operating systems the pipe lyxserver runs.

To compile, apply the patch lyxsocket.diff, copy

  LyXSocket.{C,h} in src
  socktools.{C,h} in src/support

and run autogen and configure. Also attached is the corresponding client
lyxclient.C. Compile it with

  g++ -o lyxclient lyxclient.C

Once lyx starts a socket special file named lyxsocket will be created on
its temporary directory, say

  /tmp/lyx_tmpdir511223hyx2/lyxsocket

To send only one comand to this running lyx, use

  lyxclient -a /tmp/lyx_tmpdir511223hyx2/lyxsocket -c <command>

Several lyxes can run at the same time! To send more than one command,
don't use the switch -c and type directly on lyxclient standard input. If
the -a switch is not used, lyxclient will try to find a running lyx and
connect to it. 'lyxclient -h' should provide a comprehensive help.

If this patch gets accepted, I will provide Changelog entries and also
write the lyxclient manpage.

Regards,
Joćo.
/**
 * \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;


// support ------------------------------------------------------------
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


}
// 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;
}
// -*- 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>

using std::string;

namespace lyx
{
namespace support
{
namespace socktools
{

int listen(string const &, int);
int accept(int);

}
}
}

#endif // SOCKTOOLS_H
/**
 * \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 "socktools.h"
#include "debug.h"
#include "lyxlib.h"

#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>

#include <cerrno>

using std::strerror;
using std::endl;

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
// -*- 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>

using std::string;

class LyXServerSocket;
class LyXDataSocket;

//typedef void (*serversocket_callback_t)(LyXServerSocket *);
//typedef void (*datasocket_callback_t)(LyXDataSocket *);
//void add_serversocket_callback(serversocket_callback_t, LyXServerSocket *);
//void add_datasocket_callback(datasocket_callback_t, LyXDataSocket *);
//void remove_datasocket_callback(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 *, string const &);
	~LyXServerSocket();
	// File descriptor of the socket
	int fd() const;
	// Address of the local socket
	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
	string address_;
	// Maximum number of simultaneous clients
	enum {
                MAX_CLIENTS = 10
        };
	// All connections
	std::set<LyXDataSocket *> clients;
	//void dump() const;
};


// 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(string &);
	// Write the string + '\n' to the socket
	void writeln(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
	string buffer;
};


#endif // LYXSOCKET_H
/**
 * \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 "support/socktools.h"
#include "support/lyxlib.h"
#include "frontends/lyx_gui.h"
#include "debug.h"
#include "lyxfunc.h"
#include "LyXSocket.h"

#include <iostream>

using std::endl;

// 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)
{
        //dump();
        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");
                } else {
                        string const key = line.substr(0, pos);
                        string const value = line.substr(pos + 1);
                        if (key == "LYXCMD") {
                                func->dispatch(value);
                                string const rval = func->getMessage();
                                if (func->errorStat()) {
                                        client->writeln("ERROR:" + value + ':' + rval);
                                } else {
                                        client->writeln("INFO:" + value + ':' + 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;
        }
}
Index: src/lyx_main.C
===================================================================
RCS file: /cvs/lyx/lyx-devel/src/lyx_main.C,v
retrieving revision 1.178
diff -u -r1.178 lyx_main.C
--- src/lyx_main.C	2003/10/06 15:42:20	1.178
+++ src/lyx_main.C	2003/10/08 05:08:52
@@ -214,6 +214,13 @@
 	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 @@
 	signal(SIGFPE, SIG_DFL);
 	signal(SIGSEGV, SIG_DFL);
 	signal(SIGTERM, SIG_DFL);
+	signal(SIGPIPE, SIG_DFL);
 
 	LyX::emergencyCleanup();
 
@@ -250,6 +258,7 @@
 	signal(SIGSEGV, error_handler);
 	signal(SIGINT, error_handler);
 	signal(SIGTERM, error_handler);
+	signal(SIGPIPE, error_handler);
 
 	bool const explicit_userdir = setLyxPaths();
 
Index: src/Makefile.am
===================================================================
RCS file: /cvs/lyx/lyx-devel/src/Makefile.am,v
retrieving revision 1.192
diff -u -r1.192 Makefile.am
--- src/Makefile.am	2003/09/17 16:44:51	1.192
+++ src/Makefile.am	2003/10/08 05:08:52
@@ -88,6 +88,8 @@
 	latexrunparams.h \
 	LyXAction.C \
 	LyXAction.h \
+	LyXSocket.C \
+	LyXSocket.h \
 	MenuBackend.C \
 	MenuBackend.h \
 	paragraph_funcs.C \
Index: src/support/Makefile.am
===================================================================
RCS file: /cvs/lyx/lyx-devel/src/support/Makefile.am,v
retrieving revision 1.70
diff -u -r1.70 Makefile.am
--- src/support/Makefile.am	2003/10/07 13:32:17	1.70
+++ src/support/Makefile.am	2003/10/08 05:08:52
@@ -65,6 +65,8 @@
 	rmdir.C \
 	snprintf.h \
 	snprintf.c \
+	socktools.C \
+	socktools.h \
 	sstream.h \
 	std_istream.h \
 	std_ostream.h \
Index: src/frontends/lyx_gui.h
===================================================================
RCS file: /cvs/lyx/lyx-devel/src/frontends/lyx_gui.h,v
retrieving revision 1.23
diff -u -r1.23 lyx_gui.h
--- src/frontends/lyx_gui.h	2003/10/07 06:45:24	1.23
+++ src/frontends/lyx_gui.h	2003/10/08 05:08:55
@@ -22,6 +22,8 @@
 class LColor_color;
 class LyXFont;
 class LyXComm;
+class LyXDataSocket;
+class LyXServerSocket;
 class FuncRequest;
 
 /// GUI interaction
@@ -94,12 +96,16 @@
  * 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: src/frontends/xforms/lyx_gui.C
===================================================================
RCS file: /cvs/lyx/lyx-devel/src/frontends/xforms/lyx_gui.C,v
retrieving revision 1.54
diff -u -r1.54 lyx_gui.C
--- src/frontends/xforms/lyx_gui.C	2003/10/06 15:42:57	1.54
+++ src/frontends/xforms/lyx_gui.C	2003/10/08 05:08:56
@@ -27,6 +27,7 @@
 #include "lyxfunc.h"
 #include "lyxrc.h"
 #include "lyxserver.h"
+#include "LyXSocket.h"
 
 #include "graphics/LoaderQueue.h"
 
@@ -67,6 +68,7 @@
 
 // FIXME: wrong place !
 LyXServer * lyxserver;
+LyXServerSocket * lyxsocket;
 
 namespace {
 
@@ -287,6 +289,8 @@
 	// 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();
@@ -311,6 +315,7 @@
 	}
 
 	// FIXME: breaks emergencyCleanup
+	delete lyxsocket;
 	delete lyxserver;
 }
 
@@ -380,6 +385,20 @@
 	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)
@@ -387,12 +406,30 @@
 	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()
 {

Reply via email to