1 /*
2  * BinReloc - a library for creating relocatable executables
3  * Written by: Hongli Lai <h.lai@chello.nl>
4  * http://autopackage.org/
5  *
6  * This source code is public domain. You can relicense this code
7  * under whatever license you want.
8  *
9  * See http://autopackage.org/docs/binreloc/ for
10  * more information and how to use this.
11  */
12 
13 #ifndef __BINRELOC_C__
14 #define __BINRELOC_C__
15 
16 #ifdef ENABLE_BINRELOC
17 	#include <sys/types.h>
18 	#include <sys/stat.h>
19 	#include <unistd.h>
20 #endif /* ENABLE_BINRELOC */
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <limits.h>
24 #include <string.h>
25 #include "binreloc.h"
26 
27 #ifdef __cplusplus
28 extern "C" {
29 #endif /* __cplusplus */
30 
31 
32 
33 /** @internal
34  * Find the canonical filename of the executable. Returns the filename
35  * (which must be freed) or NULL on error. If the parameter 'error' is
36  * not NULL, the error code will be stored there, if an error occured.
37  */
38 static char *
_br_find_exe(BrInitError * error)39 _br_find_exe (BrInitError *error)
40 {
41 #ifndef ENABLE_BINRELOC
42 	if (error)
43 		*error = BR_INIT_ERROR_DISABLED;
44 	return NULL;
45 #else
46 	char *path, *path2, *line, *result;
47 	size_t buf_size;
48 	ssize_t size;
49 	struct stat stat_buf;
50 	FILE *f;
51 
52 	/* Read from /proc/self/exe (symlink) */
53 	if (sizeof (path) > SSIZE_MAX)
54 		buf_size = SSIZE_MAX - 1;
55 	else
56 		buf_size = PATH_MAX - 1;
57 	path = (char *) malloc (buf_size);
58 	if (path == NULL) {
59 		/* Cannot allocate memory. */
60 		if (error)
61 			*error = BR_INIT_ERROR_NOMEM;
62 		return NULL;
63 	}
64 	path2 = (char *) malloc (buf_size);
65 	if (path2 == NULL) {
66 		/* Cannot allocate memory. */
67 		if (error)
68 			*error = BR_INIT_ERROR_NOMEM;
69 		free (path);
70 		return NULL;
71 	}
72 
73 	strncpy (path2, "/proc/self/exe", buf_size - 1);
74 
75 	while (1) {
76 		int i;
77 
78 		size = readlink (path2, path, buf_size - 1);
79 		if (size == -1) {
80 			/* Error. */
81 			free (path2);
82 			break;
83 		}
84 
85 		/* readlink() success. */
86 		path[size] = '\0';
87 
88 		/* Check whether the symlink's target is also a symlink.
89 		 * We want to get the final target. */
90 		i = stat (path, &stat_buf);
91 		if (i == -1) {
92 			/* Error. */
93 			free (path2);
94 			break;
95 		}
96 
97 		/* stat() success. */
98 		if (!S_ISLNK (stat_buf.st_mode)) {
99 			/* path is not a symlink. Done. */
100 			free (path2);
101 			return path;
102 		}
103 
104 		/* path is a symlink. Continue loop and resolve this. */
105 		strncpy (path, path2, buf_size - 1);
106 	}
107 
108 
109 	/* readlink() or stat() failed; this can happen when the program is
110 	 * running in Valgrind 2.2. Read from /proc/self/maps as fallback. */
111 
112 	buf_size = PATH_MAX + 128;
113 	line = (char *) realloc (path, buf_size);
114 	if (line == NULL) {
115 		/* Cannot allocate memory. */
116 		free (path);
117 		if (error)
118 			*error = BR_INIT_ERROR_NOMEM;
119 		return NULL;
120 	}
121 
122 	f = fopen ("/proc/self/maps", "r");
123 	if (f == NULL) {
124 		free (line);
125 		if (error)
126 			*error = BR_INIT_ERROR_OPEN_MAPS;
127 		return NULL;
128 	}
129 
130 	/* The first entry should be the executable name. */
131 	result = fgets (line, (int) buf_size, f);
132 	if (result == NULL) {
133 		fclose (f);
134 		free (line);
135 		if (error)
136 			*error = BR_INIT_ERROR_READ_MAPS;
137 		return NULL;
138 	}
139 
140 	/* Get rid of newline character. */
141 	buf_size = strlen (line);
142 	if (buf_size <= 0) {
143 		/* Huh? An empty string? */
144 		fclose (f);
145 		free (line);
146 		if (error)
147 			*error = BR_INIT_ERROR_INVALID_MAPS;
148 		return NULL;
149 	}
150 	if (line[buf_size - 1] == 10)
151 		line[buf_size - 1] = 0;
152 
153 	/* Extract the filename; it is always an absolute path. */
154 	path = strchr (line, '/');
155 
156 	/* Sanity check. */
157 	if (strstr (line, " r-xp ") == NULL || path == NULL) {
158 		fclose (f);
159 		free (line);
160 		if (error)
161 			*error = BR_INIT_ERROR_INVALID_MAPS;
162 		return NULL;
163 	}
164 
165 	path = strdup (path);
166 	free (line);
167 	fclose (f);
168 	return path;
169 #endif /* ENABLE_BINRELOC */
170 }
171 
172 
173 /** @internal
174  * Find the canonical filename of the executable which owns symbol.
175  * Returns a filename which must be freed, or NULL on error.
176  */
177 static char *
_br_find_exe_for_symbol(const void * symbol,BrInitError * error)178 _br_find_exe_for_symbol (const void *symbol, BrInitError *error)
179 {
180 #ifndef ENABLE_BINRELOC
181 	if (error)
182 		*error = BR_INIT_ERROR_DISABLED;
183 	return (char *) NULL;
184 #else
185 	#define SIZE PATH_MAX + 100
186 	FILE *f;
187 	size_t address_string_len;
188 	char *address_string, line[SIZE], *found;
189 
190 	if (symbol == NULL)
191 		return (char *) NULL;
192 
193 	f = fopen ("/proc/self/maps", "r");
194 	if (f == NULL)
195 		return (char *) NULL;
196 
197 	address_string_len = 4;
198 	address_string = (char *) malloc (address_string_len);
199 	found = (char *) NULL;
200 
201 	while (!feof (f)) {
202 		char *start_addr, *end_addr, *end_addr_end, *file;
203 		void *start_addr_p, *end_addr_p;
204 		size_t len;
205 
206 		if (fgets (line, SIZE, f) == NULL)
207 			break;
208 
209 		/* Sanity check. */
210 		if (strstr (line, " r-xp ") == NULL || strchr (line, '/') == NULL)
211 			continue;
212 
213 		/* Parse line. */
214 		start_addr = line;
215 		end_addr = strchr (line, '-');
216 		file = strchr (line, '/');
217 
218 		/* More sanity check. */
219 		if (!(file > end_addr && end_addr != NULL && end_addr[0] == '-'))
220 			continue;
221 
222 		end_addr[0] = '\0';
223 		end_addr++;
224 		end_addr_end = strchr (end_addr, ' ');
225 		if (end_addr_end == NULL)
226 			continue;
227 
228 		end_addr_end[0] = '\0';
229 		len = strlen (file);
230 		if (len == 0)
231 			continue;
232 		if (file[len - 1] == '\n')
233 			file[len - 1] = '\0';
234 
235 		/* Get rid of "(deleted)" from the filename. */
236 		len = strlen (file);
237 		if (len > 10 && strcmp (file + len - 10, " (deleted)") == 0)
238 			file[len - 10] = '\0';
239 
240 		/* I don't know whether this can happen but better safe than sorry. */
241 		len = strlen (start_addr);
242 		if (len != strlen (end_addr))
243 			continue;
244 
245 
246 		/* Transform the addresses into a string in the form of 0xdeadbeef,
247 		 * then transform that into a pointer. */
248 		if (address_string_len < len + 3) {
249 			address_string_len = len + 3;
250 			address_string = (char *) realloc (address_string, address_string_len);
251 		}
252 
253 		memcpy (address_string, "0x", 2);
254 		memcpy (address_string + 2, start_addr, len);
255 		address_string[2 + len] = '\0';
256 		sscanf (address_string, "%p", &start_addr_p);
257 
258 		memcpy (address_string, "0x", 2);
259 		memcpy (address_string + 2, end_addr, len);
260 		address_string[2 + len] = '\0';
261 		sscanf (address_string, "%p", &end_addr_p);
262 
263 
264 		if (symbol >= start_addr_p && symbol < end_addr_p) {
265 			found = file;
266 			break;
267 		}
268 	}
269 
270 	free (address_string);
271 	fclose (f);
272 
273 	if (found == NULL)
274 		return (char *) NULL;
275 	else
276 		return strdup (found);
277 #endif /* ENABLE_BINRELOC */
278 }
279 
280 
281 #ifndef BINRELOC_RUNNING_DOXYGEN
282 	#undef NULL
283 	#define NULL ((void *) 0) /* typecasted as char* for C++ type safeness */
284 #endif
285 
286 static char *exe = (char *) NULL;
287 
288 
289 /** Initialize the BinReloc library (for applications).
290  *
291  * This function must be called before using any other BinReloc functions.
292  * It attempts to locate the application's canonical filename.
293  *
294  * @note If you want to use BinReloc for a library, then you should call
295  *       br_init_lib() instead.
296  *
297  * @param error  If BinReloc failed to initialize, then the error code will
298  *               be stored in this variable. Set to NULL if you want to
299  *               ignore this. See #BrInitError for a list of error codes.
300  *
301  * @returns 1 on success, 0 if BinReloc failed to initialize.
302  */
303 int
br_init(BrInitError * error)304 br_init (BrInitError *error)
305 {
306 	exe = _br_find_exe (error);
307 	return exe != NULL;
308 }
309 
310 
311 /** Initialize the BinReloc library (for libraries).
312  *
313  * This function must be called before using any other BinReloc functions.
314  * It attempts to locate the calling library's canonical filename.
315  *
316  * @note The BinReloc source code MUST be included in your library, or this
317  *       function won't work correctly.
318  *
319  * @param error  If BinReloc failed to initialize, then the error code will
320  *               be stored in this variable. Set to NULL if you want to
321  *               ignore this. See #BrInitError for a list of error codes.
322  *
323  * @returns 1 on success, 0 if a filename cannot be found.
324  */
325 int
br_init_lib(BrInitError * error)326 br_init_lib (BrInitError *error)
327 {
328 	exe = _br_find_exe_for_symbol ((const void *) "", error);
329 	return exe != NULL;
330 }
331 
332 
333 /** Find the canonical filename of the current application.
334  *
335  * @param default_exe  A default filename which will be used as fallback.
336  * @returns A string containing the application's canonical filename,
337  *          which must be freed when no longer necessary. If BinReloc is
338  *          not initialized, or if br_init() failed, then a copy of
339  *          default_exe will be returned. If default_exe is NULL, then
340  *          NULL will be returned.
341  */
342 char *
br_find_exe(const char * default_exe)343 br_find_exe (const char *default_exe)
344 {
345 	if (exe == (char *) NULL) {
346 		/* BinReloc is not initialized. */
347 		if (default_exe != (const char *) NULL)
348 			return strdup (default_exe);
349 		else
350 			return (char *) NULL;
351 	}
352 	return strdup (exe);
353 }
354 
355 
356 /** Locate the directory in which the current application is installed.
357  *
358  * The prefix is generated by the following pseudo-code evaluation:
359  * \code
360  * dirname(exename)
361  * \endcode
362  *
363  * @param default_dir  A default directory which will used as fallback.
364  * @return A string containing the directory, which must be freed when no
365  *         longer necessary. If BinReloc is not initialized, or if the
366  *         initialization function failed, then a copy of default_dir
367  *         will be returned. If default_dir is NULL, then NULL will be
368  *         returned.
369  */
370 char *
br_find_exe_dir(const char * default_dir)371 br_find_exe_dir (const char *default_dir)
372 {
373 	if (exe == NULL) {
374 		/* BinReloc not initialized. */
375 		if (default_dir != NULL)
376 			return strdup (default_dir);
377 		else
378 			return NULL;
379 	}
380 
381 	return br_dirname (exe);
382 }
383 
384 
385 /** Locate the prefix in which the current application is installed.
386  *
387  * The prefix is generated by the following pseudo-code evaluation:
388  * \code
389  * dirname(dirname(exename))
390  * \endcode
391  *
392  * @param default_prefix  A default prefix which will used as fallback.
393  * @return A string containing the prefix, which must be freed when no
394  *         longer necessary. If BinReloc is not initialized, or if
395  *         the initialization function failed, then a copy of default_prefix
396  *         will be returned. If default_prefix is NULL, then NULL will be returned.
397  */
398 char *
br_find_prefix(const char * default_prefix)399 br_find_prefix (const char *default_prefix)
400 {
401 	char *dir1, *dir2;
402 
403 	if (exe == (char *) NULL) {
404 		/* BinReloc not initialized. */
405 		if (default_prefix != (const char *) NULL)
406 			return strdup (default_prefix);
407 		else
408 			return (char *) NULL;
409 	}
410 
411 	dir1 = br_dirname (exe);
412 	dir2 = br_dirname (dir1);
413 	free (dir1);
414 	return dir2;
415 }
416 
417 
418 /** Locate the application's binary folder.
419  *
420  * The path is generated by the following pseudo-code evaluation:
421  * \code
422  * prefix + "/bin"
423  * \endcode
424  *
425  * @param default_bin_dir  A default path which will used as fallback.
426  * @return A string containing the bin folder's path, which must be freed when
427  *         no longer necessary. If BinReloc is not initialized, or if
428  *         the initialization function failed, then a copy of default_bin_dir will
429  *         be returned. If default_bin_dir is NULL, then NULL will be returned.
430  */
431 char *
br_find_bin_dir(const char * default_bin_dir)432 br_find_bin_dir (const char *default_bin_dir)
433 {
434 	char *prefix, *dir;
435 
436 	prefix = br_find_prefix ((const char *) NULL);
437 	if (prefix == (char *) NULL) {
438 		/* BinReloc not initialized. */
439 		if (default_bin_dir != (const char *) NULL)
440 			return strdup (default_bin_dir);
441 		else
442 			return (char *) NULL;
443 	}
444 
445 	dir = br_build_path (prefix, "bin");
446 	free (prefix);
447 	return dir;
448 }
449 
450 
451 /** Locate the application's superuser binary folder.
452  *
453  * The path is generated by the following pseudo-code evaluation:
454  * \code
455  * prefix + "/sbin"
456  * \endcode
457  *
458  * @param default_sbin_dir  A default path which will used as fallback.
459  * @return A string containing the sbin folder's path, which must be freed when
460  *         no longer necessary. If BinReloc is not initialized, or if the
461  *         initialization function failed, then a copy of default_sbin_dir will
462  *         be returned. If default_bin_dir is NULL, then NULL will be returned.
463  */
464 char *
br_find_sbin_dir(const char * default_sbin_dir)465 br_find_sbin_dir (const char *default_sbin_dir)
466 {
467 	char *prefix, *dir;
468 
469 	prefix = br_find_prefix ((const char *) NULL);
470 	if (prefix == (char *) NULL) {
471 		/* BinReloc not initialized. */
472 		if (default_sbin_dir != (const char *) NULL)
473 			return strdup (default_sbin_dir);
474 		else
475 			return (char *) NULL;
476 	}
477 
478 	dir = br_build_path (prefix, "sbin");
479 	free (prefix);
480 	return dir;
481 }
482 
483 
484 /** Locate the application's data folder.
485  *
486  * The path is generated by the following pseudo-code evaluation:
487  * \code
488  * prefix + "/share"
489  * \endcode
490  *
491  * @param default_data_dir  A default path which will used as fallback.
492  * @return A string containing the data folder's path, which must be freed when
493  *         no longer necessary. If BinReloc is not initialized, or if the
494  *         initialization function failed, then a copy of default_data_dir
495  *         will be returned. If default_data_dir is NULL, then NULL will be
496  *         returned.
497  */
498 char *
br_find_data_dir(const char * default_data_dir)499 br_find_data_dir (const char *default_data_dir)
500 {
501 	char *prefix, *dir;
502 
503 	prefix = br_find_prefix ((const char *) NULL);
504 	if (prefix == (char *) NULL) {
505 		/* BinReloc not initialized. */
506 		if (default_data_dir != (const char *) NULL)
507 			return strdup (default_data_dir);
508 		else
509 			return (char *) NULL;
510 	}
511 
512 	dir = br_build_path (prefix, "share");
513 	free (prefix);
514 	return dir;
515 }
516 
517 
518 /** Locate the application's localization folder.
519  *
520  * The path is generated by the following pseudo-code evaluation:
521  * \code
522  * prefix + "/share/locale"
523  * \endcode
524  *
525  * @param default_locale_dir  A default path which will used as fallback.
526  * @return A string containing the localization folder's path, which must be freed when
527  *         no longer necessary. If BinReloc is not initialized, or if the
528  *         initialization function failed, then a copy of default_locale_dir will be returned.
529  *         If default_locale_dir is NULL, then NULL will be returned.
530  */
531 char *
br_find_locale_dir(const char * default_locale_dir)532 br_find_locale_dir (const char *default_locale_dir)
533 {
534 	char *data_dir, *dir;
535 
536 	data_dir = br_find_data_dir ((const char *) NULL);
537 	if (data_dir == (char *) NULL) {
538 		/* BinReloc not initialized. */
539 		if (default_locale_dir != (const char *) NULL)
540 			return strdup (default_locale_dir);
541 		else
542 			return (char *) NULL;
543 	}
544 
545 	dir = br_build_path (data_dir, "locale");
546 	free (data_dir);
547 	return dir;
548 }
549 
550 
551 /** Locate the application's library folder.
552  *
553  * The path is generated by the following pseudo-code evaluation:
554  * \code
555  * prefix + "/lib"
556  * \endcode
557  *
558  * @param default_lib_dir  A default path which will used as fallback.
559  * @return A string containing the library folder's path, which must be freed when
560  *         no longer necessary. If BinReloc is not initialized, or if the initialization
561  *         function failed, then a copy of default_lib_dir will be returned.
562  *         If default_lib_dir is NULL, then NULL will be returned.
563  */
564 char *
br_find_lib_dir(const char * default_lib_dir)565 br_find_lib_dir (const char *default_lib_dir)
566 {
567 	char *prefix, *dir;
568 
569 	prefix = br_find_prefix ((const char *) NULL);
570 	if (prefix == (char *) NULL) {
571 		/* BinReloc not initialized. */
572 		if (default_lib_dir != (const char *) NULL)
573 			return strdup (default_lib_dir);
574 		else
575 			return (char *) NULL;
576 	}
577 
578 	dir = br_build_path (prefix, "lib");
579 	free (prefix);
580 	return dir;
581 }
582 
583 
584 /** Locate the application's libexec folder.
585  *
586  * The path is generated by the following pseudo-code evaluation:
587  * \code
588  * prefix + "/libexec"
589  * \endcode
590  *
591  * @param default_libexec_dir  A default path which will used as fallback.
592  * @return A string containing the libexec folder's path, which must be freed when
593  *         no longer necessary. If BinReloc is not initialized, or if the initialization
594  *         function failed, then a copy of default_libexec_dir will be returned.
595  *         If default_libexec_dir is NULL, then NULL will be returned.
596  */
597 char *
br_find_libexec_dir(const char * default_libexec_dir)598 br_find_libexec_dir (const char *default_libexec_dir)
599 {
600 	char *prefix, *dir;
601 
602 	prefix = br_find_prefix ((const char *) NULL);
603 	if (prefix == (char *) NULL) {
604 		/* BinReloc not initialized. */
605 		if (default_libexec_dir != (const char *) NULL)
606 			return strdup (default_libexec_dir);
607 		else
608 			return (char *) NULL;
609 	}
610 
611 	dir = br_build_path (prefix, "libexec");
612 	free (prefix);
613 	return dir;
614 }
615 
616 
617 /** Locate the application's configuration files folder.
618  *
619  * The path is generated by the following pseudo-code evaluation:
620  * \code
621  * prefix + "/etc"
622  * \endcode
623  *
624  * @param default_etc_dir  A default path which will used as fallback.
625  * @return A string containing the etc folder's path, which must be freed when
626  *         no longer necessary. If BinReloc is not initialized, or if the initialization
627  *         function failed, then a copy of default_etc_dir will be returned.
628  *         If default_etc_dir is NULL, then NULL will be returned.
629  */
630 char *
br_find_etc_dir(const char * default_etc_dir)631 br_find_etc_dir (const char *default_etc_dir)
632 {
633 	char *prefix, *dir;
634 
635 	prefix = br_find_prefix ((const char *) NULL);
636 	if (prefix == (char *) NULL) {
637 		/* BinReloc not initialized. */
638 		if (default_etc_dir != (const char *) NULL)
639 			return strdup (default_etc_dir);
640 		else
641 			return (char *) NULL;
642 	}
643 
644 	dir = br_build_path (prefix, "etc");
645 	free (prefix);
646 	return dir;
647 }
648 
649 
650 /***********************
651  * Utility functions
652  ***********************/
653 
654 /** Concatenate str1 and str2 to a newly allocated string.
655  *
656  * @param str1 A string.
657  * @param str2 Another string.
658  * @returns A newly-allocated string. This string should be freed when no longer needed.
659  */
660 char *
br_strcat(const char * str1,const char * str2)661 br_strcat (const char *str1, const char *str2)
662 {
663 	char *result;
664 	size_t len1, len2;
665 
666 	if (str1 == NULL)
667 		str1 = "";
668 	if (str2 == NULL)
669 		str2 = "";
670 
671 	len1 = strlen (str1);
672 	len2 = strlen (str2);
673 
674 	result = (char *) malloc (len1 + len2 + 1);
675 	memcpy (result, str1, len1);
676 	memcpy (result + len1, str2, len2);
677 	result[len1 + len2] = '\0';
678 
679 	return result;
680 }
681 
682 
683 char *
br_build_path(const char * dir,const char * file)684 br_build_path (const char *dir, const char *file)
685 {
686 	char *dir2, *result;
687 	size_t len;
688 	int must_free = 0;
689 
690 	len = strlen (dir);
691 	if (len > 0 && dir[len - 1] != '/') {
692 		dir2 = br_strcat (dir, "/");
693 		must_free = 1;
694 	} else
695 		dir2 = (char *) dir;
696 
697 	result = br_strcat (dir2, file);
698 	if (must_free)
699 		free (dir2);
700 	return result;
701 }
702 
703 
704 /* Emulates glibc's strndup() */
705 static char *
br_strndup(const char * str,size_t size)706 br_strndup (const char *str, size_t size)
707 {
708 	char *result = (char *) NULL;
709 	size_t len;
710 
711 	if (str == (const char *) NULL)
712 		return (char *) NULL;
713 
714 	len = strlen (str);
715 	if (len == 0)
716 		return strdup ("");
717 	if (size > len)
718 		size = len;
719 
720 	result = (char *) malloc (len + 1);
721 	memcpy (result, str, size);
722 	result[size] = '\0';
723 	return result;
724 }
725 
726 
727 /** Extracts the directory component of a path.
728  *
729  * Similar to g_dirname() or the dirname commandline application.
730  *
731  * Example:
732  * \code
733  * br_dirname ("/usr/local/foobar");  --> Returns: "/usr/local"
734  * \endcode
735  *
736  * @param path  A path.
737  * @returns     A directory name. This string should be freed when no longer needed.
738  */
739 char *
br_dirname(const char * path)740 br_dirname (const char *path)
741 {
742 	char *end, *result;
743 
744 	if (path == (const char *) NULL)
745 		return (char *) NULL;
746 
747 	end = strrchr (path, '/');
748 	if (end == (const char *) NULL)
749 		return strdup (".");
750 
751 	while (end > path && *end == '/')
752 		end--;
753 	result = br_strndup (path, end - path + 1);
754 	if (result[0] == 0) {
755 		free (result);
756 		return strdup ("/");
757 	} else
758 		return result;
759 }
760 
761 
762 #ifdef __cplusplus
763 }
764 #endif /* __cplusplus */
765 
766 #endif /* __BINRELOC_C__ */
767