Runtime: Allow recursive extraction of partial sections of the AppImage
[appimagekit/gsi.git] / src / binreloc.c
blobc9b5db285fc9c0912b0c90b643a2204f06a6bf2d
1 /*
2 * BinReloc - a library for creating relocatable executables
3 * Written by: Hongli Lai <h.lai@chello.nl>
4 * http://autopackage.org/
6 * This source code is public domain. You can relicense this code
7 * under whatever license you want.
9 * See http://autopackage.org/docs/binreloc/ for
10 * more information and how to use this.
13 #ifndef BINRELOC_C
14 #define BINRELOC_C
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"
27 #ifdef __cplusplus
28 extern "C" {
29 #endif /* __cplusplus */
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.
38 static char *
39 _br_find_exe (BrInitError *error)
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;
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;
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;
73 strncpy (path2, "/proc/self/exe", buf_size - 1);
75 while (1) {
76 int i;
78 size = readlink (path2, path, buf_size - 1);
79 if (size == -1) {
80 /* Error. */
81 free (path2);
82 break;
85 /* readlink() success. */
86 path[size] = '\0';
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;
97 /* stat() success. */
98 if (!S_ISLNK (stat_buf.st_mode)) {
99 /* path is not a symlink. Done. */
100 free (path2);
101 return path;
104 /* path is a symlink. Continue loop and resolve this. */
105 strncpy (path, path2, buf_size - 1);
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. */
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;
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;
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;
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;
150 if (line[buf_size - 1] == 10)
151 line[buf_size - 1] = 0;
153 /* Extract the filename; it is always an absolute path. */
154 path = strchr (line, '/');
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;
165 path = strdup (path);
166 free (line);
167 fclose (f);
168 return path;
169 #endif /* ENABLE_BINRELOC */
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.
177 static char *
178 _br_find_exe_for_symbol (const void *symbol, BrInitError *error)
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;
190 if (symbol == NULL)
191 return (char *) NULL;
193 f = fopen ("/proc/self/maps", "r");
194 if (f == NULL)
195 return (char *) NULL;
197 address_string_len = 4;
198 address_string = (char *) malloc (address_string_len);
199 found = (char *) NULL;
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;
206 if (fgets (line, SIZE, f) == NULL)
207 break;
209 /* Sanity check. */
210 if (strstr (line, " r-xp ") == NULL || strchr (line, '/') == NULL)
211 continue;
213 /* Parse line. */
214 start_addr = line;
215 end_addr = strchr (line, '-');
216 file = strchr (line, '/');
218 /* More sanity check. */
219 if (!(file > end_addr && end_addr != NULL && end_addr[0] == '-'))
220 continue;
222 end_addr[0] = '\0';
223 end_addr++;
224 end_addr_end = strchr (end_addr, ' ');
225 if (end_addr_end == NULL)
226 continue;
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';
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';
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;
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);
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);
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);
264 if (symbol >= start_addr_p && symbol < end_addr_p) {
265 found = file;
266 break;
270 free (address_string);
271 fclose (f);
273 if (found == NULL)
274 return (char *) NULL;
275 else
276 return strdup (found);
277 #endif /* ENABLE_BINRELOC */
281 #ifndef BINRELOC_RUNNING_DOXYGEN
282 #undef NULL
283 #define NULL ((void *) 0) /* typecasted as char* for C++ type safeness */
284 #endif
286 static char *exe = (char *) NULL;
289 /** Initialize the BinReloc library (for applications).
291 * This function must be called before using any other BinReloc functions.
292 * It attempts to locate the application's canonical filename.
294 * @note If you want to use BinReloc for a library, then you should call
295 * br_init_lib() instead.
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.
301 * @returns 1 on success, 0 if BinReloc failed to initialize.
304 br_init (BrInitError *error)
306 exe = _br_find_exe (error);
307 return exe != NULL;
311 /** Initialize the BinReloc library (for libraries).
313 * This function must be called before using any other BinReloc functions.
314 * It attempts to locate the calling library's canonical filename.
316 * @note The BinReloc source code MUST be included in your library, or this
317 * function won't work correctly.
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.
323 * @returns 1 on success, 0 if a filename cannot be found.
326 br_init_lib (BrInitError *error)
328 exe = _br_find_exe_for_symbol ((const void *) "", error);
329 return exe != NULL;
333 /** Find the canonical filename of the current application.
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.
342 char *
343 br_find_exe (const char *default_exe)
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;
352 return strdup (exe);
356 /** Locate the directory in which the current application is installed.
358 * The prefix is generated by the following pseudo-code evaluation:
359 * \code
360 * dirname(exename)
361 * \endcode
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.
370 char *
371 br_find_exe_dir (const char *default_dir)
373 if (exe == NULL) {
374 /* BinReloc not initialized. */
375 if (default_dir != NULL)
376 return strdup (default_dir);
377 else
378 return NULL;
381 return br_dirname (exe);
385 /** Locate the prefix in which the current application is installed.
387 * The prefix is generated by the following pseudo-code evaluation:
388 * \code
389 * dirname(dirname(exename))
390 * \endcode
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.
398 char *
399 br_find_prefix (const char *default_prefix)
401 char *dir1, *dir2;
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;
411 dir1 = br_dirname (exe);
412 dir2 = br_dirname (dir1);
413 free (dir1);
414 return dir2;
418 /** Locate the application's binary folder.
420 * The path is generated by the following pseudo-code evaluation:
421 * \code
422 * prefix + "/bin"
423 * \endcode
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.
431 char *
432 br_find_bin_dir (const char *default_bin_dir)
434 char *prefix, *dir;
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;
445 dir = br_build_path (prefix, "bin");
446 free (prefix);
447 return dir;
451 /** Locate the application's superuser binary folder.
453 * The path is generated by the following pseudo-code evaluation:
454 * \code
455 * prefix + "/sbin"
456 * \endcode
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.
464 char *
465 br_find_sbin_dir (const char *default_sbin_dir)
467 char *prefix, *dir;
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;
478 dir = br_build_path (prefix, "sbin");
479 free (prefix);
480 return dir;
484 /** Locate the application's data folder.
486 * The path is generated by the following pseudo-code evaluation:
487 * \code
488 * prefix + "/share"
489 * \endcode
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.
498 char *
499 br_find_data_dir (const char *default_data_dir)
501 char *prefix, *dir;
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;
512 dir = br_build_path (prefix, "share");
513 free (prefix);
514 return dir;
518 /** Locate the application's localization folder.
520 * The path is generated by the following pseudo-code evaluation:
521 * \code
522 * prefix + "/share/locale"
523 * \endcode
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.
531 char *
532 br_find_locale_dir (const char *default_locale_dir)
534 char *data_dir, *dir;
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;
545 dir = br_build_path (data_dir, "locale");
546 free (data_dir);
547 return dir;
551 /** Locate the application's library folder.
553 * The path is generated by the following pseudo-code evaluation:
554 * \code
555 * prefix + "/lib"
556 * \endcode
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.
564 char *
565 br_find_lib_dir (const char *default_lib_dir)
567 char *prefix, *dir;
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;
578 dir = br_build_path (prefix, "lib");
579 free (prefix);
580 return dir;
584 /** Locate the application's libexec folder.
586 * The path is generated by the following pseudo-code evaluation:
587 * \code
588 * prefix + "/libexec"
589 * \endcode
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.
597 char *
598 br_find_libexec_dir (const char *default_libexec_dir)
600 char *prefix, *dir;
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;
611 dir = br_build_path (prefix, "libexec");
612 free (prefix);
613 return dir;
617 /** Locate the application's configuration files folder.
619 * The path is generated by the following pseudo-code evaluation:
620 * \code
621 * prefix + "/etc"
622 * \endcode
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.
630 char *
631 br_find_etc_dir (const char *default_etc_dir)
633 char *prefix, *dir;
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;
644 dir = br_build_path (prefix, "etc");
645 free (prefix);
646 return dir;
650 /***********************
651 * Utility functions
652 ***********************/
654 /** Concatenate str1 and str2 to a newly allocated string.
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.
660 char *
661 br_strcat (const char *str1, const char *str2)
663 char *result;
664 size_t len1, len2;
666 if (str1 == NULL)
667 str1 = "";
668 if (str2 == NULL)
669 str2 = "";
671 len1 = strlen (str1);
672 len2 = strlen (str2);
674 result = (char *) malloc (len1 + len2 + 1);
675 memcpy (result, str1, len1);
676 memcpy (result + len1, str2, len2);
677 result[len1 + len2] = '\0';
679 return result;
683 char *
684 br_build_path (const char *dir, const char *file)
686 char *dir2, *result;
687 size_t len;
688 int must_free = 0;
690 len = strlen (dir);
691 if (len > 0 && dir[len - 1] != '/') {
692 dir2 = br_strcat (dir, "/");
693 must_free = 1;
694 } else
695 dir2 = (char *) dir;
697 result = br_strcat (dir2, file);
698 if (must_free)
699 free (dir2);
700 return result;
704 /* Emulates glibc's strndup() */
705 static char *
706 br_strndup (const char *str, size_t size)
708 char *result = (char *) NULL;
709 size_t len;
711 if (str == (const char *) NULL)
712 return (char *) NULL;
714 len = strlen (str);
715 if (len == 0)
716 return strdup ("");
717 if (size > len)
718 size = len;
720 result = (char *) malloc (len + 1);
721 memcpy (result, str, size);
722 result[size] = '\0';
723 return result;
727 /** Extracts the directory component of a path.
729 * Similar to g_dirname() or the dirname commandline application.
731 * Example:
732 * \code
733 * br_dirname ("/usr/local/foobar"); --> Returns: "/usr/local"
734 * \endcode
736 * @param path A path.
737 * @returns A directory name. This string should be freed when no longer needed.
739 char *
740 br_dirname (const char *path)
742 char *end, *result;
744 if (path == (const char *) NULL)
745 return (char *) NULL;
747 end = strrchr (path, '/');
748 if (end == (const char *) NULL)
749 return strdup (".");
751 while (end > path && *end == '/')
752 end--;
753 result = br_strndup (path, end - path + 1);
754 if (result[0] == 0) {
755 free (result);
756 return strdup ("/");
757 } else
758 return result;
762 #ifdef __cplusplus
764 #endif /* __cplusplus */
766 #endif /* BINRELOC_C */