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