Instrument Neutral Distributed Interface INDI 2.2.5
indidriverio.c
Go to the documentation of this file.
1#if 0
2INDI Driver Functions
3
4Copyright (C) 2022 by Ludovic Pollet
5
6This library is free software;
7you can redistribute it and / or
8modify it under the terms of the GNU Lesser General Public
9License as published by the Free Software Foundation;
10either
11version 2.1 of the License, or (at your option) any later version.
12
13This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY;
15without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17Lesser General Public License for more details.
18
19You should have received a copy of the GNU Lesser General Public
20License along with this library;
21if not, write to the Free Software
22Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA
23
24#endif
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <stdarg.h>
29#include <string.h>
30#include <unistd.h>
31#include <sys/types.h>
32#include <sys/socket.h>
33#include <sys/un.h>
34#include <errno.h>
35#include <pthread.h>
36
37#include "indidriver.h"
38#include "sharedblob.h"
39#include "userio.h"
40#include "indiuserio.h"
41#include "indidriverio.h"
42
43
44
45/* Buffer size. Must be ^ 2 */
46#define OUTPUTBUFF_ALLOC 32768
47
48/* Dump whole buffer when growing over this */
49#define OUTPUTBUFF_FLUSH_THRESOLD 65536
50
51#define MAXFD_PER_MESSAGE 16
52
53static void driverio_flush(driverio * dio, const void * additional, size_t add_size);
54
55static pthread_mutex_t stdout_mutex = PTHREAD_MUTEX_INITIALIZER;
56
57/* Return the buffer size required for storage (rounded to next OUTPUTBUFF_ALLOC) */
58static unsigned int outBuffRequired(unsigned int storage)
59{
60 return (storage + OUTPUTBUFF_ALLOC - 1) & ~(OUTPUTBUFF_ALLOC - 1);
61}
62
63static int outBuffAllocated(struct driverio * dio)
64{
65 return outBuffRequired(dio->outPos);
66}
67
68static void outBuffGrow(struct driverio * dio, int required)
69{
70 dio->outBuff = realloc(dio->outBuff, required);
71 if (dio->outBuff == NULL)
72 {
73 perror("malloc");
74 _exit(1);
75 }
76}
77
78static ssize_t driverio_write(void *user, const void * ptr, size_t count)
79{
80 struct driverio * dio = (struct driverio*) user;
81
82 if (dio->outPos + count > OUTPUTBUFF_FLUSH_THRESOLD)
83 {
84 driverio_flush(dio, ptr, count);
85 }
86 else
87 {
88 unsigned int allocated = outBuffAllocated(dio);
89 unsigned int required = outBuffRequired(dio->outPos + count);
90 if (required != allocated)
91 {
92 outBuffGrow(dio, required);
93 }
94 memcpy(dio->outBuff + dio->outPos, ptr, count);
95
96 dio->outPos += count;
97 }
98 return count;
99}
100
101static int driverio_vprintf(void *user, const char * fmt, va_list arg)
102{
103 struct driverio * dio = (struct driverio*) user;
104 int available;
105 int size = 0;
106
107 unsigned int allocated = outBuffAllocated(dio);
108 while(1)
109 {
110 available = allocated - dio->outPos;
111 /* Determine required size */
112 size = vsnprintf(dio->outBuff + dio->outPos, available, fmt, arg);
113
114 if (size < 0)
115 return size;
116
117 if (size < available)
118 {
119 break;
120 }
121 allocated = outBuffRequired(available + size + 1);
122 outBuffGrow(dio, allocated);
123 }
124 dio->outPos += size;
125 return size;
126}
127
128static void driverio_join(void * user, const char * xml, void * blob, size_t bloblen)
129{
130 struct driverio * dio = (struct driverio*) user;
131 dio->joinCount++;
132 dio->joins = (void **)realloc((void*)dio->joins, sizeof(void*) * dio->joinCount);
133 dio->joinSizes = (size_t *)realloc((void*)dio->joinSizes, sizeof(size_t) * dio->joinCount);
134
135 dio->joins[dio->joinCount - 1] = blob;
136 dio->joinSizes[dio->joinCount - 1] = bloblen;
137
138 driverio_write(user, xml, strlen(xml));
139}
140
141
142static void driverio_flush(driverio * dio, const void * additional, size_t add_size)
143{
144 struct msghdr msgh;
145 struct iovec iov[2];
146 int cmsghdrlength;
147 struct cmsghdr * cmsgh;
148
149 if (dio->outPos + add_size)
150 {
151 int ret = -1;
152 void ** temporaryBuffers = NULL;
153 int fdCount = dio->joinCount;
154 if (fdCount > 0)
155 {
156
157 if (dio->joinCount > MAXFD_PER_MESSAGE)
158 {
159 errno = EMSGSIZE;
160 perror("sendmsg");
161 exit(1);
162 }
163
164 cmsghdrlength = CMSG_SPACE((fdCount * sizeof(int)));
165 cmsgh = (struct cmsghdr*)malloc(cmsghdrlength);
166 // FIXME: abort on alloc error here
167 temporaryBuffers = (void**)malloc(sizeof(void*)*fdCount);
168
169 /* Write the fd as ancillary data */
170 cmsgh->cmsg_len = CMSG_LEN(sizeof(int));
171 cmsgh->cmsg_level = SOL_SOCKET;
172 cmsgh->cmsg_type = SCM_RIGHTS;
173 msgh.msg_control = cmsgh;
174 msgh.msg_controllen = cmsghdrlength;
175 for(int i = 0; i < fdCount; ++i)
176 {
177 void * blob = dio->joins[i];
178 size_t size = dio->joinSizes[i];
179
180 int fd = IDSharedBlobGetFd(blob);
181 if (fd == -1)
182 {
183 // Can't avoid a copy here. Update the driver to change that
184 temporaryBuffers[i] = IDSharedBlobAlloc(size);
185 memcpy(temporaryBuffers[i], blob, size);
186 fd = IDSharedBlobGetFd(temporaryBuffers[i]);
187 }
188 else
189 {
190 temporaryBuffers[i] = NULL;
191 }
192
193 ((int *) CMSG_DATA(CMSG_FIRSTHDR(&msgh)))[i] = fd;
194 }
195 }
196 else
197 {
198 cmsgh = NULL;
199 cmsghdrlength = 0;
200 msgh.msg_control = cmsgh;
201 msgh.msg_controllen = cmsghdrlength;
202 }
203
204 iov[0].iov_base = dio->outBuff;
205 iov[0].iov_len = dio->outPos;
206 if (add_size)
207 {
208 iov[1].iov_base = (void*)additional;
209 iov[1].iov_len = add_size;
210 }
211
212 msgh.msg_flags = 0;
213 msgh.msg_name = NULL;
214 msgh.msg_namelen = 0;
215 msgh.msg_iov = iov;
216 msgh.msg_iovlen = add_size ? 2 : 1;
217
218 if (!dio->locked)
219 {
220 pthread_mutex_lock(&stdout_mutex);
221 dio->locked = 1;
222 }
223
224 ret = sendmsg(1, &msgh, 0);
225 if (ret == -1)
226 {
227 perror("sendmsg");
228 // FIXME: exiting the driver seems abrupt. Is this the right thing to do ? what about cleanup ?
229 exit(1);
230 }
231 else if ((unsigned)ret != dio->outPos + add_size)
232 {
233 // This is not expected on blocking socket
234 fprintf(stderr, "short write\n");
235 exit(1);
236 }
237
238 if (fdCount > 0)
239 {
240 for(int i = 0; i < fdCount; ++i)
241 {
242 if (temporaryBuffers[i] != NULL)
243 {
244 IDSharedBlobFree(temporaryBuffers[i]);
245 }
246 }
247 free(cmsgh);
248 free(temporaryBuffers);
249 }
250 }
251
252 if (dio->joins != NULL)
253 {
254 free(dio->joins);
255 }
256 dio->joins = NULL;
257
258 if (dio->joinSizes != NULL)
259 {
260 free(dio->joinSizes);
261 }
262 dio->joinSizes = NULL;
263
264 if (dio->outBuff != NULL)
265 {
266 free(dio->outBuff);
267 }
268 dio->outPos = 0;
269
270}
271
272
273static int driverio_is_unix = -1;
274
275static int is_unix_io()
276{
277#ifndef ENABLE_INDI_SHARED_MEMORY
278 return 0;
279#endif
280 if (driverio_is_unix != -1)
281 {
282 return driverio_is_unix;
283 }
284
285#ifdef SO_DOMAIN
286 int domain;
287 socklen_t result = sizeof(domain);
288
289 if (getsockopt(1, SOL_SOCKET, SO_DOMAIN, (void*)&domain, &result) == -1)
290 {
291 driverio_is_unix = 0;
292 }
293 else if (result != sizeof(domain) || domain != AF_UNIX)
294 {
295 driverio_is_unix = 0;
296 }
297 else
298 {
299 driverio_is_unix = 1;
300 }
301#else
302 struct sockaddr_un sockName;
303 socklen_t sockNameLen = sizeof(sockName);
304
305 if (getsockname(1, (struct sockaddr*)&sockName, &sockNameLen) == -1)
306 {
307 driverio_is_unix = 0;
308 }
309 else if (sockName.sun_family == AF_UNIX)
310 {
311 driverio_is_unix = 1;
312 }
313 else
314 {
315 driverio_is_unix = 0;
316 }
317#endif
318 return driverio_is_unix;
319}
320
321/* Unix io allow attaching buffer in ancillary data. */
322static void driverio_init_unix(driverio * dio)
323{
324 dio->userio.vprintf = &driverio_vprintf;
325 dio->userio.write = &driverio_write;
326 dio->userio.joinbuff = &driverio_join;
327 dio->user = (void*)dio;
328 dio->joins = NULL;
329 dio->joinSizes = NULL;
330 dio->joinCount = 0;
331 dio->outBuff = NULL;
332 dio->outPos = 0;
333 pthread_mutex_lock(&stdout_mutex);
334 dio->locked = 1;
335}
336
337static void driverio_finish_unix(driverio * dio)
338{
339 driverio_flush(dio, NULL, 0);
340 if (dio->locked)
341 {
342 pthread_mutex_unlock(&stdout_mutex);
343 dio->locked = 0;
344 }
345}
346
347static void driverio_init_stdout(driverio * dio)
348{
349 dio->userio = *userio_file();
350 dio->user = stdout;
351 pthread_mutex_lock(&stdout_mutex);
352}
353
354static void driverio_finish_stdout(driverio * dio)
355{
356 (void)dio;
357 fflush(stdout);
358 pthread_mutex_unlock(&stdout_mutex);
359}
360
362{
363 if (is_unix_io())
364 {
365 driverio_init_unix(dio);
366 }
367 else
368 {
369 driverio_init_stdout(dio);
370 }
371}
372
374{
375 if (is_unix_io())
376 {
377 driverio_finish_unix(dio);
378 }
379 else
380 {
381 driverio_finish_stdout(dio);
382 }
383}
std::string fmt(const char *fmt,...)
Definition Utils.cpp:241
int errno
void driverio_init(driverio *dio)
#define OUTPUTBUFF_FLUSH_THRESOLD
void driverio_finish(driverio *dio)
#define OUTPUTBUFF_ALLOC
#define MAXFD_PER_MESSAGE
int fd
Namespace to encapsulate INDI client, drivers, and mediator classes.
void IDSharedBlobFree(void *ptr)
Free a buffer allocated using IDSharedBlobAlloc. Fall back to free for buffer that are not shared blo...
Definition sharedblob.c:139
void * IDSharedBlobAlloc(size_t size)
Allocate a buffer suitable for fast exchange over local links. Warning : the buffer will be sealed (r...
Definition sharedblob.c:73
int IDSharedBlobGetFd(void *ptr)
Return the filedescriptor backing up the given shared buffer.
Definition sharedblob.c:265
void ** joins
char * outBuff
void * user
unsigned int outPos
struct userio userio
size_t * joinSizes
int joinCount
void(* joinbuff)(void *user, const char *xml, void *buffer, size_t bloblen)
Definition userio.h:34
int(* vprintf)(void *user, const char *format, va_list arg)
Definition userio.h:31
ssize_t(* write)(void *user, const void *ptr, size_t count)
Definition userio.h:30
const struct userio * userio_file()
Definition userio.c:39