lyx_mirror/src/client/client.cpp
Jean-Marc Lasgouttes 39a8000589 Do not use Application for a 'cancel export' boolean
It is not good for a support function to depend of anything outside of
it, especially Application.

Here the boolean that indicates that export should be canceled is put
in Systemcall. This allows to remove all the dummy theApp() function
that have been added here and there for linking needs.
2018-01-02 16:01:07 +01:00

739 lines
15 KiB
C++

/**
* \file client.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author João Luis M. Assirati
* \author Lars Gullik Bjønnes
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "support/ConsoleApplication.h"
#include "support/debug.h"
#include "support/FileName.h"
#include "support/FileNameList.h"
#include "support/lstrings.h"
#include "support/Messages.h"
#include "support/unicode.h"
#include "support/unique_ptr.h"
// getpid(), getppid()
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
// struct timeval
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
// select()
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
// socket(), connect()
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif
#include <sys/un.h>
// fcntl()
#include <fcntl.h>
// strerror()
#include <string.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <string>
#include <vector>
#include <map>
#include <iostream>
using namespace std;
using namespace lyx::support;
namespace lyx {
// Dummy verbose support
bool verbose = false;
// Dummy LyXRC support
struct LyXRC {
string icon_set;
} lyxrc;
// Keep the linker happy on Windows
void lyx_exit(int)
{}
// Dummy language support
Messages const & getGuiMessages()
{
static Messages lyx_messages;
return lyx_messages;
}
Messages const & getMessages(string const &)
{
return getGuiMessages();
}
namespace support {
string itoa(unsigned int i)
{
char buf[20];
sprintf(buf, "%d", i);
return buf;
}
/// Returns the absolute pathnames of all lyx local sockets in
/// file system encoding.
/// Parts stolen from lyx::support::DirList().
FileNameList lyxSockets(string const & dir, string const & pid)
{
FileNameList dirlist;
FileName dirpath(dir + "/");
if (!dirpath.exists() || !dirpath.isDirectory()) {
lyxerr << dir << " does not exist or is not a directory."
<< endl;
return dirlist;
}
FileNameList dirs = dirpath.dirList("");
FileNameList::const_iterator it = dirs.begin();
FileNameList::const_iterator end = dirs.end();
for (; it != end; ++it) {
if (!it->isDirectory())
continue;
string const tmpdir = it->absFileName();
if (!contains(tmpdir, "lyx_tmpdir" + pid))
continue;
FileName lyxsocket(tmpdir + "/lyxsocket");
if (lyxsocket.exists())
dirlist.push_back(lyxsocket);
}
return dirlist;
}
namespace socktools {
/// Connect to the socket \p name.
int connect(FileName const & name)
{
int fd; // File descriptor for the socket
sockaddr_un addr; // Structure that hold the socket address
string const encoded = name.toFilesystemEncoding();
// char sun_path[108]
string::size_type len = encoded.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;
encoded.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 descriptor: "
<< strerror(errno) << endl;
return -1;
}
if (::connect(fd,
reinterpret_cast<struct sockaddr *>(&addr),
sizeof(addr)) == -1) {
cerr << "lyxclient: Could not connect to socket " << name.absFileName()
<< ": " << 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
/////////////////////////////////////////////////////////////////////
//
// 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);
}
/////////////////////////////////////////////////////////////////////
//
// LyXDataSocket
//
/////////////////////////////////////////////////////////////////////
// Modified LyXDataSocket class for use with the client
class LyXDataSocket {
public:
LyXDataSocket(FileName 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(FileName const & address)
{
if ((fd_ = 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 split 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;
}
}
/////////////////////////////////////////////////////////////////////
//
// CmdLineParser
//
/////////////////////////////////////////////////////////////////////
class CmdLineParser {
public:
typedef int (*optfunc)(vector<docstring> const & args);
map<string, optfunc> helper;
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<docstring> args;
if (helper[argv[opt]]) {
isset[argv[opt]] = true;
int arg = opt + 1;
while ((arg < argc) && (!helper[argv[arg]])) {
args.push_back(from_local8bit(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 {
docstring mainTmp(from_ascii("/tmp"));
class StopException : public exception
{
public:
StopException(int status) : status_(status) {}
int status() const { return status_; }
private:
int status_;
};
void usage()
{
cerr <<
"Usage: lyxclient [options]\n"
"Options are:\n"
" -a address set address of the lyx socket\n"
" -t directory set system temporary directory (for detecting sockets)\n"
" -p pid select a running lyx by pidi\n"
" -c command send a single command and quit (LYXCMD prefix needed)\n"
" -g file row send a command to go to file and row\n"
" -n name set client name\n"
" -h name display this help end exit\n"
"If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
"a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
"lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
"cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
"will read commands from standard input and disconnect when command read is BYE:\n"
"\n"
"System directory is: " << to_utf8(cmdline::mainTmp)
<< endl;
}
int h(vector<docstring> const &)
{
usage();
throw StopException(EXIT_SUCCESS);
}
docstring clientName =
from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
int n(vector<docstring> const & arg)
{
if (arg.empty()) {
cerr << "lyxclient: The option -n requires 1 argument."
<< endl;
return -1;
}
clientName = arg[0];
return 1;
}
docstring singleCommand;
int c(vector<docstring> const & arg)
{
if (arg.empty()) {
cerr << "lyxclient: The option -c requires 1 argument."
<< endl;
return -1;
}
singleCommand = arg[0];
return 1;
}
int g(vector<docstring> const & arg)
{
if (arg.size() < 2) {
cerr << "lyxclient: The option -g requires 2 arguments."
<< endl;
return -1;
}
singleCommand = "LYXCMD:command-sequence "
"server-goto-file-row "
+ arg[0] + ' '
+ arg[1] + "; " +
"lyx-activate";
return 2;
}
// empty if LYXSOCKET is not set in the environment
docstring serverAddress;
int a(vector<docstring> const & arg)
{
if (arg.empty()) {
cerr << "lyxclient: The option -a requires 1 argument."
<< endl;
return -1;
}
// -a supercedes LYXSOCKET environment variable
serverAddress = arg[0];
return 1;
}
int t(vector<docstring> const & arg)
{
if (arg.empty()) {
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<docstring> const & arg)
{
if (arg.empty()) {
cerr << "lyxclient: The option -p requires 1 argument."
<< endl;
return -1;
}
serverPid = to_ascii(arg[0]);
return 1;
}
} // namespace cmdline
/// The main application class
class LyXClientApp : public ConsoleApplication
{
public:
LyXClientApp(int & argc, char * argv[])
: ConsoleApplication("client" PROGRAM_SUFFIX, argc, argv),
argc_(argc), argv_(argv)
{
}
void doExec()
{
try {
int const exit_status = run();
exit(exit_status);
}
catch (cmdline::StopException & e) {
exit(e.status());
}
}
private:
int run();
int & argc_;
char ** argv_;
};
int LyXClientApp::run()
{
// qt changes this, and our numeric conversions require the C locale
setlocale(LC_NUMERIC, "C");
// Set defaults
char const * const lyxsocket = getenv("LYXSOCKET");
if (lyxsocket)
cmdline::serverAddress = from_local8bit(lyxsocket);
// Default temporary
cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
// Command line builder
CmdLineParser args;
args.helper["-h"] = cmdline::h;
args.helper["-c"] = cmdline::c;
args.helper["-g"] = cmdline::g;
args.helper["-n"] = cmdline::n;
args.helper["-a"] = cmdline::a;
args.helper["-t"] = cmdline::t;
args.helper["-p"] = cmdline::p;
// Command line failure conditions:
if ((!args.parse(argc_, argv_))
|| (args.isset["-c"] && args.isset["-g"])
|| (args.isset["-a"] && args.isset["-p"])) {
cmdline::usage();
return EXIT_FAILURE;
}
unique_ptr<LyXDataSocket> server;
if (!cmdline::serverAddress.empty()) {
server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
if (!server->connected()) {
cerr << "lyxclient: " << "Could not connect to "
<< to_utf8(cmdline::serverAddress) << endl;
return EXIT_FAILURE;
}
} else {
// We have to look for an address.
// serverPid can be empty.
FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
FileNameList::const_iterator addr = addrs.begin();
FileNameList::const_iterator end = addrs.end();
for (; addr != end; ++addr) {
// Caution: addr->string() is in filesystem encoding
server.reset(new LyXDataSocket(*addr));
if (server->connected())
break;
lyxerr << "lyxclient: " << "Could not connect to "
<< addr->absFileName() << endl;
}
if (addr == end) {
lyxerr << "lyxclient: No suitable server found."
<< endl;
return EXIT_FAILURE;
}
cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
}
int const serverfd = server->fd();
IOWatch iowatch;
iowatch.addfd(serverfd);
// Used to read from server
string answer;
// Send greeting
server->writeln("HELLO:" + to_utf8(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 EXIT_FAILURE;
}
} else {
cerr << "lyxclient: No answer from server." << endl;
return EXIT_FAILURE;
}
if (args.isset["-g"] || args.isset["-c"]) {
server->writeln(to_utf8(cmdline::singleCommand));
iowatch.wait(2.0);
if (iowatch.isset(serverfd) && server->readln(answer)) {
cout << answer;
if (prefixIs(answer, "ERROR:"))
return EXIT_FAILURE;
return EXIT_SUCCESS;
} else {
cerr << "lyxclient: No answer from server." << endl;
return EXIT_FAILURE;
}
}
// Take commands from stdin
iowatch.addfd(0); // stdin
bool saidbye = false;
while ((!saidbye) && server->connected()) {
iowatch.wait();
if (iowatch.isset(0)) {
string command;
getline(cin, command);
if (command.empty())
continue;
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 EXIT_SUCCESS;
}
} // namespace lyx
int main(int argc, char * argv[])
{
lyx::lyxerr.setStream(cerr);
lyx::LyXClientApp app(argc, argv);
return app.exec();
}
namespace boost {
void assertion_failed(char const* a, char const* b, char const* c, long d)
{
lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '
<< d << '\n';
}
} // namespace boost