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/ConsoleApplication.h"
16 #include "support/debug.h"
17 #include "support/FileName.h"
18 #include "support/FileNameList.h"
19 #include "support/lstrings.h"
20 #include "support/Messages.h"
21 #include "support/unicode.h"
22 #include "support/unique_ptr.h"
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>
65 using namespace lyx::support;
69 // Dummy verbose support
77 App * theApp() { return &app; }
79 // Dummy LyXRC support
84 // Keep the linker happy on Windows
88 // Dummy language support
89 Messages const & getGuiMessages()
91 static Messages lyx_messages;
97 Messages const & getMessages(string const &)
99 return getGuiMessages();
105 string itoa(unsigned int i)
108 sprintf(buf, "%d", i);
113 /// Returns the absolute pathnames of all lyx local sockets in
114 /// file system encoding.
115 /// Parts stolen from lyx::support::DirList().
116 FileNameList lyxSockets(string const & dir, string const & pid)
118 FileNameList dirlist;
120 FileName dirpath(dir + "/");
122 if (!dirpath.exists() || !dirpath.isDirectory()) {
123 lyxerr << dir << " does not exist or is not a directory."
128 FileNameList dirs = dirpath.dirList("");
129 FileNameList::const_iterator it = dirs.begin();
130 FileNameList::const_iterator end = dirs.end();
132 for (; it != end; ++it) {
133 if (!it->isDirectory())
135 string const tmpdir = it->absFileName();
136 if (!contains(tmpdir, "lyx_tmpdir" + pid))
139 FileName lyxsocket(tmpdir + "/lyxsocket");
140 if (lyxsocket.exists())
141 dirlist.push_back(lyxsocket);
148 namespace socktools {
151 /// Connect to the socket \p name.
152 int connect(FileName const & name)
154 int fd; // File descriptor for the socket
155 sockaddr_un addr; // Structure that hold the socket address
157 string const encoded = name.toFilesystemEncoding();
158 // char sun_path[108]
159 string::size_type len = encoded.size();
161 cerr << "lyxclient: Socket address '" << name
162 << "' too long." << endl;
165 // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
166 addr.sun_family = AF_UNIX;
167 encoded.copy(addr.sun_path, 107);
168 addr.sun_path[len] = '\0';
170 if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
171 cerr << "lyxclient: Could not create socket descriptor: "
172 << strerror(errno) << endl;
176 reinterpret_cast<struct sockaddr *>(&addr),
177 sizeof(addr)) == -1) {
178 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
179 << ": " << strerror(errno) << endl;
183 if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
184 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
185 << strerror(errno) << endl;
193 } // namespace socktools
194 } // namespace support
198 /////////////////////////////////////////////////////////////////////
202 /////////////////////////////////////////////////////////////////////
224 void IOWatch::clear()
230 void IOWatch::addfd(int fd)
236 bool IOWatch::wait(double timeout)
239 to.tv_sec = static_cast<long int>(timeout);
240 to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
242 return select(FD_SETSIZE, &act,
243 (fd_set *)0, (fd_set *)0, &to);
250 return select(FD_SETSIZE, &act,
251 (fd_set *)0, (fd_set *)0, (timeval *)0);
255 bool IOWatch::isset(int fd)
257 return FD_ISSET(fd, &act);
262 /////////////////////////////////////////////////////////////////////
266 /////////////////////////////////////////////////////////////////////
268 // Modified LyXDataSocket class for use with the client
269 class LyXDataSocket {
271 LyXDataSocket(FileName const &);
273 // File descriptor of the connection
276 bool connected() const;
277 // Line buffered input from the socket
278 bool readln(string &);
279 // Write the string + '\n' to the socket
280 void writeln(string const &);
282 // File descriptor for the data socket
284 // True if the connection is up
286 // buffer for input data
291 LyXDataSocket::LyXDataSocket(FileName const & address)
293 if ((fd_ = socktools::connect(address)) == -1) {
301 LyXDataSocket::~LyXDataSocket()
307 int LyXDataSocket::fd() const
313 bool LyXDataSocket::connected() const
319 // Returns true if there was a complete line to input
320 // A line is of the form <key>:<value>
321 // A line not of this form will not be passed
322 // The line read is split and stored in 'key' and 'value'
323 bool LyXDataSocket::readln(string & line)
325 int const charbuf_size = 100;
326 char charbuf[charbuf_size]; // buffer for the ::read() system call
328 string::size_type pos;
330 // read and store characters in buffer
331 while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
332 charbuf[count] = '\0'; // turn it into a c string
336 // Error conditions. The buffer must still be
337 // processed for lines read
338 if (count == 0) { // EOF -- connection closed
340 } else if ((count == -1) && (errno != EAGAIN)) { // IO error
341 cerr << "lyxclient: IO error." << endl;
345 // Cut a line from buffer
346 if ((pos = buffer.find('\n')) == string::npos)
347 return false; // No complete line stored
348 line = buffer.substr(0, pos);
349 buffer = buffer.substr(pos + 1);
354 // Write a line of the form <key>:<value> to the socket
355 void LyXDataSocket::writeln(string const & line)
357 string linen(line + '\n');
358 int size = linen.size();
359 int written = ::write(fd_, linen.c_str(), size);
360 if (written < size) { // Allways mean end of connection.
361 if ((written == -1) && (errno == EPIPE)) {
362 // The program will also receive a SIGPIPE
363 // that must be catched
364 cerr << "lyxclient: connection closed while writing."
367 // Anything else, including errno == EAGAIN, must be
368 // considered IO error. EAGAIN should never happen
369 // when line is small
370 cerr << "lyxclient: IO error: " << strerror(errno);
377 /////////////////////////////////////////////////////////////////////
381 /////////////////////////////////////////////////////////////////////
383 class CmdLineParser {
385 typedef int (*optfunc)(vector<docstring> const & args);
386 map<string, optfunc> helper;
387 map<string, bool> isset;
388 bool parse(int, char * []);
389 vector<char *> nonopt;
393 bool CmdLineParser::parse(int argc, char * argv[])
397 vector<docstring> args;
398 if (helper[argv[opt]]) {
399 isset[argv[opt]] = true;
401 while ((arg < argc) && (!helper[argv[arg]])) {
402 args.push_back(from_local8bit(argv[arg]));
405 int taken = helper[argv[opt]](args);
410 if (argv[opt][0] == '-') {
411 if ((argv[opt][1] == '-')
412 && (argv[opt][2]== '\0')) {
415 nonopt.push_back(argv[opt]);
420 cerr << "lyxclient: unknown option "
421 << argv[opt] << endl;
425 nonopt.push_back(argv[opt]);
431 // ~Class CmdLineParser -------------------------------------------------------
437 docstring mainTmp(from_ascii("/tmp"));
440 class StopException : public exception
443 StopException(int status) : status_(status) {}
444 int status() const { return status_; }
453 "Usage: lyxclient [options]\n"
455 " -a address set address of the lyx socket\n"
456 " -t directory set system temporary directory (for detecting sockets)\n"
457 " -p pid select a running lyx by pidi\n"
458 " -c command send a single command and quit (LYXCMD prefix needed)\n"
459 " -g file row send a command to go to file and row\n"
460 " -n name set client name\n"
461 " -h name display this help end exit\n"
462 "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
463 "a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
464 "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
465 "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
466 "will read commands from standard input and disconnect when command read is BYE:\n"
468 "System directory is: " << to_utf8(cmdline::mainTmp)
473 int h(vector<docstring> const &)
476 throw StopException(EXIT_SUCCESS);
480 docstring clientName =
481 from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
483 int n(vector<docstring> const & arg)
486 cerr << "lyxclient: The option -n requires 1 argument."
495 docstring singleCommand;
498 int c(vector<docstring> const & arg)
501 cerr << "lyxclient: The option -c requires 1 argument."
505 singleCommand = arg[0];
510 int g(vector<docstring> const & arg)
512 if (arg.size() < 2) {
513 cerr << "lyxclient: The option -g requires 2 arguments."
517 singleCommand = "LYXCMD:command-sequence "
518 "server-goto-file-row "
526 // empty if LYXSOCKET is not set in the environment
527 docstring serverAddress;
530 int a(vector<docstring> const & arg)
533 cerr << "lyxclient: The option -a requires 1 argument."
537 // -a supercedes LYXSOCKET environment variable
538 serverAddress = arg[0];
545 int t(vector<docstring> const & arg)
548 cerr << "lyxclient: The option -t requires 1 argument."
557 string serverPid; // Init to empty string
560 int p(vector<docstring> const & arg)
563 cerr << "lyxclient: The option -p requires 1 argument."
567 serverPid = to_ascii(arg[0]);
572 } // namespace cmdline
574 /// The main application class
575 class LyXClientApp : public ConsoleApplication
578 LyXClientApp(int & argc, char * argv[])
579 : ConsoleApplication("client" PROGRAM_SUFFIX, argc, argv),
580 argc_(argc), argv_(argv)
586 int const exit_status = run();
589 catch (cmdline::StopException & e) {
600 int LyXClientApp::run()
602 // qt changes this, and our numeric conversions require the C locale
603 setlocale(LC_NUMERIC, "C");
606 char const * const lyxsocket = getenv("LYXSOCKET");
608 cmdline::serverAddress = from_local8bit(lyxsocket);
611 cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
613 // Command line builder
615 args.helper["-h"] = cmdline::h;
616 args.helper["-c"] = cmdline::c;
617 args.helper["-g"] = cmdline::g;
618 args.helper["-n"] = cmdline::n;
619 args.helper["-a"] = cmdline::a;
620 args.helper["-t"] = cmdline::t;
621 args.helper["-p"] = cmdline::p;
623 // Command line failure conditions:
624 if ((!args.parse(argc_, argv_))
625 || (args.isset["-c"] && args.isset["-g"])
626 || (args.isset["-a"] && args.isset["-p"])) {
631 unique_ptr<LyXDataSocket> server;
633 if (!cmdline::serverAddress.empty()) {
634 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
635 if (!server->connected()) {
636 cerr << "lyxclient: " << "Could not connect to "
637 << to_utf8(cmdline::serverAddress) << endl;
641 // We have to look for an address.
642 // serverPid can be empty.
643 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
644 FileNameList::const_iterator addr = addrs.begin();
645 FileNameList::const_iterator end = addrs.end();
646 for (; addr != end; ++addr) {
647 // Caution: addr->string() is in filesystem encoding
648 server.reset(new LyXDataSocket(*addr));
649 if (server->connected())
651 lyxerr << "lyxclient: " << "Could not connect to "
652 << addr->absFileName() << endl;
655 lyxerr << "lyxclient: No suitable server found."
659 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
662 int const serverfd = server->fd();
665 iowatch.addfd(serverfd);
667 // Used to read from server
671 server->writeln("HELLO:" + to_utf8(cmdline::clientName));
672 // wait at most 2 seconds until server responds
674 if (iowatch.isset(serverfd) && server->readln(answer)) {
675 if (prefixIs(answer, "BYE:")) {
676 cerr << "lyxclient: Server disconnected." << endl;
677 cout << answer << endl;
681 cerr << "lyxclient: No answer from server." << endl;
685 if (args.isset["-g"] || args.isset["-c"]) {
686 server->writeln(to_utf8(cmdline::singleCommand));
688 if (iowatch.isset(serverfd) && server->readln(answer)) {
690 if (prefixIs(answer, "ERROR:"))
694 cerr << "lyxclient: No answer from server." << endl;
699 // Take commands from stdin
700 iowatch.addfd(0); // stdin
701 bool saidbye = false;
702 while ((!saidbye) && server->connected()) {
704 if (iowatch.isset(0)) {
706 getline(cin, command);
709 if (command == "BYE:") {
710 server->writeln("BYE:");
713 server->writeln("LYXCMD:" + command);
716 if (iowatch.isset(serverfd)) {
717 while(server->readln(answer))
718 cout << answer << endl;
728 int main(int argc, char * argv[])
730 lyx::lyxerr.setStream(cerr);
732 lyx::LyXClientApp app(argc, argv);
739 void assertion_failed(char const* a, char const* b, char const* c, long d)
741 lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '