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