]> git.lyx.org Git - lyx.git/blob - src/client/client.cpp
Russian layouttranslations reviewed by Yuriy, Dec 13 2017.
[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 verbose support
70 bool verbose = false;
71
72 struct App {
73         bool cancel_export;
74 };
75
76 App app;
77 App * theApp() { return &app; }
78
79 // Dummy LyXRC support
80 struct LyXRC {
81         string icon_set;
82 } lyxrc;
83
84 // Keep the linker happy on Windows
85 void lyx_exit(int)
86 {}
87
88 // Dummy language support
89 Messages const & getGuiMessages()
90 {
91         static Messages lyx_messages;
92
93         return lyx_messages;
94 }
95
96
97 Messages const & getMessages(string const &)
98 {
99         return getGuiMessages();
100 }
101
102
103 namespace support {
104
105 string itoa(unsigned int i)
106 {
107         char buf[20];
108         sprintf(buf, "%d", i);
109         return buf;
110 }
111
112
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)
117 {
118         FileNameList dirlist;
119
120         FileName dirpath(dir + "/");
121
122         if (!dirpath.exists() || !dirpath.isDirectory()) {
123                 lyxerr << dir << " does not exist or is not a directory."
124                        << endl;
125                 return dirlist;
126         }
127
128         FileNameList dirs = dirpath.dirList("");
129         FileNameList::const_iterator it = dirs.begin();
130         FileNameList::const_iterator end = dirs.end();
131
132         for (; it != end; ++it) {
133                 if (!it->isDirectory())
134                         continue;
135                 string const tmpdir = it->absFileName();
136                 if (!contains(tmpdir, "lyx_tmpdir" + pid))
137                         continue;
138
139                 FileName lyxsocket(tmpdir + "/lyxsocket");
140                 if (lyxsocket.exists())
141                         dirlist.push_back(lyxsocket);
142         }
143
144         return dirlist;
145 }
146
147
148 namespace socktools {
149
150
151 /// Connect to the socket \p name.
152 int connect(FileName const & name)
153 {
154         int fd; // File descriptor for the socket
155         sockaddr_un addr; // Structure that hold the socket address
156
157         string const encoded = name.toFilesystemEncoding();
158         // char sun_path[108]
159         string::size_type len = encoded.size();
160         if (len > 107) {
161                 cerr << "lyxclient: Socket address '" << name
162                      << "' too long." << endl;
163                 return -1;
164         }
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';
169
170         if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
171                 cerr << "lyxclient: Could not create socket descriptor: "
172                      << strerror(errno) << endl;
173                 return -1;
174         }
175         if (::connect(fd,
176                       reinterpret_cast<struct sockaddr *>(&addr),
177                       sizeof(addr)) == -1) {
178                 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
179                      << ": " << strerror(errno) << endl;
180                 ::close(fd);
181                 return -1;
182         }
183         if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
184                 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
185                      << strerror(errno) << endl;
186                 ::close(fd);
187                 return -1;
188         }
189         return fd;
190 }
191
192
193 } // namespace socktools
194 } // namespace support
195
196
197
198 /////////////////////////////////////////////////////////////////////
199 //
200 // IOWatch
201 //
202 /////////////////////////////////////////////////////////////////////
203
204 class IOWatch {
205 public:
206         IOWatch();
207         void clear();
208         void addfd(int);
209         bool wait(double);
210         bool wait();
211         bool isset(int fd);
212 private:
213         fd_set des;
214         fd_set act;
215 };
216
217
218 IOWatch::IOWatch()
219 {
220         clear();
221 }
222
223
224 void IOWatch::clear()
225 {
226         FD_ZERO(&des);
227 }
228
229
230 void IOWatch::addfd(int fd)
231 {
232         FD_SET(fd, &des);
233 }
234
235
236 bool IOWatch::wait(double timeout)
237 {
238         timeval to;
239         to.tv_sec = static_cast<long int>(timeout);
240         to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
241         act = des;
242         return select(FD_SETSIZE, &act,
243                       (fd_set *)0, (fd_set *)0, &to);
244 }
245
246
247 bool IOWatch::wait()
248 {
249         act = des;
250         return select(FD_SETSIZE, &act,
251                       (fd_set *)0, (fd_set *)0, (timeval *)0);
252 }
253
254
255 bool IOWatch::isset(int fd)
256 {
257         return FD_ISSET(fd, &act);
258 }
259
260
261
262 /////////////////////////////////////////////////////////////////////
263 //
264 // LyXDataSocket
265 //
266 /////////////////////////////////////////////////////////////////////
267
268 // Modified LyXDataSocket class for use with the client
269 class LyXDataSocket {
270 public:
271         LyXDataSocket(FileName const &);
272         ~LyXDataSocket();
273         // File descriptor of the connection
274         int fd() const;
275         // Connection status
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 &);
281 private:
282         // File descriptor for the data socket
283         int fd_;
284         // True if the connection is up
285         bool connected_;
286         // buffer for input data
287         string buffer;
288 };
289
290
291 LyXDataSocket::LyXDataSocket(FileName const & address)
292 {
293         if ((fd_ = socktools::connect(address)) == -1) {
294                 connected_ = false;
295         } else {
296                 connected_ = true;
297         }
298 }
299
300
301 LyXDataSocket::~LyXDataSocket()
302 {
303         ::close(fd_);
304 }
305
306
307 int LyXDataSocket::fd() const
308 {
309         return fd_;
310 }
311
312
313 bool LyXDataSocket::connected() const
314 {
315         return connected_;
316 }
317
318
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)
324 {
325         int const charbuf_size = 100;
326         char charbuf[charbuf_size]; // buffer for the ::read() system call
327         int count;
328         string::size_type pos;
329
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
333                 buffer += charbuf;
334         }
335
336         // Error conditions. The buffer must still be
337         // processed for lines read
338         if (count == 0) { // EOF -- connection closed
339                 connected_ = false;
340         } else if ((count == -1) && (errno != EAGAIN)) { // IO error
341                 cerr << "lyxclient: IO error." << endl;
342                 connected_ = false;
343         }
344
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);
350         return true;
351 }
352
353
354 // Write a line of the form <key>:<value> to the socket
355 void LyXDataSocket::writeln(string const & line)
356 {
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."
365                              << endl;
366                 } else {
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);
371                 }
372                 connected_ = false;
373         }
374 }
375
376
377 /////////////////////////////////////////////////////////////////////
378 //
379 // CmdLineParser
380 //
381 /////////////////////////////////////////////////////////////////////
382
383 class CmdLineParser {
384 public:
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;
390 };
391
392
393 bool CmdLineParser::parse(int argc, char * argv[])
394 {
395         int opt = 1;
396         while (opt < argc) {
397                 vector<docstring> args;
398                 if (helper[argv[opt]]) {
399                         isset[argv[opt]] = true;
400                         int arg = opt + 1;
401                         while ((arg < argc) && (!helper[argv[arg]])) {
402                                 args.push_back(from_local8bit(argv[arg]));
403                                 ++arg;
404                         }
405                         int taken = helper[argv[opt]](args);
406                         if (taken == -1)
407                                 return false;
408                         opt += 1 + taken;
409                 } else {
410                         if (argv[opt][0] == '-') {
411                                 if ((argv[opt][1] == '-')
412                                    && (argv[opt][2]== '\0')) {
413                                         ++opt;
414                                         while (opt < argc) {
415                                                 nonopt.push_back(argv[opt]);
416                                                 ++opt;
417                                         }
418                                         return true;
419                                 } else {
420                                         cerr << "lyxclient: unknown option "
421                                              << argv[opt] << endl;
422                                         return false;
423                                 }
424                         }
425                         nonopt.push_back(argv[opt]);
426                         ++opt;
427                 }
428         }
429         return true;
430 }
431 // ~Class CmdLineParser -------------------------------------------------------
432
433
434
435 namespace cmdline {
436
437 docstring mainTmp(from_ascii("/tmp"));
438
439
440 class StopException : public exception
441 {
442 public:
443         StopException(int status) : status_(status) {}
444         int status() const { return status_; }
445 private:
446         int status_;
447 };
448
449
450 void usage()
451 {
452         cerr <<
453                 "Usage: lyxclient [options]\n"
454           "Options are:\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"
467           "\n"
468           "System directory is: " << to_utf8(cmdline::mainTmp)
469            << endl;
470 }
471
472
473 int h(vector<docstring> const &)
474 {
475         usage();
476         throw StopException(EXIT_SUCCESS);
477 }
478
479
480 docstring clientName =
481         from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
482
483 int n(vector<docstring> const & arg)
484 {
485         if (arg.empty()) {
486                 cerr << "lyxclient: The option -n requires 1 argument."
487                      << endl;
488                 return -1;
489         }
490         clientName = arg[0];
491         return 1;
492 }
493
494
495 docstring singleCommand;
496
497
498 int c(vector<docstring> const & arg)
499 {
500         if (arg.empty()) {
501                 cerr << "lyxclient: The option -c requires 1 argument."
502                      << endl;
503                 return -1;
504         }
505         singleCommand = arg[0];
506         return 1;
507 }
508
509
510 int g(vector<docstring> const & arg)
511 {
512         if (arg.size() < 2) {
513                 cerr << "lyxclient: The option -g requires 2 arguments."
514                      << endl;
515                 return -1;
516         }
517         singleCommand = "LYXCMD:command-sequence "
518                 "server-goto-file-row "
519                         + arg[0] + ' '
520                         + arg[1] + "; " +
521                 "lyx-activate";
522         return 2;
523 }
524
525
526 // empty if LYXSOCKET is not set in the environment
527 docstring serverAddress;
528
529
530 int a(vector<docstring> const & arg)
531 {
532         if (arg.empty()) {
533                 cerr << "lyxclient: The option -a requires 1 argument."
534                      << endl;
535                 return -1;
536         }
537         // -a supercedes LYXSOCKET environment variable
538         serverAddress = arg[0];
539         return 1;
540 }
541
542
543
544
545 int t(vector<docstring> const & arg)
546 {
547         if (arg.empty()) {
548                 cerr << "lyxclient: The option -t requires 1 argument."
549                      << endl;
550                 return -1;
551         }
552         mainTmp = arg[0];
553         return 1;
554 }
555
556
557 string serverPid; // Init to empty string
558
559
560 int p(vector<docstring> const & arg)
561 {
562         if (arg.empty()) {
563                 cerr << "lyxclient: The option -p requires 1 argument."
564                      << endl;
565                 return -1;
566         }
567         serverPid = to_ascii(arg[0]);
568         return 1;
569 }
570
571
572 } // namespace cmdline
573
574 /// The main application class
575 class LyXClientApp : public ConsoleApplication
576 {
577 public:
578         LyXClientApp(int & argc, char * argv[])
579                 : ConsoleApplication("client" PROGRAM_SUFFIX, argc, argv),
580                   argc_(argc), argv_(argv)
581         {
582         }
583         void doExec()
584         {
585                 try {
586                         int const exit_status = run();
587                         exit(exit_status);
588                 }
589                 catch (cmdline::StopException & e) {
590                         exit(e.status());
591                 }
592         }
593 private:
594         int run();
595         int & argc_;
596         char ** argv_;
597 };
598
599
600 int LyXClientApp::run()
601 {
602         // qt changes this, and our numeric conversions require the C locale
603         setlocale(LC_NUMERIC, "C");
604
605         // Set defaults
606         char const * const lyxsocket = getenv("LYXSOCKET");
607         if (lyxsocket)
608                 cmdline::serverAddress = from_local8bit(lyxsocket);
609
610         // Default temporary
611         cmdline::mainTmp = FileName::tempPath().absoluteFilePath();
612
613         // Command line builder
614         CmdLineParser args;
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;
622
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"])) {
627                 cmdline::usage();
628                 return EXIT_FAILURE;
629         }
630
631         unique_ptr<LyXDataSocket> server;
632
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;
638                         return EXIT_FAILURE;
639                 }
640         } else {
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())
650                                 break;
651                         lyxerr << "lyxclient: " << "Could not connect to "
652                              << addr->absFileName() << endl;
653                 }
654                 if (addr == end) {
655                         lyxerr << "lyxclient: No suitable server found."
656                                << endl;
657                         return EXIT_FAILURE;
658                 }
659                 cerr << "lyxclient: " << "Connected to " << addr->absFileName() << endl;
660         }
661
662         int const serverfd = server->fd();
663
664         IOWatch iowatch;
665         iowatch.addfd(serverfd);
666
667         // Used to read from server
668         string answer;
669
670         // Send greeting
671         server->writeln("HELLO:" + to_utf8(cmdline::clientName));
672         // wait at most 2 seconds until server responds
673         iowatch.wait(2.0);
674         if (iowatch.isset(serverfd) && server->readln(answer)) {
675                 if (prefixIs(answer, "BYE:")) {
676                         cerr << "lyxclient: Server disconnected." << endl;
677                         cout << answer << endl;
678                         return EXIT_FAILURE;
679                 }
680         } else {
681                 cerr << "lyxclient: No answer from server." << endl;
682                 return EXIT_FAILURE;
683         }
684
685         if (args.isset["-g"] || args.isset["-c"]) {
686                 server->writeln(to_utf8(cmdline::singleCommand));
687                 iowatch.wait(2.0);
688                 if (iowatch.isset(serverfd) && server->readln(answer)) {
689                         cout << answer;
690                         if (prefixIs(answer, "ERROR:"))
691                                 return EXIT_FAILURE;
692                         return EXIT_SUCCESS;
693                 } else {
694                         cerr << "lyxclient: No answer from server." << endl;
695                         return EXIT_FAILURE;
696                 }
697         }
698
699         // Take commands from stdin
700         iowatch.addfd(0); // stdin
701         bool saidbye = false;
702         while ((!saidbye) && server->connected()) {
703                 iowatch.wait();
704                 if (iowatch.isset(0)) {
705                         string command;
706                         getline(cin, command);
707                         if (command.empty())
708                                 continue;
709                         if (command == "BYE:") {
710                                 server->writeln("BYE:");
711                                 saidbye = true;
712                         } else {
713                                 server->writeln("LYXCMD:" + command);
714                         }
715                 }
716                 if (iowatch.isset(serverfd)) {
717                         while(server->readln(answer))
718                                 cout << answer << endl;
719                 }
720         }
721
722         return EXIT_SUCCESS;
723 }
724
725 } // namespace lyx
726
727
728 int main(int argc, char * argv[])
729 {
730         lyx::lyxerr.setStream(cerr);
731
732         lyx::LyXClientApp app(argc, argv);
733         return app.exec();
734 }
735
736
737 namespace boost {
738
739 void assertion_failed(char const* a, char const* b, char const* c, long d)
740 {
741         lyx::lyxerr << "Assertion failed: " << a << ' ' << b << ' ' << c << ' '
742                 << d << '\n';
743 }
744
745 } // namespace boost