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