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 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"));
430 class StopException : public exception
433 StopException(int status) : status_(status) {}
434 int status() const { return status_; }
443 "Usage: lyxclient [options]\n"
445 " -a address set address of the lyx socket\n"
446 " -t directory set system temporary directory (for detecting sockets)\n"
447 " -p pid select a running lyx by pidi\n"
448 " -c command send a single command and quit (LYXCMD prefix needed)\n"
449 " -g file row send a command to go to file and row\n"
450 " -n name set client name\n"
451 " -h name display this help end exit\n"
452 "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
453 "a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
454 "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
455 "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
456 "will read commands from standard input and disconnect when command read is BYE:\n"
458 "System directory is: " << to_utf8(cmdline::mainTmp)
463 int h(vector<docstring> const &)
466 throw StopException(EXIT_SUCCESS);
470 docstring clientName =
471 from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
473 int n(vector<docstring> const & arg)
476 cerr << "lyxclient: The option -n requires 1 argument."
485 docstring singleCommand;
488 int c(vector<docstring> const & arg)
491 cerr << "lyxclient: The option -c requires 1 argument."
495 singleCommand = arg[0];
500 int g(vector<docstring> const & arg)
502 if (arg.size() < 2) {
503 cerr << "lyxclient: The option -g requires 2 arguments."
507 singleCommand = "LYXCMD:server-goto-file-row "
514 // empty if LYXSOCKET is not set in the environment
515 docstring serverAddress;
518 int a(vector<docstring> const & arg)
521 cerr << "lyxclient: The option -a requires 1 argument."
525 // -a supercedes LYXSOCKET environment variable
526 serverAddress = arg[0];
533 int t(vector<docstring> const & arg)
536 cerr << "lyxclient: The option -t requires 1 argument."
545 string serverPid; // Init to empty string
548 int p(vector<docstring> const & arg)
551 cerr << "lyxclient: The option -p requires 1 argument."
555 serverPid = to_ascii(arg[0]);
560 } // namespace cmdline
562 /// The main application class
563 class LyXClientApp : public ConsoleApplication
566 LyXClientApp(int & argc, char * argv[])
567 : ConsoleApplication("client" PROGRAM_SUFFIX, argc, argv),
568 argc_(argc), argv_(argv)
574 int const exit_status = run();
577 catch (cmdline::StopException & e) {
588 int LyXClientApp::run()
590 // qt changes this, and our numeric conversions require the C locale
591 setlocale(LC_NUMERIC, "C");
594 char const * const lyxsocket = getenv("LYXSOCKET");
596 cmdline::serverAddress = from_local8bit(lyxsocket);
599 cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
601 // Command line builder
603 args.helper["-h"] = cmdline::h;
604 args.helper["-c"] = cmdline::c;
605 args.helper["-g"] = cmdline::g;
606 args.helper["-n"] = cmdline::n;
607 args.helper["-a"] = cmdline::a;
608 args.helper["-t"] = cmdline::t;
609 args.helper["-p"] = cmdline::p;
611 // Command line failure conditions:
612 if ((!args.parse(argc_, argv_))
613 || (args.isset["-c"] && args.isset["-g"])
614 || (args.isset["-a"] && args.isset["-p"])) {
619 unique_ptr<LyXDataSocket> server;
621 if (!cmdline::serverAddress.empty()) {
622 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
623 if (!server->connected()) {
624 cerr << "lyxclient: " << "Could not connect to "
625 << to_utf8(cmdline::serverAddress) << endl;
629 // We have to look for an address.
630 // serverPid can be empty.
631 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
632 FileNameList::const_iterator addr = addrs.begin();
633 FileNameList::const_iterator end = addrs.end();
634 for (; addr != end; ++addr) {
635 // Caution: addr->string() is in filesystem encoding
636 server.reset(new LyXDataSocket(*addr));
637 if (server->connected())
639 lyxerr << "lyxclient: " << "Could not connect to "
640 << addr->absFileName() << endl;
643 lyxerr << "lyxclient: No suitable server found."
647 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
650 int const serverfd = server->fd();
653 iowatch.addfd(serverfd);
655 // Used to read from server
659 server->writeln("HELLO:" + to_utf8(cmdline::clientName));
660 // wait at most 2 seconds until server responds
662 if (iowatch.isset(serverfd) && server->readln(answer)) {
663 if (prefixIs(answer, "BYE:")) {
664 cerr << "lyxclient: Server disconnected." << endl;
665 cout << answer << endl;
669 cerr << "lyxclient: No answer from server." << endl;
673 if (args.isset["-g"] || args.isset["-c"]) {
674 server->writeln(to_utf8(cmdline::singleCommand));
676 if (iowatch.isset(serverfd) && server->readln(answer)) {
678 if (prefixIs(answer, "ERROR:"))
682 cerr << "lyxclient: No answer from server." << endl;
687 // Take commands from stdin
688 iowatch.addfd(0); // stdin
689 bool saidbye = false;
690 while ((!saidbye) && server->connected()) {
692 if (iowatch.isset(0)) {
694 getline(cin, command);
697 if (command == "BYE:") {
698 server->writeln("BYE:");
701 server->writeln("LYXCMD:" + command);
704 if (iowatch.isset(serverfd)) {
705 while(server->readln(answer))
706 cout << answer << endl;
716 int main(int argc, char * argv[])
718 lyx::lyxerr.setStream(cerr);
720 lyx::LyXClientApp app(argc, argv);
727 void assertion_failed(char const* a, char const* b, char const* c, long d)
729 lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '