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