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     //rounds length up to a 8-byte multiple to prevent extraneous mem checker warnings upon data read
675     //rounding up to 4 byte multiple might be sufficient, but went with 8 to be safe.
676     result = (char *) malloc ((len1 + len2 + 1 + 7) & ~ 0x07);
677     memcpy (result, str1, len1);
678     memcpy (result + len1, str2, len2);
679     result[len1 + len2] = '\0';
680 
681     return result;
682 }
683 
684 
685 char *
br_build_path(const char * dir,const char * file)686 br_build_path (const char *dir, const char *file)
687 {
688     char *dir2, *result;
689     size_t len;
690     int must_free = 0;
691 
692     len = strlen (dir);
693     if (len > 0 && dir[len - 1] != '/') {
694         dir2 = br_strcat (dir, "/");
695         must_free = 1;
696     } else
697         dir2 = (char *) dir;
698 
699     result = br_strcat (dir2, file);
700     if (must_free)
701         free (dir2);
702     return result;
703 }
704 
705 
706 /* Emulates glibc's strndup() */
707 static char *
br_strndup(const char * str,size_t size)708 br_strndup (const char *str, size_t size)
709 {
710     char *result = (char *) NULL;
711     size_t len;
712 
713     if (str == (const char *) NULL)
714         return (char *) NULL;
715 
716     len = strlen (str);
717     if (len == 0)
718         return strdup ("");
719     if (size > len)
720         size = len;
721 
722     result = (char *) malloc (len + 1);
723     memcpy (result, str, size);
724     result[size] = '\0';
725     return result;
726 }
727 
728 
729 /** Extracts the directory component of a path.
730  *
731  * Similar to g_dirname() or the dirname commandline application.
732  *
733  * Example:
734  * \code
735  * br_dirname ("/usr/local/foobar");  --> Returns: "/usr/local"
736  * \endcode
737  *
738  * @param path  A path.
739  * @returns     A directory name. This string should be freed when no longer needed.
740  */
741 char *
br_dirname(const char * path)742 br_dirname (const char *path)
743 {
744     char *end, *result;
745 
746     if (path == (const char *) NULL)
747         return (char *) NULL;
748 
749     end = strrchr (path, '/');
750     if (end == (const char *) NULL)
751         return strdup (".");
752 
753     while (end > path && *end == '/')
754         end--;
755     result = br_strndup (path, end - path + 1);
756     if (result[0] == 0) {
757         free (result);
758         return strdup ("/");
759     } else
760         return result;
761 }
762 
763 
764 #ifdef __cplusplus
765 }
766 #endif /* __cplusplus */
767 
768 #endif /* __BINRELOC_C__ */
769