]> git.lyx.org Git - lyx.git/blob - src/lyxserver.C
The std::string mammoth path.
[lyx.git] / src / lyxserver.C
1 /**
2  * \file lyxserver.C
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 "lyxserver.h"
43 #include "debug.h"
44 #include "lyxfunc.h"
45 #include "support/lstrings.h"
46 #include "support/lyxlib.h"
47 #include "frontends/lyx_gui.h"
48
49 #include <cerrno>
50 #include <sys/stat.h>
51 #include <fcntl.h>
52
53 #ifdef __EMX__
54 #include <cstdlib>
55 #include <io.h>
56 #define OS2EMX_PLAIN_CHAR
57 #define INCL_DOSNMPIPES
58 #define INCL_DOSERRORS
59 #include <os2.h>
60 #include "support/os2_errortable.h"
61 #endif
62
63 using lyx::support::compare;
64 using lyx::support::rtrim;
65 using lyx::support::split;
66 using lyx::support::unlink;
67
68 using std::endl;
69 using std::string;
70
71
72 // provide an empty mkfifo() if we do not have one. This disables the
73 // lyxserver.
74 #ifndef HAVE_MKFIFO
75 int mkfifo(char const * __path, mode_t __mode) {
76         return 0;
77 }
78 #endif
79
80
81 void LyXComm::openConnection()
82 {
83         lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
84
85         // If we are up, that's an error
86         if (ready) {
87                 lyxerr << "LyXComm: Already connected" << endl;
88                 return;
89         }
90         // We assume that we don't make it
91         ready = false;
92
93         if (pipename.empty()) {
94                 lyxerr[Debug::LYXSERVER]
95                         << "LyXComm: server is disabled, nothing to do"
96                         << endl;
97                 return;
98         }
99
100         if ((infd = startPipe(inPipeName(), false)) == -1)
101                 return;
102
103         if ((outfd = startPipe(outPipeName(), true)) == -1) {
104                 endPipe(infd, inPipeName(), false);
105                 return;
106         }
107
108         if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
109                 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
110                        << '\n' << strerror(errno) << endl;
111                 return;
112         }
113
114         // We made it!
115         ready = true;
116         lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
117 }
118
119
120 /// Close pipes
121 void LyXComm::closeConnection()
122 {
123         lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
124
125         if (pipename.empty()) {
126                 lyxerr[Debug::LYXSERVER]
127                         << "LyXComm: server is disabled, nothing to do"
128                         << endl;
129                 return;
130         }
131
132         if (!ready) {
133                 lyxerr << "LyXComm: Already disconnected" << endl;
134                 return;
135         }
136
137         endPipe(infd, inPipeName(), false);
138         endPipe(outfd, outPipeName(), true);
139
140         ready = false;
141 }
142
143
144 int LyXComm::startPipe(string const & filename, bool write)
145 {
146         int fd;
147
148 #ifdef __EMX__
149         HPIPE os2fd;
150         APIRET rc;
151         int errnum;
152         // Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
153         // The current emx implementation of access() won't work with pipes.
154         rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
155                 NP_NOWAIT|0x01, 0600, 0600, 0);
156         if (rc == ERROR_PIPE_BUSY) {
157                 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
158                        << "If no other LyX program is active, please delete"
159                         " the pipe by hand and try again." << endl;
160                 pipename.erase();
161                 return -1;
162         }
163
164         if (rc != NO_ERROR) {
165                 errnum = TranslateOS2Error(rc);
166                 lyxerr <<"LyXComm: Could not create pipe " << filename
167                        << strerror(errnum) << endl;
168                 return -1;
169         };
170         // Listen to it.
171         rc = DosConnectNPipe(os2fd);
172         if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
173                 errnum = TranslateOS2Error(rc);
174                 lyxerr <<"LyXComm: Could not create pipe " << filename
175                        << strerror(errnum) << endl;
176                 return -1;
177         };
178         // Imported handles can be used both with OS/2 APIs and emx
179         // library functions.
180         fd = _imphandle(os2fd);
181 #else
182         if (::access(filename.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.c_str(), 0600) < 0) {
191                 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
192                        << strerror(errno) << endl;
193                 return -1;
194         };
195         fd = ::open(filename.c_str(), write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
196 #endif
197
198         if (fd < 0) {
199                 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
200                        << strerror(errno) << endl;
201                 unlink(filename);
202                 return -1;
203         }
204
205         if (!write) {
206                 lyx_gui::set_read_callback(fd, this);
207         }
208
209         return fd;
210 }
211
212
213 void LyXComm::endPipe(int & fd, string const & filename, bool write)
214 {
215         if (fd < 0)
216                 return;
217
218         if (!write) {
219                 lyx_gui::remove_read_callback(fd);
220         }
221
222 #ifdef __EMX__
223         APIRET rc;
224         int errnum;
225
226         rc = DosDisConnectNPipe(fd);
227         if (rc != NO_ERROR) {
228                 errnum = TranslateOS2Error(rc);
229                 lyxerr << "LyXComm: Could not disconnect pipe " << filename
230                        << '\n' << strerror(errnum) << endl;
231                 return;
232         }
233 #endif
234
235         if (::close(fd) < 0) {
236                 lyxerr << "LyXComm: Could not close pipe " << filename
237                        << '\n' << strerror(errno) << endl;
238         }
239
240 // OS/2 pipes are deleted automatically
241 #ifndef __EMX__
242         if (unlink(filename) < 0) {
243                 lyxerr << "LyXComm: Could not remove pipe " << filename
244                        << '\n' << strerror(errno) << endl;
245         };
246 #endif
247
248         fd = -1;
249 }
250
251
252 void LyXComm::emergencyCleanup()
253 {
254         if (!pipename.empty()) {
255                 endPipe(infd, inPipeName(), false);
256                 endPipe(outfd, outPipeName(), true);
257         }
258 }
259
260
261 // Receives messages and sends then to client
262 void LyXComm::read_ready()
263 {
264         // nb! make read_buffer_ a class-member for multiple sessions
265         static string read_buffer_;
266         read_buffer_.erase();
267
268         int const charbuf_size = 100;
269         char charbuf[charbuf_size];
270
271         errno = 0;
272         int status;
273         // the single = is intended here.
274         while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
275
276                 if (status > 0) {
277                         charbuf[status] = '\0'; // turn it into a c string
278                         read_buffer_ += rtrim(charbuf, "\r");
279                         // commit any commands read
280                         while (read_buffer_.find('\n') != string::npos) {
281                                 // split() grabs the entire string if
282                                 // the delim /wasn't/ found. ?:-P
283                                 string cmd;
284                                 read_buffer_= split(read_buffer_, cmd,'\n');
285                                 lyxerr[Debug::LYXSERVER]
286                                         << "LyXComm: status:" << status
287                                         << ", read_buffer_:" << read_buffer_
288                                         << ", cmd:" << cmd << endl;
289                                 if (!cmd.empty())
290                                         clientcb(client, cmd);
291                                         //\n or not \n?
292                         }
293                 }
294                 if (errno == EAGAIN) {
295                         errno = 0;
296                         return;
297                 }
298                 if (errno != 0) {
299                         lyxerr << "LyXComm: " << strerror(errno) << endl;
300                         if (!read_buffer_.empty()) {
301                                 lyxerr << "LyXComm: truncated command: "
302                                        << read_buffer_ << endl;
303                                 read_buffer_.erase();
304                         }
305                         break; // reset connection
306                 }
307         }
308
309         // The connection gets reset in errno != EAGAIN
310         // Why does it need to be reset if errno == 0?
311         closeConnection();
312         openConnection();
313         errno = 0;
314 }
315
316
317 void LyXComm::send(string const & msg)
318 {
319         if (msg.empty()) {
320                 lyxerr << "LyXComm: Request to send empty string. Ignoring."
321                        << endl;
322                 return;
323         }
324
325         if (lyxerr.debugging(Debug::LYXSERVER)) {
326                 lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
327         }
328
329         if (pipename.empty()) return;
330
331         if (!ready) {
332                 lyxerr << "LyXComm: Pipes are closed. Could not send "
333                        << msg << endl;
334         } else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
335                 lyxerr << "LyXComm: Error sending message: " << msg
336                        << '\n' << strerror(errno)
337                        << "\nLyXComm: Resetting connection" << endl;
338                 closeConnection();
339                 openConnection();
340         }
341 #ifdef __EMX__
342         APIRET rc;
343         int errnum;
344         rc = DosResetBuffer(outfd);     // To avoid synchronization problems.
345         if (rc != NO_ERROR) {
346                 errnum = TranslateOS2Error(rc);
347                 lyxerr << "LyXComm: Message could not be flushed: " << msg
348                        << '\n' << strerror(errnum) << endl;
349         }
350 #endif
351 }
352
353
354 // LyXServer class
355
356 LyXServer::~LyXServer()
357 {
358         // say goodbye to clients so they stop sending messages
359         // modified june 1999 by stefano@zool.su.se to send as many bye
360         // messages as there are clients, each with client's name.
361         string message;
362         for (int i= 0; i<numclients; ++i) {
363                 message = "LYXSRV:" + clients[i] + ":bye\n";
364                 pipes.send(message);
365         }
366 }
367
368
369 /* ---F+------------------------------------------------------------------ *\
370    Function  : ServerCallback
371     Called by : LyXComm
372     Purpose   : handle data gotten from communication
373 \* ---F------------------------------------------------------------------- */
374
375 void LyXServer::callback(LyXServer * serv, string const & msg)
376 {
377         lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
378                                  << msg << '\'' << endl;
379
380         char const * p = msg.c_str();
381
382         // --- parse the string --------------------------------------------
383         //
384         //  Format: LYXCMD:<client>:<func>:<argstring>\n
385         //
386         bool server_only = false;
387         while (*p) {
388                 // --- 1. check 'header' ---
389
390                 if (compare(p, "LYXSRV:", 7) == 0) {
391                         server_only = true;
392                 } else if (0 != compare(p, "LYXCMD:", 7)) {
393                         lyxerr << "LyXServer: Unknown request \""
394                                << p << '"' << endl;
395                         return;
396                 }
397                 p += 7;
398
399                 // --- 2. for the moment ignore the client name ---
400                 string client;
401                 while (*p && *p != ':')
402                         client += char(*p++);
403                 if (*p == ':') ++p;
404                 if (!*p) return;
405
406                 // --- 3. get function name ---
407                 string cmd;
408                 while (*p && *p != ':')
409                         cmd += char(*p++);
410
411                 // --- 4. parse the argument ---
412                 string arg;
413                 if (!server_only && *p == ':' && *(++p)) {
414                         while (*p && *p != '\n')
415                                 arg += char(*p++);
416                         if (*p) ++p;
417                 }
418
419                 lyxerr[Debug::LYXSERVER]
420                         << "LyXServer: Client: '" << client
421                         << "' Command: '" << cmd
422                         << "' Argument: '" << arg << '\'' << endl;
423
424                 // --- lookup and exec the command ------------------
425
426                 if (server_only) {
427                         string buf;
428                         // return the greeting to inform the client that
429                         // we are listening.
430                         if (cmd == "hello") {
431                                 // One more client
432                                 if (serv->numclients == MAX_CLIENTS) { //paranoid check
433                                         lyxerr[Debug::LYXSERVER]
434                                                 << "LyXServer: too many clients..."
435                                                 << endl;
436                                         return;
437                                 }
438                                 int i= 0; //find place in clients[]
439                                 while (!serv->clients[i].empty()
440                                        && i<serv->numclients)
441                                         ++i;
442                                 serv->clients[i] = client;
443                                 serv->numclients++;
444                                 buf = "LYXSRV:" + client + ":hello\n";
445                                 lyxerr[Debug::LYXSERVER]
446                                         << "LyXServer: Greeting "
447                                         << client << endl;
448                                 serv->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 < serv->numclients; ++i) {
454                                         if (serv->clients[i] == client) break;
455                                 }
456                                 if (i < serv->numclients) {
457                                         serv->numclients--;
458                                         serv->clients[i].erase();
459                                         lyxerr[Debug::LYXSERVER]
460                                                 << "LyXServer: Client "
461                                                 << client << " said goodbye"
462                                                 << endl;
463                                 } else {
464                                         lyxerr[Debug::LYXSERVER]
465                                                 << "LyXServer: ignoring bye messge from unregistered client"
466                                                 << client << endl;
467                                 }
468                         } else {
469                                 lyxerr <<"LyXServer: Undefined server command "
470                                        << cmd << '.' << endl;
471                         }
472                         return;
473                 }
474
475                 if (!cmd.empty()) {
476                         // which lyxfunc should we let it connect to?
477                         // The correct solution would be to have a
478                         // specialized (non-gui) BufferView. But how do
479                         // we do it now? Probably we should just let it
480                         // connect to the lyxfunc in the single LyXView we
481                         // support currently. (Lgb)
482
483
484                         serv->func->dispatch(cmd + ' ' + arg);
485                         string const rval = serv->func->getMessage();
486
487                         //modified june 1999 stefano@zool.su.se:
488                         //all commands produce an INFO or ERROR message
489                         //in the output pipe, even if they do not return
490                         //anything. See chapter 4 of Customization doc.
491                         string buf;
492                         if (serv->func->errorStat())
493                                 buf = "ERROR:";
494                         else
495                                 buf = "INFO:";
496                         buf += client + ':' + cmd + ':' +  rval + '\n';
497                         serv->pipes.send(buf);
498
499                         // !!! we don't do any error checking -
500                         //  if the client won't listen, the
501                         //  message is lost and others too
502                         //  maybe; so the client should empty
503                         //  the outpipe before issuing a request.
504
505                         // not found
506                 }
507         }  /* while *p */
508 }
509
510
511 /* ---F+------------------------------------------------------------------ *\
512    Function  : LyXNotifyClient
513    Called by : WorkAreaKeyPress
514    Purpose   : send a notify messge to a client
515    Parameters: s - string to send
516    Returns   : nothing
517    \* ---F------------------------------------------------------------------- */
518
519 void LyXServer::notifyClient(string const & s)
520 {
521         string buf = string("NOTIFY:") + s + "\n";
522         pipes.send(buf);
523 }