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/FileNameList.h"
18 #include "support/lstrings.h"
19 #include "support/unicode.h"
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>
62 using namespace lyx::support;
64 using ::boost::scoped_ptr;
68 // Dummy LyXRC support
73 // Keep the linker happy on Windows
79 string itoa(unsigned int i)
82 sprintf(buf, "%d", i);
87 /// Returns the absolute pathnames of all lyx local sockets in
88 /// file system encoding.
89 /// Parts stolen from lyx::support::DirList().
90 FileNameList lyxSockets(string const & dir, string const & pid)
94 FileName dirpath(dir + "/");
96 if (!dirpath.exists() || !dirpath.isDirectory()) {
97 lyxerr << dir << " does not exist or is not a directory."
102 FileNameList dirs = dirpath.dirList("");
103 FileNameList::const_iterator it = dirs.begin();
104 FileNameList::const_iterator end = dirs.end();
106 for (; it != end; ++it) {
107 if (!it->isDirectory())
109 string const tmpdir = it->absFileName();
110 if (!contains(tmpdir, "lyx_tmpdir" + pid))
113 FileName lyxsocket(tmpdir + "/lyxsocket");
114 if (lyxsocket.exists())
115 dirlist.push_back(lyxsocket);
122 namespace socktools {
125 /// Connect to the socket \p name.
126 int connect(FileName const & name)
128 int fd; // File descriptor for the socket
129 sockaddr_un addr; // Structure that hold the socket address
131 string const encoded = name.toFilesystemEncoding();
132 // char sun_path[108]
133 string::size_type len = encoded.size();
135 cerr << "lyxclient: Socket address '" << name
136 << "' too long." << endl;
139 // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
140 addr.sun_family = AF_UNIX;
141 encoded.copy(addr.sun_path, 107);
142 addr.sun_path[len] = '\0';
144 if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
145 cerr << "lyxclient: Could not create socket descriptor: "
146 << strerror(errno) << endl;
150 reinterpret_cast<struct sockaddr *>(&addr),
151 sizeof(addr)) == -1) {
152 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
153 << ": " << strerror(errno) << endl;
157 if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
158 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
159 << strerror(errno) << endl;
167 } // namespace socktools
168 } // namespace support
172 /////////////////////////////////////////////////////////////////////
176 /////////////////////////////////////////////////////////////////////
198 void IOWatch::clear()
204 void IOWatch::addfd(int fd)
210 bool IOWatch::wait(double timeout)
213 to.tv_sec = static_cast<long int>(timeout);
214 to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
216 return select(FD_SETSIZE, &act,
217 (fd_set *)0, (fd_set *)0, &to);
224 return select(FD_SETSIZE, &act,
225 (fd_set *)0, (fd_set *)0, (timeval *)0);
229 bool IOWatch::isset(int fd)
231 return FD_ISSET(fd, &act);
236 /////////////////////////////////////////////////////////////////////
240 /////////////////////////////////////////////////////////////////////
242 // Modified LyXDataSocket class for use with the client
243 class LyXDataSocket {
245 LyXDataSocket(FileName const &);
247 // File descriptor of the connection
250 bool connected() const;
251 // Line buffered input from the socket
252 bool readln(string &);
253 // Write the string + '\n' to the socket
254 void writeln(string const &);
256 // File descriptor for the data socket
258 // True if the connection is up
260 // buffer for input data
265 LyXDataSocket::LyXDataSocket(FileName const & address)
267 if ((fd_ = socktools::connect(address)) == -1) {
275 LyXDataSocket::~LyXDataSocket()
281 int LyXDataSocket::fd() const
287 bool LyXDataSocket::connected() const
293 // Returns true if there was a complete line to input
294 // A line is of the form <key>:<value>
295 // A line not of this form will not be passed
296 // The line read is splitted and stored in 'key' and 'value'
297 bool LyXDataSocket::readln(string & line)
299 int const charbuf_size = 100;
300 char charbuf[charbuf_size]; // buffer for the ::read() system call
302 string::size_type pos;
304 // read and store characters in buffer
305 while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
306 charbuf[count] = '\0'; // turn it into a c string
310 // Error conditions. The buffer must still be
311 // processed for lines read
312 if (count == 0) { // EOF -- connection closed
314 } else if ((count == -1) && (errno != EAGAIN)) { // IO error
315 cerr << "lyxclient: IO error." << endl;
319 // Cut a line from buffer
320 if ((pos = buffer.find('\n')) == string::npos)
321 return false; // No complete line stored
322 line = buffer.substr(0, pos);
323 buffer = buffer.substr(pos + 1);
328 // Write a line of the form <key>:<value> to the socket
329 void LyXDataSocket::writeln(string const & line)
331 string linen(line + '\n');
332 int size = linen.size();
333 int written = ::write(fd_, linen.c_str(), size);
334 if (written < size) { // Allways mean end of connection.
335 if ((written == -1) && (errno == EPIPE)) {
336 // The program will also receive a SIGPIPE
337 // that must be catched
338 cerr << "lyxclient: connection closed while writing."
341 // Anything else, including errno == EAGAIN, must be
342 // considered IO error. EAGAIN should never happen
343 // when line is small
344 cerr << "lyxclient: IO error: " << strerror(errno);
351 /////////////////////////////////////////////////////////////////////
355 /////////////////////////////////////////////////////////////////////
357 class CmdLineParser {
359 typedef int (*optfunc)(vector<docstring> const & args);
360 map<string, optfunc> helper;
361 map<string, bool> isset;
362 bool parse(int, char * []);
363 vector<char *> nonopt;
367 bool CmdLineParser::parse(int argc, char * argv[])
371 vector<docstring> args;
372 if (helper[argv[opt]]) {
373 isset[argv[opt]] = true;
375 while ((arg < argc) && (!helper[argv[arg]])) {
376 args.push_back(from_local8bit(argv[arg]));
379 int taken = helper[argv[opt]](args);
384 if (argv[opt][0] == '-') {
385 if ((argv[opt][1] == '-')
386 && (argv[opt][2]== '\0')) {
389 nonopt.push_back(argv[opt]);
394 cerr << "lyxclient: unknown option "
395 << argv[opt] << endl;
399 nonopt.push_back(argv[opt]);
405 // ~Class CmdLineParser -------------------------------------------------------
411 docstring mainTmp(from_ascii("/tmp"));
417 "Usage: lyxclient [options]\n"
419 " -a address set address of the lyx socket\n"
420 " -t directory set system temporary directory (for detecting sockets)\n"
421 " -p pid select a running lyx by pidi\n"
422 " -c command send a single command and quit (LYXCMD prefix needed)\n"
423 " -g file row send a command to go to file and row\n"
424 " -n name set client name\n"
425 " -h name display this help end exit\n"
426 "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
427 "a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
428 "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
429 "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
430 "will read commands from standard input and disconnect when command read is BYE:\n"
432 "System directory is: " << to_utf8(cmdline::mainTmp)
437 int h(vector<docstring> const &)
444 docstring clientName =
445 from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
447 int n(vector<docstring> const & arg)
449 if (arg.size() < 1) {
450 cerr << "lyxclient: The option -n requires 1 argument."
459 docstring singleCommand;
462 int c(vector<docstring> const & arg)
464 if (arg.size() < 1) {
465 cerr << "lyxclient: The option -c requires 1 argument."
469 singleCommand = arg[0];
474 int g(vector<docstring> const & arg)
476 if (arg.size() < 2) {
477 cerr << "lyxclient: The option -g requires 2 arguments."
481 singleCommand = "LYXCMD:server-goto-file-row "
488 // empty if LYXSOCKET is not set in the environment
489 docstring serverAddress;
492 int a(vector<docstring> const & arg)
494 if (arg.size() < 1) {
495 cerr << "lyxclient: The option -a requires 1 argument."
499 // -a supercedes LYXSOCKET environment variable
500 serverAddress = arg[0];
507 int t(vector<docstring> const & arg)
509 if (arg.size() < 1) {
510 cerr << "lyxclient: The option -t requires 1 argument."
519 string serverPid; // Init to empty string
522 int p(vector<docstring> const & arg)
524 if (arg.size() < 1) {
525 cerr << "lyxclient: The option -p requires 1 argument."
529 serverPid = to_ascii(arg[0]);
534 } // namespace cmdline
538 int main(int argc, char * argv[])
541 lyxerr.setStream(cerr);
545 char const * const lyxsocket = getenv("LYXSOCKET");
547 cmdline::serverAddress = from_local8bit(lyxsocket);
550 cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
552 // Command line builder
554 args.helper["-h"] = cmdline::h;
555 args.helper["-c"] = cmdline::c;
556 args.helper["-g"] = cmdline::g;
557 args.helper["-n"] = cmdline::n;
558 args.helper["-a"] = cmdline::a;
559 args.helper["-t"] = cmdline::t;
560 args.helper["-p"] = cmdline::p;
562 // Command line failure conditions:
563 if ((!args.parse(argc, argv))
564 || (args.isset["-c"] && args.isset["-g"])
565 || (args.isset["-a"] && args.isset["-p"])) {
570 scoped_ptr<LyXDataSocket> server;
572 if (!cmdline::serverAddress.empty()) {
573 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
574 if (!server->connected()) {
575 cerr << "lyxclient: " << "Could not connect to "
576 << to_utf8(cmdline::serverAddress) << endl;
580 // We have to look for an address.
581 // serverPid can be empty.
582 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
583 FileNameList::const_iterator addr = addrs.begin();
584 FileNameList::const_iterator end = addrs.end();
585 for (; addr != end; ++addr) {
586 // Caution: addr->string() is in filesystem encoding
587 server.reset(new LyXDataSocket(*addr));
588 if (server->connected())
590 lyxerr << "lyxclient: " << "Could not connect to "
591 << addr->absFileName() << endl;
594 lyxerr << "lyxclient: No suitable server found."
598 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
601 int const serverfd = server->fd();
604 iowatch.addfd(serverfd);
606 // Used to read from server
610 server->writeln("HELLO:" + to_utf8(cmdline::clientName));
611 // wait at most 2 seconds until server responds
613 if (iowatch.isset(serverfd) && server->readln(answer)) {
614 if (prefixIs(answer, "BYE:")) {
615 cerr << "lyxclient: Server disconnected." << endl;
616 cout << answer << endl;
620 cerr << "lyxclient: No answer from server." << endl;
624 if (args.isset["-g"] || args.isset["-c"]) {
625 server->writeln(to_utf8(cmdline::singleCommand));
627 if (iowatch.isset(serverfd) && server->readln(answer)) {
629 if (prefixIs(answer, "ERROR:"))
633 cerr << "lyxclient: No answer from server." << endl;
638 // Take commands from stdin
639 iowatch.addfd(0); // stdin
640 bool saidbye = false;
641 while ((!saidbye) && server->connected()) {
643 if (iowatch.isset(0)) {
645 getline(cin, command);
648 if (command == "BYE:") {
649 server->writeln("BYE:");
652 server->writeln("LYXCMD:" + command);
655 if (iowatch.isset(serverfd)) {
656 while(server->readln(answer))
657 cout << answer << endl;
667 void assertion_failed(char const* a, char const* b, char const* c, long d)
669 lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '