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