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