]> git.lyx.org Git - features.git/blob - src/Server.cpp
Fix bug 641 (Detect stale lyxpipes after crash)
[features.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                         errno = 0;
187                         int fd = ::open(filename.toFilesystemEncoding().c_str(),
188                                         O_WRONLY | O_NONBLOCK);
189                         if (fd >= 0) {
190                                 // Another LyX instance is using it.
191                                 close(fd);
192                         } else if (errno == ENXIO) {
193                                 // No process is reading from the other end.
194                                 stalepipe = true;
195                                 LYXERR(Debug::LYXSERVER,
196                                         "LyXComm: trying to remove "
197                                         << filename);
198                                 filename.removeFile();
199                         }
200                 } else if (stalepipe) {
201                         LYXERR(Debug::LYXSERVER, "LyXComm: trying to remove "
202                                 << filename);
203                         filename.removeFile();
204                         stalepipe = false;
205                 }
206                 if (filename.exists()) {
207                         lyxerr << "LyXComm: Pipe " << filename
208                                << " already exists.\nIf no other LyX program"
209                                   " is active, please delete the pipe by hand"
210                                   " and try again."
211                                << endl;
212                         pipename_.erase();
213                         return -1;
214                 }
215         }
216
217         if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
218                 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
219                        << strerror(errno) << endl;
220                 return -1;
221         }
222         int const fd = ::open(filename.toFilesystemEncoding().c_str(),
223                               write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
224
225         if (fd < 0) {
226                 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
227                        << strerror(errno) << endl;
228                 filename.removeFile();
229                 return -1;
230         }
231
232         if (!write) {
233                 theApp()->registerSocketCallback(fd,
234                         boost::bind(&LyXComm::read_ready, this));
235         }
236
237         return fd;
238 }
239
240
241 void LyXComm::endPipe(int & fd, string const & filename, bool write)
242 {
243         if (fd < 0)
244                 return;
245
246         if (!write)
247                 theApp()->unregisterSocketCallback(fd);
248
249         if (::close(fd) < 0) {
250                 lyxerr << "LyXComm: Could not close pipe " << filename
251                        << '\n' << strerror(errno) << endl;
252         }
253
254         if (FileName(filename).removeFile() < 0) {
255                 lyxerr << "LyXComm: Could not remove pipe " << filename
256                        << '\n' << strerror(errno) << endl;
257         }
258
259         fd = -1;
260 }
261
262
263 void LyXComm::emergencyCleanup()
264 {
265         if (!pipename_.empty()) {
266                 endPipe(infd_, inPipeName(), false);
267                 endPipe(outfd_, outPipeName(), true);
268         }
269 }
270
271
272 // Receives messages and sends then to client
273 void LyXComm::read_ready()
274 {
275         // FIXME: make read_buffer_ a class-member for multiple sessions
276         static string read_buffer_;
277         read_buffer_.erase();
278
279         int const charbuf_size = 100;
280         char charbuf[charbuf_size];
281
282         // As O_NONBLOCK is set, until no data is available for reading,
283         // read() doesn't block but returns -1 and set errno to EAGAIN.
284         // After a client that opened the pipe for writing, closes it
285         // (and no other client is using the pipe), read() would always
286         // return 0 and thus the connection has to be reset.
287
288         errno = 0;
289         int status;
290         // the single = is intended here.
291         while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
292
293                 if (status > 0) {
294                         charbuf[status] = '\0'; // turn it into a c string
295                         read_buffer_ += rtrim(charbuf, "\r");
296                         // commit any commands read
297                         while (read_buffer_.find('\n') != string::npos) {
298                                 // split() grabs the entire string if
299                                 // the delim /wasn't/ found. ?:-P
300                                 string cmd;
301                                 read_buffer_= split(read_buffer_, cmd,'\n');
302                                 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
303                                         << ", read_buffer_:" << read_buffer_
304                                         << ", cmd:" << cmd);
305                                 if (!cmd.empty())
306                                         clientcb_(client_, cmd);
307                                         //\n or not \n?
308                         }
309                 } else {
310                         if (errno == EAGAIN) {
311                                 // Nothing to read, continue
312                                 errno = 0;
313                                 return;
314                         }
315                         // An error occurred, better bailing out
316                         LYXERR0("LyXComm: " << strerror(errno));
317                         if (!read_buffer_.empty()) {
318                                 LYXERR0("LyXComm: truncated command: " << read_buffer_);
319                                 read_buffer_.erase();
320                         }
321                         break; // reset connection
322                 }
323         }
324
325         // The connection gets reset when read() returns 0 (meaning that the
326         // last client closed the pipe) or an error occurred, in which case
327         // read() returns -1 and errno != EAGAIN.
328         closeConnection();
329         openConnection();
330         errno = 0;
331 }
332
333
334 void LyXComm::send(string const & msg)
335 {
336         if (msg.empty()) {
337                 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
338                 return;
339         }
340
341         LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
342
343         if (pipename_.empty()) return;
344
345         if (!ready_) {
346                 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
347         } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
348                 lyxerr << "LyXComm: Error sending message: " << msg
349                        << '\n' << strerror(errno)
350                        << "\nLyXComm: Resetting connection" << endl;
351                 closeConnection();
352                 openConnection();
353         }
354 }
355
356 #endif // defined (HAVE_MKFIFO)
357
358
359 string const LyXComm::inPipeName() const
360 {
361         return pipename_ + ".in";
362 }
363
364
365 string const LyXComm::outPipeName() const
366 {
367         return pipename_ + ".out";
368 }
369
370
371 /////////////////////////////////////////////////////////////////////
372 //
373 // Server
374 //
375 /////////////////////////////////////////////////////////////////////
376
377 void ServerCallback(Server * server, string const & msg)
378 {
379         server->callback(msg);
380 }
381
382 Server::Server(LyXFunc * f, string const & pipes)
383         : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
384 {}
385
386
387 Server::~Server()
388 {
389         // say goodbye to clients so they stop sending messages
390         // send as many bye messages as there are clients,
391         // each with client's name.
392         string message;
393         for (int i = 0; i != numclients_; ++i) {
394                 message = "LYXSRV:" + clients_[i] + ":bye\n";
395                 pipes_.send(message);
396         }
397 }
398
399
400 int compare(char const * a, char const * b, unsigned int len)
401 {
402         using namespace std;
403         return strncmp(a, b, len);
404 }
405
406
407 // Handle data gotten from communication, called by LyXComm
408 void Server::callback(string const & msg)
409 {
410         LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
411
412         char const * p = msg.c_str();
413
414         // --- parse the string --------------------------------------------
415         //
416         //  Format: LYXCMD:<client>:<func>:<argstring>\n
417         //
418         bool server_only = false;
419         while (*p) {
420                 // --- 1. check 'header' ---
421
422                 if (compare(p, "LYXSRV:", 7) == 0) {
423                         server_only = true;
424                 } else if (0 != compare(p, "LYXCMD:", 7)) {
425                         lyxerr << "Server: Unknown request \""
426                                << p << '"' << endl;
427                         return;
428                 }
429                 p += 7;
430
431                 // --- 2. for the moment ignore the client name ---
432                 string client;
433                 while (*p && *p != ':')
434                         client += char(*p++);
435                 if (*p == ':')
436                         ++p;
437                 if (!*p)
438                         return;
439
440                 // --- 3. get function name ---
441                 string cmd;
442                 while (*p && *p != ':')
443                         cmd += char(*p++);
444
445                 // --- 4. parse the argument ---
446                 string arg;
447                 if (!server_only && *p == ':' && *(++p)) {
448                         while (*p && *p != '\n')
449                                 arg += char(*p++);
450                         if (*p) ++p;
451                 }
452
453                 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
454                         << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
455
456                 // --- lookup and exec the command ------------------
457
458                 if (server_only) {
459                         string buf;
460                         // return the greeting to inform the client that
461                         // we are listening.
462                         if (cmd == "hello") {
463                                 // One more client
464                                 if (numclients_ == MAX_CLIENTS) { //paranoid check
465                                         LYXERR(Debug::LYXSERVER, "Server: too many clients...");
466                                         return;
467                                 }
468                                 int i = 0;
469                                 while (!clients_[i].empty() && i < numclients_)
470                                         ++i;
471                                 clients_[i] = client;
472                                 ++numclients_;
473                                 buf = "LYXSRV:" + client + ":hello\n";
474                                 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
475                                 pipes_.send(buf);
476                         } else if (cmd == "bye") {
477                                 // If clients_ == 0 maybe we should reset the pipes
478                                 // to prevent fake callbacks
479                                 int i = 0; //look if client is registered
480                                 for (; i < numclients_; ++i) {
481                                         if (clients_[i] == client)
482                                                 break;
483                                 }
484                                 if (i < numclients_) {
485                                         --numclients_;
486                                         clients_[i].erase();
487                                         LYXERR(Debug::LYXSERVER, "Server: Client "
488                                                 << client << " said goodbye");
489                                 } else {
490                                         LYXERR(Debug::LYXSERVER,
491                                                 "Server: ignoring bye messge from unregistered client" << client);
492                                 }
493                         } else {
494                                 LYXERR0("Server: Undefined server command " << cmd << '.');
495                         }
496                         return;
497                 }
498
499                 if (!cmd.empty()) {
500                         // which lyxfunc should we let it connect to?
501                         // The correct solution would be to have a
502                         // specialized (non-gui) BufferView. But how do
503                         // we do it now? Probably we should just let it
504                         // connect to the lyxfunc in the single LyXView we
505                         // support currently. (Lgb)
506
507                         func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
508                         string const rval = to_utf8(func_->getMessage());
509
510                         // all commands produce an INFO or ERROR message
511                         // in the output pipe, even if they do not return
512                         // anything. See chapter 4 of Customization doc.
513                         string buf;
514                         if (func_->errorStat())
515                                 buf = "ERROR:";
516                         else
517                                 buf = "INFO:";
518                         buf += client + ':' + cmd + ':' +  rval + '\n';
519                         pipes_.send(buf);
520
521                         // !!! we don't do any error checking -
522                         //  if the client won't listen, the
523                         //  message is lost and others too
524                         //  maybe; so the client should empty
525                         //  the outpipe before issuing a request.
526
527                         // not found
528                 }
529         }  // while *p
530 }
531
532
533 // Send a notify message to a client, called by WorkAreaKeyPress
534 void Server::notifyClient(string const & s)
535 {
536         pipes_.send("NOTIFY:" + s + "\n");
537 }
538
539
540 } // namespace lyx