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