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