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