3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author João Luis M. Assirati
7 * \author Lars Gullik Bjønnes
9 * Full author contact details are available in file CREDITS.
15 #include "support/debug.h"
16 #include "support/FileName.h"
17 #include "support/unicode.h"
18 #include "support/lstrings.h"
20 #include <boost/filesystem/operations.hpp>
21 #include <boost/scoped_ptr.hpp>
23 // getpid(), getppid()
24 #ifdef HAVE_SYS_TYPES_H
25 # include <sys/types.h>
32 #ifdef HAVE_SYS_TIME_H
33 # include <sys/time.h>
37 #ifdef HAVE_SYS_SELECT_H
38 # include <sys/select.h>
41 // socket(), connect()
42 #ifdef HAVE_SYS_SOCKET_H
43 # include <sys/socket.h>
58 using namespace lyx::support;
60 using ::boost::scoped_ptr;
61 namespace fs = ::boost::filesystem;
67 string itoa(unsigned int i)
70 sprintf(buf, "%d", i);
75 /// Returns the absolute pathnames of all lyx local sockets in
76 /// file system encoding.
77 /// Parts stolen from lyx::support::DirList().
78 vector<fs::path> lyxSockets(string const & dir, string const & pid)
80 vector<fs::path> dirlist;
82 fs::path dirpath(dir);
84 if (!fs::exists(dirpath) || !fs::is_directory(dirpath)) {
85 lyxerr << dir << " does not exist or is not a directory."
90 fs::directory_iterator beg((fs::path(dir)));
91 fs::directory_iterator end;
93 for (; beg != end; ++beg) {
94 if (prefixIs(beg->leaf(), "lyx_tmpdir" + pid)) {
95 fs::path lyxsocket = beg->path() / "lyxsocket";
96 if (fs::exists(lyxsocket)) {
97 dirlist.push_back(lyxsocket);
106 namespace socktools {
109 /// Connect to the socket \p name.
110 int connect(FileName const & name)
112 int fd; // File descriptor for the socket
113 sockaddr_un addr; // Structure that hold the socket address
115 string const encoded = name.toFilesystemEncoding();
116 // char sun_path[108]
117 string::size_type len = encoded.size();
119 cerr << "lyxclient: Socket address '" << name
120 << "' too long." << endl;
123 // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
124 addr.sun_family = AF_UNIX;
125 encoded.copy(addr.sun_path, 107);
126 addr.sun_path[len] = '\0';
128 if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
129 cerr << "lyxclient: Could not create socket descriptor: "
130 << strerror(errno) << endl;
134 reinterpret_cast<struct sockaddr *>(&addr),
135 sizeof(addr)) == -1) {
136 cerr << "lyxclient: Could not connect to socket " << name.absFilename()
137 << ": " << strerror(errno) << endl;
141 if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
142 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
143 << strerror(errno) << endl;
151 } // namespace socktools
152 } // namespace support
156 /////////////////////////////////////////////////////////////////////
160 /////////////////////////////////////////////////////////////////////
182 void IOWatch::clear()
188 void IOWatch::addfd(int fd)
194 bool IOWatch::wait(double timeout)
197 to.tv_sec = static_cast<long int>(timeout);
198 to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
200 return select(FD_SETSIZE, &act,
201 (fd_set *)0, (fd_set *)0, &to);
208 return select(FD_SETSIZE, &act,
209 (fd_set *)0, (fd_set *)0, (timeval *)0);
213 bool IOWatch::isset(int fd)
215 return FD_ISSET(fd, &act);
220 /////////////////////////////////////////////////////////////////////
224 /////////////////////////////////////////////////////////////////////
226 // Modified LyXDataSocket class for use with the client
227 class LyXDataSocket {
229 LyXDataSocket(FileName const &);
231 // File descriptor of the connection
234 bool connected() const;
235 // Line buffered input from the socket
236 bool readln(string &);
237 // Write the string + '\n' to the socket
238 void writeln(string const &);
240 // File descriptor for the data socket
242 // True if the connection is up
244 // buffer for input data
249 LyXDataSocket::LyXDataSocket(FileName const & address)
251 if ((fd_ = support::socktools::connect(address)) == -1) {
259 LyXDataSocket::~LyXDataSocket()
265 int LyXDataSocket::fd() const
271 bool LyXDataSocket::connected() const
277 // Returns true if there was a complete line to input
278 // A line is of the form <key>:<value>
279 // A line not of this form will not be passed
280 // The line read is splitted and stored in 'key' and 'value'
281 bool LyXDataSocket::readln(string & line)
283 int const charbuf_size = 100;
284 char charbuf[charbuf_size]; // buffer for the ::read() system call
286 string::size_type pos;
288 // read and store characters in buffer
289 while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
290 charbuf[count] = '\0'; // turn it into a c string
294 // Error conditions. The buffer must still be
295 // processed for lines read
296 if (count == 0) { // EOF -- connection closed
298 } else if ((count == -1) && (errno != EAGAIN)) { // IO error
299 cerr << "lyxclient: IO error." << endl;
303 // Cut a line from buffer
304 if ((pos = buffer.find('\n')) == string::npos)
305 return false; // No complete line stored
306 line = buffer.substr(0, pos);
307 buffer = buffer.substr(pos + 1);
312 // Write a line of the form <key>:<value> to the socket
313 void LyXDataSocket::writeln(string const & line)
315 string linen(line + '\n');
316 int size = linen.size();
317 int written = ::write(fd_, linen.c_str(), size);
318 if (written < size) { // Allways mean end of connection.
319 if ((written == -1) && (errno == EPIPE)) {
320 // The program will also receive a SIGPIPE
321 // that must be catched
322 cerr << "lyxclient: connection closed while writing."
325 // Anything else, including errno == EAGAIN, must be
326 // considered IO error. EAGAIN should never happen
327 // when line is small
328 cerr << "lyxclient: IO error: " << strerror(errno);
335 /////////////////////////////////////////////////////////////////////
339 /////////////////////////////////////////////////////////////////////
341 class CmdLineParser {
343 typedef int (*optfunc)(vector<docstring> const & args);
344 map<string, optfunc> helper;
345 map<string, bool> isset;
346 bool parse(int, char * []);
347 vector<char *> nonopt;
351 bool CmdLineParser::parse(int argc, char * argv[])
355 vector<docstring> args;
356 if (helper[argv[opt]]) {
357 isset[argv[opt]] = true;
359 while ((arg < argc) && (!helper[argv[arg]])) {
360 args.push_back(from_local8bit(argv[arg]));
363 int taken = helper[argv[opt]](args);
368 if (argv[opt][0] == '-') {
369 if ((argv[opt][1] == '-')
370 && (argv[opt][2]== '\0')) {
373 nonopt.push_back(argv[opt]);
378 cerr << "lyxclient: unknown option "
379 << argv[opt] << endl;
383 nonopt.push_back(argv[opt]);
389 // ~Class CmdLineParser -------------------------------------------------------
398 "Usage: lyxclient [options]\n"
400 " -a address set address of the lyx socket\n"
401 " -t directory set system temporary directory\n"
402 " -p pid select a running lyx by pidi\n"
403 " -c command send a single command and quit\n"
404 " -g file row send a command to go to file and row\n"
405 " -n name set client name\n"
406 " -h name display this help end exit\n"
407 "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
408 "a running lyx. If -t is not set, 'directory' defaults to /tmp. If -p is set,\n"
409 "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
410 "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
411 "will read commands from standard input and disconnect when command read is BYE:"
416 int h(vector<docstring> const &)
423 docstring clientName =
424 from_ascii(support::itoa(::getppid()) + ">" + support::itoa(::getpid()));
426 int n(vector<docstring> const & arg)
428 if (arg.size() < 1) {
429 cerr << "lyxclient: The option -n requires 1 argument."
438 docstring singleCommand;
441 int c(vector<docstring> const & arg)
443 if (arg.size() < 1) {
444 cerr << "lyxclient: The option -c requires 1 argument."
448 singleCommand = arg[0];
453 int g(vector<docstring> const & arg)
455 if (arg.size() < 2) {
456 cerr << "lyxclient: The option -g requires 2 arguments."
460 singleCommand = "LYXCMD:server-goto-file-row "
467 // empty if LYXSOCKET is not set in the environment
468 docstring serverAddress;
471 int a(vector<docstring> const & arg)
473 if (arg.size() < 1) {
474 cerr << "lyxclient: The option -a requires 1 argument."
478 // -a supercedes LYXSOCKET environment variable
479 serverAddress = arg[0];
484 docstring mainTmp(from_ascii("/tmp"));
487 int t(vector<docstring> const & arg)
489 if (arg.size() < 1) {
490 cerr << "lyxclient: The option -t requires 1 argument."
499 string serverPid; // Init to empty string
502 int p(vector<docstring> const & arg)
504 if (arg.size() < 1) {
505 cerr << "lyxclient: The option -p requires 1 argument."
509 serverPid = to_ascii(arg[0]);
514 } // namespace cmdline
518 int main(int argc, char * argv[])
521 lyxerr.setStream(cerr);
523 char const * const lyxsocket = getenv("LYXSOCKET");
525 cmdline::serverAddress = from_local8bit(lyxsocket);
528 args.helper["-h"] = cmdline::h;
529 args.helper["-c"] = cmdline::c;
530 args.helper["-g"] = cmdline::g;
531 args.helper["-n"] = cmdline::n;
532 args.helper["-a"] = cmdline::a;
533 args.helper["-t"] = cmdline::t;
534 args.helper["-p"] = cmdline::p;
536 // Command line failure conditions:
537 if ((!args.parse(argc, argv))
538 || (args.isset["-c"] && args.isset["-g"])
539 || (args.isset["-a"] && args.isset["-p"])) {
544 scoped_ptr<LyXDataSocket> server;
546 if (!cmdline::serverAddress.empty()) {
547 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
548 if (!server->connected()) {
549 cerr << "lyxclient: " << "Could not connect to "
550 << to_utf8(cmdline::serverAddress) << endl;
554 // We have to look for an address.
555 // serverPid can be empty.
556 vector<fs::path> addrs = support::lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
557 vector<fs::path>::const_iterator addr = addrs.begin();
558 vector<fs::path>::const_iterator end = addrs.end();
559 for (; addr != end; ++addr) {
560 // Caution: addr->string() is in filesystem encoding
561 server.reset(new LyXDataSocket(FileName(to_utf8(from_filesystem8bit(addr->string())))));
562 if (server->connected())
564 lyxerr << "lyxclient: " << "Could not connect to "
565 << addr->string() << endl;
568 lyxerr << "lyxclient: No suitable server found."
572 cerr << "lyxclient: " << "Connected to " << addr->string() << endl;
575 int const serverfd = server->fd();
578 iowatch.addfd(serverfd);
580 // Used to read from server
584 server->writeln("HELLO:" + to_utf8(cmdline::clientName));
585 // wait at most 2 seconds until server responds
587 if (iowatch.isset(serverfd) && server->readln(answer)) {
588 if (prefixIs(answer, "BYE:")) {
589 cerr << "lyxclient: Server disconnected." << endl;
590 cout << answer << endl;
594 cerr << "lyxclient: No answer from server." << endl;
598 if (args.isset["-g"] || args.isset["-c"]) {
599 server->writeln(to_utf8(cmdline::singleCommand));
601 if (iowatch.isset(serverfd) && server->readln(answer)) {
603 if (prefixIs(answer, "ERROR:"))
607 cerr << "lyxclient: No answer from server." << endl;
612 // Take commands from stdin
613 iowatch.addfd(0); // stdin
614 bool saidbye = false;
615 while ((!saidbye) && server->connected()) {
617 if (iowatch.isset(0)) {
620 if (command == "BYE:") {
621 server->writeln("BYE:");
624 server->writeln("LYXCMD:" + command);
627 if (iowatch.isset(serverfd)) {
628 while(server->readln(answer))
629 cout << answer << endl;
639 void assertion_failed(char const* a, char const* b, char const* c, long d)
641 lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '