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_log(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_log(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_log(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)
149 {
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_log(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)
196 {
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)
220 {
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_log(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_log(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_log(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)
269 {
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_log(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)
292 {
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_log(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, ...)
323 {
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_log(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_log(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_log(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)
410 {
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)
432 {
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)
440 {
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)
450 {
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)
460 {
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)
477 {
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 : */