1 /*
2 * Copyright (c) 2017-2021 Free Software Foundation, Inc.
3 *
4 * This file is part of Wget.
5 *
6 * Wget is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Wget is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Wget. If not, see <https://www.gnu.org/licenses/>.
18 *
19 *
20 * Dynamic loading related testing
21 *
22 */
23
24 #include <config.h>
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <stdarg.h>
30 #include <sys/stat.h>
31 #include <dirent.h>
32 #include <errno.h>
33 #include <fcntl.h>
34
35 #include <wget.h>
36
37 #include "../src/wget_dl.h"
38
39 #define abortmsg(...) \
40 do { \
41 printf(__FILE__ ":%d: error: ", __LINE__); \
42 printf(__VA_ARGS__); \
43 printf("\n"); \
44 abort(); \
45 } while (0)
46
47 #define libassert(expr) \
48 do { \
49 if (! (expr)) \
50 abortmsg("Failed assertion [" #expr "]: %s", strerror(errno)); \
51 } while(0)
52
53 #define OBJECT_DIR ".test_dl_dir"
54
55 #if defined _WIN32
56 #define BUILD_NAME(x) ".libs" "/lib" x ".dll"
57 #define LOCAL_NAME(x) OBJECT_DIR "/lib" x ".dll"
58 #elif defined __CYGWIN__
59 #define BUILD_NAME(x) ".libs" "/cyg" x ".dll"
60 #define LOCAL_NAME(x) OBJECT_DIR "/cyg" x ".dll"
61 #else
62 #define BUILD_NAME(x) ".libs" "/lib" x ".so"
63 #define LOCAL_NAME(x) OBJECT_DIR "/lib" x ".so"
64 #endif
65
string_vector_check(wget_vector * v,int correct_len,...)66 static int string_vector_check(wget_vector *v, int correct_len, ...)
67 {
68 int v_len = wget_vector_size(v);
69 va_list arglist;
70 const char *str;
71
72 if (v_len != correct_len)
73 return 0;
74
75 wget_vector_setcmpfunc(v, (wget_vector_compare_fn *) strcmp);
76 wget_vector_sort(v);
77
78 va_start(arglist, correct_len);
79 for (int i = 0; i < v_len; i++) {
80 str = va_arg(arglist, const char *);
81 if (strcmp((const char *) wget_vector_get(v, i), str) != 0) {
82 va_end(arglist);
83 return 0;
84 }
85 }
86 va_end(arglist);
87
88 return 1;
89 }
90
string_vector_dump(wget_vector * v)91 static void string_vector_dump(wget_vector *v)
92 {
93 int v_len = wget_vector_size(v);
94
95 for (int i = 0; i < v_len; i++)
96 printf(" %s\n", (const char *) wget_vector_get(v, i));
97 }
98
remove_rpl(const char * filename)99 static int remove_rpl(const char *filename)
100 {
101 int res;
102
103 res = remove(filename);
104 if (res < 0)
105 if (errno == EACCES)
106 res = rmdir(filename);
107
108 return res;
109 }
110
remove_object_dir(void)111 static void remove_object_dir(void)
112 {
113 DIR *dirp;
114 struct dirent *ent;
115
116 dirp = opendir(OBJECT_DIR);
117 if (! dirp)
118 return;
119
120 while((ent = readdir(dirp)) != NULL) {
121 if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
122 continue;
123 char *filename = wget_aprintf(OBJECT_DIR "/%s", ent->d_name);
124 libassert(remove_rpl(filename) == 0);
125 wget_free(filename);
126 }
127
128 closedir(dirp);
129
130 remove_rpl(OBJECT_DIR);
131 }
132
copy_file(const char * src,const char * dst)133 static void copy_file(const char *src, const char *dst)
134 {
135 struct stat statbuf;
136 int sfd, dfd;
137 char buf[256];
138 size_t size_remain;
139
140 printf(" Copying %s --> %s\n", src, dst);
141
142 if (stat(src, &statbuf) != 0)
143 exit(77); // likely a static build
144
145 libassert((sfd = open(src, O_RDONLY | O_BINARY)) >= 0);
146 libassert((dfd = open(dst, O_WRONLY | O_CREAT | O_BINARY, statbuf.st_mode)) >= 0);
147 size_remain = statbuf.st_size;
148 while(size_remain > 0) {
149 ssize_t io_size = size_remain;
150 if (io_size > (ssize_t) sizeof(buf))
151 io_size = sizeof(buf);
152 libassert(read(sfd, buf, io_size) == io_size);
153 libassert(write(dfd, buf, io_size) == io_size);
154 size_remain -= io_size;
155 }
156 close(sfd);
157 close(dfd);
158 }
159
add_empty_file(const char * filename)160 static void add_empty_file(const char *filename)
161 {
162 char *rpl_filename = wget_aprintf(OBJECT_DIR "/%s", filename);
163 FILE *stream;
164 printf(" Adding file %s\n", rpl_filename);
165 libassert(stream = fopen(rpl_filename, "w"));
166 fclose(stream);
167 wget_free(rpl_filename);
168 }
169
170 #define dl_assert(stmt) \
171 do { \
172 dl_error_t e[1]; \
173 dl_error_init(e); \
174 stmt; \
175 if (dl_error_is_set(e)) { \
176 abortmsg("Failed dynamic loading operation [" #stmt "]: %s", dl_error_get_msg(e)); \
177 dl_error_set(e, NULL); \
178 } \
179 } while(0)
180
181 typedef void (*test_fn)(char buf[16], size_t len);
test_fn_check(void * fn,const char * expected)182 static void test_fn_check(void *fn, const char *expected)
183 {
184 char buf[16];
185
186 #if defined __clang__ || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
187 // POSIX requires a conversion from 'void *' into a function pointer to work
188 // But -pedantic throws 'ISO C forbids conversion of object pointer to function pointer type'
189 #pragma GCC diagnostic push
190 #pragma GCC diagnostic ignored "-Wpedantic"
191 #endif
192 test_fn fn_p = (test_fn) fn;
193 #if defined __clang__ || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
194 #pragma GCC diagnostic pop
195 #endif
196
197 (*fn_p)(buf, sizeof(buf));
198
199 if (strncmp(buf, expected, 15) != 0)
200 abortmsg("Test function returned %s, expected %s", buf, expected);
201 }
202
203
204 // Test whether dl_list() works
test_dl_list(void)205 static void test_dl_list(void)
206 {
207 wget_vector *dirs;
208 wget_vector *names;
209
210 remove_object_dir();
211 libassert(mkdir(OBJECT_DIR, 0755) == 0);
212 copy_file(BUILD_NAME("alpha"), LOCAL_NAME("alpha"));
213 copy_file(BUILD_NAME("beta"), LOCAL_NAME("beta"));
214 add_empty_file("x");
215 add_empty_file("file_which_is_not_a_library");
216 add_empty_file("libreoffice.png");
217 add_empty_file("not_a_library.so");
218 add_empty_file("not_a_library.dylib");
219 add_empty_file("not_a_library.bundle");
220 libassert(mkdir(OBJECT_DIR "/somedir", 0755) == 0);
221 libassert(mkdir(OBJECT_DIR "/libactuallyadir.so", 0755) == 0);
222 libassert(mkdir(OBJECT_DIR "/libactuallyadir.dll", 0755) == 0);
223 libassert(mkdir(OBJECT_DIR "/libactuallyadir.dylib", 0755) == 0);
224 libassert(mkdir(OBJECT_DIR "/libactuallyadir.bundle", 0755) == 0);
225 libassert(mkdir(OBJECT_DIR "/cygactuallyadir.dll", 0755) == 0);
226
227 dirs = wget_vector_create(2, NULL);
228 names = wget_vector_create(2, NULL);
229 wget_vector_add(dirs, wget_strdup(OBJECT_DIR));
230
231 dl_list(dirs, names);
232 if (! string_vector_check(names, 2, "alpha", "beta")) {
233 printf("dl_list() returned incorrect list\n");
234 printf("List contains\n");
235 string_vector_dump(names);
236 abort();
237 }
238
239 wget_vector_free(&dirs);
240 wget_vector_free(&names);
241 }
242
243
244 // Test whether symbols from dynamically loaded libraries link as expected
test_linkage(void)245 static void test_linkage(void)
246 {
247 dl_file_t *dm_alpha, *dm_beta;
248 void *fn;
249
250 // Create test directory
251 remove_object_dir();
252 libassert(mkdir(OBJECT_DIR, 0755) == 0);
253 copy_file(BUILD_NAME("alpha"), LOCAL_NAME("alpha"));
254 copy_file(BUILD_NAME("beta"), LOCAL_NAME("beta"));
255
256 // Load both libraries
257 dl_assert(dm_alpha = dl_file_open(LOCAL_NAME("alpha"), e));
258 dl_assert(dm_beta = dl_file_open(LOCAL_NAME("beta"), e));
259
260 // Check whether symbols load
261 dl_assert(fn = dl_file_lookup(dm_alpha, "dl_test_fn_alpha", e));
262 test_fn_check(fn, "alpha");
263 dl_assert(fn = dl_file_lookup(dm_beta, "dl_test_fn_beta", e));
264 test_fn_check(fn, "beta");
265
266 // Check behavior in case of nonexistent symbol
267 {
268 dl_error_t e[1];
269
270 dl_error_init(e);
271
272 fn = dl_file_lookup(dm_alpha, "dl_test_fn_beta", e);
273 if (fn || (! dl_error_is_set(e)))
274 abortmsg("nonexistent symbols not returning error");
275
276 dl_error_set(e, NULL);
277 }
278
279 // Check behavior in case of multiple libraries exporting
280 // symbols with same name
281 dl_assert(fn = dl_file_lookup(dm_alpha, "dl_test_write_param", e));
282 test_fn_check(fn, "alpha");
283 dl_assert(fn = dl_file_lookup(dm_beta, "dl_test_write_param", e));
284 test_fn_check(fn, "beta");
285
286 dl_file_close(dm_alpha);
287 dl_file_close(dm_beta);
288 }
289
290 #define run_test(test) \
291 do { \
292 printf("Running " #test "...\n"); \
293 test(); \
294 printf("PASS " #test "\n"); \
295 } while (0)
296
297
main(WGET_GCC_UNUSED int argc,char ** argv)298 int main(WGET_GCC_UNUSED int argc, char **argv)
299 {
300 if (! dl_supported()) {
301 printf("Skipping dynamic loading tests\n");
302
303 return 77;
304 }
305
306 // if VALGRIND testing is enabled, we have to call ourselves with
307 // valgrind checking
308 const char *valgrind = getenv("VALGRIND_TESTS");
309
310 if (!valgrind || !*valgrind || !strcmp(valgrind, "0")) {
311 // fallthrough
312 }
313 else if (!strcmp(valgrind, "1")) {
314 char cmd[strlen(argv[0]) + 256];
315
316 wget_snprintf(cmd, sizeof(cmd), "VALGRIND_TESTS=\"\" valgrind "
317 "--error-exitcode=301 --leak-check=yes "
318 "--show-reachable=yes --track-origins=yes %s",
319 argv[0]);
320 return system(cmd) != 0;
321 } else {
322 char cmd[strlen(valgrind) + strlen(argv[0]) + 32];
323
324 wget_snprintf(cmd, sizeof(cmd), "VALGRIND_TESTS="" %s %s",
325 valgrind, argv[0]);
326 return system(cmd) != 0;
327 }
328
329 run_test(test_dl_list);
330 run_test(test_linkage);
331
332 remove_object_dir();
333
334 return 0;
335 }
336