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