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/error.h"
29 #include "utils/unixsock.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 = (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)
148 {
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)
195 {
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)
219 {
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 /* enable line-buffering */
262 setvbuf(client->fh, NULL, _IOLBF, 0);
264 client->shutdown = 0;
265 return 0;
266 } /* sdb_unixsock_client_connect */
268 int
269 sdb_unixsock_client_send(sdb_unixsock_client_t *client,
270 const char *msg)
271 {
272 int status;
274 if ((! client) || (! client->fh))
275 return -1;
277 if (client->shutdown & SDB_SHUT_WR) /* reconnect */
278 sdb_unixsock_client_connect(client);
280 status = fprintf(client->fh, "%s\r\n", msg);
281 if (status < 0) {
282 char errbuf[1024];
283 sdb_log(SDB_LOG_ERR, "unixsock: Failed to write to "
284 "socket (%s): %s", client->path,
285 sdb_strerror(errno, errbuf, sizeof(errbuf)));
286 return status;
287 }
288 return status;
289 } /* sdb_unixsock_client_send */
291 char *
292 sdb_unixsock_client_recv(sdb_unixsock_client_t *client,
293 char *buffer, size_t buflen)
294 {
295 char *tmp;
297 if ((! client) || (! client->fh) || (! buffer))
298 return NULL;
300 if (client->shutdown & SDB_SHUT_RD) /* reconnect */
301 sdb_unixsock_client_connect(client);
303 tmp = NULL;
304 while (tmp == NULL) {
305 errno = 0;
306 tmp = fgets(buffer, (int)buflen - 1, client->fh);
307 if (! tmp) {
308 if ((errno == EAGAIN) || (errno == EINTR))
309 continue;
311 if (! feof(client->fh)) {
312 char errbuf[1024];
313 sdb_log(SDB_LOG_ERR, "unixsock: Failed to read "
314 "from socket (%s): %s", client->path,
315 sdb_strerror(errno, errbuf, sizeof(errbuf)));
316 }
317 return NULL;
318 }
319 }
320 buffer[buflen - 1] = '\0';
322 buflen = strlen(buffer);
323 while (buflen && ((buffer[buflen - 1] == '\n') || (buffer[buflen - 1] == '\r'))) {
324 buffer[buflen - 1] = '\0';
325 --buflen;
326 }
327 return buffer;
328 } /* sdb_unixsock_client_recv */
330 int
331 sdb_unixsock_client_process_lines(sdb_unixsock_client_t *client,
332 sdb_unixsock_client_data_cb callback, sdb_object_t *user_data,
333 long int max_lines, const char *delim, int n_cols, ...)
334 {
335 int *types = NULL;
336 int success = 0;
338 if ((! client) || (! client->fh) || (! callback))
339 return -1;
341 if (n_cols > 0) {
342 va_list ap;
343 int i;
345 types = calloc((size_t)n_cols, sizeof(*types));
346 if (! types)
347 return -1;
349 va_start(ap, n_cols);
351 for (i = 0; i < n_cols; ++i) {
352 types[i] = va_arg(ap, int);
354 if ((types[i] < 1) || (types[i] > SDB_TYPE_BINARY)) {
355 sdb_log(SDB_LOG_ERR, "unixsock: Unknown column "
356 "type %i while processing response from the "
357 "UNIX socket @ %s.", types[i], client->path);
358 va_end(ap);
359 free(types);
360 return -1;
361 }
362 }
364 va_end(ap);
365 }
367 while (42) {
368 char buffer[1024];
369 char *line;
371 int column_count;
373 if (! max_lines)
374 break;
376 if (max_lines > 0)
377 --max_lines;
379 sdb_unixsock_client_clearerr(client);
380 line = sdb_unixsock_client_recv(client, buffer, sizeof(buffer));
382 if (! line)
383 break;
385 column_count = sdb_unixsock_get_column_count(line, delim);
387 if ((n_cols >= 0) && (n_cols != column_count)) {
388 sdb_log(SDB_LOG_ERR, "unixsock: number of columns (%i) "
389 "does not match the number of requested columns (%i) "
390 "while processing response from the UNIX socket @ %s: %s",
391 column_count, n_cols, client->path, line);
392 continue;
393 }
395 if (column_count <= 0) /* no data */
396 continue;
398 if (! sdb_unixsock_client_process_one_line(client, line, callback,
399 user_data, delim, column_count, types))
400 ++success;
401 }
403 free(types);
405 if ((max_lines > 0)
406 || ((max_lines < 0) && (! sdb_unixsock_client_eof(client)))
407 || sdb_unixsock_client_error(client)) {
408 char errbuf[1024];
409 sdb_log(SDB_LOG_ERR, "unixsock: Unexpected end of data while "
410 "reading from socket (%s): %s", client->path,
411 sdb_strerror(errno, errbuf, sizeof(errbuf)));
412 return -1;
413 }
414 if (! success)
415 return -1;
416 return 0;
417 } /* sdb_unixsock_client_process_lines */
419 int
420 sdb_unixsock_client_shutdown(sdb_unixsock_client_t *client, int how)
421 {
422 int status;
424 if (! client) {
425 errno = ENOTSOCK;
426 return -1;
427 }
429 fflush(client->fh);
430 status = shutdown(fileno(client->fh), how);
432 if (! status) {
433 if (how == SHUT_RDWR)
434 client->shutdown |= SDB_SHUT_RDWR;
435 else
436 client->shutdown |= 1 << how;
437 }
438 return status;
439 } /* sdb_unixsock_client_shutdown */
441 void
442 sdb_unixsock_client_clearerr(sdb_unixsock_client_t *client)
443 {
444 if ((! client) || (! client->fh))
445 return;
446 clearerr(client->fh);
447 } /* sdb_unixsock_client_clearerr */
449 int
450 sdb_unixsock_client_eof(sdb_unixsock_client_t *client)
451 {
452 if ((! client) || (! client->fh)) {
453 errno = EBADF;
454 return -1;
455 }
456 return feof(client->fh);
457 } /* sdb_unixsock_client_eof */
459 int
460 sdb_unixsock_client_error(sdb_unixsock_client_t *client)
461 {
462 if ((! client) || (! client->fh)) {
463 errno = EBADF;
464 return -1;
465 }
466 return ferror(client->fh);
467 } /* sdb_unixsock_client_error */
469 void
470 sdb_unixsock_client_destroy(sdb_unixsock_client_t *client)
471 {
472 if (! client)
473 return;
475 if (client->path)
476 free(client->path);
477 client->path = NULL;
479 if (client->fh)
480 fclose(client->fh);
481 client->fh = NULL;
483 free(client);
484 } /* sdb_unixsock_client_destroy */
486 const char *
487 sdb_unixsock_client_path(sdb_unixsock_client_t *client)
488 {
489 if (! client)
490 return NULL;
491 return client->path;
492 } /* sdb_unixsock_client_path */
494 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */