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 #if HAVE_CONFIG_H
29 # include "config.h"
30 #endif /* HAVE_CONFIG_H */
32 #include "utils/error.h"
33 #include "utils/unixsock.h"
35 #include <assert.h>
36 #include <errno.h>
38 #include <stdarg.h>
39 #include <stdio.h>
40 #include <stdlib.h>
42 #include <string.h>
43 #include <strings.h>
45 #include <unistd.h>
47 #include <sys/socket.h>
48 #include <sys/un.h>
50 /*
51 * private data types
52 */
54 struct sdb_unixsock_client {
55 char *path;
56 FILE *fh;
58 int shutdown;
59 };
61 #define SDB_SHUT_RD (1 << SHUT_RD)
62 #define SDB_SHUT_WR (1 << SHUT_WR)
63 #define SDB_SHUT_RDWR (SDB_SHUT_RD | SDB_SHUT_WR)
65 /*
66 * private helper functions
67 */
69 static int
70 sdb_unixsock_get_column_count(const char *string, const char *delim)
71 {
72 int count = 1;
74 assert(string);
76 if ((! delim) || (*string == '\0'))
77 return 1;
79 if ((delim[0] == '\0') || (delim[1] == '\0')) {
80 while ((string = strchr(string, (int)delim[0]))) {
81 ++string;
82 ++count;
83 }
84 }
85 else {
86 while ((string = strpbrk(string, delim))) {
87 ++string;
88 ++count;
89 }
90 }
91 return count;
92 } /* sdb_unixsock_get_column_count */
94 static int
95 sdb_unixsock_client_process_one_line(sdb_unixsock_client_t *client,
96 char *line, sdb_unixsock_client_data_cb callback,
97 sdb_object_t *user_data, const char *delim,
98 int column_count, int *types)
99 {
100 sdb_data_t data[column_count];
101 char *orig_line = line;
103 int i;
105 assert(column_count > 0);
107 for (i = 0; i < column_count; ++i) {
108 char *next;
110 if (! line) { /* this must no happen */
111 sdb_log(SDB_LOG_ERR, "unixsock: Unexpected EOL while "
112 "parsing line (expected %i columns delimited by '%s'; "
113 "got %i): %s", column_count, delim,
114 /* last line number */ i, orig_line);
115 return -1;
116 }
118 if ((delim[0] == '\0') || (delim[1] == '\0'))
119 next = strchr(line, (int)delim[0]);
120 else
121 next = strpbrk(line, delim);
123 if (next) {
124 *next = '\0';
125 ++next;
126 }
128 if (sdb_data_parse(line,
129 types ? types[i] : SDB_TYPE_STRING, &data[i]))
130 return -1;
132 line = next;
133 }
135 if (callback(client, (size_t)column_count, data, user_data))
136 return -1;
138 for (i = 0; i < column_count; ++i)
139 sdb_data_free_datum(&data[i]);
140 return 0;
141 } /* sdb_unixsock_client_process_one_line */
143 /*
144 * public API
145 */
147 sdb_unixsock_client_t *
148 sdb_unixsock_client_create(const char *path)
149 {
150 sdb_unixsock_client_t *client;
152 if (! path)
153 return NULL;
155 client = malloc(sizeof(*client));
156 if (! client)
157 return NULL;
158 memset(client, 0, sizeof(*client));
159 client->fh = NULL;
161 client->path = strdup(path);
162 if (! client->path) {
163 sdb_unixsock_client_destroy(client);
164 return NULL;
165 }
167 client->shutdown = 0;
168 return client;
169 } /* sdb_unixsock_client_create */
171 int
172 sdb_unixsock_client_connect(sdb_unixsock_client_t *client)
173 {
174 struct sockaddr_un sa;
175 int fd;
177 if ((! client) || (! client->path))
178 return -1;
180 memset(&sa, 0, sizeof(sa));
182 if (client->fh)
183 fclose(client->fh);
185 fd = socket(AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
186 if (fd < 0) {
187 char errbuf[1024];
188 sdb_log(SDB_LOG_ERR, "unixsock: Failed to open socket: %s",
189 sdb_strerror(errno, errbuf, sizeof(errbuf)));
190 return -1;
191 }
193 sa.sun_family = AF_UNIX;
194 strncpy(sa.sun_path, client->path, sizeof(sa.sun_path));
195 sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
197 if (connect(fd, (struct sockaddr *)&sa, sizeof(sa))) {
198 char errbuf[1024];
199 sdb_log(SDB_LOG_ERR, "unixsock: Failed to connect to %s: %s",
200 sa.sun_path, sdb_strerror(errno, errbuf, sizeof(errbuf)));
201 close(fd);
202 return -1;
203 }
205 client->fh = fdopen(fd, "r+");
206 if (! client->fh) {
207 char errbuf[1024];
208 sdb_log(SDB_LOG_ERR, "unixsock: Failed to open I/O "
209 "stream for %s: %s", sa.sun_path,
210 sdb_strerror(errno, errbuf, sizeof(errbuf)));
211 close(fd);
212 return -1;
213 }
215 /* enable line-buffering */
216 setvbuf(client->fh, NULL, _IOLBF, 0);
218 client->shutdown = 0;
219 return 0;
220 } /* sdb_unixsock_client_connect */
222 int
223 sdb_unixsock_client_send(sdb_unixsock_client_t *client,
224 const char *msg)
225 {
226 int status;
228 if ((! client) || (! client->fh))
229 return -1;
231 if (client->shutdown & SDB_SHUT_WR) /* reconnect */
232 sdb_unixsock_client_connect(client);
234 status = fprintf(client->fh, "%s\r\n", msg);
235 if (status < 0) {
236 char errbuf[1024];
237 sdb_log(SDB_LOG_ERR, "unixsock: Failed to write to "
238 "socket (%s): %s", client->path,
239 sdb_strerror(errno, errbuf, sizeof(errbuf)));
240 return status;
241 }
242 return status;
243 } /* sdb_unixsock_client_send */
245 char *
246 sdb_unixsock_client_recv(sdb_unixsock_client_t *client,
247 char *buffer, size_t buflen)
248 {
249 char *tmp;
251 if ((! client) || (! client->fh) || (! buffer))
252 return NULL;
254 if (client->shutdown & SDB_SHUT_RD) /* reconnect */
255 sdb_unixsock_client_connect(client);
257 tmp = NULL;
258 while (tmp == NULL) {
259 errno = 0;
260 tmp = fgets(buffer, (int)buflen - 1, client->fh);
261 if (! tmp) {
262 if ((errno == EAGAIN) || (errno == EINTR))
263 continue;
265 if (! feof(client->fh)) {
266 char errbuf[1024];
267 sdb_log(SDB_LOG_ERR, "unixsock: Failed to read "
268 "from socket (%s): %s", client->path,
269 sdb_strerror(errno, errbuf, sizeof(errbuf)));
270 }
271 return NULL;
272 }
273 }
274 buffer[buflen - 1] = '\0';
276 buflen = strlen(buffer);
277 while (buflen && ((buffer[buflen - 1] == '\n') || (buffer[buflen - 1] == '\r'))) {
278 buffer[buflen - 1] = '\0';
279 --buflen;
280 }
281 return buffer;
282 } /* sdb_unixsock_client_recv */
284 int
285 sdb_unixsock_client_process_lines(sdb_unixsock_client_t *client,
286 sdb_unixsock_client_data_cb callback, sdb_object_t *user_data,
287 long int max_lines, const char *delim, int n_cols, ...)
288 {
289 int *types = NULL;
290 int success = 0;
292 if ((! client) || (! client->fh) || (! callback))
293 return -1;
295 if (n_cols > 0) {
296 va_list ap;
297 int i;
299 types = calloc((size_t)n_cols, sizeof(*types));
300 if (! types)
301 return -1;
303 va_start(ap, n_cols);
305 for (i = 0; i < n_cols; ++i) {
306 types[i] = va_arg(ap, int);
308 if ((types[i] < 1) || (types[i] > SDB_TYPE_BINARY)) {
309 sdb_log(SDB_LOG_ERR, "unixsock: Unknown column "
310 "type %i while processing response from the "
311 "UNIX socket @ %s.", types[i], client->path);
312 va_end(ap);
313 free(types);
314 return -1;
315 }
316 }
318 va_end(ap);
319 }
321 while (42) {
322 char buffer[1024];
323 char *line;
325 int column_count;
327 if (! max_lines)
328 break;
330 if (max_lines > 0)
331 --max_lines;
333 sdb_unixsock_client_clearerr(client);
334 line = sdb_unixsock_client_recv(client, buffer, sizeof(buffer));
336 if (! line)
337 break;
339 column_count = sdb_unixsock_get_column_count(line, delim);
341 if ((n_cols >= 0) && (n_cols != column_count)) {
342 sdb_log(SDB_LOG_ERR, "unixsock: number of columns (%i) "
343 "does not match the number of requested columns (%i) "
344 "while processing response from the UNIX socket @ %s: %s",
345 column_count, n_cols, client->path, line);
346 continue;
347 }
349 if (column_count <= 0) /* no data */
350 continue;
352 if (! sdb_unixsock_client_process_one_line(client, line, callback,
353 user_data, delim, column_count, types))
354 ++success;
355 }
357 free(types);
359 if ((max_lines > 0)
360 || ((max_lines < 0) && (! sdb_unixsock_client_eof(client)))
361 || sdb_unixsock_client_error(client)) {
362 char errbuf[1024];
363 sdb_log(SDB_LOG_ERR, "unixsock: Unexpected end of data while "
364 "reading from socket (%s): %s", client->path,
365 sdb_strerror(errno, errbuf, sizeof(errbuf)));
366 return -1;
367 }
368 if (! success)
369 return -1;
370 return 0;
371 } /* sdb_unixsock_client_process_lines */
373 int
374 sdb_unixsock_client_shutdown(sdb_unixsock_client_t *client, int how)
375 {
376 int status;
378 if (! client) {
379 errno = ENOTSOCK;
380 return -1;
381 }
383 fflush(client->fh);
384 status = shutdown(fileno(client->fh), how);
386 if (! status) {
387 if (how == SHUT_RDWR)
388 client->shutdown |= SDB_SHUT_RDWR;
389 else
390 client->shutdown |= 1 << how;
391 }
392 return status;
393 } /* sdb_unixsock_client_shutdown */
395 void
396 sdb_unixsock_client_clearerr(sdb_unixsock_client_t *client)
397 {
398 if ((! client) || (! client->fh))
399 return;
400 clearerr(client->fh);
401 } /* sdb_unixsock_client_clearerr */
403 int
404 sdb_unixsock_client_eof(sdb_unixsock_client_t *client)
405 {
406 if ((! client) || (! client->fh)) {
407 errno = EBADF;
408 return -1;
409 }
410 return feof(client->fh);
411 } /* sdb_unixsock_client_eof */
413 int
414 sdb_unixsock_client_error(sdb_unixsock_client_t *client)
415 {
416 if ((! client) || (! client->fh)) {
417 errno = EBADF;
418 return -1;
419 }
420 return ferror(client->fh);
421 } /* sdb_unixsock_client_error */
423 void
424 sdb_unixsock_client_destroy(sdb_unixsock_client_t *client)
425 {
426 if (! client)
427 return;
429 if (client->path)
430 free(client->path);
431 client->path = NULL;
433 if (client->fh)
434 fclose(client->fh);
435 client->fh = NULL;
437 free(client);
438 } /* sdb_unixsock_client_destroy */
440 const char *
441 sdb_unixsock_client_path(sdb_unixsock_client_t *client)
442 {
443 if (! client)
444 return NULL;
445 return client->path;
446 } /* sdb_unixsock_client_path */
448 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */