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