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