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