Code

parser: Let the TIMESERIES command accept optional data-source names.
[sysdb.git] / src / utils / unixsock.c
1 /*
2  * SysDB - src/utils/unixsock.c
3  * Copyright (C) 2012 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 #if HAVE_CONFIG_H
29 #       include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "utils/error.h"
33 #include "utils/unixsock.h"
35 #include <assert.h>
36 #include <errno.h>
38 #include <stdarg.h>
39 #include <stdio.h>
40 #include <stdlib.h>
42 #include <string.h>
43 #include <strings.h>
45 #include <unistd.h>
47 #include <sys/socket.h>
48 #include <sys/un.h>
50 /*
51  * private data types
52  */
54 struct sdb_unixsock_client {
55         char *path;
56         FILE *fh;
58         int shutdown;
59 };
61 #define SDB_SHUT_RD   (1 << SHUT_RD)
62 #define SDB_SHUT_WR   (1 << SHUT_WR)
63 #define SDB_SHUT_RDWR (SDB_SHUT_RD | SDB_SHUT_WR)
65 /*
66  * private helper functions
67  */
69 static int
70 sdb_unixsock_get_column_count(const char *string, const char *delim)
71 {
72         int count = 1;
74         assert(string);
76         if ((! delim) || (*string == '\0'))
77                 return 1;
79         if ((delim[0] == '\0') || (delim[1] == '\0')) {
80                 while ((string = strchr(string, (int)delim[0]))) {
81                         ++string;
82                         ++count;
83                 }
84         }
85         else {
86                 while ((string = strpbrk(string, delim))) {
87                         ++string;
88                         ++count;
89                 }
90         }
91         return count;
92 } /* sdb_unixsock_get_column_count */
94 static int
95 sdb_unixsock_client_process_one_line(sdb_unixsock_client_t *client,
96                 char *line, sdb_unixsock_client_data_cb callback,
97                 sdb_object_t *user_data, const char *delim,
98                 int column_count, int *types)
99 {
100         sdb_data_t data[column_count];
101         char *orig_line = line;
103         int i;
105         assert(column_count > 0);
107         for (i = 0; i < column_count; ++i) {
108                 char *next;
110                 if (! line) { /* this must not happen */
111                         sdb_log(SDB_LOG_ERR, "unixsock: Unexpected EOL while "
112                                         "parsing line (expected %i columns delimited by '%s'; "
113                                         "got %i): %s", column_count, delim,
114                                         /* last line number */ i, orig_line);
115                         return -1;
116                 }
118                 if ((delim[0] == '\0') || (delim[1] == '\0'))
119                         next = strchr(line, (int)delim[0]);
120                 else
121                         next = strpbrk(line, delim);
123                 if (next) {
124                         *next = '\0';
125                         ++next;
126                 }
128                 if (types && (types[i] != SDB_TYPE_STRING)) {
129                         if (sdb_data_parse(line, types[i], &data[i]))
130                                 return -1;
131                 }
132                 else {
133                         /* Avoid a copy in common cases */
134                         data[i].type = SDB_TYPE_STRING;
135                         data[i].data.string = line;
136                 }
138                 line = next;
139         }
141         if (callback(client, (size_t)column_count, data, user_data))
142                 return -1;
144         for (i = 0; i < column_count; ++i)
145                 if (types && (types[i] != SDB_TYPE_STRING))
146                         sdb_data_free_datum(&data[i]);
147         return 0;
148 } /* sdb_unixsock_client_process_one_line */
150 /*
151  * public API
152  */
154 sdb_unixsock_client_t *
155 sdb_unixsock_client_create(const char *path)
157         sdb_unixsock_client_t *client;
159         if (! path)
160                 return NULL;
162         client = malloc(sizeof(*client));
163         if (! client)
164                 return NULL;
165         memset(client, 0, sizeof(*client));
166         client->fh = NULL;
168         client->path = strdup(path);
169         if (! client->path) {
170                 sdb_unixsock_client_destroy(client);
171                 return NULL;
172         }
174         client->shutdown = 0;
175         return client;
176 } /* sdb_unixsock_client_create */
178 int
179 sdb_unixsock_client_connect(sdb_unixsock_client_t *client)
181         struct sockaddr_un sa;
182         int fd;
184         if ((! client) || (! client->path))
185                 return -1;
187         memset(&sa, 0, sizeof(sa));
189         if (client->fh)
190                 fclose(client->fh);
192         fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
193         if (fd < 0) {
194                 char errbuf[1024];
195                 sdb_log(SDB_LOG_ERR, "unixsock: Failed to open socket: %s",
196                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
197                 return -1;
198         }
200         sa.sun_family = AF_UNIX;
201         strncpy(sa.sun_path, client->path, sizeof(sa.sun_path));
202         sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
204         if (connect(fd, (struct sockaddr *)&sa, sizeof(sa))) {
205                 char errbuf[1024];
206                 sdb_log(SDB_LOG_ERR, "unixsock: Failed to connect to %s: %s",
207                                 sa.sun_path, sdb_strerror(errno, errbuf, sizeof(errbuf)));
208                 close(fd);
209                 return -1;
210         }
212         client->fh = fdopen(fd, "r+");
213         if (! client->fh) {
214                 char errbuf[1024];
215                 sdb_log(SDB_LOG_ERR, "unixsock: Failed to open I/O "
216                                 "stream for %s: %s", sa.sun_path,
217                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
218                 close(fd);
219                 return -1;
220         }
222         /* enable line-buffering */
223         setvbuf(client->fh, NULL, _IOLBF, 0);
225         client->shutdown = 0;
226         return 0;
227 } /* sdb_unixsock_client_connect */
229 int
230 sdb_unixsock_client_send(sdb_unixsock_client_t *client,
231                 const char *msg)
233         int status;
235         if ((! client) || (! client->fh))
236                 return -1;
238         if (client->shutdown & SDB_SHUT_WR) /* reconnect */
239                 sdb_unixsock_client_connect(client);
241         status = fprintf(client->fh, "%s\r\n", msg);
242         if (status < 0) {
243                 char errbuf[1024];
244                 sdb_log(SDB_LOG_ERR, "unixsock: Failed to write to "
245                                 "socket (%s): %s", client->path,
246                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
247                 return status;
248         }
249         return status;
250 } /* sdb_unixsock_client_send */
252 char *
253 sdb_unixsock_client_recv(sdb_unixsock_client_t *client,
254                 char *buffer, size_t buflen)
256         char *tmp;
258         if ((! client) || (! client->fh) || (! buffer))
259                 return NULL;
261         if (client->shutdown & SDB_SHUT_RD) /* reconnect */
262                 sdb_unixsock_client_connect(client);
264         tmp = NULL;
265         while (tmp == NULL) {
266                 errno = 0;
267                 tmp = fgets(buffer, (int)buflen - 1, client->fh);
268                 if (! tmp) {
269                         if ((errno == EAGAIN) || (errno == EINTR))
270                                 continue;
272                         if (! feof(client->fh)) {
273                                 char errbuf[1024];
274                                 sdb_log(SDB_LOG_ERR, "unixsock: Failed to read "
275                                                 "from socket (%s): %s", client->path,
276                                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
277                         }
278                         return NULL;
279                 }
280         }
281         buffer[buflen - 1] = '\0';
283         buflen = strlen(buffer);
284         while (buflen && ((buffer[buflen - 1] == '\n') || (buffer[buflen - 1] == '\r'))) {
285                 buffer[buflen - 1] = '\0';
286                 --buflen;
287         }
288         return buffer;
289 } /* sdb_unixsock_client_recv */
291 int
292 sdb_unixsock_client_process_lines(sdb_unixsock_client_t *client,
293                 sdb_unixsock_client_data_cb callback, sdb_object_t *user_data,
294                 long int max_lines, const char *delim, int n_cols, ...)
296         int *types = NULL;
297         int success = 0;
299         if ((! client) || (! client->fh) || (! callback))
300                 return -1;
302         if (n_cols > 0) {
303                 va_list ap;
304                 int i;
306                 types = calloc((size_t)n_cols, sizeof(*types));
307                 if (! types)
308                         return -1;
310                 va_start(ap, n_cols);
312                 for (i = 0; i < n_cols; ++i) {
313                         types[i] = va_arg(ap, int);
315                         if ((types[i] < 1) || (types[i] > SDB_TYPE_BINARY)) {
316                                 sdb_log(SDB_LOG_ERR, "unixsock: Unknown column "
317                                                 "type %i while processing response from the "
318                                                 "UNIX socket @ %s.", types[i], client->path);
319                                 va_end(ap);
320                                 free(types);
321                                 return -1;
322                         }
323                 }
325                 va_end(ap);
326         }
328         while (42) {
329                 char  buffer[1024];
330                 char *line;
332                 int column_count;
334                 if (! max_lines)
335                         break;
337                 if (max_lines > 0)
338                         --max_lines;
340                 sdb_unixsock_client_clearerr(client);
341                 line = sdb_unixsock_client_recv(client, buffer, sizeof(buffer));
343                 if (! line)
344                         break;
346                 column_count = sdb_unixsock_get_column_count(line, delim);
348                 if ((n_cols >= 0) && (n_cols != column_count)) {
349                         sdb_log(SDB_LOG_ERR, "unixsock: number of columns (%i) "
350                                         "does not match the number of requested columns (%i) "
351                                         "while processing response from the UNIX socket @ %s: %s",
352                                         column_count, n_cols, client->path, line);
353                         continue;
354                 }
356                 if (column_count <= 0) /* no data */
357                         continue;
359                 if (! sdb_unixsock_client_process_one_line(client, line, callback,
360                                         user_data, delim, column_count, types))
361                         ++success;
362         }
364         free(types);
366         if ((max_lines > 0)
367                         || ((max_lines < 0) && (! sdb_unixsock_client_eof(client)))
368                         || sdb_unixsock_client_error(client)) {
369                 char errbuf[1024];
370                 sdb_log(SDB_LOG_ERR, "unixsock: Unexpected end of data while "
371                                 "reading from socket (%s): %s", client->path,
372                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
373                 return -1;
374         }
375         if (! success)
376                 return -1;
377         return 0;
378 } /* sdb_unixsock_client_process_lines */
380 int
381 sdb_unixsock_client_shutdown(sdb_unixsock_client_t *client, int how)
383         int status;
385         if (! client) {
386                 errno = ENOTSOCK;
387                 return -1;
388         }
390         fflush(client->fh);
391         status = shutdown(fileno(client->fh), how);
393         if (! status) {
394                 if (how == SHUT_RDWR)
395                         client->shutdown |= SDB_SHUT_RDWR;
396                 else
397                         client->shutdown |= 1 << how;
398         }
399         return status;
400 } /* sdb_unixsock_client_shutdown */
402 void
403 sdb_unixsock_client_clearerr(sdb_unixsock_client_t *client)
405         if ((! client) || (! client->fh))
406                 return;
407         clearerr(client->fh);
408 } /* sdb_unixsock_client_clearerr */
410 int
411 sdb_unixsock_client_eof(sdb_unixsock_client_t *client)
413         if ((! client) || (! client->fh)) {
414                 errno = EBADF;
415                 return -1;
416         }
417         return feof(client->fh);
418 } /* sdb_unixsock_client_eof */
420 int
421 sdb_unixsock_client_error(sdb_unixsock_client_t *client)
423         if ((! client) || (! client->fh)) {
424                 errno = EBADF;
425                 return -1;
426         }
427         return ferror(client->fh);
428 } /* sdb_unixsock_client_error */
430 void
431 sdb_unixsock_client_destroy(sdb_unixsock_client_t *client)
433         if (! client)
434                 return;
436         if (client->path)
437                 free(client->path);
438         client->path = NULL;
440         if (client->fh)
441                 fclose(client->fh);
442         client->fh = NULL;
444         free(client);
445 } /* sdb_unixsock_client_destroy */
447 const char *
448 sdb_unixsock_client_path(sdb_unixsock_client_t *client)
450         if (! client)
451                 return NULL;
452         return client->path;
453 } /* sdb_unixsock_client_path */
455 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */