]> git.lyx.org Git - lyx.git/blob - src/Server.cpp
Fix bug #6063: Keylogger lyx::lyxbreaker: Crash in Math.
[lyx.git] / src / Server.cpp
1 /**
2  * \file Server.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  * \author Angus Leeming
9  * \author John Levon
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 /**
15   Docu   : To use the lyxserver define the name of the pipe in your
16            lyxrc:
17            \serverpipe "/home/myhome/.lyxpipe"
18            Then use .lyxpipe.in and .lyxpipe.out to communicate to LyX.
19            Each message consists of a single line in ASCII. Input lines
20            (client -> LyX) have the following format:
21             "LYXCMD:<clientname>:<functionname>:<argument>"
22            Answers from LyX look like this:
23            "INFO:<clientname>:<functionname>:<data>"
24  [asierra970531] Or like this in case of error:
25            "ERROR:<clientname>:<functionname>:<error message>"
26            where <clientname> and <functionname> are just echoed.
27            If LyX notifies about a user defined extension key-sequence,
28            the line looks like this:
29            "NOTIFY:<key-sequence>"
30  [asierra970531] New server-only messages to implement a simple protocol
31            "LYXSRV:<clientname>:<protocol message>"
32            where <protocol message> can be "hello" or "bye". If hello is
33            received LyX will inform the client that it's listening its
34            messages, and 'bye' will inform that lyx is closing.
35
36            See development/server_monitor.c for an example client.
37   Purpose: implement a client/server lib for LyX
38 */
39
40 #include <config.h>
41
42 #include "Server.h"
43 #include "FuncRequest.h"
44 #include "LyXAction.h"
45 #include "LyXFunc.h"
46
47 #include "frontends/Application.h"
48
49 #include "support/debug.h"
50 #include "support/FileName.h"
51 #include "support/lstrings.h"
52
53 #include <boost/bind.hpp>
54
55 #include <cerrno>
56 #ifdef HAVE_SYS_STAT_H
57 # include <sys/stat.h>
58 #endif
59 #include <fcntl.h>
60
61 using namespace std;
62 using namespace lyx::support;
63
64 namespace lyx {
65
66 /////////////////////////////////////////////////////////////////////
67 //
68 // LyXComm
69 //
70 /////////////////////////////////////////////////////////////////////
71
72 #if !defined (HAVE_MKFIFO)
73 // We provide a stub class that disables the lyxserver.
74
75 LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
76 {}
77
78 void LyXComm::openConnection()
79 {}
80
81
82 void LyXComm::closeConnection()
83 {}
84
85
86 int LyXComm::startPipe(string const & filename, bool write)
87 {
88         return -1;
89 }
90
91
92 void LyXComm::endPipe(int & fd, string const & filename, bool write)
93 {}
94
95
96 void LyXComm::emergencyCleanup()
97 {}
98
99 void LyXComm::read_ready()
100 {}
101
102
103 void LyXComm::send(string const & msg)
104 {}
105
106
107 #else // defined (HAVE_MKFIFO)
108
109
110 LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
111         : pipename_(pip), client_(cli), clientcb_(ccb)
112 {
113         ready_ = false;
114         openConnection();
115 }
116
117
118 void LyXComm::openConnection()
119 {
120         LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
121
122         // If we are up, that's an error
123         if (ready_) {
124                 lyxerr << "LyXComm: Already connected" << endl;
125                 return;
126         }
127         // We assume that we don't make it
128         ready_ = false;
129
130         if (pipename_.empty()) {
131                 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
132                 return;
133         }
134
135         infd_ = startPipe(inPipeName(), false);
136         if (infd_ == -1)
137                 return;
138
139         outfd_ = startPipe(outPipeName(), true);
140         if (outfd_ == -1) {
141                 endPipe(infd_, inPipeName(), false);
142                 return;
143         }
144
145         if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
146                 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
147                        << '\n' << strerror(errno) << endl;
148                 return;
149         }
150
151         // We made it!
152         ready_ = true;
153         LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
154 }
155
156
157 /// Close pipes
158 void LyXComm::closeConnection()
159 {
160         LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
161
162         if (pipename_.empty()) {
163                 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
164                 return;
165         }
166
167         if (!ready_) {
168                 LYXERR0("LyXComm: Already disconnected");
169                 return;
170         }
171
172         endPipe(infd_, inPipeName(), false);
173         endPipe(outfd_, outPipeName(), true);
174
175         ready_ = false;
176 }
177
178
179 int LyXComm::startPipe(string const & file, bool write)
180 {
181         static bool stalepipe = false;
182         FileName const filename(file);
183         if (filename.exists()) {
184                 if (!write) {
185                         // Let's see whether we have a stale pipe.
186                         int fd = ::open(filename.toFilesystemEncoding().c_str(),
187                                         O_WRONLY | O_NONBLOCK);
188                         if (fd >= 0) {
189                                 // Another LyX instance is using it.
190                                 ::close(fd);
191                         } else if (errno == ENXIO) {
192                                 // No process is reading from the other end.
193                                 stalepipe = true;
194                                 LYXERR(Debug::LYXSERVER,
195                                         "LyXComm: trying to remove "
196                                         << filename);
197                                 filename.removeFile();
198                         }
199                 } else if (stalepipe) {
200                         LYXERR(Debug::LYXSERVER, "LyXComm: trying to remove "
201                                 << filename);
202                         filename.removeFile();
203                         stalepipe = false;
204                 }
205                 if (filename.exists()) {
206                         lyxerr << "LyXComm: Pipe " << filename
207                                << " already exists.\nIf no other LyX program"
208                                   " is active, please delete the pipe by hand"
209                                   " and try again."
210                                << endl;
211                         pipename_.erase();
212                         return -1;
213                 }
214         }
215
216         if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
217                 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
218                        << strerror(errno) << endl;
219                 return -1;
220         }
221         int const fd = ::open(filename.toFilesystemEncoding().c_str(),
222                               write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
223
224         if (fd < 0) {
225                 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
226                        << strerror(errno) << endl;
227                 filename.removeFile();
228                 return -1;
229         }
230
231         if (!write) {
232                 theApp()->registerSocketCallback(fd,
233                         boost::bind(&LyXComm::read_ready, this));
234         }
235
236         return fd;
237 }
238
239
240 void LyXComm::endPipe(int & fd, string const & filename, bool write)
241 {
242         if (fd < 0)
243                 return;
244
245         if (!write)
246                 theApp()->unregisterSocketCallback(fd);
247
248         if (::close(fd) < 0) {
249                 lyxerr << "LyXComm: Could not close pipe " << filename
250                        << '\n' << strerror(errno) << endl;
251         }
252
253         if (FileName(filename).removeFile() < 0) {
254                 lyxerr << "LyXComm: Could not remove pipe " << filename
255                        << '\n' << strerror(errno) << endl;
256         }
257
258         fd = -1;
259 }
260
261
262 void LyXComm::emergencyCleanup()
263 {
264         if (!pipename_.empty()) {
265                 endPipe(infd_, inPipeName(), false);
266                 endPipe(outfd_, outPipeName(), true);
267         }
268 }
269
270
271 // Receives messages and sends then to client
272 void LyXComm::read_ready()
273 {
274         // FIXME: make read_buffer_ a class-member for multiple sessions
275         static string read_buffer_;
276         read_buffer_.erase();
277
278         int const charbuf_size = 100;
279         char charbuf[charbuf_size];
280
281         // As O_NONBLOCK is set, until no data is available for reading,
282         // read() doesn't block but returns -1 and set errno to EAGAIN.
283         // After a client that opened the pipe for writing, closes it
284         // (and no other client is using the pipe), read() would always
285         // return 0 and thus the connection has to be reset.
286
287         errno = 0;
288         int status;
289         // the single = is intended here.
290         while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
291
292                 if (status > 0) {
293                         charbuf[status] = '\0'; // turn it into a c string
294                         read_buffer_ += rtrim(charbuf, "\r");
295                         // commit any commands read
296                         while (read_buffer_.find('\n') != string::npos) {
297                                 // split() grabs the entire string if
298                                 // the delim /wasn't/ found. ?:-P
299                                 string cmd;
300                                 read_buffer_= split(read_buffer_, cmd,'\n');
301                                 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
302                                         << ", read_buffer_:" << read_buffer_
303                                         << ", cmd:" << cmd);
304                                 if (!cmd.empty())
305                                         clientcb_(client_, cmd);
306                                         //\n or not \n?
307                         }
308                 } else {
309                         if (errno == EAGAIN) {
310                                 // Nothing to read, continue
311                                 errno = 0;
312                                 return;
313                         }
314                         // An error occurred, better bailing out
315                         LYXERR0("LyXComm: " << strerror(errno));
316                         if (!read_buffer_.empty()) {
317                                 LYXERR0("LyXComm: truncated command: " << read_buffer_);
318                                 read_buffer_.erase();
319                         }
320                         break; // reset connection
321                 }
322         }
323
324         // The connection gets reset when read() returns 0 (meaning that the
325         // last client closed the pipe) or an error occurred, in which case
326         // read() returns -1 and errno != EAGAIN.
327         closeConnection();
328         openConnection();
329         errno = 0;
330 }
331
332
333 void LyXComm::send(string const & msg)
334 {
335         if (msg.empty()) {
336                 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
337                 return;
338         }
339
340         LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
341
342         if (pipename_.empty()) return;
343
344         if (!ready_) {
345                 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
346         } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
347                 lyxerr << "LyXComm: Error sending message: " << msg
348                        << '\n' << strerror(errno)
349                        << "\nLyXComm: Resetting connection" << endl;
350                 closeConnection();
351                 openConnection();
352         }
353 }
354
355 #endif // defined (HAVE_MKFIFO)
356
357
358 string const LyXComm::inPipeName() const
359 {
360         return pipename_ + ".in";
361 }
362
363
364 string const LyXComm::outPipeName() const
365 {
366         return pipename_ + ".out";
367 }
368
369
370 /////////////////////////////////////////////////////////////////////
371 //
372 // Server
373 //
374 /////////////////////////////////////////////////////////////////////
375
376 void ServerCallback(Server * server, string const & msg)
377 {
378         server->callback(msg);
379 }
380
381 Server::Server(LyXFunc * f, string const & pipes)
382         : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
383 {}
384
385
386 Server::~Server()
387 {
388         // say goodbye to clients so they stop sending messages
389         // send as many bye messages as there are clients,
390         // each with client's name.
391         string message;
392         for (int i = 0; i != numclients_; ++i) {
393                 message = "LYXSRV:" + clients_[i] + ":bye\n";
394                 pipes_.send(message);
395         }
396 }
397
398
399 int compare(char const * a, char const * b, unsigned int len)
400 {
401         using namespace std;
402         return strncmp(a, b, len);
403 }
404
405
406 // Handle data gotten from communication, called by LyXComm
407 void Server::callback(string const & msg)
408 {
409         LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
410
411         char const * p = msg.c_str();
412
413         // --- parse the string --------------------------------------------
414         //
415         //  Format: LYXCMD:<client>:<func>:<argstring>\n
416         //
417         bool server_only = false;
418         while (*p) {
419                 // --- 1. check 'header' ---
420
421                 if (compare(p, "LYXSRV:", 7) == 0) {
422                         server_only = true;
423                 } else if (0 != compare(p, "LYXCMD:", 7)) {
424                         lyxerr << "Server: Unknown request \""
425                                << p << '"' << endl;
426                         return;
427                 }
428                 p += 7;
429
430                 // --- 2. for the moment ignore the client name ---
431                 string client;
432                 while (*p && *p != ':')
433                         client += char(*p++);
434                 if (*p == ':')
435                         ++p;
436                 if (!*p)
437                         return;
438
439                 // --- 3. get function name ---
440                 string cmd;
441                 while (*p && *p != ':')
442                         cmd += char(*p++);
443
444                 // --- 4. parse the argument ---
445                 string arg;
446                 if (!server_only && *p == ':' && *(++p)) {
447                         while (*p && *p != '\n')
448                                 arg += char(*p++);
449                         if (*p) ++p;
450                 }
451
452                 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
453                         << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
454
455                 // --- lookup and exec the command ------------------
456
457                 if (server_only) {
458                         string buf;
459                         // return the greeting to inform the client that
460                         // we are listening.
461                         if (cmd == "hello") {
462                                 // One more client
463                                 if (numclients_ == MAX_CLIENTS) { //paranoid check
464                                         LYXERR(Debug::LYXSERVER, "Server: too many clients...");
465                                         return;
466                                 }
467                                 int i = 0;
468                                 while (!clients_[i].empty() && i < numclients_)
469                                         ++i;
470                                 clients_[i] = client;
471                                 ++numclients_;
472                                 buf = "LYXSRV:" + client + ":hello\n";
473                                 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
474                                 pipes_.send(buf);
475                         } else if (cmd == "bye") {
476                                 // If clients_ == 0 maybe we should reset the pipes
477                                 // to prevent fake callbacks
478                                 int i = 0; //look if client is registered
479                                 for (; i < numclients_; ++i) {
480                                         if (clients_[i] == client)
481                                                 break;
482                                 }
483                                 if (i < numclients_) {
484                                         --numclients_;
485                                         clients_[i].erase();
486                                         LYXERR(Debug::LYXSERVER, "Server: Client "
487                                                 << client << " said goodbye");
488                                 } else {
489                                         LYXERR(Debug::LYXSERVER,
490                                                 "Server: ignoring bye messge from unregistered client" << client);
491                                 }
492                         } else {
493                                 LYXERR0("Server: Undefined server command " << cmd << '.');
494                         }
495                         return;
496                 }
497
498                 if (!cmd.empty()) {
499                         // which lyxfunc should we let it connect to?
500                         // The correct solution would be to have a
501                         // specialized (non-gui) BufferView. But how do
502                         // we do it now? Probably we should just let it
503                         // connect to the lyxfunc in the single LyXView we
504                         // support currently. (Lgb)
505
506                         func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
507                         string const rval = to_utf8(func_->getMessage());
508
509                         // all commands produce an INFO or ERROR message
510                         // in the output pipe, even if they do not return
511                         // anything. See chapter 4 of Customization doc.
512                         string buf;
513                         if (func_->errorStat())
514                                 buf = "ERROR:";
515                         else
516                                 buf = "INFO:";
517                         buf += client + ':' + cmd + ':' +  rval + '\n';
518                         pipes_.send(buf);
519
520                         // !!! we don't do any error checking -
521                         //  if the client won't listen, the
522                         //  message is lost and others too
523                         //  maybe; so the client should empty
524                         //  the outpipe before issuing a request.
525
526                         // not found
527                 }
528         }  // while *p
529 }
530
531
532 // Send a notify message to a client, called by WorkAreaKeyPress
533 void Server::notifyClient(string const & s)
534 {
535         pipes_.send("NOTIFY:" + s + "\n");
536 }
537
538
539 } // namespace lyx