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