]> git.lyx.org Git - lyx.git/blob - src/client/client.cpp
Win installer: translate 2 installer sections
[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
23 #include <boost/scoped_ptr.hpp>
24
25 // getpid(), getppid()
26 #ifdef HAVE_SYS_TYPES_H
27 # include <sys/types.h>
28 #endif
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32
33 // struct timeval
34 #ifdef HAVE_SYS_TIME_H
35 # include <sys/time.h>
36 #endif
37
38 // select()
39 #ifdef HAVE_SYS_SELECT_H
40 # include <sys/select.h>
41 #endif
42
43 // socket(), connect()
44 #ifdef HAVE_SYS_SOCKET_H
45 # include <sys/socket.h>
46 #endif
47 #include <sys/un.h>
48
49 // fcntl()
50 #include <fcntl.h>
51
52 // strerror()
53 #include <string.h>
54
55 #include <cerrno>
56 #include <cstdio>
57 #include <cstdlib>
58 #include <exception>
59 #include <string>
60 #include <vector>
61 #include <map>
62 #include <iostream>
63
64 using namespace std;
65 using namespace lyx::support;
66
67 using ::boost::scoped_ptr;
68
69 namespace lyx {
70
71 // Dummy LyXRC support
72 struct LyXRC {
73         string icon_set;
74 } lyxrc;
75
76 // Keep the linker happy on Windows
77 void lyx_exit(int)
78 {}
79
80 // Dummy language support
81 Messages const & getGuiMessages()
82 {
83         static Messages lyx_messages;
84
85         return lyx_messages;
86 }
87
88
89 Messages const & getMessages(string const &)
90 {
91         return getGuiMessages();
92 }
93
94
95 namespace support {
96
97 string itoa(unsigned int i)
98 {
99         char buf[20];
100         sprintf(buf, "%d", i);
101         return buf;
102 }
103
104
105 /// Returns the absolute pathnames of all lyx local sockets in
106 /// file system encoding.
107 /// Parts stolen from lyx::support::DirList().
108 FileNameList lyxSockets(string const & dir, string const & pid)
109 {
110         FileNameList dirlist;
111
112         FileName dirpath(dir + "/");
113
114         if (!dirpath.exists() || !dirpath.isDirectory()) {
115                 lyxerr << dir << " does not exist or is not a directory."
116                        << endl;
117                 return dirlist;
118         }
119
120         FileNameList dirs = dirpath.dirList("");
121         FileNameList::const_iterator it = dirs.begin();
122         FileNameList::const_iterator end = dirs.end();
123
124         for (; it != end; ++it) {
125                 if (!it->isDirectory())
126                         continue;
127                 string const tmpdir = it->absFileName();
128                 if (!contains(tmpdir, "lyx_tmpdir" + pid))
129                         continue;
130
131                 FileName lyxsocket(tmpdir + "/lyxsocket");
132                 if (lyxsocket.exists())
133                         dirlist.push_back(lyxsocket);
134         }
135
136         return dirlist;
137 }
138
139
140 namespace socktools {
141
142
143 /// Connect to the socket \p name.
144 int connect(FileName const & name)
145 {
146         int fd; // File descriptor for the socket
147         sockaddr_un addr; // Structure that hold the socket address
148
149         string const encoded = name.toFilesystemEncoding();
150         // char sun_path[108]
151         string::size_type len = encoded.size();
152         if (len > 107) {
153                 cerr << "lyxclient: Socket address '" << name
154                      << "' too long." << endl;
155                 return -1;
156         }
157         // Synonims for AF_UNIX are AF_LOCAL and AF_FILE
158         addr.sun_family = AF_UNIX;
159         encoded.copy(addr.sun_path, 107);
160         addr.sun_path[len] = '\0';
161
162         if ((fd = ::socket(PF_UNIX, SOCK_STREAM, 0))== -1) {
163                 cerr << "lyxclient: Could not create socket descriptor: "
164                      << strerror(errno) << endl;
165                 return -1;
166         }
167         if (::connect(fd,
168                       reinterpret_cast<struct sockaddr *>(&addr),
169                       sizeof(addr)) == -1) {
170                 cerr << "lyxclient: Could not connect to socket " << name.absFileName()
171                      << ": " << strerror(errno) << endl;
172                 ::close(fd);
173                 return -1;
174         }
175         if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
176                 cerr << "lyxclient: Could not set O_NONBLOCK for socket: "
177                      << strerror(errno) << endl;
178                 ::close(fd);
179                 return -1;
180         }
181         return fd;
182 }
183
184
185 } // namespace socktools
186 } // namespace support
187
188
189
190 /////////////////////////////////////////////////////////////////////
191 //
192 // IOWatch
193 //
194 /////////////////////////////////////////////////////////////////////
195
196 class IOWatch {
197 public:
198         IOWatch();
199         void clear();
200         void addfd(int);
201         bool wait(double);
202         bool wait();
203         bool isset(int fd);
204 private:
205         fd_set des;
206         fd_set act;
207 };
208
209
210 IOWatch::IOWatch()
211 {
212         clear();
213 }
214
215
216 void IOWatch::clear()
217 {
218         FD_ZERO(&des);
219 }
220
221
222 void IOWatch::addfd(int fd)
223 {
224         FD_SET(fd, &des);
225 }
226
227
228 bool IOWatch::wait(double timeout)
229 {
230         timeval to;
231         to.tv_sec = static_cast<long int>(timeout);
232         to.tv_usec = static_cast<long int>((timeout - to.tv_sec)*1E6);
233         act = des;
234         return select(FD_SETSIZE, &act,
235                       (fd_set *)0, (fd_set *)0, &to);
236 }
237
238
239 bool IOWatch::wait()
240 {
241         act = des;
242         return select(FD_SETSIZE, &act,
243                       (fd_set *)0, (fd_set *)0, (timeval *)0);
244 }
245
246
247 bool IOWatch::isset(int fd)
248 {
249         return FD_ISSET(fd, &act);
250 }
251
252
253
254 /////////////////////////////////////////////////////////////////////
255 //
256 // LyXDataSocket
257 //
258 /////////////////////////////////////////////////////////////////////
259
260 // Modified LyXDataSocket class for use with the client
261 class LyXDataSocket {
262 public:
263         LyXDataSocket(FileName const &);
264         ~LyXDataSocket();
265         // File descriptor of the connection
266         int fd() const;
267         // Connection status
268         bool connected() const;
269         // Line buffered input from the socket
270         bool readln(string &);
271         // Write the string + '\n' to the socket
272         void writeln(string const &);
273 private:
274         // File descriptor for the data socket
275         int fd_;
276         // True if the connection is up
277         bool connected_;
278         // buffer for input data
279         string buffer;
280 };
281
282
283 LyXDataSocket::LyXDataSocket(FileName const & address)
284 {
285         if ((fd_ = socktools::connect(address)) == -1) {
286                 connected_ = false;
287         } else {
288                 connected_ = true;
289         }
290 }
291
292
293 LyXDataSocket::~LyXDataSocket()
294 {
295         ::close(fd_);
296 }
297
298
299 int LyXDataSocket::fd() const
300 {
301         return fd_;
302 }
303
304
305 bool LyXDataSocket::connected() const
306 {
307         return connected_;
308 }
309
310
311 // Returns true if there was a complete line to input
312 // A line is of the form <key>:<value>
313 //   A line not of this form will not be passed
314 // The line read is split and stored in 'key' and 'value'
315 bool LyXDataSocket::readln(string & line)
316 {
317         int const charbuf_size = 100;
318         char charbuf[charbuf_size]; // buffer for the ::read() system call
319         int count;
320         string::size_type pos;
321
322         // read and store characters in buffer
323         while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
324                 charbuf[count] = '\0'; // turn it into a c string
325                 buffer += charbuf;
326         }
327
328         // Error conditions. The buffer must still be
329         // processed for lines read
330         if (count == 0) { // EOF -- connection closed
331                 connected_ = false;
332         } else if ((count == -1) && (errno != EAGAIN)) { // IO error
333                 cerr << "lyxclient: IO error." << endl;
334                 connected_ = false;
335         }
336
337         // Cut a line from buffer
338         if ((pos = buffer.find('\n')) == string::npos)
339                 return false; // No complete line stored
340         line = buffer.substr(0, pos);
341         buffer = buffer.substr(pos + 1);
342         return true;
343 }
344
345
346 // Write a line of the form <key>:<value> to the socket
347 void LyXDataSocket::writeln(string const & line)
348 {
349         string linen(line + '\n');
350         int size = linen.size();
351         int written = ::write(fd_, linen.c_str(), size);
352         if (written < size) { // Allways mean end of connection.
353                 if ((written == -1) && (errno == EPIPE)) {
354                         // The program will also receive a SIGPIPE
355                         // that must be catched
356                         cerr << "lyxclient: connection closed while writing."
357                              << endl;
358                 } else {
359                         // Anything else, including errno == EAGAIN, must be
360                         // considered IO error. EAGAIN should never happen
361                         // when line is small
362                         cerr << "lyxclient: IO error: " << strerror(errno);
363                 }
364                 connected_ = false;
365         }
366 }
367
368
369 /////////////////////////////////////////////////////////////////////
370 //
371 // CmdLineParser
372 //
373 /////////////////////////////////////////////////////////////////////
374
375 class CmdLineParser {
376 public:
377         typedef int (*optfunc)(vector<docstring> const & args);
378         map<string, optfunc> helper;
379         map<string, bool> isset;
380         bool parse(int, char * []);
381         vector<char *> nonopt;
382 };
383
384
385 bool CmdLineParser::parse(int argc, char * argv[])
386 {
387         int opt = 1;
388         while (opt < argc) {
389                 vector<docstring> args;
390                 if (helper[argv[opt]]) {
391                         isset[argv[opt]] = true;
392                         int arg = opt + 1;
393                         while ((arg < argc) && (!helper[argv[arg]])) {
394                                 args.push_back(from_local8bit(argv[arg]));
395                                 ++arg;
396                         }
397                         int taken = helper[argv[opt]](args);
398                         if (taken == -1)
399                                 return false;
400                         opt += 1 + taken;
401                 } else {
402                         if (argv[opt][0] == '-') {
403                                 if ((argv[opt][1] == '-')
404                                    && (argv[opt][2]== '\0')) {
405                                         ++opt;
406                                         while (opt < argc) {
407                                                 nonopt.push_back(argv[opt]);
408                                                 ++opt;
409                                         }
410                                         return true;
411                                 } else {
412                                         cerr << "lyxclient: unknown option "
413                                              << argv[opt] << endl;
414                                         return false;
415                                 }
416                         }
417                         nonopt.push_back(argv[opt]);
418                         ++opt;
419                 }
420         }
421         return true;
422 }
423 // ~Class CmdLineParser -------------------------------------------------------
424
425
426
427 namespace cmdline {
428
429 docstring mainTmp(from_ascii("/tmp"));
430
431
432 class StopException : public exception
433 {
434 public:
435         StopException(int status) : status_(status) {}
436         int status() const { return status_; }
437 private:
438         int status_;
439 };
440
441
442 void usage()
443 {
444         cerr <<
445                 "Usage: lyxclient [options]\n"
446           "Options are:\n"
447           "  -a address    set address of the lyx socket\n"
448           "  -t directory  set system temporary directory (for detecting sockets)\n"
449           "  -p pid        select a running lyx by pidi\n"
450           "  -c command    send a single command and quit (LYXCMD prefix needed)\n"
451           "  -g file row   send a command to go to file and row\n"
452           "  -n name       set client name\n"
453           "  -h name       display this help end exit\n"
454           "If -a is not used, lyxclient will use the arguments of -t and -p to look for\n"
455           "a running lyx. If -t is not set, 'directory' defaults to the system directory. If -p is set,\n"
456           "lyxclient will connect only to a lyx with the specified pid. Options -c and -g\n"
457           "cannot be set simultaneoulsly. If no -c or -g options are given, lyxclient\n"
458           "will read commands from standard input and disconnect when command read is BYE:\n"
459           "\n"
460           "System directory is: " << to_utf8(cmdline::mainTmp)
461            << endl;
462 }
463
464
465 int h(vector<docstring> const &)
466 {
467         usage();
468         throw StopException(EXIT_SUCCESS);
469 }
470
471
472 docstring clientName =
473         from_ascii(itoa(::getppid()) + ">" + itoa(::getpid()));
474
475 int n(vector<docstring> const & arg)
476 {
477         if (arg.empty()) {
478                 cerr << "lyxclient: The option -n requires 1 argument."
479                      << endl;
480                 return -1;
481         }
482         clientName = arg[0];
483         return 1;
484 }
485
486
487 docstring singleCommand;
488
489
490 int c(vector<docstring> const & arg)
491 {
492         if (arg.empty()) {
493                 cerr << "lyxclient: The option -c requires 1 argument."
494                      << endl;
495                 return -1;
496         }
497         singleCommand = arg[0];
498         return 1;
499 }
500
501
502 int g(vector<docstring> const & arg)
503 {
504         if (arg.size() < 2) {
505                 cerr << "lyxclient: The option -g requires 2 arguments."
506                      << endl;
507                 return -1;
508         }
509         singleCommand = "LYXCMD:server-goto-file-row "
510                 + arg[0] + ' '
511                 + arg[1];
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         scoped_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