mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-11-23 18:24:48 +00:00
7871470eb0
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@8039 a592a061-630c-0410-9148-cb99ea01b6c8
568 lines
13 KiB
C
568 lines
13 KiB
C
/**
|
|
* \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>
|
|
|
|
// getenv()
|
|
#include <cstdlib>
|
|
|
|
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 the absolute pathnames of all lyx local sockets
|
|
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
|
|
// and has reading permissions
|
|
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 descriptor: "
|
|
<< 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;
|
|
}
|
|
|
|
// 0 if LYXSOCKET is not set in the environment
|
|
char * serverAddress = getenv("LYXSOCKET");
|
|
int a(vector<char *> const & arg)
|
|
{
|
|
if(arg.size() < 1) {
|
|
cerr << "lyxclient: The option -a requires 1 argument."
|
|
<< endl;
|
|
return -1;
|
|
}
|
|
// -a supercedes LYXSOCKET environment variable
|
|
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 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 1;
|
|
}
|
|
|
|
LyXDataSocket * server;
|
|
|
|
if(cmdline::serverAddress) {
|
|
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(args.isset["-g"] || args.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;
|
|
}
|