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