Instrument Neutral Distributed Interface INDI 2.2.5
MsgQueue.cpp
Go to the documentation of this file.
1/* INDI Server for protocol version 1.7.
2 * Copyright (C) 2007 Elwood C. Downey <[email protected]>
3 2013 Jasem Mutlaq <[email protected]>
4 2022 Ludovic Pollet
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18*/
19#include "MsgQueue.hpp"
20#include "Utils.hpp"
21#include "Constants.hpp"
22#include "SerializedMsg.hpp"
23#include "Msg.hpp"
24#include "CommandLineArgs.hpp"
25
26#include <sys/socket.h>
27#include <fcntl.h>
28#include <unistd.h>
29
30using namespace indiserver::constants;
31
32void MsgQueue::writeToFd()
33{
34 ssize_t nw;
35 void * data;
36 ssize_t nsend;
37 std::vector<int> sharedBuffers;
38
39 /* get current message */
40 auto mp = headMsg();
41 if (mp == nullptr)
42 {
43 log("Unexpected write notification");
44 return;
45 }
46
47
48 do
49 {
50 if (!mp->getContent(nsent, data, nsend, sharedBuffers))
51 {
52 wio.stop();
53 return;
54 }
55
56 if (nsend == 0)
57 {
59 mp = headMsg();
60 if (mp == nullptr)
61 {
62 return;
63 }
64 }
65 }
66 while(nsend == 0);
67
68 /* send next chunk, never more than maxWriteBufferLength to reduce blocking */
69 if (nsend > static_cast<ssize_t>(maxWriteBufferLength))
70 nsend = static_cast<ssize_t>(maxWriteBufferLength);
71
72 if (!useSharedBuffer)
73 {
74 nw = write(wFd, data, nsend);
75 }
76 else
77 {
78 struct msghdr msgh;
79 struct iovec iov[1];
80 int cmsghdrlength;
81 struct cmsghdr * cmsgh;
82
83 size_t fdCount = sharedBuffers.size();
84 if (fdCount > 0)
85 {
86 if (fdCount > maxFDPerMessage)
87 {
88 log(fmt("attempt to send too many FD\n"));
89 close();
90 return;
91 }
92
93 cmsghdrlength = CMSG_SPACE((fdCount * sizeof(int)));
94 // FIXME: abort on alloc error here
95 cmsgh = (struct cmsghdr*)malloc(cmsghdrlength);
96 memset(cmsgh, 0, cmsghdrlength);
97
98 /* Write the fd as ancillary data */
99 cmsgh->cmsg_len = CMSG_LEN(fdCount * sizeof(int));
100 cmsgh->cmsg_level = SOL_SOCKET;
101 cmsgh->cmsg_type = SCM_RIGHTS;
102 msgh.msg_control = cmsgh;
103 msgh.msg_controllen = cmsghdrlength;
104 for(size_t i = 0; i < fdCount; ++i)
105 {
106 ((int *) CMSG_DATA(CMSG_FIRSTHDR(&msgh)))[i] = sharedBuffers[i];
107 }
108 }
109 else
110 {
111 cmsgh = NULL;
112 cmsghdrlength = 0;
113 msgh.msg_control = cmsgh;
114 msgh.msg_controllen = cmsghdrlength;
115 }
116
117 iov[0].iov_base = data;
118 iov[0].iov_len = nsend;
119
120 msgh.msg_flags = 0;
121 msgh.msg_name = NULL;
122 msgh.msg_namelen = 0;
123 msgh.msg_iov = iov;
124 msgh.msg_iovlen = 1;
125
126 nw = sendmsg(wFd, &msgh, MSG_NOSIGNAL);
127
128 free(cmsgh);
129 }
130
131 /* shut down if trouble */
132 if (nw <= 0)
133 {
134 if (nw == 0)
135 log("write returned 0\n");
136 else
137 log(fmt("write: %s\n", strerror(errno)));
138
139 // Keep the read part open
141 return;
142 }
143
144 /* trace */
146 {
147 log(fmt("sending msg nq %ld:\n%.*s\n",
148 msgq.size(), (int)nw, data));
149 }
151 {
152 log(fmt("sending %.*s\n", (int)nw, data));
153 }
154
155 /* update amount sent. when complete: free message if we are the last
156 * to use it and pop from our queue.
157 */
158 mp->advance(nsent, nw);
159 if (nsent.done())
161}
162
163void MsgQueue::log(const std::string &str) const
164{
165 // This is only invoked from destructor
166 std::string logLine = "Dying Connection ";
167 logLine += ": ";
168 logLine += str;
169 ::log(logLine);
170}
171
172
173
174void MsgQueue::crackBLOB(const char *enableBLOB, BLOBHandling *bp)
175{
176 if (!strcmp(enableBLOB, "Also"))
177 *bp = B_ALSO;
178 else if (!strcmp(enableBLOB, "Only"))
179 *bp = B_ONLY;
180 else if (!strcmp(enableBLOB, "Never"))
181 *bp = B_NEVER;
182}
183
184
185
186void MsgQueue::traceMsg(const std::string &logMsg, XMLEle *root)
187{
188 log(logMsg);
189
190 static const char *prtags[] =
191 {
192 "defNumber", "oneNumber", "defText", "oneText", "defSwitch", "oneSwitch", "defLight", "oneLight",
193 };
194 XMLEle *e;
195 const char *msg, *perm, *pcd;
196 unsigned int i;
197
198 /* print tag header */
199 fprintf(stderr, "%s %s %s %s", tagXMLEle(root), findXMLAttValu(root, "device"), findXMLAttValu(root, "name"),
200 findXMLAttValu(root, "state"));
201 pcd = pcdataXMLEle(root);
202 if (pcd[0])
203 fprintf(stderr, " %s", pcd);
204 perm = findXMLAttValu(root, "perm");
205 if (perm[0])
206 fprintf(stderr, " %s", perm);
207 msg = findXMLAttValu(root, "message");
208 if (msg[0])
209 fprintf(stderr, " '%s'", msg);
210
211 /* print each array value */
212 for (e = nextXMLEle(root, 1); e; e = nextXMLEle(root, 0))
213 for (i = 0; i < sizeof(prtags) / sizeof(prtags[0]); i++)
214 if (strcmp(prtags[i], tagXMLEle(e)) == 0)
215 fprintf(stderr, "\n %10s='%s'", findXMLAttValu(e, "name"), pcdataXMLEle(e));
216
217 fprintf(stderr, "\n");
218}
219
220MsgQueue::MsgQueue(bool useSharedBuffer): useSharedBuffer(useSharedBuffer)
221{
222 lp = newLilXML();
223 rio.set<MsgQueue, &MsgQueue::ioCb>(this);
224 wio.set<MsgQueue, &MsgQueue::ioCb>(this);
225 rFd = -1;
226 wFd = -1;
227}
228
230{
231 rio.stop();
232 wio.stop();
233
235 delLilXML(lp);
236 lp = nullptr;
237
238 setFds(-1, -1);
239
240 /* unreference messages queue for this client */
241 auto msgqcp = msgq;
242 msgq.clear();
243 for(auto mp : msgqcp)
244 {
245 mp->release(this);
246 }
247}
248
250{
251 if (wFd == -1)
252 {
253 // Already closed
254 return;
255 }
256
257 int oldWFd = wFd;
258
259 wFd = -1;
260 // Clear the queue and stop the io slot
262
263 if (oldWFd == rFd)
264 {
265 if (shutdown(oldWFd, SHUT_WR) == -1)
266 {
267 if (errno != ENOTCONN)
268 {
269 log(fmt("socket shutdown failed: %s\n", strerror(errno)));
270 close();
271 }
272 }
273 }
274 else
275 {
276 if (::close(oldWFd) == -1)
277 {
278 log(fmt("socket close failed: %s\n", strerror(errno)));
279 close();
280 }
281 }
282}
283
284void MsgQueue::setFds(int rFd, int wFd)
285{
286 if (this->rFd != -1)
287 {
288 rio.stop();
289 wio.stop();
290 ::close(this->rFd);
291 if (this->rFd != this->wFd)
292 {
293 ::close(this->wFd);
294 }
295 }
296 else if (this->wFd != -1)
297 {
298 wio.stop();
299 ::close(this->wFd);
300 }
301
302 this->rFd = rFd;
303 this->wFd = wFd;
304 this->nsent.reset();
305
306 if (rFd != -1)
307 {
308 fcntl(rFd, F_SETFL, fcntl(rFd, F_GETFL, 0) | O_NONBLOCK);
309 if (wFd != rFd)
310 {
311 fcntl(wFd, F_SETFL, fcntl(wFd, F_GETFL, 0) | O_NONBLOCK);
312 }
313
314 rio.set(rFd, ev::READ);
315 wio.set(wFd, ev::WRITE);
316 updateIos();
317 }
318}
319
321{
322 if (msgq.empty()) return nullptr;
323 return *(msgq.begin());
324}
325
327{
328 auto msg = headMsg();
329 msgq.pop_front();
330 msg->release(this);
331 nsent.reset();
332
333 updateIos();
334}
335
337{
338 // Don't write messages to client that have been disconnected
339 if (wFd == -1)
340 {
341 return;
342 }
343
344 auto serialized = mp->serialize(this);
345
346 msgq.push_back(serialized);
347 serialized->addAwaiter(this);
348
349 // Register for client write
350 updateIos();
351}
352
353void MsgQueue::updateIos()
354{
355 if (wFd != -1)
356 {
357 if (msgq.empty() || !msgq.front()->requestContent(nsent))
358 {
359 wio.stop();
360 }
361 else
362 {
363 wio.start();
364 }
365 }
366 if (rFd != -1)
367 {
368 rio.start();
369 }
370}
371
373{
374 if ((!msgq.empty()) && (msgq.front() == msg))
375 {
376 updateIos();
377 }
378}
379
381{
382 nsent.reset();
383
384 auto queueCopy = msgq;
385 for(auto mp : queueCopy)
386 {
387 mp->release(this);
388 }
389 msgq.clear();
390
391 // Cancel io write events
392 updateIos();
393 wio.stop();
394}
395
396unsigned long MsgQueue::msgQSize() const
397{
398 unsigned long l = 0;
399
400 for (auto mp : msgq)
401 {
402 l += sizeof(Msg);
403 l += mp->queueSize();
404 }
405
406 return (l);
407}
408
409void MsgQueue::ioCb(ev::io &, int revents)
410{
411 if (EV_ERROR & revents)
412 {
413 int sockErrno = readFdError(this->rFd);
414 if ((!sockErrno) && this->wFd != this->rFd)
415 {
416 sockErrno = readFdError(this->wFd);
417 }
418
419 if (sockErrno)
420 {
421 log(fmt("Communication error: %s\n", strerror(sockErrno)));
422 close();
423 return;
424 }
425 }
426
427 if (revents & EV_READ)
428 readFromFd();
429
430 if (revents & EV_WRITE)
431 writeToFd();
432}
433
434size_t MsgQueue::doRead(char * buf, size_t nr)
435{
436 if (!useSharedBuffer)
437 {
438 /* read client - works for all kinds of fds incl pipe*/
439 return read(rFd, buf, nr);
440 }
441 else
442 {
443 // Use recvmsg for ancillary data
444 struct msghdr msgh;
445 struct iovec iov;
446
447 union
448 {
449 struct cmsghdr cmsgh;
450 /* Space large enough to hold an 'int' */
451 char control[CMSG_SPACE(maxFDPerMessage * sizeof(int))];
452 } control_un;
453
454 iov.iov_base = buf;
455 iov.iov_len = nr;
456
457 msgh.msg_name = NULL;
458 msgh.msg_namelen = 0;
459 msgh.msg_iov = &iov;
460 msgh.msg_iovlen = 1;
461 msgh.msg_flags = 0;
462 msgh.msg_control = control_un.control;
463 msgh.msg_controllen = sizeof(control_un.control);
464
465 int recvflag;
466#ifdef __linux__
467 recvflag = MSG_CMSG_CLOEXEC;
468#else
469 recvflag = 0;
470#endif
471 int size = recvmsg(rFd, &msgh, recvflag);
472 if (size == -1)
473 {
474 return -1;
475 }
476
477 for (struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg))
478 {
479 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
480 {
481 int fdCount = 0;
482 while(cmsg->cmsg_len >= CMSG_LEN((fdCount + 1) * sizeof(int)))
483 {
484 fdCount++;
485 }
486 //log(fmt("Received %d fds\n", fdCount));
487 int * fds = (int*)CMSG_DATA(cmsg);
488 for(int i = 0; i < fdCount; ++i)
489 {
490#ifndef __linux__
491 fcntl(fds[i], F_SETFD, FD_CLOEXEC);
492#endif
493 incomingSharedBuffers.push_back(fds[i]);
494 }
495 }
496 else
497 {
498 log(fmt("Ignoring ancillary data level %d, type %d\n", cmsg->cmsg_level, cmsg->cmsg_type));
499 }
500 }
501 return size;
502 }
503}
504
505void MsgQueue::readFromFd()
506{
507 char buf[maxReadBufferLength];
508 ssize_t nr;
509
510 /* read client */
511 nr = doRead(buf, sizeof(buf));
512 if (nr <= 0)
513 {
514 if (errno == EAGAIN || errno == EWOULDBLOCK) return;
515
516 if (nr < 0)
517 log(fmt("read: %s\n", strerror(errno)));
519 log(fmt("read EOF\n"));
520 close();
521 return;
522 }
523
524 /* process XML chunk */
525 char err[XML_ERROR_SIZE];
526 XMLEle **nodes = parseXMLChunk(lp, buf, nr, err);
527 if (!nodes)
528 {
529 log(fmt("XML error: %s\n", err));
530 log(fmt("XML read: %.*s\n", (int)nr, buf));
531 close();
532 return;
533 }
534
535 int inode = 0;
536
537 XMLEle *root = nodes[inode];
538 // Stop processing message in case of deletion...
539 auto hb = heartBeat();
540 while (root)
541 {
542 if (hb.alive())
543 {
545 traceMsg("read ", root);
547 {
548 log(fmt("read <%s device='%s' name='%s'>\n",
549 tagXMLEle(root), findXMLAttValu(root, "device"), findXMLAttValu(root, "name")));
550 }
551
552 onMessage(root, incomingSharedBuffers);
553 }
554 else
555 {
556 // Otherwise, client got killed. Just release pending messages
557 delXMLEle(root);
558 }
559 inode++;
560 root = nodes[inode];
561 }
562
563 free(nodes);
564}
CommandLineArgs * userConfigurableArguments
std::string fmt(const char *fmt,...)
Definition Utils.cpp:241
int readFdError(int fd)
Definition Utils.cpp:167
HeartBeat heartBeat() const
virtual ~MsgQueue()
Definition MsgQueue.cpp:229
static void crackBLOB(const char *enableBLOB, BLOBHandling *bp)
Definition MsgQueue.cpp:174
void consumeHeadMsg()
Definition MsgQueue.cpp:326
void messageMayHaveProgressed(const SerializedMsg *msg)
Definition MsgQueue.cpp:372
MsgQueue(bool useSharedBuffer)
Definition MsgQueue.cpp:220
virtual void onMessage(XMLEle *root, std::list< int > &sharedBuffers)=0
void clearMsgQueue()
Definition MsgQueue.cpp:380
void traceMsg(const std::string &log, XMLEle *root)
Definition MsgQueue.cpp:186
virtual void closeWritePart()
Definition MsgQueue.cpp:249
void pushMsg(Msg *msg)
Definition MsgQueue.cpp:336
virtual void close()=0
unsigned long msgQSize() const
Definition MsgQueue.cpp:396
virtual void log(const std::string &log) const
Definition MsgQueue.cpp:163
bool useSharedBuffer
Definition MsgQueue.hpp:66
SerializedMsg * headMsg() const
Definition MsgQueue.cpp:320
void setFds(int rFd, int wFd)
Definition MsgQueue.cpp:284
Definition Msg.hpp:33
SerializedMsg * serialize(MsgQueue *from)
Definition Msg.cpp:211
int errno
BLOBHandling
How drivers handle BLOBs incoming from snooping drivers.
Definition indidevapi.h:267
@ B_ONLY
Definition indidevapi.h:270
@ B_ALSO
Definition indidevapi.h:269
@ B_NEVER
Definition indidevapi.h:268
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition lilxml.cpp:644
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition lilxml.cpp:555
LilXML * newLilXML()
Create a new lilxml parser.
Definition lilxml.cpp:150
char * tagXMLEle(XMLEle *ep)
Return the tag of an XML element.
Definition lilxml.cpp:600
XMLEle ** parseXMLChunk(LilXML *lp, char *buf, int size, char ynot[])
Process an XML chunk.
Definition lilxml.cpp:215
void delXMLEle(XMLEle *ep)
delXMLEle Delete XML element.
Definition lilxml.cpp:167
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition lilxml.cpp:606
void delLilXML(LilXML *lp)
Delete a lilxml parser.
Definition lilxml.cpp:159
#define XML_ERROR_SIZE
Definition lilxml.h:73