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