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
72 // Dummy LyXRC support
77 // Keep the linker happy on Windows
81 // Dummy language support
82 Messages const & getGuiMessages()
84 static Messages lyx_messages;
90 Messages const & getMessages(string const &)
92 return getGuiMessages();
98 string itoa(unsigned int i)
101 sprintf(buf, "%d", i);
106 /// Returns the absolute pathnames of all lyx local sockets in
107 /// file system encoding.
108 /// Parts stolen from lyx::support::DirList().
109 FileNameList lyxSockets(string const & dir, string const & pid)
111 FileNameList dirlist;
113 FileName dirpath(dir + "/");
115 if (!dirpath.exists() || !dirpath.isDirectory()) {
116 lyxerr << dir << " does not exist or is not a directory."
121 FileNameList dirs = dirpath.dirList("");
122 FileNameList::const_iterator it = dirs.begin();
123 FileNameList::const_iterator end = dirs.end();
125 for (; it != end; ++it) {
126 if (!it->isDirectory())
128 string const tmpdir = it->absFileName();
129 if (!contains(tmpdir, "lyx_tmpdir" + pid))
132 FileName lyxsocket(tmpdir + "/lyxsocket");
133 if (lyxsocket.exists())
134 dirlist.push_back(lyxsocket);
141 namespace socktools {
144 /// Connect to the socket \p name.
145 int connect(FileName const & name)
147 int fd; // File descriptor for the socket
148 sockaddr_un addr; // Structure that hold the socket address
150 string const encoded = name.toFilesystemEncoding();
151 // char sun_path[108]
152 string::size_type len = encoded.size();
154 cerr << "lyxclient: Socket address '" << name
155 << "' too long." << endl;
158 // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
159 addr.sun_family = AF_UNIX;
160 encoded.copy(addr.sun_path, 107);
161 addr.sun_path[len] = '\0';
163 if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
164 cerr << "lyxclient: Could not create socket descriptor: "
165 << strerror(errno) << endl;
169 reinterpret_cast<struct sockaddr *>(&addr),
170 sizeof(addr)) == -1) {
171 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
172 << ": " << strerror(errno) << endl;
176 if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
177 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
178 << strerror(errno) << endl;
186 } // namespace socktools
187 } // namespace support
191 /////////////////////////////////////////////////////////////////////
195 /////////////////////////////////////////////////////////////////////
217 void IOWatch::clear()
223 void IOWatch::addfd(int fd)
229 bool IOWatch::wait(double timeout)
232 to.tv_sec = static_cast<long int>(timeout);
233 to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
235 return select(FD_SETSIZE, &act,
236 (fd_set *)0, (fd_set *)0, &to);
243 return select(FD_SETSIZE, &act,
244 (fd_set *)0, (fd_set *)0, (timeval *)0);
248 bool IOWatch::isset(int fd)
250 return FD_ISSET(fd, &act);
255 /////////////////////////////////////////////////////////////////////
259 /////////////////////////////////////////////////////////////////////
261 // Modified LyXDataSocket class for use with the client
262 class LyXDataSocket {
264 LyXDataSocket(FileName const &);
266 // File descriptor of the connection
269 bool connected() const;
270 // Line buffered input from the socket
271 bool readln(string &);
272 // Write the string + '\n' to the socket
273 void writeln(string const &);
275 // File descriptor for the data socket
277 // True if the connection is up
279 // buffer for input data
284 LyXDataSocket::LyXDataSocket(FileName const & address)
286 if ((fd_ = socktools::connect(address)) == -1) {
294 LyXDataSocket::~LyXDataSocket()
300 int LyXDataSocket::fd() const
306 bool LyXDataSocket::connected() const
312 // Returns true if there was a complete line to input
313 // A line is of the form <key>:<value>
314 // A line not of this form will not be passed
315 // The line read is split and stored in 'key' and 'value'
316 bool LyXDataSocket::readln(string & line)
318 int const charbuf_size = 100;
319 char charbuf[charbuf_size]; // buffer for the ::read() system call
321 string::size_type pos;
323 // read and store characters in buffer
324 while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
325 charbuf[count] = '\0'; // turn it into a c string
329 // Error conditions. The buffer must still be
330 // processed for lines read
331 if (count == 0) { // EOF -- connection closed
333 } else if ((count == -1) && (errno != EAGAIN)) { // IO error
334 cerr << "lyxclient: IO error." << endl;
338 // Cut a line from buffer
339 if ((pos = buffer.find('\n')) == string::npos)
340 return false; // No complete line stored
341 line = buffer.substr(0, pos);
342 buffer = buffer.substr(pos + 1);
347 // Write a line of the form <key>:<value> to the socket
348 void LyXDataSocket::writeln(string const & line)
350 string linen(line + '\n');
351 int size = linen.size();
352 int written = ::write(fd_, linen.c_str(), size);
353 if (written < size) { // Allways mean end of connection.
354 if ((written == -1) && (errno == EPIPE)) {
355 // The program will also receive a SIGPIPE
356 // that must be catched
357 cerr << "lyxclient: connection closed while writing."
360 // Anything else, including errno == EAGAIN, must be
361 // considered IO error. EAGAIN should never happen
362 // when line is small
363 cerr << "lyxclient: IO error: " << strerror(errno);
370 /////////////////////////////////////////////////////////////////////
374 /////////////////////////////////////////////////////////////////////
376 class CmdLineParser {
378 typedef int (*optfunc)(vector<docstring> const & args);
379 map<string, optfunc> helper;
380 map<string, bool> isset;
381 bool parse(int, char * []);
382 vector<char *> nonopt;
386 bool CmdLineParser::parse(int argc, char * argv[])
390 vector<docstring> args;
391 if (helper[argv[opt]]) {
392 isset[argv[opt]] = true;
394 while ((arg < argc) && (!helper[argv[arg]])) {
395 args.push_back(from_local8bit(argv[arg]));
398 int taken = helper[argv[opt]](args);
403 if (argv[opt][0] == '-') {
404 if ((argv[opt][1] == '-')
405 && (argv[opt][2]== '\0')) {
408 nonopt.push_back(argv[opt]);
413 cerr << "lyxclient: unknown option "
414 << argv[opt] << endl;
418 nonopt.push_back(argv[opt]);
424 // ~Class CmdLineParser -------------------------------------------------------
430 docstring mainTmp(from_ascii("/tmp"));
433 class StopException : public exception
436 StopException(int status) : status_(status) {}
437 int status() const { return status_; }
446 "Usage: lyxclient [options]\n"
448 " -a address set address of the lyx socket\n"
449 " -t directory set system temporary directory (for detecting sockets)\n"
450 " -p pid select a running lyx by pidi\n"
451 " -c command send a single command and quit (LYXCMD prefix needed)\n"
452 " -g file row send a command to go to file and row\n"
453 " -n name set client name\n"
454 " -h name display this help end exit\n"
455 "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
456 "a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
457 "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
458 "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
459 "will read commands from standard input and disconnect when command read is BYE:\n"
461 "System directory is: " << to_utf8(cmdline::mainTmp)
466 int h(vector<docstring> const &)
469 throw StopException(EXIT_SUCCESS);
473 docstring clientName =
474 from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
476 int n(vector<docstring> const & arg)
479 cerr << "lyxclient: The option -n requires 1 argument."
488 docstring singleCommand;
491 int c(vector<docstring> const & arg)
494 cerr << "lyxclient: The option -c requires 1 argument."
498 singleCommand = arg[0];
503 int g(vector<docstring> const & arg)
505 if (arg.size() < 2) {
506 cerr << "lyxclient: The option -g requires 2 arguments."
510 singleCommand = "LYXCMD:command-sequence "
511 "server-goto-file-row "
519 // empty if LYXSOCKET is not set in the environment
520 docstring serverAddress;
523 int a(vector<docstring> const & arg)
526 cerr << "lyxclient: The option -a requires 1 argument."
530 // -a supercedes LYXSOCKET environment variable
531 serverAddress = arg[0];
538 int t(vector<docstring> const & arg)
541 cerr << "lyxclient: The option -t requires 1 argument."
550 string serverPid; // Init to empty string
553 int p(vector<docstring> const & arg)
556 cerr << "lyxclient: The option -p requires 1 argument."
560 serverPid = to_ascii(arg[0]);
565 } // namespace cmdline
567 /// The main application class
568 class LyXClientApp : public ConsoleApplication
571 LyXClientApp(int & argc, char * argv[])
572 : ConsoleApplication("client" PROGRAM_SUFFIX, argc, argv),
573 argc_(argc), argv_(argv)
579 int const exit_status = run();
582 catch (cmdline::StopException & e) {
593 int LyXClientApp::run()
595 // qt changes this, and our numeric conversions require the C locale
596 setlocale(LC_NUMERIC, "C");
599 char const * const lyxsocket = getenv("LYXSOCKET");
601 cmdline::serverAddress = from_local8bit(lyxsocket);
604 cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
606 // Command line builder
608 args.helper["-h"] = cmdline::h;
609 args.helper["-c"] = cmdline::c;
610 args.helper["-g"] = cmdline::g;
611 args.helper["-n"] = cmdline::n;
612 args.helper["-a"] = cmdline::a;
613 args.helper["-t"] = cmdline::t;
614 args.helper["-p"] = cmdline::p;
616 // Command line failure conditions:
617 if ((!args.parse(argc_, argv_))
618 || (args.isset["-c"] && args.isset["-g"])
619 || (args.isset["-a"] && args.isset["-p"])) {
624 unique_ptr<LyXDataSocket> server;
626 if (!cmdline::serverAddress.empty()) {
627 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
628 if (!server->connected()) {
629 cerr << "lyxclient: " << "Could not connect to "
630 << to_utf8(cmdline::serverAddress) << endl;
634 // We have to look for an address.
635 // serverPid can be empty.
636 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
637 FileNameList::const_iterator addr = addrs.begin();
638 FileNameList::const_iterator end = addrs.end();
639 for (; addr != end; ++addr) {
640 // Caution: addr->string() is in filesystem encoding
641 server.reset(new LyXDataSocket(*addr));
642 if (server->connected())
644 lyxerr << "lyxclient: " << "Could not connect to "
645 << addr->absFileName() << endl;
648 lyxerr << "lyxclient: No suitable server found."
652 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
655 int const serverfd = server->fd();
658 iowatch.addfd(serverfd);
660 // Used to read from server
664 server->writeln("HELLO:" + to_utf8(cmdline::clientName));
665 // wait at most 2 seconds until server responds
667 if (iowatch.isset(serverfd) && server->readln(answer)) {
668 if (prefixIs(answer, "BYE:")) {
669 cerr << "lyxclient: Server disconnected." << endl;
670 cout << answer << endl;
674 cerr << "lyxclient: No answer from server." << endl;
678 if (args.isset["-g"] || args.isset["-c"]) {
679 server->writeln(to_utf8(cmdline::singleCommand));
681 if (iowatch.isset(serverfd) && server->readln(answer)) {
683 if (prefixIs(answer, "ERROR:"))
687 cerr << "lyxclient: No answer from server." << endl;
692 // Take commands from stdin
693 iowatch.addfd(0); // stdin
694 bool saidbye = false;
695 while ((!saidbye) && server->connected()) {
697 if (iowatch.isset(0)) {
699 getline(cin, command);
702 if (command == "BYE:") {
703 server->writeln("BYE:");
706 server->writeln("LYXCMD:" + command);
709 if (iowatch.isset(serverfd)) {
710 while(server->readln(answer))
711 cout << answer << endl;
721 int main(int argc, char * argv[])
723 lyx::lyxerr.setStream(cerr);
725 lyx::LyXClientApp app(argc, argv);
732 void assertion_failed(char const* a, char const* b, char const* c, long d)
734 lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '