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