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