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/Messages.h"
20 #include "support/unicode.h"
22 #include <boost/scoped_ptr.hpp>
24 // getpid(), getppid()
25 #ifdef HAVE_SYS_TYPES_H
26 # include <sys/types.h>
33 #ifdef HAVE_SYS_TIME_H
34 # include <sys/time.h>
38 #ifdef HAVE_SYS_SELECT_H
39 # include <sys/select.h>
42 // socket(), connect()
43 #ifdef HAVE_SYS_SOCKET_H
44 # include <sys/socket.h>
63 using namespace lyx::support;
65 using ::boost::scoped_ptr;
69 // Dummy LyXRC support
74 // Keep the linker happy on Windows
78 // Dummy language support
79 Messages const & getGuiMessages()
81 static Messages lyx_messages;
87 Messages const & getMessages(string const &)
89 return getGuiMessages();
95 string itoa(unsigned int i)
98 sprintf(buf, "%d", i);
103 /// Returns the absolute pathnames of all lyx local sockets in
104 /// file system encoding.
105 /// Parts stolen from lyx::support::DirList().
106 FileNameList lyxSockets(string const & dir, string const & pid)
108 FileNameList dirlist;
110 FileName dirpath(dir + "/");
112 if (!dirpath.exists() || !dirpath.isDirectory()) {
113 lyxerr << dir << " does not exist or is not a directory."
118 FileNameList dirs = dirpath.dirList("");
119 FileNameList::const_iterator it = dirs.begin();
120 FileNameList::const_iterator end = dirs.end();
122 for (; it != end; ++it) {
123 if (!it->isDirectory())
125 string const tmpdir = it->absFileName();
126 if (!contains(tmpdir, "lyx_tmpdir" + pid))
129 FileName lyxsocket(tmpdir + "/lyxsocket");
130 if (lyxsocket.exists())
131 dirlist.push_back(lyxsocket);
138 namespace socktools {
141 /// Connect to the socket \p name.
142 int connect(FileName const & name)
144 int fd; // File descriptor for the socket
145 sockaddr_un addr; // Structure that hold the socket address
147 string const encoded = name.toFilesystemEncoding();
148 // char sun_path[108]
149 string::size_type len = encoded.size();
151 cerr << "lyxclient: Socket address '" << name
152 << "' too long." << endl;
155 // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
156 addr.sun_family = AF_UNIX;
157 encoded.copy(addr.sun_path, 107);
158 addr.sun_path[len] = '\0';
160 if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
161 cerr << "lyxclient: Could not create socket descriptor: "
162 << strerror(errno) << endl;
166 reinterpret_cast<struct sockaddr *>(&addr),
167 sizeof(addr)) == -1) {
168 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
169 << ": " << strerror(errno) << endl;
173 if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
174 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
175 << strerror(errno) << endl;
183 } // namespace socktools
184 } // namespace support
188 /////////////////////////////////////////////////////////////////////
192 /////////////////////////////////////////////////////////////////////
214 void IOWatch::clear()
220 void IOWatch::addfd(int fd)
226 bool IOWatch::wait(double timeout)
229 to.tv_sec = static_cast<long int>(timeout);
230 to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
232 return select(FD_SETSIZE, &act,
233 (fd_set *)0, (fd_set *)0, &to);
240 return select(FD_SETSIZE, &act,
241 (fd_set *)0, (fd_set *)0, (timeval *)0);
245 bool IOWatch::isset(int fd)
247 return FD_ISSET(fd, &act);
252 /////////////////////////////////////////////////////////////////////
256 /////////////////////////////////////////////////////////////////////
258 // Modified LyXDataSocket class for use with the client
259 class LyXDataSocket {
261 LyXDataSocket(FileName const &);
263 // File descriptor of the connection
266 bool connected() const;
267 // Line buffered input from the socket
268 bool readln(string &);
269 // Write the string + '\n' to the socket
270 void writeln(string const &);
272 // File descriptor for the data socket
274 // True if the connection is up
276 // buffer for input data
281 LyXDataSocket::LyXDataSocket(FileName const & address)
283 if ((fd_ = socktools::connect(address)) == -1) {
291 LyXDataSocket::~LyXDataSocket()
297 int LyXDataSocket::fd() const
303 bool LyXDataSocket::connected() const
309 // Returns true if there was a complete line to input
310 // A line is of the form <key>:<value>
311 // A line not of this form will not be passed
312 // The line read is split and stored in 'key' and 'value'
313 bool LyXDataSocket::readln(string & line)
315 int const charbuf_size = 100;
316 char charbuf[charbuf_size]; // buffer for the ::read() system call
318 string::size_type pos;
320 // read and store characters in buffer
321 while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
322 charbuf[count] = '\0'; // turn it into a c string
326 // Error conditions. The buffer must still be
327 // processed for lines read
328 if (count == 0) { // EOF -- connection closed
330 } else if ((count == -1) && (errno != EAGAIN)) { // IO error
331 cerr << "lyxclient: IO error." << endl;
335 // Cut a line from buffer
336 if ((pos = buffer.find('\n')) == string::npos)
337 return false; // No complete line stored
338 line = buffer.substr(0, pos);
339 buffer = buffer.substr(pos + 1);
344 // Write a line of the form <key>:<value> to the socket
345 void LyXDataSocket::writeln(string const & line)
347 string linen(line + '\n');
348 int size = linen.size();
349 int written = ::write(fd_, linen.c_str(), size);
350 if (written < size) { // Allways mean end of connection.
351 if ((written == -1) && (errno == EPIPE)) {
352 // The program will also receive a SIGPIPE
353 // that must be catched
354 cerr << "lyxclient: connection closed while writing."
357 // Anything else, including errno == EAGAIN, must be
358 // considered IO error. EAGAIN should never happen
359 // when line is small
360 cerr << "lyxclient: IO error: " << strerror(errno);
367 /////////////////////////////////////////////////////////////////////
371 /////////////////////////////////////////////////////////////////////
373 class CmdLineParser {
375 typedef int (*optfunc)(vector<docstring> const & args);
376 map<string, optfunc> helper;
377 map<string, bool> isset;
378 bool parse(int, char * []);
379 vector<char *> nonopt;
383 bool CmdLineParser::parse(int argc, char * argv[])
387 vector<docstring> args;
388 if (helper[argv[opt]]) {
389 isset[argv[opt]] = true;
391 while ((arg < argc) && (!helper[argv[arg]])) {
392 args.push_back(from_local8bit(argv[arg]));
395 int taken = helper[argv[opt]](args);
400 if (argv[opt][0] == '-') {
401 if ((argv[opt][1] == '-')
402 && (argv[opt][2]== '\0')) {
405 nonopt.push_back(argv[opt]);
410 cerr << "lyxclient: unknown option "
411 << argv[opt] << endl;
415 nonopt.push_back(argv[opt]);
421 // ~Class CmdLineParser -------------------------------------------------------
427 docstring mainTmp(from_ascii("/tmp"));
433 "Usage: lyxclient [options]\n"
435 " -a address set address of the lyx socket\n"
436 " -t directory set system temporary directory (for detecting sockets)\n"
437 " -p pid select a running lyx by pidi\n"
438 " -c command send a single command and quit (LYXCMD prefix needed)\n"
439 " -g file row send a command to go to file and row\n"
440 " -n name set client name\n"
441 " -h name display this help end exit\n"
442 "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
443 "a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
444 "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
445 "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
446 "will read commands from standard input and disconnect when command read is BYE:\n"
448 "System directory is: " << to_utf8(cmdline::mainTmp)
453 int h(vector<docstring> const &)
460 docstring clientName =
461 from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
463 int n(vector<docstring> const & arg)
466 cerr << "lyxclient: The option -n requires 1 argument."
475 docstring singleCommand;
478 int c(vector<docstring> const & arg)
481 cerr << "lyxclient: The option -c requires 1 argument."
485 singleCommand = arg[0];
490 int g(vector<docstring> const & arg)
492 if (arg.size() < 2) {
493 cerr << "lyxclient: The option -g requires 2 arguments."
497 singleCommand = "LYXCMD:server-goto-file-row "
504 // empty if LYXSOCKET is not set in the environment
505 docstring serverAddress;
508 int a(vector<docstring> const & arg)
511 cerr << "lyxclient: The option -a requires 1 argument."
515 // -a supercedes LYXSOCKET environment variable
516 serverAddress = arg[0];
523 int t(vector<docstring> const & arg)
526 cerr << "lyxclient: The option -t requires 1 argument."
535 string serverPid; // Init to empty string
538 int p(vector<docstring> const & arg)
541 cerr << "lyxclient: The option -p requires 1 argument."
545 serverPid = to_ascii(arg[0]);
550 } // namespace cmdline
554 int main(int argc, char * argv[])
557 lyxerr.setStream(cerr);
561 char const * const lyxsocket = getenv("LYXSOCKET");
563 cmdline::serverAddress = from_local8bit(lyxsocket);
566 cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
568 // Command line builder
570 args.helper["-h"] = cmdline::h;
571 args.helper["-c"] = cmdline::c;
572 args.helper["-g"] = cmdline::g;
573 args.helper["-n"] = cmdline::n;
574 args.helper["-a"] = cmdline::a;
575 args.helper["-t"] = cmdline::t;
576 args.helper["-p"] = cmdline::p;
578 // Command line failure conditions:
579 if ((!args.parse(argc, argv))
580 || (args.isset["-c"] && args.isset["-g"])
581 || (args.isset["-a"] && args.isset["-p"])) {
586 scoped_ptr<LyXDataSocket> server;
588 if (!cmdline::serverAddress.empty()) {
589 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
590 if (!server->connected()) {
591 cerr << "lyxclient: " << "Could not connect to "
592 << to_utf8(cmdline::serverAddress) << endl;
596 // We have to look for an address.
597 // serverPid can be empty.
598 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
599 FileNameList::const_iterator addr = addrs.begin();
600 FileNameList::const_iterator end = addrs.end();
601 for (; addr != end; ++addr) {
602 // Caution: addr->string() is in filesystem encoding
603 server.reset(new LyXDataSocket(*addr));
604 if (server->connected())
606 lyxerr << "lyxclient: " << "Could not connect to "
607 << addr->absFileName() << endl;
610 lyxerr << "lyxclient: No suitable server found."
614 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
617 int const serverfd = server->fd();
620 iowatch.addfd(serverfd);
622 // Used to read from server
626 server->writeln("HELLO:" + to_utf8(cmdline::clientName));
627 // wait at most 2 seconds until server responds
629 if (iowatch.isset(serverfd) && server->readln(answer)) {
630 if (prefixIs(answer, "BYE:")) {
631 cerr << "lyxclient: Server disconnected." << endl;
632 cout << answer << endl;
636 cerr << "lyxclient: No answer from server." << endl;
640 if (args.isset["-g"] || args.isset["-c"]) {
641 server->writeln(to_utf8(cmdline::singleCommand));
643 if (iowatch.isset(serverfd) && server->readln(answer)) {
645 if (prefixIs(answer, "ERROR:"))
649 cerr << "lyxclient: No answer from server." << endl;
654 // Take commands from stdin
655 iowatch.addfd(0); // stdin
656 bool saidbye = false;
657 while ((!saidbye) && server->connected()) {
659 if (iowatch.isset(0)) {
661 getline(cin, command);
664 if (command == "BYE:") {
665 server->writeln("BYE:");
668 server->writeln("LYXCMD:" + command);
671 if (iowatch.isset(serverfd)) {
672 while(server->readln(answer))
673 cout << answer << endl;
683 void assertion_failed(char const* a, char const* b, char const* c, long d)
685 lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '