/** * \file ServerSocket.cpp * 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 #include "ServerSocket.h" #include "debug.h" #include "FuncRequest.h" #include "LyXAction.h" #include "LyXFunc.h" #include "frontends/Application.h" #include "support/environment.h" #include "support/FileName.h" #include "support/lyxlib.h" #include "support/socktools.h" #include #include #if defined (_WIN32) # include #endif using boost::shared_ptr; using std::auto_ptr; using std::endl; using std::string; namespace lyx { // Address is the unix address for the socket. // MAX_CLIENTS is the maximum number of clients // that can connect at the same time. ServerSocket::ServerSocket(LyXFunc * f, support::FileName const & addr) : func(f), fd_(support::socktools::listen(addr, 3)), address_(addr) { if (fd_ == -1) { LYXERR(Debug::LYXSERVER) << "lyx: Disabling LyX socket." << endl; return; } // These env vars are used by DVI inverse search // Needed by xdvi support::setEnv("XEDITOR", "lyxclient -g %f %l"); // Needed by lyxclient support::setEnv("LYXSOCKET", address_.absFilename()); theApp()->registerSocketCallback( fd_, boost::bind(&ServerSocket::serverCallback, this) ); LYXERR(Debug::LYXSERVER) << "lyx: New server socket " << fd_ << ' ' << address_.absFilename() << endl; } // Close the socket and remove the address of the filesystem. ServerSocket::~ServerSocket() { if (fd_ != -1) { BOOST_ASSERT (theApp()); theApp()->unregisterSocketCallback(fd_); if (::close(fd_) != 0) lyxerr << "lyx: Server socket " << fd_ << " IO error on closing: " << strerror(errno); } support::unlink(address_); LYXERR(Debug::LYXSERVER) << "lyx: Server socket quitting" << endl; } string const ServerSocket::address() const { return address_.absFilename(); } // 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 ServerSocket::serverCallback() { int const client_fd = support::socktools::accept(fd_); if (fd_ == -1) { LYXERR(Debug::LYXSERVER) << "lyx: Failed to accept new client" << endl; return; } if (clients.size() >= MAX_CLIENTS) { writeln("BYE:Too many clients connected"); return; } // Register the new client. clients[client_fd] = shared_ptr(new LyXDataSocket(client_fd)); theApp()->registerSocketCallback( client_fd, boost::bind(&ServerSocket::dataCallback, this, client_fd) ); } // Reads and processes input from client and check // if the connection has been closed void ServerSocket::dataCallback(int fd) { shared_ptr client = clients[fd]; 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 = to_utf8(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()) { clients.erase(fd); } } void ServerSocket::writeln(string const & line) { string const linen = line + '\n'; int const size = linen.size(); int const written = ::write(fd_, linen.c_str(), size); if (written < size) { // Always mean end of connection. if (written == -1 && errno == EPIPE) { // The program will also receive a SIGPIPE // that must be caught lyxerr << "lyx: Server 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: Server socket " << fd_ << " IO error: " << strerror(errno); } } } // Debug // void ServerSocket::dump() const // { // lyxerr << "ServerSocket debug dump.\n" // << "fd = " << fd_ << ", address = " << address_.absFilename() << ".\n" // << "Clients: " << clients.size() << ".\n"; // std::map >::const_iterator client = clients.begin(); // std::map >::const_iterator end = clients.end(); // for (; client != end; ++client) // lyxerr << "fd = " << client->first << '\n'; // } LyXDataSocket::LyXDataSocket(int fd) : fd_(fd), connected_(true) { LYXERR(Debug::LYXSERVER) << "lyx: New data socket " << fd_ << endl; } LyXDataSocket::~LyXDataSocket() { if (::close(fd_) != 0) lyxerr << "lyx: Data socket " << fd_ << " IO error on closing: " << strerror(errno); theApp()->unregisterSocketCallback(fd_); LYXERR(Debug::LYXSERVER) << "lyx: Data socket " << fd_ << " quitting." << endl; } 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; // read and store characters in buffer while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) { buffer_.append(charbuf, charbuf + count); } // 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 string::size_type pos = buffer_.find('\n'); if (pos == 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_.erase(0, pos + 1); return true; } // Write a line of the form : to the socket void LyXDataSocket::writeln(string const & line) { string const linen = line + '\n'; int const size = linen.size(); int const written = ::write(fd_, linen.c_str(), size); if (written < size) { // Always 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; } } } // namespace lyx