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