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