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