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