Code

db025f80f9f654ffc70d528c8b870b3c62b97768
[sysdb.git] / src / utils / unixsock.c
1 /*
2  * syscollector - 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 #include "utils/unixsock.h"
29 #include "utils/string.h"
31 #include <assert.h>
32 #include <errno.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
38 #include <string.h>
39 #include <strings.h>
41 #include <unistd.h>
43 #include <sys/socket.h>
44 #include <sys/un.h>
46 /*
47  * private data types
48  */
50 struct sc_unixsock_client {
51         char *path;
52         FILE *fh;
54         int shutdown;
55 };
57 #define SC_SHUT_RD   (1 << SHUT_RD)
58 #define SC_SHUT_WR   (1 << SHUT_WR)
59 #define SC_SHUT_RDWR (SC_SHUT_RD | SC_SHUT_WR)
61 /*
62  * private helper functions
63  */
65 static int
66 sc_unixsock_get_column_count(const char *string, const char *delim)
67 {
68         int count = 1;
70         assert(string);
72         if ((! delim) || (*string == '\0'))
73                 return 1;
75         if ((delim[0] == '\0') || (delim[1] == '\0')) {
76                 while ((string = strchr(string, (int)delim[0]))) {
77                         ++string;
78                         ++count;
79                 }
80         }
81         else {
82                 while ((string = strpbrk(string, delim))) {
83                         ++string;
84                         ++count;
85                 }
86         }
87         return count;
88 } /* sc_unixsock_get_column_count */
90 static int
91 sc_unixsock_parse_cell(char *string, int type, sc_data_t *data)
92 {
93         char *endptr = NULL;
95         switch (type) {
96                 case SC_TYPE_INTEGER:
97                         errno = 0;
98                         data->data.integer = strtoll(string, &endptr, 0);
99                         break;
100                 case SC_TYPE_DECIMAL:
101                         errno = 0;
102                         data->data.decimal = strtod(string, &endptr);
103                         break;
104                 case SC_TYPE_STRING:
105                         data->data.string = string;
106                         break;
107                 case SC_TYPE_DATETIME:
108                         {
109                                 double datetime = strtod(string, &endptr);
110                                 data->data.datetime = DOUBLE_TO_SC_TIME(datetime);
111                         }
112                         break;
113                 case SC_TYPE_BINARY:
114                         /* we don't support any binary information containing 0-bytes */
115                         data->data.binary.length = strlen(string);
116                         data->data.binary.datum = (const unsigned char *)string;
117                         break;
118                 default:
119                         fprintf(stderr, "unixsock: Unexpected type %i while "
120                                         "parsing query result.\n", type);
121                         return -1;
122         }
124         if ((type == SC_TYPE_INTEGER) || (type == SC_TYPE_DECIMAL)
125                         || (type == SC_TYPE_DATETIME)) {
126                 if (errno || (string == endptr)) {
127                         char errbuf[1024];
128                         fprintf(stderr, "unixsock: Failed to parse string '%s' "
129                                         "as numeric value (type %i): %s\n", string, type,
130                                         sc_strerror(errno, errbuf, sizeof(errbuf)));
131                         return -1;
132                 }
133                 else if (endptr && (*endptr != '\0'))
134                         fprintf(stderr, "unixsock: Ignoring garbage after number "
135                                         "while parsing numeric value (type %i): %s.\n",
136                                         type, endptr);
137         }
139         data->type = type;
140         return 0;
141 } /* sc_unixsock_parse_cell */
143 static int
144 sc_unixsock_client_process_one_line(sc_unixsock_client_t *client,
145                 char *line, sc_unixsock_client_data_cb callback,
146                 const char *delim, int column_count, int *types)
148         sc_data_t data[column_count];
149         char *orig_line = line;
151         int i;
153         assert(column_count > 0);
155         for (i = 0; i < column_count; ++i) {
156                 char *next;
158                 if (! line) { /* this must no happen */
159                         fprintf(stderr, "unixsock: Unexpected EOL while parsing line "
160                                         "(expected %i columns delimited by '%s'; got %i): %s\n",
161                                         column_count, delim, /* last line number */ i, orig_line);
162                         return -1;
163                 }
165                 if ((delim[0] == '\0') || (delim[1] == '\0'))
166                         next = strchr(line, (int)delim[0]);
167                 else
168                         next = strpbrk(line, delim);
170                 if (next) {
171                         *next = '\0';
172                         ++next;
173                 }
175                 if (sc_unixsock_parse_cell(line,
176                                         types ? types[i] : SC_TYPE_STRING, &data[i]))
177                         return -1;
179                 line = next;
180         }
182         if (callback(client, (size_t)column_count, data))
183                 return -1;
184         return 0;
185 } /* sc_unixsock_client_process_one_line */
187 /*
188  * public API
189  */
191 sc_unixsock_client_t *
192 sc_unixsock_client_create(const char *path)
194         sc_unixsock_client_t *client;
196         if (! path)
197                 return NULL;
199         client = malloc(sizeof(*client));
200         if (! client)
201                 return NULL;
202         memset(client, 0, sizeof(*client));
203         client->fh = NULL;
205         client->path = strdup(path);
206         if (! client->path) {
207                 sc_unixsock_client_destroy(client);
208                 return NULL;
209         }
211         client->shutdown = 0;
212         return client;
213 } /* sc_unixsock_client_create */
215 int
216 sc_unixsock_client_connect(sc_unixsock_client_t *client)
218         struct sockaddr_un sa;
219         int fd;
221         if ((! client) || (! client->path))
222                 return -1;
224         memset(&sa, 0, sizeof(sa));
226         if (client->fh)
227                 fclose(client->fh);
229         fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
230         if (fd < 0) {
231                 char errbuf[1024];
232                 fprintf(stderr, "unixsock: Failed to open socket: %s\n",
233                                 sc_strerror(errno, errbuf, sizeof(errbuf)));
234                 return -1;
235         }
237         sa.sun_family = AF_UNIX;
238         strncpy(sa.sun_path, client->path, sizeof(sa.sun_path));
239         sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
241         if (connect(fd, (struct sockaddr *)&sa, sizeof(sa))) {
242                 char errbuf[1024];
243                 fprintf(stderr, "unixsock: Failed to connect to %s: %s\n",
244                                 sa.sun_path, sc_strerror(errno, errbuf, sizeof(errbuf)));
245                 close(fd);
246                 return -1;
247         }
249         client->fh = fdopen(fd, "r+");
250         if (! client->fh) {
251                 char errbuf[1024];
252                 fprintf(stderr, "unixsock: Failed to open I/O stream for %s: %s\n",
253                                 sa.sun_path, sc_strerror(errno, errbuf, sizeof(errbuf)));
254                 close(fd);
255                 return -1;
256         }
258         client->shutdown = 0;
259         return 0;
260 } /* sc_unixsock_client_connect */
262 int
263 sc_unixsock_client_send(sc_unixsock_client_t *client, const char *msg)
265         int status;
267         if ((! client) || (! client->fh))
268                 return -1;
270         if (client->shutdown & SC_SHUT_WR) /* reconnect */
271                 sc_unixsock_client_connect(client);
273         status = fprintf(client->fh, "%s\r\n", msg);
274         if (status < 0) {
275                 char errbuf[1024];
276                 fprintf(stderr, "unixsock: Failed to write to socket (%s): %s\n",
277                                 client->path, sc_strerror(errno, errbuf, sizeof(errbuf)));
278                 return status;
279         }
280         return status;
281 } /* sc_unixsock_client_send */
283 char *
284 sc_unixsock_client_recv(sc_unixsock_client_t *client, char *buffer, size_t buflen)
286         if ((! client) || (! client->fh) || (! buffer))
287                 return NULL;
289         if (client->shutdown & SC_SHUT_RD) /* reconnect */
290                 sc_unixsock_client_connect(client);
292         buffer = fgets(buffer, (int)buflen - 1, client->fh);
293         if (! buffer) {
294                 if (! feof(client->fh)) {
295                         char errbuf[1024];
296                         fprintf(stderr, "unixsock: Failed to read from socket (%s): %s\n",
297                                         client->path, sc_strerror(errno, errbuf, sizeof(errbuf)));
298                 }
299                 return buffer;
300         }
301         buffer[buflen - 1] = '\0';
303         buflen = strlen(buffer);
304         while ((buffer[buflen - 1] == '\n') || (buffer[buflen - 1] == '\r')) {
305                 buffer[buflen - 1] = '\0';
306                 --buflen;
307         }
308         return buffer;
309 } /* sc_unixsock_client_recv */
311 int
312 sc_unixsock_client_process_lines(sc_unixsock_client_t *client,
313                 sc_unixsock_client_data_cb callback, long int max_lines,
314                 const char *delim, int n_cols, ...)
316         int *types = NULL;
317         int success = 0;
319         if ((! client) || (! client->fh) || (! callback))
320                 return -1;
322         if (n_cols > 0) {
323                 va_list ap;
324                 int i;
326                 types = calloc((size_t)n_cols, sizeof(*types));
327                 if (! types)
328                         return -1;
330                 va_start(ap, n_cols);
332                 for (i = 0; i < n_cols; ++i) {
333                         types[i] = va_arg(ap, int);
335                         if ((types[i] < 1) || (types[i] > SC_TYPE_BINARY)) {
336                                 fprintf(stderr, "unixsock: Unknown column type %i while "
337                                                 "processing response from the UNIX socket @ %s.\n",
338                                                 types[i], client->path);
339                                 va_end(ap);
340                                 free(types);
341                                 return -1;
342                         }
343                 }
345                 va_end(ap);
346         }
348         while (42) {
349                 char  buffer[1024];
350                 char *line;
352                 int column_count;
354                 if (! max_lines)
355                         break;
357                 if (max_lines > 0)
358                         --max_lines;
360                 sc_unixsock_client_clearerr(client);
361                 line = sc_unixsock_client_recv(client, buffer, sizeof(buffer));
363                 if (! line)
364                         break;
366                 column_count = sc_unixsock_get_column_count(line, delim);
368                 if ((n_cols >= 0) && (n_cols != column_count)) {
369                         fprintf(stderr, "unixsock: number of columns (%i) does not "
370                                         "match the number of requested columns (%i) while "
371                                         "processing response from the UNIX socket @ %s: %s\n",
372                                         column_count, n_cols, client->path, line);
373                         continue;
374                 }
376                 if (column_count <= 0) /* no data */
377                         continue;
379                 if (! sc_unixsock_client_process_one_line(client, line, callback,
380                                         delim, column_count, types))
381                         ++success;
382         }
384         free(types);
386         if ((max_lines > 0)
387                         || ((max_lines < 0) && (! sc_unixsock_client_eof(client)))
388                         || sc_unixsock_client_error(client)) {
389                 char errbuf[1024];
390                 fprintf(stderr, "unixsock: Unexpected end of data while reading "
391                                 "from socket (%s): %s\n", client->path,
392                                 sc_strerror(errno, errbuf, sizeof(errbuf)));
393                 return -1;
394         }
395         if (! success)
396                 return -1;
397         return 0;
398 } /* sc_unixsock_client_process_lines */
400 int
401 sc_unixsock_client_shutdown(sc_unixsock_client_t *client, int how)
403         int status;
405         if (! client) {
406                 errno = ENOTSOCK;
407                 return -1;
408         }
410         fflush(client->fh);
411         status = shutdown(fileno(client->fh), how);
413         if (! status) {
414                 if (how == SHUT_RDWR)
415                         client->shutdown |= SC_SHUT_RDWR;
416                 else
417                         client->shutdown |= 1 << how;
418         }
419         return status;
420 } /* sc_unixsock_client_shutdown */
422 void
423 sc_unixsock_client_clearerr(sc_unixsock_client_t *client)
425         if ((! client) || (! client->fh))
426                 return;
427         clearerr(client->fh);
428 } /* sc_unixsock_client_clearerr */
430 int
431 sc_unixsock_client_eof(sc_unixsock_client_t *client)
433         if ((! client) || (! client->fh)) {
434                 errno = EBADF;
435                 return -1;
436         }
437         return feof(client->fh);
438 } /* sc_unixsock_client_eof */
440 int
441 sc_unixsock_client_error(sc_unixsock_client_t *client)
443         if ((! client) || (! client->fh)) {
444                 errno = EBADF;
445                 return -1;
446         }
447         return ferror(client->fh);
448 } /* sc_unixsock_client_error */
450 void
451 sc_unixsock_client_destroy(sc_unixsock_client_t *client)
453         if (! client)
454                 return;
456         if (client->path)
457                 free(client->path);
458         client->path = NULL;
460         if (client->fh)
461                 fclose(client->fh);
462         client->fh = NULL;
464         free(client);
465 } /* sc_unixsock_client_destroy */
467 const char *
468 sc_unixsock_client_path(sc_unixsock_client_t *client)
470         if (! client)
471                 return NULL;
472         return client->path;
473 } /* sc_unixsock_client_path */
475 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */