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