]> git.lyx.org Git - lyx.git/blob - src/client/client.cpp
a950f8303bd02ae90261426905ce84a654ed3e26
[lyx.git] / src / client / client.cpp
1 /**
2  * \file client.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author João Luis M. Assirati
7  * \author Lars Gullik Bjønnes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12
13 #include <config.h>
14
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"
23
24 // getpid(), getppid()
25 #ifdef HAVE_SYS_TYPES_H
26 # include <sys/types.h>
27 #endif
28 #ifdef HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31
32 // struct timeval
33 #ifdef HAVE_SYS_TIME_H
34 # include <sys/time.h>
35 #endif
36
37 // select()
38 #ifdef HAVE_SYS_SELECT_H
39 # include <sys/select.h>
40 #endif
41
42 // socket(), connect()
43 #ifdef HAVE_SYS_SOCKET_H
44 # include <sys/socket.h>
45 #endif
46 #include <sys/un.h>
47
48 // fcntl()
49 #include <fcntl.h>
50
51 // strerror()
52 #include <string.h>
53
54 #include <cerrno>
55 #include <cstdio>
56 #include <cstdlib>
57 #include <exception>
58 #include <string>
59 #include <vector>
60 #include <map>
61 #include <iostream>
62
63
64 using namespace std;
65 using namespace lyx::support;
66
67 namespace lyx {
68
69 // Dummy LyXRC support
70 struct LyXRC {
71         string icon_set;
72 } lyxrc;
73
74 // Keep the linker happy on Windows
75 void lyx_exit(int)
76 {}
77
78 // Dummy language support
79 Messages const & getGuiMessages()
80 {
81         static Messages lyx_messages;
82
83         return lyx_messages;
84 }
85
86
87 Messages const & getMessages(string const &)
88 {
89         return getGuiMessages();
90 }
91
92
93 namespace support {
94
95 string itoa(unsigned int i)
96 {
97         char buf[20];
98         sprintf(buf, "%d", i);
99         return buf;
100 }
101
102
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)
107 {
108         FileNameList dirlist;
109
110         FileName dirpath(dir + "/");
111
112         if (!dirpath.exists() || !dirpath.isDirectory()) {
113                 lyxerr << dir << " does not exist or is not a directory."
114                        << endl;
115                 return dirlist;
116         }
117
118         FileNameList dirs = dirpath.dirList("");
119         FileNameList::const_iterator it = dirs.begin();
120         FileNameList::const_iterator end = dirs.end();
121
122         for (; it != end; ++it) {
123                 if (!it->isDirectory())
124                         continue;
125                 string const tmpdir = it->absFileName();
126                 if (!contains(tmpdir, "lyx_tmpdir" + pid))
127                         continue;
128
129                 FileName lyxsocket(tmpdir + "/lyxsocket");
130                 if (lyxsocket.exists())
131                         dirlist.push_back(lyxsocket);
132         }
133
134         return dirlist;
135 }
136
137
138 namespace socktools {
139
140
141 /// Connect to the socket \p name.
142 int connect(FileName const & name)
143 {
144         int fd; // File descriptor for the socket
145         sockaddr_un addr; // Structure that hold the socket address
146
147         string const encoded = name.toFilesystemEncoding();
148         // char sun_path[108]
149         string::size_type len = encoded.size();
150         if (len > 107) {
151                 cerr << "lyxclient: Socket address '" << name
152                      << "' too long." << endl;
153                 return -1;
154         }
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';
159
160         if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
161                 cerr << "lyxclient: Could not create socket descriptor: "
162                      << strerror(errno) << endl;
163                 return -1;
164         }
165         if (::connect(fd,
166                       reinterpret_cast<struct sockaddr *>(&addr),
167                       sizeof(addr)) == -1) {
168                 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
169                      << ": " << strerror(errno) << endl;
170                 ::close(fd);
171                 return -1;
172         }
173         if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
174                 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
175                      << strerror(errno) << endl;
176                 ::close(fd);
177                 return -1;
178         }
179         return fd;
180 }
181
182
183 } // namespace socktools
184 } // namespace support
185
186
187
188 /////////////////////////////////////////////////////////////////////
189 //
190 // IOWatch
191 //
192 /////////////////////////////////////////////////////////////////////
193
194 class IOWatch {
195 public:
196         IOWatch();
197         void clear();
198         void addfd(int);
199         bool wait(double);
200         bool wait();
201         bool isset(int fd);
202 private:
203         fd_set des;
204         fd_set act;
205 };
206
207
208 IOWatch::IOWatch()
209 {
210         clear();
211 }
212
213
214 void IOWatch::clear()
215 {
216         FD_ZERO(&des);
217 }
218
219
220 void IOWatch::addfd(int fd)
221 {
222         FD_SET(fd, &des);
223 }
224
225
226 bool IOWatch::wait(double timeout)
227 {
228         timeval to;
229         to.tv_sec = static_cast<long int>(timeout);
230         to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
231         act = des;
232         return select(FD_SETSIZE, &act,
233                       (fd_set *)0, (fd_set *)0, &to);
234 }
235
236
237 bool IOWatch::wait()
238 {
239         act = des;
240         return select(FD_SETSIZE, &act,
241                       (fd_set *)0, (fd_set *)0, (timeval *)0);
242 }
243
244
245 bool IOWatch::isset(int fd)
246 {
247         return FD_ISSET(fd, &act);
248 }
249
250
251
252 /////////////////////////////////////////////////////////////////////
253 //
254 // LyXDataSocket
255 //
256 /////////////////////////////////////////////////////////////////////
257
258 // Modified LyXDataSocket class for use with the client
259 class LyXDataSocket {
260 public:
261         LyXDataSocket(FileName const &);
262         ~LyXDataSocket();
263         // File descriptor of the connection
264         int fd() const;
265         // Connection status
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 &);
271 private:
272         // File descriptor for the data socket
273         int fd_;
274         // True if the connection is up
275         bool connected_;
276         // buffer for input data
277         string buffer;
278 };
279
280
281 LyXDataSocket::LyXDataSocket(FileName const & address)
282 {
283         if ((fd_ = socktools::connect(address)) == -1) {
284                 connected_ = false;
285         } else {
286                 connected_ = true;
287         }
288 }
289
290
291 LyXDataSocket::~LyXDataSocket()
292 {
293         ::close(fd_);
294 }
295
296
297 int LyXDataSocket::fd() const
298 {
299         return fd_;
300 }
301
302
303 bool LyXDataSocket::connected() const
304 {
305         return connected_;
306 }
307
308
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)
314 {
315         int const charbuf_size = 100;
316         char charbuf[charbuf_size]; // buffer for the ::read() system call
317         int count;
318         string::size_type pos;
319
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
323                 buffer += charbuf;
324         }
325
326         // Error conditions. The buffer must still be
327         // processed for lines read
328         if (count == 0) { // EOF -- connection closed
329                 connected_ = false;
330         } else if ((count == -1) && (errno != EAGAIN)) { // IO error
331                 cerr << "lyxclient: IO error." << endl;
332                 connected_ = false;
333         }
334
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);
340         return true;
341 }
342
343
344 // Write a line of the form <key>:<value> to the socket
345 void LyXDataSocket::writeln(string const & line)
346 {
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."
355                              << endl;
356                 } else {
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);
361                 }
362                 connected_ = false;
363         }
364 }
365
366
367 /////////////////////////////////////////////////////////////////////
368 //
369 // CmdLineParser
370 //
371 /////////////////////////////////////////////////////////////////////
372
373 class CmdLineParser {
374 public:
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;
380 };
381
382
383 bool CmdLineParser::parse(int argc, char * argv[])
384 {
385         int opt = 1;
386         while (opt < argc) {
387                 vector<docstring> args;
388                 if (helper[argv[opt]]) {
389                         isset[argv[opt]] = true;
390                         int arg = opt + 1;
391                         while ((arg < argc) && (!helper[argv[arg]])) {
392                                 args.push_back(from_local8bit(argv[arg]));
393                                 ++arg;
394                         }
395                         int taken = helper[argv[opt]](args);
396                         if (taken == -1)
397                                 return false;
398                         opt += 1 + taken;
399                 } else {
400                         if (argv[opt][0] == '-') {
401                                 if ((argv[opt][1] == '-')
402                                    && (argv[opt][2]== '\0')) {
403                                         ++opt;
404                                         while (opt < argc) {
405                                                 nonopt.push_back(argv[opt]);
406                                                 ++opt;
407                                         }
408                                         return true;
409                                 } else {
410                                         cerr << "lyxclient: unknown option "
411                                              << argv[opt] << endl;
412                                         return false;
413                                 }
414                         }
415                         nonopt.push_back(argv[opt]);
416                         ++opt;
417                 }
418         }
419         return true;
420 }
421 // ~Class CmdLineParser -------------------------------------------------------
422
423
424
425 namespace cmdline {
426
427 docstring mainTmp(from_ascii("/tmp"));
428
429
430 class StopException : public exception
431 {
432 public:
433         StopException(int status) : status_(status) {}
434         int status() const { return status_; }
435 private:
436         int status_;
437 };
438
439
440 void usage()
441 {
442         cerr <<
443                 "Usage: lyxclient [options]\n"
444           "Options are:\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"
457           "\n"
458           "System directory is: " << to_utf8(cmdline::mainTmp)
459            << endl;
460 }
461
462
463 int h(vector<docstring> const &)
464 {
465         usage();
466         throw StopException(EXIT_SUCCESS);
467 }
468
469
470 docstring clientName =
471         from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
472
473 int n(vector<docstring> const & arg)
474 {
475         if (arg.empty()) {
476                 cerr << "lyxclient: The option -n requires 1 argument."
477                      << endl;
478                 return -1;
479         }
480         clientName = arg[0];
481         return 1;
482 }
483
484
485 docstring singleCommand;
486
487
488 int c(vector<docstring> const & arg)
489 {
490         if (arg.empty()) {
491                 cerr << "lyxclient: The option -c requires 1 argument."
492                      << endl;
493                 return -1;
494         }
495         singleCommand = arg[0];
496         return 1;
497 }
498
499
500 int g(vector<docstring> const & arg)
501 {
502         if (arg.size() < 2) {
503                 cerr << "lyxclient: The option -g requires 2 arguments."
504                      << endl;
505                 return -1;
506         }
507         singleCommand = "LYXCMD:command-sequence "
508                 "server-goto-file-row "
509                         + arg[0] + ' '
510                         + arg[1] + "; " +
511                 "lyx-activate";
512         return 2;
513 }
514
515
516 // empty if LYXSOCKET is not set in the environment
517 docstring serverAddress;
518
519
520 int a(vector<docstring> const & arg)
521 {
522         if (arg.empty()) {
523                 cerr << "lyxclient: The option -a requires 1 argument."
524                      << endl;
525                 return -1;
526         }
527         // -a supercedes LYXSOCKET environment variable
528         serverAddress = arg[0];
529         return 1;
530 }
531
532
533
534
535 int t(vector<docstring> const & arg)
536 {
537         if (arg.empty()) {
538                 cerr << "lyxclient: The option -t requires 1 argument."
539                      << endl;
540                 return -1;
541         }
542         mainTmp = arg[0];
543         return 1;
544 }
545
546
547 string serverPid; // Init to empty string
548
549
550 int p(vector<docstring> const & arg)
551 {
552         if (arg.empty()) {
553                 cerr << "lyxclient: The option -p requires 1 argument."
554                      << endl;
555                 return -1;
556         }
557         serverPid = to_ascii(arg[0]);
558         return 1;
559 }
560
561
562 } // namespace cmdline
563
564 /// The main application class
565 class LyXClientApp : public ConsoleApplication
566 {
567 public:
568         LyXClientApp(int & argc, char * argv[])
569                 : ConsoleApplication("client" PROGRAM_SUFFIX, argc, argv),
570                   argc_(argc), argv_(argv)
571         {
572         }
573         void doExec()
574         {
575                 try {
576                         int const exit_status = run();
577                         exit(exit_status);
578                 }
579                 catch (cmdline::StopException & e) {
580                         exit(e.status());
581                 }
582         }
583 private:
584         int run();
585         int & argc_;
586         char ** argv_;
587 };
588
589
590 int LyXClientApp::run()
591 {
592         // qt changes this, and our numeric conversions require the C locale
593         setlocale(LC_NUMERIC, "C");
594
595         // Set defaults
596         char const * const lyxsocket = getenv("LYXSOCKET");
597         if (lyxsocket)
598                 cmdline::serverAddress = from_local8bit(lyxsocket);
599
600         // Default temporary
601         cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
602
603         // Command line builder
604         CmdLineParser args;
605         args.helper["-h"] = cmdline::h;
606         args.helper["-c"] = cmdline::c;
607         args.helper["-g"] = cmdline::g;
608         args.helper["-n"] = cmdline::n;
609         args.helper["-a"] = cmdline::a;
610         args.helper["-t"] = cmdline::t;
611         args.helper["-p"] = cmdline::p;
612
613         // Command line failure conditions:
614         if ((!args.parse(argc_, argv_))
615            || (args.isset["-c"] && args.isset["-g"])
616            || (args.isset["-a"] && args.isset["-p"])) {
617                 cmdline::usage();
618                 return EXIT_FAILURE;
619         }
620
621         unique_ptr<LyXDataSocket> server;
622
623         if (!cmdline::serverAddress.empty()) {
624                 server.reset(new LyXDataSocket(FileName(to_utf8(cmdline::serverAddress))));
625                 if (!server->connected()) {
626                         cerr << "lyxclient: " << "Could not connect to "
627                              << to_utf8(cmdline::serverAddress) << endl;
628                         return EXIT_FAILURE;
629                 }
630         } else {
631                 // We have to look for an address.
632                 // serverPid can be empty.
633                 FileNameList addrs = lyxSockets(to_filesystem8bit(cmdline::mainTmp), cmdline::serverPid);
634                 FileNameList::const_iterator addr = addrs.begin();
635                 FileNameList::const_iterator end = addrs.end();
636                 for (; addr != end; ++addr) {
637                         // Caution: addr->string() is in filesystem encoding
638                         server.reset(new LyXDataSocket(*addr));
639                         if (server->connected())
640                                 break;
641                         lyxerr << "lyxclient: " << "Could not connect to "
642                              << addr->absFileName() << endl;
643                 }
644                 if (addr == end) {
645                         lyxerr << "lyxclient: No suitable server found."
646                                << endl;
647                         return EXIT_FAILURE;
648                 }
649                 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
650         }
651
652         int const serverfd = server->fd();
653
654         IOWatch iowatch;
655         iowatch.addfd(serverfd);
656
657         // Used to read from server
658         string answer;
659
660         // Send greeting
661         server->writeln("HELLO:" + to_utf8(cmdline::clientName));
662         // wait at most 2 seconds until server responds
663         iowatch.wait(2.0);
664         if (iowatch.isset(serverfd) && server->readln(answer)) {
665                 if (prefixIs(answer, "BYE:")) {
666                         cerr << "lyxclient: Server disconnected." << endl;
667                         cout << answer << endl;
668                         return EXIT_FAILURE;
669                 }
670         } else {
671                 cerr << "lyxclient: No answer from server." << endl;
672                 return EXIT_FAILURE;
673         }
674
675         if (args.isset["-g"] || args.isset["-c"]) {
676                 server->writeln(to_utf8(cmdline::singleCommand));
677                 iowatch.wait(2.0);
678                 if (iowatch.isset(serverfd) && server->readln(answer)) {
679                         cout << answer;
680                         if (prefixIs(answer, "ERROR:"))
681                                 return EXIT_FAILURE;
682                         return EXIT_SUCCESS;
683                 } else {
684                         cerr << "lyxclient: No answer from server." << endl;
685                         return EXIT_FAILURE;
686                 }
687         }
688
689         // Take commands from stdin
690         iowatch.addfd(0); // stdin
691         bool saidbye = false;
692         while ((!saidbye) && server->connected()) {
693                 iowatch.wait();
694                 if (iowatch.isset(0)) {
695                         string command;
696                         getline(cin, command);
697                         if (command.empty())
698                                 continue;
699                         if (command == "BYE:") {
700                                 server->writeln("BYE:");
701                                 saidbye = true;
702                         } else {
703                                 server->writeln("LYXCMD:" + command);
704                         }
705                 }
706                 if (iowatch.isset(serverfd)) {
707                         while(server->readln(answer))
708                                 cout << answer << endl;
709                 }
710         }
711
712         return EXIT_SUCCESS;
713 }
714
715 } // namespace lyx
716
717
718 int main(int argc, char * argv[])
719 {
720         lyx::lyxerr.setStream(cerr);
721
722         lyx::LyXClientApp app(argc, argv);
723         return app.exec();
724 }
725
726
727 namespace boost {
728
729 void assertion_failed(char const* a, char const* b, char const* c, long d)
730 {
731         lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '
732                 << d << '\n';
733 }
734
735 } // namespace boost