1 /*
2 * SysDB - t/unit/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 #ifndef _GNU_SOURCE
34 # define _GNU_SOURCE
35 #endif
37 #include "utils/unixsock.h"
38 #include "testutils.h"
40 #include <check.h>
42 #include <dlfcn.h>
44 #include <errno.h>
46 #include <sys/types.h>
47 #include <sys/socket.h>
48 #include <sys/un.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
54 #include <unistd.h>
56 /*
57 * I/O "hook" functions
58 */
60 typedef struct {
61 int fd;
62 size_t pos;
63 } io_cookie_t;
65 static struct {
66 const char *data;
67 size_t len;
68 } golden_data[] = {
69 { "a", 1 },
70 { "abc", 3 },
71 { "12345", 5 },
72 { "", 0 },
73 };
75 static char *last_write = NULL;
77 static unsigned long long mock_read_called = 0;
78 static ssize_t
79 mock_read(void *cookie, char *buf, size_t size)
80 {
81 io_cookie_t *c = cookie;
82 ssize_t ret;
84 ++mock_read_called;
86 if (c->pos >= SDB_STATIC_ARRAY_LEN(golden_data))
87 return 0;
89 ret = snprintf(buf, size, "%s\n", golden_data[c->pos].data);
90 ++c->pos;
91 return ret;
92 } /* mock_read */
94 static unsigned long long mock_write_called = 0;
95 static ssize_t
96 mock_write(void *cookie, const char *buf, size_t size)
97 {
98 io_cookie_t *c = cookie;
100 ++mock_write_called;
102 if (c->pos >= SDB_STATIC_ARRAY_LEN(golden_data))
103 return 0;
105 if (last_write)
106 free(last_write);
107 last_write = strndup(buf, size);
108 ++c->pos;
109 return (ssize_t)size;
110 } /* mock_write */
112 /* unsupported: int seek(void *cookie, off64_t *offset, int whence) */
114 static int
115 mock_close(void *cookie)
116 {
117 io_cookie_t *c = cookie;
119 if (! c)
120 return EBADF;
122 close(c->fd);
123 free(c);
124 return 0;
125 } /* mock_close */
127 static cookie_io_functions_t mock_io = {
128 /* read = */ mock_read,
129 /* write = */ mock_write,
130 /* seek = */ NULL,
131 /* close = */ mock_close,
132 };
134 /*
135 * mocked functions
136 */
138 static int myfd = -1;
140 static void *
141 dlopen_libc(void)
142 {
143 static void *libc = NULL;
145 if (libc)
146 return libc;
148 libc = dlopen("libc.so.6", RTLD_LAZY);
149 if (! libc)
150 fail("INTERNAL ERROR: failed to load libc");
151 return libc;
152 } /* dlopen_libc */
154 int
155 socket(int domain, int __attribute__((unused)) type,
156 int __attribute__((unused)) protocol)
157 {
158 char tmp_file[] = "unixsock_test_socket.XXXXXX";
159 int tmp_fd;
161 /* we only want to mock UNIX sockets; return an error else */
162 if (domain != AF_UNIX) {
163 errno = EAFNOSUPPORT;
164 return -1;
165 }
167 /* create an 'anonymous' file to have a valid file-descriptor
168 * which can be close()ed by the caller */
169 tmp_fd = mkstemp(tmp_file);
170 if (tmp_fd < 0)
171 return -1;
173 unlink(tmp_file);
174 myfd = tmp_fd;
175 return tmp_fd;
176 } /* socket */
178 int
179 connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
180 {
181 if (sockfd < 0) {
182 errno = EBADF;
183 return -1;
184 }
186 /* we only want to mock UNIX sockets; return an error else */
187 if ((addrlen != sizeof(struct sockaddr_un)) || (! addr)
188 || (! ((const struct sockaddr_un *)addr)->sun_path)) {
189 errno = EAFNOSUPPORT;
190 return -1;
191 }
192 return 0;
193 } /* connect */
195 FILE *
196 fdopen(int fd, const char *mode)
197 {
198 io_cookie_t *cookie;
200 if (fd < 0) {
201 errno = EBADF;
202 return NULL;
203 }
205 /* check also uses fdopen; in that case we need
206 * to use the original implementation */
207 if (fd != myfd) {
208 void *libc = dlopen_libc();
209 FILE *(*orig_fdopen)(int, const char *);
211 orig_fdopen = (FILE *(*)(int, const char *))dlsym(libc, "fdopen");
213 if (! orig_fdopen)
214 fail("INTERNAL ERROR: failed to load fdopen() from libc");
216 return orig_fdopen(fd, mode);
217 }
219 cookie = calloc(sizeof(*cookie), 1);
220 if (! cookie)
221 return NULL;
223 cookie->fd = fd;
224 cookie->pos = 0;
225 return fopencookie(cookie, mode, mock_io);
226 } /* fdopen */
228 /*
229 * private variables
230 */
232 static sdb_unixsock_client_t *client;
234 static void
235 setup(void)
236 {
237 client = sdb_unixsock_client_create("unixsock_test_path");
238 fail_unless(client != NULL,
239 "sdb_unixsock_client_create() = NULL; "
240 "expected unixsock client object");
241 } /* setup */
243 static void
244 teardown(void)
245 {
246 sdb_unixsock_client_destroy(client);
247 client = NULL;
248 } /* teardown */
250 static void
251 conn(void)
252 {
253 int check;
255 check = sdb_unixsock_client_connect(client);
256 fail_unless(check == 0,
257 "sdb_unixsock_client_connect() = %i; expected: 0", check);
258 } /* conn */
260 /*
261 * tests
262 */
264 START_TEST(test_sdb_unixsock_client_create)
265 {
266 sdb_unixsock_client_t *c;
267 const char *check;
269 c = sdb_unixsock_client_create(NULL);
270 fail_unless(c == NULL,
271 "sdb_unixsock_client_create() = %p; expected: NULL", c);
273 c = sdb_unixsock_client_create("unixsock_test_path");
274 fail_unless(c != NULL,
275 "sdb_unixsock_client_create() = NULL; "
276 "expected unixsock client object");
278 check = sdb_unixsock_client_path(c);
279 fail_unless(check != NULL,
280 "sdb_unixsock_client_create() did not store path name");
281 fail_unless(!strcmp(check, "unixsock_test_path"),
282 "sdb_unixsock_client_create() did not store correct path name; "
283 "got: '%s'; expected: 'unixsock_test_path'", check);
284 sdb_unixsock_client_destroy(c);
285 }
286 END_TEST
288 START_TEST(test_sdb_unixsock_client_connect)
289 {
290 int check;
292 check = sdb_unixsock_client_connect(client);
293 fail_unless(check == 0,
294 "sdb_unixsock_client_connect() = %i; expected: 0", check);
295 }
296 END_TEST
298 START_TEST(test_sdb_unixsock_client_send)
299 {
300 size_t i;
302 conn();
304 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
305 int check;
307 mock_write_called = 0;
308 check = sdb_unixsock_client_send(client, golden_data[i].data);
309 /* client_send appends \r\n */
310 fail_unless((size_t)check == golden_data[i].len + 2,
311 "sdb_unixsock_client_send() = %i; expected: %zu",
312 check, golden_data[i].len + 2);
313 fail_unless(mock_write_called == 1,
314 "sdb_unixsock_client_send() called mock_write %llu times; "
315 "expected: 1", mock_write_called);
316 fail_unless(last_write != NULL,
317 "INTERNAL ERROR: mock_write did not record last write");
318 fail_unless((last_write[check - 1] == '\n')
319 && (last_write[check - 2] == '\r'),
320 "sdb_unixsock_client_send() did not append \\r\\n "
321 "before sending; got: '%s'", last_write);
322 fail_unless(!strncmp(last_write, golden_data[i].data,
323 (size_t)check - 2),
324 "sdb_unixsock_client_send() sent unexpected string '%s'; "
325 "expected: '%s'", last_write, golden_data[i].data);
326 free(last_write);
327 last_write = NULL;
328 }
329 }
330 END_TEST
332 START_TEST(test_sdb_unixsock_client_recv)
333 {
334 size_t i;
336 conn();
338 for (i = 0; i < SDB_STATIC_ARRAY_LEN(golden_data); ++i) {
339 char *check;
340 char buf[64];
342 mock_read_called = 0;
343 check = sdb_unixsock_client_recv(client, buf, sizeof(buf));
344 fail_unless(check != NULL,
345 "sdb_unixsock_client_recv() = NULL; expected: a string");
346 fail_unless(check == buf,
347 "sdb_unixsock_client_recv() did not return a pointer "
348 "to the user-provided buffer");
349 fail_unless(mock_read_called == 1,
350 "sdb_unixsock_client_recv() called mock_read %llu times; "
351 "expected: 1", mock_read_called);
352 fail_unless(strlen(check) == golden_data[i].len,
353 "sdb_unixsock_client_recv() returned string of length "
354 "%zu ('%s'); expected: %zu",
355 strlen(check), check, golden_data[i].len);
356 fail_unless(check[golden_data[i].len] != '\n',
357 "sdb_unixsock_client_recv() did not strip newline");
358 fail_unless(!strcmp(check, golden_data[i].data),
359 "sdb_unixsock_client_recv() = '%s'; expected: '%s'",
360 check, golden_data[i].data);
361 }
362 }
363 END_TEST
365 TEST_MAIN("utils::unixsock")
366 {
367 TCase *tc = tcase_create("core");
368 tcase_add_checked_fixture(tc, setup, teardown);
369 tcase_add_test(tc, test_sdb_unixsock_client_create);
370 tcase_add_test(tc, test_sdb_unixsock_client_connect);
371 tcase_add_test(tc, test_sdb_unixsock_client_send);
372 tcase_add_test(tc, test_sdb_unixsock_client_recv);
373 ADD_TCASE(tc);
374 }
375 TEST_MAIN_END
377 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */