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