Code

Merged branch 'master' of git://git.tokkee.org/sysdb.git.
[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 #include "utils/unixsock.h"
29 #include "core/error.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 sdb_unixsock_client {
51         char *path;
52         FILE *fh;
54         int shutdown;
55 };
57 #define SDB_SHUT_RD   (1 << SHUT_RD)
58 #define SDB_SHUT_WR   (1 << SHUT_WR)
59 #define SDB_SHUT_RDWR (SDB_SHUT_RD | SDB_SHUT_WR)
61 /*
62  * private helper functions
63  */
65 static int
66 sdb_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 } /* sdb_unixsock_get_column_count */
90 static int
91 sdb_unixsock_parse_cell(char *string, int type, sdb_data_t *data)
92 {
93         char *endptr = NULL;
95         switch (type) {
96                 case SDB_TYPE_INTEGER:
97                         errno = 0;
98                         data->data.integer = strtoll(string, &endptr, 0);
99                         break;
100                 case SDB_TYPE_DECIMAL:
101                         errno = 0;
102                         data->data.decimal = strtod(string, &endptr);
103                         break;
104                 case SDB_TYPE_STRING:
105                         data->data.string = string;
106                         break;
107                 case SDB_TYPE_DATETIME:
108                         {
109                                 double datetime = strtod(string, &endptr);
110                                 data->data.datetime = DOUBLE_TO_SDB_TIME(datetime);
111                         }
112                         break;
113                 case SDB_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                         sdb_log(SDB_LOG_ERR, "unixsock: Unexpected type %i while "
120                                         "parsing query result.", type);
121                         return -1;
122         }
124         if ((type == SDB_TYPE_INTEGER) || (type == SDB_TYPE_DECIMAL)
125                         || (type == SDB_TYPE_DATETIME)) {
126                 if (errno || (string == endptr)) {
127                         char errbuf[1024];
128                         sdb_log(SDB_LOG_ERR, "unixsock: Failed to parse string "
129                                         "'%s' as numeric value (type %i): %s", string, type,
130                                         sdb_strerror(errno, errbuf, sizeof(errbuf)));
131                         return -1;
132                 }
133                 else if (endptr && (*endptr != '\0'))
134                         sdb_log(SDB_LOG_WARNING, "unixsock: Ignoring garbage after "
135                                         "number while parsing numeric value (type %i): %s.",
136                                         type, endptr);
137         }
139         data->type = type;
140         return 0;
141 } /* sdb_unixsock_parse_cell */
143 static int
144 sdb_unixsock_client_process_one_line(sdb_unixsock_client_t *client,
145                 char *line, sdb_unixsock_client_data_cb callback,
146                 sdb_object_t *user_data, const char *delim,
147                 int column_count, int *types)
149         sdb_data_t data[column_count];
150         char *orig_line = line;
152         int i;
154         assert(column_count > 0);
156         for (i = 0; i < column_count; ++i) {
157                 char *next;
159                 if (! line) { /* this must no happen */
160                         sdb_log(SDB_LOG_ERR, "unixsock: Unexpected EOL while "
161                                         "parsing line (expected %i columns delimited by '%s'; "
162                                         "got %i): %s", column_count, delim,
163                                         /* last line number */ i, orig_line);
164                         return -1;
165                 }
167                 if ((delim[0] == '\0') || (delim[1] == '\0'))
168                         next = strchr(line, (int)delim[0]);
169                 else
170                         next = strpbrk(line, delim);
172                 if (next) {
173                         *next = '\0';
174                         ++next;
175                 }
177                 if (sdb_unixsock_parse_cell(line,
178                                         types ? types[i] : SDB_TYPE_STRING, &data[i]))
179                         return -1;
181                 line = next;
182         }
184         if (callback(client, (size_t)column_count, data, user_data))
185                 return -1;
186         return 0;
187 } /* sdb_unixsock_client_process_one_line */
189 /*
190  * public API
191  */
193 sdb_unixsock_client_t *
194 sdb_unixsock_client_create(const char *path)
196         sdb_unixsock_client_t *client;
198         if (! path)
199                 return NULL;
201         client = malloc(sizeof(*client));
202         if (! client)
203                 return NULL;
204         memset(client, 0, sizeof(*client));
205         client->fh = NULL;
207         client->path = strdup(path);
208         if (! client->path) {
209                 sdb_unixsock_client_destroy(client);
210                 return NULL;
211         }
213         client->shutdown = 0;
214         return client;
215 } /* sdb_unixsock_client_create */
217 int
218 sdb_unixsock_client_connect(sdb_unixsock_client_t *client)
220         struct sockaddr_un sa;
221         int fd;
223         if ((! client) || (! client->path))
224                 return -1;
226         memset(&sa, 0, sizeof(sa));
228         if (client->fh)
229                 fclose(client->fh);
231         fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
232         if (fd < 0) {
233                 char errbuf[1024];
234                 sdb_log(SDB_LOG_ERR, "unixsock: Failed to open socket: %s",
235                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
236                 return -1;
237         }
239         sa.sun_family = AF_UNIX;
240         strncpy(sa.sun_path, client->path, sizeof(sa.sun_path));
241         sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
243         if (connect(fd, (struct sockaddr *)&sa, sizeof(sa))) {
244                 char errbuf[1024];
245                 sdb_log(SDB_LOG_ERR, "unixsock: Failed to connect to %s: %s",
246                                 sa.sun_path, sdb_strerror(errno, errbuf, sizeof(errbuf)));
247                 close(fd);
248                 return -1;
249         }
251         client->fh = fdopen(fd, "r+");
252         if (! client->fh) {
253                 char errbuf[1024];
254                 sdb_log(SDB_LOG_ERR, "unixsock: Failed to open I/O "
255                                 "stream for %s: %s", sa.sun_path,
256                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
257                 close(fd);
258                 return -1;
259         }
261         client->shutdown = 0;
262         return 0;
263 } /* sdb_unixsock_client_connect */
265 int
266 sdb_unixsock_client_send(sdb_unixsock_client_t *client,
267                 const char *msg)
269         int status;
271         if ((! client) || (! client->fh))
272                 return -1;
274         if (client->shutdown & SDB_SHUT_WR) /* reconnect */
275                 sdb_unixsock_client_connect(client);
277         status = fprintf(client->fh, "%s\r\n", msg);
278         if (status < 0) {
279                 char errbuf[1024];
280                 sdb_log(SDB_LOG_ERR, "unixsock: Failed to write to "
281                                 "socket (%s): %s", client->path,
282                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
283                 return status;
284         }
285         return status;
286 } /* sdb_unixsock_client_send */
288 char *
289 sdb_unixsock_client_recv(sdb_unixsock_client_t *client,
290                 char *buffer, size_t buflen)
292         if ((! client) || (! client->fh) || (! buffer))
293                 return NULL;
295         if (client->shutdown & SDB_SHUT_RD) /* reconnect */
296                 sdb_unixsock_client_connect(client);
298         buffer = fgets(buffer, (int)buflen - 1, client->fh);
299         if (! buffer) {
300                 if (! feof(client->fh)) {
301                         char errbuf[1024];
302                         sdb_log(SDB_LOG_ERR, "unixsock: Failed to read "
303                                         "from socket (%s): %s", client->path,
304                                         sdb_strerror(errno, errbuf, sizeof(errbuf)));
305                 }
306                 return buffer;
307         }
308         buffer[buflen - 1] = '\0';
310         buflen = strlen(buffer);
311         while ((buffer[buflen - 1] == '\n') || (buffer[buflen - 1] == '\r')) {
312                 buffer[buflen - 1] = '\0';
313                 --buflen;
314         }
315         return buffer;
316 } /* sdb_unixsock_client_recv */
318 int
319 sdb_unixsock_client_process_lines(sdb_unixsock_client_t *client,
320                 sdb_unixsock_client_data_cb callback, sdb_object_t *user_data,
321                 long int max_lines, const char *delim, int n_cols, ...)
323         int *types = NULL;
324         int success = 0;
326         if ((! client) || (! client->fh) || (! callback))
327                 return -1;
329         if (n_cols > 0) {
330                 va_list ap;
331                 int i;
333                 types = calloc((size_t)n_cols, sizeof(*types));
334                 if (! types)
335                         return -1;
337                 va_start(ap, n_cols);
339                 for (i = 0; i < n_cols; ++i) {
340                         types[i] = va_arg(ap, int);
342                         if ((types[i] < 1) || (types[i] > SDB_TYPE_BINARY)) {
343                                 sdb_log(SDB_LOG_ERR, "unixsock: Unknown column "
344                                                 "type %i while processing response from the "
345                                                 "UNIX socket @ %s.", types[i], client->path);
346                                 va_end(ap);
347                                 free(types);
348                                 return -1;
349                         }
350                 }
352                 va_end(ap);
353         }
355         while (42) {
356                 char  buffer[1024];
357                 char *line;
359                 int column_count;
361                 if (! max_lines)
362                         break;
364                 if (max_lines > 0)
365                         --max_lines;
367                 sdb_unixsock_client_clearerr(client);
368                 line = sdb_unixsock_client_recv(client, buffer, sizeof(buffer));
370                 if (! line)
371                         break;
373                 column_count = sdb_unixsock_get_column_count(line, delim);
375                 if ((n_cols >= 0) && (n_cols != column_count)) {
376                         sdb_log(SDB_LOG_ERR, "unixsock: number of columns (%i) "
377                                         "does not match the number of requested columns (%i) "
378                                         "while processing response from the UNIX socket @ %s: %s",
379                                         column_count, n_cols, client->path, line);
380                         continue;
381                 }
383                 if (column_count <= 0) /* no data */
384                         continue;
386                 if (! sdb_unixsock_client_process_one_line(client, line, callback,
387                                         user_data, delim, column_count, types))
388                         ++success;
389         }
391         free(types);
393         if ((max_lines > 0)
394                         || ((max_lines < 0) && (! sdb_unixsock_client_eof(client)))
395                         || sdb_unixsock_client_error(client)) {
396                 char errbuf[1024];
397                 sdb_log(SDB_LOG_ERR, "unixsock: Unexpected end of data while "
398                                 "reading from socket (%s): %s", client->path,
399                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
400                 return -1;
401         }
402         if (! success)
403                 return -1;
404         return 0;
405 } /* sdb_unixsock_client_process_lines */
407 int
408 sdb_unixsock_client_shutdown(sdb_unixsock_client_t *client, int how)
410         int status;
412         if (! client) {
413                 errno = ENOTSOCK;
414                 return -1;
415         }
417         fflush(client->fh);
418         status = shutdown(fileno(client->fh), how);
420         if (! status) {
421                 if (how == SHUT_RDWR)
422                         client->shutdown |= SDB_SHUT_RDWR;
423                 else
424                         client->shutdown |= 1 << how;
425         }
426         return status;
427 } /* sdb_unixsock_client_shutdown */
429 void
430 sdb_unixsock_client_clearerr(sdb_unixsock_client_t *client)
432         if ((! client) || (! client->fh))
433                 return;
434         clearerr(client->fh);
435 } /* sdb_unixsock_client_clearerr */
437 int
438 sdb_unixsock_client_eof(sdb_unixsock_client_t *client)
440         if ((! client) || (! client->fh)) {
441                 errno = EBADF;
442                 return -1;
443         }
444         return feof(client->fh);
445 } /* sdb_unixsock_client_eof */
447 int
448 sdb_unixsock_client_error(sdb_unixsock_client_t *client)
450         if ((! client) || (! client->fh)) {
451                 errno = EBADF;
452                 return -1;
453         }
454         return ferror(client->fh);
455 } /* sdb_unixsock_client_error */
457 void
458 sdb_unixsock_client_destroy(sdb_unixsock_client_t *client)
460         if (! client)
461                 return;
463         if (client->path)
464                 free(client->path);
465         client->path = NULL;
467         if (client->fh)
468                 fclose(client->fh);
469         client->fh = NULL;
471         free(client);
472 } /* sdb_unixsock_client_destroy */
474 const char *
475 sdb_unixsock_client_path(sdb_unixsock_client_t *client)
477         if (! client)
478                 return NULL;
479         return client->path;
480 } /* sdb_unixsock_client_path */
482 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */