Code

Use the sdb_error* function throughout the project.
[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 "utils/error.h"
30 #include "utils/string.h"
32 #include <assert.h>
33 #include <errno.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 #include <stdlib.h>
39 #include <string.h>
40 #include <strings.h>
42 #include <unistd.h>
44 #include <sys/socket.h>
45 #include <sys/un.h>
47 /*
48  * private data types
49  */
51 struct sdb_unixsock_client {
52         char *path;
53         FILE *fh;
55         int shutdown;
56 };
58 #define SDB_SHUT_RD   (1 << SHUT_RD)
59 #define SDB_SHUT_WR   (1 << SHUT_WR)
60 #define SDB_SHUT_RDWR (SDB_SHUT_RD | SDB_SHUT_WR)
62 /*
63  * private helper functions
64  */
66 static int
67 sdb_unixsock_get_column_count(const char *string, const char *delim)
68 {
69         int count = 1;
71         assert(string);
73         if ((! delim) || (*string == '\0'))
74                 return 1;
76         if ((delim[0] == '\0') || (delim[1] == '\0')) {
77                 while ((string = strchr(string, (int)delim[0]))) {
78                         ++string;
79                         ++count;
80                 }
81         }
82         else {
83                 while ((string = strpbrk(string, delim))) {
84                         ++string;
85                         ++count;
86                 }
87         }
88         return count;
89 } /* sdb_unixsock_get_column_count */
91 static int
92 sdb_unixsock_parse_cell(char *string, int type, sdb_data_t *data)
93 {
94         char *endptr = NULL;
96         switch (type) {
97                 case SDB_TYPE_INTEGER:
98                         errno = 0;
99                         data->data.integer = strtoll(string, &endptr, 0);
100                         break;
101                 case SDB_TYPE_DECIMAL:
102                         errno = 0;
103                         data->data.decimal = strtod(string, &endptr);
104                         break;
105                 case SDB_TYPE_STRING:
106                         data->data.string = string;
107                         break;
108                 case SDB_TYPE_DATETIME:
109                         {
110                                 double datetime = strtod(string, &endptr);
111                                 data->data.datetime = DOUBLE_TO_SDB_TIME(datetime);
112                         }
113                         break;
114                 case SDB_TYPE_BINARY:
115                         /* we don't support any binary information containing 0-bytes */
116                         data->data.binary.length = strlen(string);
117                         data->data.binary.datum = (const unsigned char *)string;
118                         break;
119                 default:
120                         sdb_error_set(SDB_LOG_ERR, "unixsock: Unexpected type %i while "
121                                         "parsing query result.\n", type);
122                         return -1;
123         }
125         if ((type == SDB_TYPE_INTEGER) || (type == SDB_TYPE_DECIMAL)
126                         || (type == SDB_TYPE_DATETIME)) {
127                 if (errno || (string == endptr)) {
128                         char errbuf[1024];
129                         sdb_error_set(SDB_LOG_ERR, "unixsock: Failed to parse string "
130                                         "'%s' as numeric value (type %i): %s\n", string, type,
131                                         sdb_strerror(errno, errbuf, sizeof(errbuf)));
132                         return -1;
133                 }
134                 else if (endptr && (*endptr != '\0'))
135                         sdb_error_set(SDB_LOG_WARNING, "unixsock: Ignoring garbage after "
136                                         "number while parsing numeric value (type %i): %s.\n",
137                                         type, endptr);
138         }
140         data->type = type;
141         return 0;
142 } /* sdb_unixsock_parse_cell */
144 static int
145 sdb_unixsock_client_process_one_line(sdb_unixsock_client_t *client,
146                 char *line, sdb_unixsock_client_data_cb callback,
147                 sdb_object_t *user_data, const char *delim,
148                 int column_count, int *types)
150         sdb_data_t data[column_count];
151         char *orig_line = line;
153         int i;
155         assert(column_count > 0);
157         for (i = 0; i < column_count; ++i) {
158                 char *next;
160                 if (! line) { /* this must no happen */
161                         sdb_error_set(SDB_LOG_ERR, "unixsock: Unexpected EOL while "
162                                         "parsing line (expected %i columns delimited by '%s'; "
163                                         "got %i): %s\n", column_count, delim,
164                                         /* last line number */ i, orig_line);
165                         return -1;
166                 }
168                 if ((delim[0] == '\0') || (delim[1] == '\0'))
169                         next = strchr(line, (int)delim[0]);
170                 else
171                         next = strpbrk(line, delim);
173                 if (next) {
174                         *next = '\0';
175                         ++next;
176                 }
178                 if (sdb_unixsock_parse_cell(line,
179                                         types ? types[i] : SDB_TYPE_STRING, &data[i]))
180                         return -1;
182                 line = next;
183         }
185         if (callback(client, (size_t)column_count, data, user_data))
186                 return -1;
187         return 0;
188 } /* sdb_unixsock_client_process_one_line */
190 /*
191  * public API
192  */
194 sdb_unixsock_client_t *
195 sdb_unixsock_client_create(const char *path)
197         sdb_unixsock_client_t *client;
199         if (! path)
200                 return NULL;
202         client = malloc(sizeof(*client));
203         if (! client)
204                 return NULL;
205         memset(client, 0, sizeof(*client));
206         client->fh = NULL;
208         client->path = strdup(path);
209         if (! client->path) {
210                 sdb_unixsock_client_destroy(client);
211                 return NULL;
212         }
214         client->shutdown = 0;
215         return client;
216 } /* sdb_unixsock_client_create */
218 int
219 sdb_unixsock_client_connect(sdb_unixsock_client_t *client)
221         struct sockaddr_un sa;
222         int fd;
224         if ((! client) || (! client->path))
225                 return -1;
227         memset(&sa, 0, sizeof(sa));
229         if (client->fh)
230                 fclose(client->fh);
232         fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
233         if (fd < 0) {
234                 char errbuf[1024];
235                 sdb_error_set(SDB_LOG_ERR, "unixsock: Failed to open socket: %s\n",
236                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
237                 return -1;
238         }
240         sa.sun_family = AF_UNIX;
241         strncpy(sa.sun_path, client->path, sizeof(sa.sun_path));
242         sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
244         if (connect(fd, (struct sockaddr *)&sa, sizeof(sa))) {
245                 char errbuf[1024];
246                 sdb_error_set(SDB_LOG_ERR, "unixsock: Failed to connect to %s: %s\n",
247                                 sa.sun_path, sdb_strerror(errno, errbuf, sizeof(errbuf)));
248                 close(fd);
249                 return -1;
250         }
252         client->fh = fdopen(fd, "r+");
253         if (! client->fh) {
254                 char errbuf[1024];
255                 sdb_error_set(SDB_LOG_ERR, "unixsock: Failed to open I/O "
256                                 "stream for %s: %s\n", sa.sun_path,
257                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
258                 close(fd);
259                 return -1;
260         }
262         client->shutdown = 0;
263         return 0;
264 } /* sdb_unixsock_client_connect */
266 int
267 sdb_unixsock_client_send(sdb_unixsock_client_t *client,
268                 const char *msg)
270         int status;
272         if ((! client) || (! client->fh))
273                 return -1;
275         if (client->shutdown & SDB_SHUT_WR) /* reconnect */
276                 sdb_unixsock_client_connect(client);
278         status = fprintf(client->fh, "%s\r\n", msg);
279         if (status < 0) {
280                 char errbuf[1024];
281                 sdb_error_set(SDB_LOG_ERR, "unixsock: Failed to write to "
282                                 "socket (%s): %s\n", client->path,
283                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
284                 return status;
285         }
286         return status;
287 } /* sdb_unixsock_client_send */
289 char *
290 sdb_unixsock_client_recv(sdb_unixsock_client_t *client,
291                 char *buffer, size_t buflen)
293         if ((! client) || (! client->fh) || (! buffer))
294                 return NULL;
296         if (client->shutdown & SDB_SHUT_RD) /* reconnect */
297                 sdb_unixsock_client_connect(client);
299         buffer = fgets(buffer, (int)buflen - 1, client->fh);
300         if (! buffer) {
301                 if (! feof(client->fh)) {
302                         char errbuf[1024];
303                         sdb_error_set(SDB_LOG_ERR, "unixsock: Failed to read "
304                                         "from socket (%s): %s\n", client->path,
305                                         sdb_strerror(errno, errbuf, sizeof(errbuf)));
306                 }
307                 return buffer;
308         }
309         buffer[buflen - 1] = '\0';
311         buflen = strlen(buffer);
312         while ((buffer[buflen - 1] == '\n') || (buffer[buflen - 1] == '\r')) {
313                 buffer[buflen - 1] = '\0';
314                 --buflen;
315         }
316         return buffer;
317 } /* sdb_unixsock_client_recv */
319 int
320 sdb_unixsock_client_process_lines(sdb_unixsock_client_t *client,
321                 sdb_unixsock_client_data_cb callback, sdb_object_t *user_data,
322                 long int max_lines, const char *delim, int n_cols, ...)
324         int *types = NULL;
325         int success = 0;
327         if ((! client) || (! client->fh) || (! callback))
328                 return -1;
330         if (n_cols > 0) {
331                 va_list ap;
332                 int i;
334                 types = calloc((size_t)n_cols, sizeof(*types));
335                 if (! types)
336                         return -1;
338                 va_start(ap, n_cols);
340                 for (i = 0; i < n_cols; ++i) {
341                         types[i] = va_arg(ap, int);
343                         if ((types[i] < 1) || (types[i] > SDB_TYPE_BINARY)) {
344                                 sdb_error_set(SDB_LOG_ERR, "unixsock: Unknown column "
345                                                 "type %i while processing response from the "
346                                                 "UNIX socket @ %s.\n", types[i], client->path);
347                                 va_end(ap);
348                                 free(types);
349                                 return -1;
350                         }
351                 }
353                 va_end(ap);
354         }
356         while (42) {
357                 char  buffer[1024];
358                 char *line;
360                 int column_count;
362                 if (! max_lines)
363                         break;
365                 if (max_lines > 0)
366                         --max_lines;
368                 sdb_unixsock_client_clearerr(client);
369                 line = sdb_unixsock_client_recv(client, buffer, sizeof(buffer));
371                 if (! line)
372                         break;
374                 column_count = sdb_unixsock_get_column_count(line, delim);
376                 if ((n_cols >= 0) && (n_cols != column_count)) {
377                         sdb_error_set(SDB_LOG_ERR, "unixsock: number of columns (%i) "
378                                         "does not match the number of requested columns (%i) "
379                                         "while processing response from the UNIX socket @ %s: "
380                                         "%s\n", column_count, n_cols, client->path, line);
381                         continue;
382                 }
384                 if (column_count <= 0) /* no data */
385                         continue;
387                 if (! sdb_unixsock_client_process_one_line(client, line, callback,
388                                         user_data, delim, column_count, types))
389                         ++success;
390         }
392         free(types);
394         if ((max_lines > 0)
395                         || ((max_lines < 0) && (! sdb_unixsock_client_eof(client)))
396                         || sdb_unixsock_client_error(client)) {
397                 char errbuf[1024];
398                 sdb_error_set(SDB_LOG_ERR, "unixsock: Unexpected end of data while "
399                                 "reading from socket (%s): %s\n", client->path,
400                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
401                 return -1;
402         }
403         if (! success)
404                 return -1;
405         return 0;
406 } /* sdb_unixsock_client_process_lines */
408 int
409 sdb_unixsock_client_shutdown(sdb_unixsock_client_t *client, int how)
411         int status;
413         if (! client) {
414                 errno = ENOTSOCK;
415                 return -1;
416         }
418         fflush(client->fh);
419         status = shutdown(fileno(client->fh), how);
421         if (! status) {
422                 if (how == SHUT_RDWR)
423                         client->shutdown |= SDB_SHUT_RDWR;
424                 else
425                         client->shutdown |= 1 << how;
426         }
427         return status;
428 } /* sdb_unixsock_client_shutdown */
430 void
431 sdb_unixsock_client_clearerr(sdb_unixsock_client_t *client)
433         if ((! client) || (! client->fh))
434                 return;
435         clearerr(client->fh);
436 } /* sdb_unixsock_client_clearerr */
438 int
439 sdb_unixsock_client_eof(sdb_unixsock_client_t *client)
441         if ((! client) || (! client->fh)) {
442                 errno = EBADF;
443                 return -1;
444         }
445         return feof(client->fh);
446 } /* sdb_unixsock_client_eof */
448 int
449 sdb_unixsock_client_error(sdb_unixsock_client_t *client)
451         if ((! client) || (! client->fh)) {
452                 errno = EBADF;
453                 return -1;
454         }
455         return ferror(client->fh);
456 } /* sdb_unixsock_client_error */
458 void
459 sdb_unixsock_client_destroy(sdb_unixsock_client_t *client)
461         if (! client)
462                 return;
464         if (client->path)
465                 free(client->path);
466         client->path = NULL;
468         if (client->fh)
469                 fclose(client->fh);
470         client->fh = NULL;
472         free(client);
473 } /* sdb_unixsock_client_destroy */
475 const char *
476 sdb_unixsock_client_path(sdb_unixsock_client_t *client)
478         if (! client)
479                 return NULL;
480         return client->path;
481 } /* sdb_unixsock_client_path */
483 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */