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