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