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