1 /*
2 * SysDB - t/utils/unixsock_test.c
3 * Copyright (C) 2013 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 /* required for fopencookie support */
33 #define _GNU_SOURCE
35 #include "utils/unixsock.h"
36 #include "libsysdb_test.h"
38 #include <check.h>
40 #include <dlfcn.h>
42 #include <errno.h>
44 #include <sys/types.h>
45 #include <sys/socket.h>
46 #include <sys/un.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
52 #include <unistd.h>
54 /*
55 * I/O "hook" functions
56 */
58 typedef struct {
59 int fd;
60 size_t pos;
61 } io_cookie_t;
63 static struct {
64 const char *data;
65 size_t len;
66 } golden_data[] = {
67 { "a", 1 },
68 { "abc", 3 },
69 { "12345", 5 },
70 { "", 0 },
71 };
73 static char *last_write = NULL;
75 static unsigned long long mock_read_called = 0;
76 static ssize_t
77 mock_read(void *cookie, char *buf, size_t size)
78 {
79 io_cookie_t *c = cookie;
80 ssize_t ret;
82 ++mock_read_called;
84 if (c->pos >= SDB_STATIC_ARRAY_LEN(golden_data))
85 return 0;
87 ret = snprintf(buf, size, "%s\n", golden_data[c->pos].data);
88 ++c->pos;
89 return ret;
90 } /* mock_read */
92 static unsigned long long mock_write_called = 0;
93 static ssize_t
94 mock_write(void *cookie, const char *buf, size_t size)
95 {
96 io_cookie_t *c = cookie;
98 ++mock_write_called;
100 if (c->pos >= SDB_STATIC_ARRAY_LEN(golden_data))
101 return 0;
103 if (last_write)
104 free(last_write);
105 last_write = strdup(buf);
106 ++c->pos;
107 return (ssize_t)size;
108 } /* mock_write */
110 /* unsupported: int seek(void *cookie, off64_t *offset, int whence) */
112 static int
113 mock_close(void *cookie)
114 {
115 io_cookie_t *c = cookie;
117 if (! c)
118 return EBADF;
120 close(c->fd);
121 free(c);
122 return 0;
123 } /* mock_close */
125 static cookie_io_functions_t mock_io = {
126 /* read = */ mock_read,
127 /* write = */ mock_write,
128 /* seek = */ NULL,
129 /* close = */ mock_close,
130 };
132 /*
133 * mocked functions
134 */
136 static int myfd = -1;
138 static void *
139 dlopen_libc(void)
140 {
141 static void *libc = NULL;
143 if (libc)
144 return libc;
146 libc = dlopen("libc.so.6", RTLD_LAZY);
147 if (! libc)
148 fail("INTERNAL ERROR: failed to load libc");
149 return libc;
150 } /* dlopen_libc */
152 int
153 socket(int domain, int __attribute__((unused)) type,
154 int __attribute__((unused)) protocol)
155 {
156 char tmp_file[] = "unixsock_test_socket.XXXXXX";
157 int tmp_fd;
159 /* we only want to mock UNIX sockets; return an error else */
160 if (domain != AF_UNIX) {
161 errno = EAFNOSUPPORT;
162 return -1;
163 }
165 /* create an 'anonymous' file to have a valid file-descriptor
166 * which can be close()ed by the caller */
167 tmp_fd = mkstemp(tmp_file);
168 if (tmp_fd < 0)
169 return -1;
171 unlink(tmp_file);
172 myfd = tmp_fd;
173 return tmp_fd;
174 } /* socket */
176 int
177 connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
178 {
179 if (sockfd < 0) {
180 errno = EBADF;
181 return -1;
182 }
184 /* we only want to mock UNIX sockets; return an error else */
185 if ((addrlen != sizeof(struct sockaddr_un)) || (! addr)
186 || (! ((const struct sockaddr_un *)addr)->sun_path)) {
187 errno = EAFNOSUPPORT;
188 return -1;
189 }
190 return 0;
191 } /* connect */
193 FILE *
194 fdopen(int fd, const char *mode)
195 {
196 io_cookie_t *cookie;
198 if (fd < 0) {
199 errno = EBADF;
200 return NULL;
201 }
203 /* check also uses fdopen; in that case we need
204 * to use the original implementation */
205 if (fd != myfd) {
206 void *libc = dlopen_libc();
207 FILE *(*orig_fdopen)(int, const char *);
209 orig_fdopen = (FILE *(*)(int, const char *))dlsym(libc, "fdopen");
211 if (! orig_fdopen)
212 fail("INTERNAL ERROR: failed to load fdopen() from libc");
214 return orig_fdopen(fd, mode);
215 }
217 cookie = calloc(sizeof(*cookie), 1);
218 if (! cookie)
219 return NULL;
221 cookie->fd = fd;
222 cookie->pos = 0;
223 return fopencookie(cookie, mode, mock_io);
224 } /* fdopen */
226 /*
227 * private variables
228 */
230 static sdb_unixsock_client_t *client;
232 static void
233 setup(void)
234 {
235 client = sdb_unixsock_client_create("unixsock_test_path");
236 fail_unless(client != NULL,
237 "sdb_unixsock_client_create() = NULL; "
238 "expected unixsock client object");
239 } /* setup */
241 static void
242 teardown(void)
243 {
244 sdb_unixsock_client_destroy(client);
245 client = NULL;
246 } /* teardown */
248 static void
249 conn(void)
250 {
251 int check;
253 check = sdb_unixsock_client_connect(client);
254 fail_unless(check == 0,
255 "sdb_unixsock_client_connect() = %i; expected: 0", check);
256 } /* conn */
258 /*
259 * tests
260 */
262 START_TEST(test_sdb_unixsock_client_create)
263 {
264 sdb_unixsock_client_t *c;
265 const char *check;
267 c = sdb_unixsock_client_create(NULL);
268 fail_unless(c == NULL,
269 "sdb_unixsock_client_create() = %p; expected: NULL", c);
271 c = sdb_unixsock_client_create("unixsock_test_path");
272 fail_unless(c != NULL,
273 "sdb_unixsock_client_create() = NULL; "
274 "expected unixsock client object");
276 check = sdb_unixsock_client_path(c);
277 fail_unless(check != NULL,
278 "sdb_unixsock_client_create() did not store path name");
279 fail_unless(!strcmp(check, "unixsock_test_path"),
280 "sdb_unixsock_client_create() did not store correct path name; "
281 "got: '%s'; expected: 'unixsock_test_path'", check);
282 sdb_unixsock_client_destroy(c);
283 }
284 END_TEST
286 START_TEST(test_sdb_unixsock_client_connect)
287 {
288 int check;
290 check = sdb_unixsock_client_connect(client);
291 fail_unless(check == 0,
292 "sdb_unixsock_client_connect() = %i; expected: 0", check);
293 }
294 END_TEST
296 START_TEST(test_sdb_unixsock_client_send)
297 {
298 size_t i;
300 conn();
302 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
303 int check;
305 mock_write_called = 0;
306 check = sdb_unixsock_client_send(client, golden_data[i].data);
307 /* client_send appends \r\n */
308 fail_unless((size_t)check == golden_data[i].len + 2,
309 "sdb_unixsock_client_send() = %i; expected: %zu",
310 check, golden_data[i].len + 2);
311 fail_unless(mock_write_called == 1,
312 "sdb_unixsock_client_send() called mock_write %llu times; "
313 "expected: 1", mock_write_called);
314 fail_unless(last_write != NULL,
315 "INTERNAL ERROR: mock_write did not record last write");
316 fail_unless((last_write[check - 1] == '\n')
317 && (last_write[check - 2] == '\r'),
318 "sdb_unixsock_client_send() did not append \\r\\n "
319 "before sending; got: '%s'", last_write);
320 fail_unless(!strncmp(last_write, golden_data[i].data,
321 (size_t)check - 2),
322 "sdb_unixsock_client_send() sent unexpected string '%s'; "
323 "expected: '%s'", last_write, golden_data[i].data);
324 free(last_write);
325 last_write = NULL;
326 }
327 }
328 END_TEST
330 START_TEST(test_sdb_unixsock_client_recv)
331 {
332 size_t i;
334 conn();
336 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
337 char *check;
338 char buf[64];
340 mock_read_called = 0;
341 check = sdb_unixsock_client_recv(client, buf, sizeof(buf));
342 fail_unless(check != NULL,
343 "sdb_unixsock_client_recv() = NULL; expected: a string");
344 fail_unless(check == buf,
345 "sdb_unixsock_client_recv() did not return a pointer "
346 "to the user-provided buffer");
347 fail_unless(mock_read_called == 1,
348 "sdb_unixsock_client_recv() called mock_read %llu times; "
349 "expected: 1", mock_read_called);
350 fail_unless(strlen(check) == golden_data[i].len,
351 "sdb_unixsock_client_recv() returned string of length "
352 "%zu ('%s'); expected: %zu",
353 strlen(check), check, golden_data[i].len);
354 fail_unless(check[golden_data[i].len] != '\n',
355 "sdb_unixsock_client_recv() did not strip newline");
356 fail_unless(!strcmp(check, golden_data[i].data),
357 "sdb_unixsock_client_recv() = '%s'; expected: '%s'",
358 check, golden_data[i].data);
359 }
360 }
361 END_TEST
363 Suite *
364 util_unixsock_suite(void)
365 {
366 Suite *s = suite_create("utils::unixsock");
367 TCase *tc;
369 tc = tcase_create("core");
370 tcase_add_checked_fixture(tc, setup, teardown);
371 tcase_add_test(tc, test_sdb_unixsock_client_create);
372 tcase_add_test(tc, test_sdb_unixsock_client_connect);
373 tcase_add_test(tc, test_sdb_unixsock_client_send);
374 tcase_add_test(tc, test_sdb_unixsock_client_recv);
375 suite_add_tcase(s, tc);
377 return s;
378 } /* util_unixsock_suite */
380 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */