]> git.lyx.org Git - lyx.git/blob - src/ServerSocket.cpp
063f3471c1afeca6595ee811d6d853032d1f768c
[lyx.git] / src / ServerSocket.cpp
1 /**
2  * \file ServerSocket.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  * \author João Luis M. Assirati
11  *
12  * Full author contact details are available in file CREDITS.
13  */
14
15 #include <config.h>
16
17 #include "ServerSocket.h"
18
19 #include "support/debug.h"
20 #include "FuncRequest.h"
21 #include "LyXAction.h"
22 #include "LyXFunc.h"
23
24 #include "frontends/Application.h"
25
26 #include "support/environment.h"
27 #include "support/FileName.h"
28 #include "support/lyxlib.h"
29 #include "support/socktools.h"
30
31 #include <boost/bind.hpp>
32
33 #include <cerrno>
34 #include <ostream>
35
36 #if defined (_WIN32)
37 # include <io.h>
38 #endif
39
40 using boost::shared_ptr;
41
42 using namespace std;
43
44 namespace lyx {
45
46 // Address is the unix address for the socket.
47 // MAX_CLIENTS is the maximum number of clients
48 // that can connect at the same time.
49 ServerSocket::ServerSocket(LyXFunc * f, support::FileName const & addr)
50         : func(f),
51           fd_(support::socktools::listen(addr, 3)),
52           address_(addr)
53 {
54         if (fd_ == -1) {
55                 lyxerr << "lyx: Disabling LyX socket." << endl;
56                 return;
57         }
58
59         // These env vars are used by DVI inverse search
60         // Needed by xdvi
61         support::setEnv("XEDITOR", "lyxclient -g %f %l");
62         // Needed by lyxclient
63         support::setEnv("LYXSOCKET", address_.absFilename());
64
65         theApp()->registerSocketCallback(
66                 fd_,
67                 boost::bind(&ServerSocket::serverCallback, this)
68                 );
69
70         LYXERR(Debug::LYXSERVER, "lyx: New server socket "
71                                  << fd_ << ' ' << address_.absFilename());
72 }
73
74
75 // Close the socket and remove the address of the filesystem.
76 ServerSocket::~ServerSocket()
77 {
78         if (fd_ != -1) {
79                 BOOST_ASSERT (theApp());
80                 theApp()->unregisterSocketCallback(fd_);
81                 if (::close(fd_) != 0)
82                         lyxerr << "lyx: Server socket " << fd_
83                                << " IO error on closing: " << strerror(errno);
84         }
85         address_.removeFile();
86         LYXERR(Debug::LYXSERVER, "lyx: Server socket quitting");
87 }
88
89
90 string const ServerSocket::address() const
91 {
92         return address_.absFilename();
93 }
94
95
96 // Creates a new LyXDataSocket and checks to see if the connection
97 // is OK and if the number of clients does not exceed MAX_CLIENTS
98 void ServerSocket::serverCallback()
99 {
100         int const client_fd = support::socktools::accept(fd_);
101
102         if (fd_ == -1) {
103                 LYXERR(Debug::LYXSERVER, "lyx: Failed to accept new client");
104                 return;
105         }
106
107         if (clients.size() >= MAX_CLIENTS) {
108                 writeln("BYE:Too many clients connected");
109                 return;
110         }
111
112         // Register the new client.
113         clients[client_fd] =
114                 shared_ptr<LyXDataSocket>(new LyXDataSocket(client_fd));
115         theApp()->registerSocketCallback(
116                 client_fd,
117                 boost::bind(&ServerSocket::dataCallback,
118                             this, client_fd)
119                 );
120 }
121
122
123 // Reads and processes input from client and check
124 // if the connection has been closed
125 void ServerSocket::dataCallback(int fd)
126 {
127         shared_ptr<LyXDataSocket> client = clients[fd];
128
129         string line;
130         size_t pos;
131         bool saidbye = false;
132         while (!saidbye && client->readln(line)) {
133                 // The protocol must be programmed here
134                 // Split the key and the data
135                 if ((pos = line.find(':')) == string::npos) {
136                         client->writeln("ERROR:" + line + ":malformed message");
137                         continue;
138                 }
139
140                 string const key = line.substr(0, pos);
141                 if (key == "LYXCMD") {
142                         string const cmd = line.substr(pos + 1);
143                         func->dispatch(lyxaction.lookupFunc(cmd));
144                         string const rval = to_utf8(func->getMessage());
145                         if (func->errorStat()) {
146                                 client->writeln("ERROR:" + cmd + ':' + rval);
147                         } else {
148                                 client->writeln("INFO:" + cmd + ':' + rval);
149                         }
150                 } else if (key == "HELLO") {
151                         // no use for client name!
152                         client->writeln("HELLO:");
153                 } else if (key == "BYE") {
154                         saidbye = true;
155                 } else {
156                         client->writeln("ERROR:unknown key " + key);
157                 }
158         }
159
160         if (saidbye || !client->connected()) {
161                 clients.erase(fd);
162         }
163 }
164
165
166 void ServerSocket::writeln(string const & line)
167 {
168         string const linen = line + '\n';
169         int const size = linen.size();
170         int const written = ::write(fd_, linen.c_str(), size);
171         if (written < size) { // Always mean end of connection.
172                 if (written == -1 && errno == EPIPE) {
173                         // The program will also receive a SIGPIPE
174                         // that must be caught
175                         lyxerr << "lyx: Server socket " << fd_
176                                << " connection closed while writing." << endl;
177                 } else {
178                         // Anything else, including errno == EAGAIN, must be
179                         // considered IO error. EAGAIN should never happen
180                         // when line is small
181                         lyxerr << "lyx: Server socket " << fd_
182                              << " IO error: " << strerror(errno);
183                 }
184         }
185 }
186
187 // Debug
188 // void ServerSocket::dump() const
189 // {
190 //      lyxerr << "ServerSocket debug dump.\n"
191 //           << "fd = " << fd_ << ", address = " << address_.absFilename() << ".\n"
192 //           << "Clients: " << clients.size() << ".\n";
193 //      map<int, shared_ptr<LyXDataSocket> >::const_iterator client = clients.begin();
194 //      map<int, shared_ptr<LyXDataSocket> >::const_iterator end = clients.end();
195 //      for (; client != end; ++client)
196 //              lyxerr << "fd = " << client->first << '\n';
197 // }
198
199
200 LyXDataSocket::LyXDataSocket(int fd)
201         : fd_(fd), connected_(true)
202 {
203         LYXERR(Debug::LYXSERVER, "lyx: New data socket " << fd_);
204 }
205
206
207 LyXDataSocket::~LyXDataSocket()
208 {
209         if (::close(fd_) != 0)
210                 lyxerr << "lyx: Data socket " << fd_
211                        << " IO error on closing: " << strerror(errno);
212
213         theApp()->unregisterSocketCallback(fd_);
214         LYXERR(Debug::LYXSERVER, "lyx: Data socket " << fd_ << " quitting.");
215 }
216
217
218 bool LyXDataSocket::connected() const
219 {
220         return connected_;
221 }
222
223
224 // Returns true if there was a complete line to input
225 bool LyXDataSocket::readln(string & line)
226 {
227         int const charbuf_size = 100;
228         char charbuf[charbuf_size]; // buffer for the ::read() system call
229         int count;
230
231         // read and store characters in buffer
232         while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
233                 buffer_.append(charbuf, charbuf + count);
234         }
235
236         // Error conditions. The buffer must still be
237         // processed for lines read
238         if (count == 0) { // EOF -- connection closed
239                 LYXERR(Debug::LYXSERVER, "lyx: Data socket " << fd_
240                                          << ": connection closed.");
241                 connected_ = false;
242         } else if ((count == -1) && (errno != EAGAIN)) { // IO error
243                 lyxerr << "lyx: Data socket " << fd_
244                        << ": IO error." << endl;
245                 connected_ = false;
246         }
247
248         // Cut a line from buffer
249         size_t pos = buffer_.find('\n');
250         if (pos == string::npos) {
251                 LYXERR(Debug::LYXSERVER, "lyx: Data socket " << fd_
252                                          << ": line not completed.");
253                 return false; // No complete line stored
254         }
255         line = buffer_.substr(0, pos);
256         buffer_.erase(0, pos + 1);
257         return true;
258 }
259
260
261 // Write a line of the form <key>:<value> to the socket
262 void LyXDataSocket::writeln(string const & line)
263 {
264         string const linen = line + '\n';
265         int const size = linen.size();
266         int const written = ::write(fd_, linen.c_str(), size);
267         if (written < size) { // Always mean end of connection.
268                 if (written == -1 && errno == EPIPE) {
269                         // The program will also receive a SIGPIPE
270                         // that must be catched
271                         lyxerr << "lyx: Data socket " << fd_
272                                << " connection closed while writing." << endl;
273                 } else {
274                         // Anything else, including errno == EAGAIN, must be
275                         // considered IO error. EAGAIN should never happen
276                         // when line is small
277                         lyxerr << "lyx: Data socket " << fd_
278                              << " IO error: " << strerror(errno);
279                 }
280                 connected_ = false;
281         }
282 }
283
284
285 } // namespace lyx