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