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