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