/* * BinReloc - a library for creating relocatable executables * Written by: Hongli Lai * Copyright: hardinfo2 project * http://autopackage.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License v2.0 or later. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef __BINRELOC_C__ #define __BINRELOC_C__ #include #include #include #include #include #include #include #include "binreloc.h" #include "config.h" G_BEGIN_DECLS /** @internal * Find the canonical filename of the executable. Returns the filename * (which must be freed) or NULL on error. If the parameter 'error' is * not NULL, the error code will be stored there, if an error occured. */ static char *_br_find_exe(GbrInitError * error) { char *path, *path2, *line, *result; size_t buf_size; ssize_t size; struct stat stat_buf; FILE *f; /* Read from /proc/self/exe (symlink) */ if (sizeof(path) > SSIZE_MAX) buf_size = SSIZE_MAX - 1; else buf_size = PATH_MAX - 1; path = (char *) g_try_malloc(buf_size); if (path == NULL) { /* Cannot allocate memory. */ if (error) *error = GBR_INIT_ERROR_NOMEM; return NULL; } path2 = (char *) g_try_malloc(buf_size); if (path2 == NULL) { /* Cannot allocate memory. */ if (error) *error = GBR_INIT_ERROR_NOMEM; g_free(path); return NULL; } strncpy(path2, "/proc/self/exe", buf_size - 1); while (1) { int i; size = readlink(path2, path, buf_size - 1); if (size == -1) { /* Error. */ g_free(path2); break; } /* readlink() success. */ path[size] = '\0'; /* Check whether the symlink's target is also a symlink. * We want to get the final target. */ i = stat(path, &stat_buf); if (i == -1) { /* Error. */ g_free(path2); break; } /* stat() success. */ if (!S_ISLNK(stat_buf.st_mode)) { /* path is not a symlink. Done. */ g_free(path2); return path; } /* path is a symlink. Continue loop and resolve this. */ strncpy(path, path2, buf_size - 1); } /* readlink() or stat() failed; this can happen when the program is * running in Valgrind 2.2. Read from /proc/self/maps as fallback. */ buf_size = PATH_MAX + 128; line = (char *) g_try_realloc(path, buf_size); if (line == NULL) { /* Cannot allocate memory. */ g_free(path); if (error) *error = GBR_INIT_ERROR_NOMEM; return NULL; } f = fopen("/proc/self/maps", "r"); if (f == NULL) { g_free(line); if (error) *error = GBR_INIT_ERROR_OPEN_MAPS; return NULL; } /* The first entry should be the executable name. */ result = fgets(line, (int) buf_size, f); if (result == NULL) { fclose(f); g_free(line); if (error) *error = GBR_INIT_ERROR_READ_MAPS; return NULL; } /* Get rid of newline character. */ buf_size = strlen(line); if (buf_size <= 0) { /* Huh? An empty string? */ fclose(f); g_free(line); if (error) *error = GBR_INIT_ERROR_INVALID_MAPS; return NULL; } if (line[buf_size - 1] == 10) line[buf_size - 1] = 0; /* Extract the filename; it is always an absolute path. */ path = strchr(line, '/'); /* Sanity check. */ if (strstr(line, " r-xp ") == NULL || path == NULL) { fclose(f); g_free(line); if (error) *error = GBR_INIT_ERROR_INVALID_MAPS; return NULL; } path = g_strdup(path); g_free(line); fclose(f); return path; } /** @internal * Find the canonical filename of the executable which owns symbol. * Returns a filename which must be freed, or NULL on error. */ static char *_br_find_exe_for_symbol(const void *symbol, GbrInitError * error) { #define SIZE PATH_MAX + 100 FILE *f; size_t address_string_len; char *address_string, line[SIZE], *found; if (symbol == NULL) return (char *) NULL; f = fopen("/proc/self/maps", "r"); if (f == NULL) return (char *) NULL; address_string_len = 4; address_string = (char *) g_try_malloc(address_string_len); found = (char *) NULL; while (!feof(f)) { char *start_addr, *end_addr, *end_addr_end, *file; void *start_addr_p, *end_addr_p; size_t len; if (fgets(line, SIZE, f) == NULL) break; /* Sanity check. */ if (strstr(line, " r-xp ") == NULL || strchr(line, '/') == NULL) continue; /* Parse line. */ start_addr = line; end_addr = strchr(line, '-'); file = strchr(line, '/'); /* More sanity check. */ if (!(file > end_addr && end_addr != NULL && end_addr[0] == '-')) continue; end_addr[0] = '\0'; end_addr++; end_addr_end = strchr(end_addr, ' '); if (end_addr_end == NULL) continue; end_addr_end[0] = '\0'; len = strlen(file); if (len == 0) continue; if (file[len - 1] == '\n') file[len - 1] = '\0'; /* Get rid of "(deleted)" from the filename. */ len = strlen(file); if (len > 10 && strcmp(file + len - 10, " (deleted)") == 0) file[len - 10] = '\0'; /* I don't know whether this can happen but better safe than sorry. */ len = strlen(start_addr); if (len != strlen(end_addr)) continue; /* Transform the addresses into a string in the form of 0xdeadbeef, * then transform that into a pointer. */ if (address_string_len < len + 3) { address_string_len = len + 3; address_string = (char *) g_try_realloc(address_string, address_string_len); } memcpy(address_string, "0x", 2); memcpy(address_string + 2, start_addr, len); address_string[2 + len] = '\0'; sscanf(address_string, "%p", &start_addr_p); memcpy(address_string, "0x", 2); memcpy(address_string + 2, end_addr, len); address_string[2 + len] = '\0'; sscanf(address_string, "%p", &end_addr_p); if (symbol >= start_addr_p && symbol < end_addr_p) { found = file; break; } } g_free(address_string); fclose(f); if (found == NULL) return (char *) NULL; else return g_strdup(found); } static gchar *exe = NULL; static void set_gerror(GError ** error, GbrInitError errcode); /** Initialize the BinReloc library (for applications). * * This function must be called before using any other BinReloc functions. * It attempts to locate the application's canonical filename. * * @note If you want to use BinReloc for a library, then you should call * gbr_init_lib() instead. * * @param error If BinReloc failed to initialize, then the error report will * be stored in this variable. Set to NULL if you don't want an * error report. See the #GbrInitError for a list of error * codes. * * @returns TRUE on success, FALSE if BinReloc failed to initialize. */ gboolean gbr_init(GError ** error) { GbrInitError errcode = 0; /* Locate the application's filename. */ exe = _br_find_exe(&errcode); if (exe != NULL) /* Success! */ return TRUE; else { /* Failed :-( */ set_gerror(error, errcode); return FALSE; } } /** Initialize the BinReloc library (for libraries). * * This function must be called before using any other BinReloc functions. * It attempts to locate the calling library's canonical filename. * * @note The BinReloc source code MUST be included in your library, or this * function won't work correctly. * * @returns TRUE on success, FALSE if a filename cannot be found. */ gboolean gbr_init_lib(GError ** error) { GbrInitError errcode = 0; exe = _br_find_exe_for_symbol((const void *) "", &errcode); if (exe != NULL) /* Success! */ return TRUE; else { /* Failed :-( */ set_gerror(error, errcode); return exe != NULL; } } static void set_gerror(GError ** error, GbrInitError errcode) { gchar *error_message; if (error == NULL) return; switch (errcode) { case GBR_INIT_ERROR_NOMEM: error_message = "Cannot allocate memory."; break; case GBR_INIT_ERROR_OPEN_MAPS: error_message = "Unable to open /proc/self/maps for reading."; break; case GBR_INIT_ERROR_READ_MAPS: error_message = "Unable to read from /proc/self/maps."; break; case GBR_INIT_ERROR_INVALID_MAPS: error_message = "The file format of /proc/self/maps is invalid."; break; case GBR_INIT_ERROR_DISABLED: error_message = "Binary relocation support is disabled."; break; default: error_message = "Unknown error."; break; }; g_set_error(error, g_quark_from_static_string("GBinReloc"), errcode, "%s", error_message); } /** Find the canonical filename of the current application. * * @param default_exe A default filename which will be used as fallback. * @returns A string containing the application's canonical filename, * which must be freed when no longer necessary. If BinReloc is * not initialized, or if the initialization function failed, * then a copy of default_exe will be returned. If default_exe * is NULL, then NULL will be returned. */ gchar *gbr_find_exe(const gchar * default_exe) { if (exe == NULL) { /* BinReloc is not initialized. */ if (default_exe != NULL) return g_strdup(default_exe); else return NULL; } return g_strdup(exe); } /** Locate the directory in which the current application is installed. * * The prefix is generated by the following pseudo-code evaluation: * \code * dirname(exename) * \endcode * * @param default_dir A default directory which will used as fallback. * @return A string containing the directory, which must be freed when no * longer necessary. If BinReloc is not initialized, or if the * initialization function failed, then a copy of default_dir * will be returned. If default_dir is NULL, then NULL will be * returned. */ gchar *gbr_find_exe_dir(const gchar * default_dir) { if (exe == NULL) { /* BinReloc not initialized. */ if (default_dir != NULL) return g_strdup(default_dir); else return NULL; } return g_path_get_dirname(exe); } /** Locate the prefix in which the current application is installed. * * The prefix is generated by the following pseudo-code evaluation: * \code * dirname(dirname(exename)) * \endcode * * @param default_prefix A default prefix which will used as fallback. * @return A string containing the prefix, which must be freed when no * longer necessary. If BinReloc is not initialized, or if the * initialization function failed, then a copy of default_prefix * will be returned. If default_prefix is NULL, then NULL will be * returned. */ gchar *gbr_find_prefix(const gchar * default_prefix) { gchar *dir1, *dir2; if (exe == NULL) { /* BinReloc not initialized. */ if (default_prefix != NULL) return g_strdup(default_prefix); else return NULL; } dir1 = g_path_get_dirname(exe); dir2 = g_path_get_dirname(dir1); g_free(dir1); return dir2; } /** Locate the application's binary folder. * * The path is generated by the following pseudo-code evaluation: * \code * prefix + "/bin" * \endcode * * @param default_bin_dir A default path which will used as fallback. * @return A string containing the bin folder's path, which must be freed when * no longer necessary. If BinReloc is not initialized, or if the * initialization function failed, then a copy of default_bin_dir will * be returned. If default_bin_dir is NULL, then NULL will be returned. */ gchar *gbr_find_bin_dir(const gchar * default_bin_dir) { gchar *prefix, *dir; prefix = gbr_find_prefix(NULL); if (prefix == NULL) { /* BinReloc not initialized. */ if (default_bin_dir != NULL) return g_strdup(default_bin_dir); else return NULL; } dir = g_build_filename(prefix, "bin", NULL); g_free(prefix); return dir; } /** Locate the application's superuser binary folder. * * The path is generated by the following pseudo-code evaluation: * \code * prefix + "/sbin" * \endcode * * @param default_sbin_dir A default path which will used as fallback. * @return A string containing the sbin folder's path, which must be freed when * no longer necessary. If BinReloc is not initialized, or if the * initialization function failed, then a copy of default_sbin_dir will * be returned. If default_bin_dir is NULL, then NULL will be returned. */ gchar *gbr_find_sbin_dir(const gchar * default_sbin_dir) { gchar *prefix, *dir; prefix = gbr_find_prefix(NULL); if (prefix == NULL) { /* BinReloc not initialized. */ if (default_sbin_dir != NULL) return g_strdup(default_sbin_dir); else return NULL; } dir = g_build_filename(prefix, "sbin", NULL); g_free(prefix); return dir; } /** Locate the application's data folder. * * The path is generated by the following pseudo-code evaluation: * \code * prefix + "/share" * \endcode * * @param default_data_dir A default path which will used as fallback. * @return A string containing the data folder's path, which must be freed when * no longer necessary. If BinReloc is not initialized, or if the * initialization function failed, then a copy of default_data_dir * will be returned. If default_data_dir is NULL, then NULL will be * returned. */ gchar *gbr_find_data_dir(const gchar * default_data_dir) { gchar *prefix, *dir; prefix = gbr_find_prefix(NULL); if (prefix == NULL) { /* BinReloc not initialized. */ if (default_data_dir != NULL) return g_strdup(default_data_dir); else return NULL; } dir = g_build_filename(prefix, "share", NULL); g_free(prefix); return dir; } /** Locate the application's localization folder. * * The path is generated by the following pseudo-code evaluation: * \code * prefix + "/share/locale" * \endcode * * @param default_locale_dir A default path which will used as fallback. * @return A string containing the localization folder's path, which must be freed when * no longer necessary. If BinReloc is not initialized, or if the * initialization function failed, then a copy of default_locale_dir will be returned. * If default_locale_dir is NULL, then NULL will be returned. */ gchar *gbr_find_locale_dir(const gchar * default_locale_dir) { gchar *data_dir, *dir; data_dir = gbr_find_data_dir(NULL); if (data_dir == NULL) { /* BinReloc not initialized. */ if (default_locale_dir != NULL) return g_strdup(default_locale_dir); else return NULL; } dir = g_build_filename(data_dir, "locale", NULL); g_free(data_dir); return dir; } /** Locate the application's library folder. * * The path is generated by the following pseudo-code evaluation: * \code * prefix + "/lib" * \endcode * * @param default_lib_dir A default path which will used as fallback. * @return A string containing the library folder's path, which must be freed when * no longer necessary. If BinReloc is not initialized, or if the * initialization function failed, then a copy of default_lib_dir will be returned. * If default_lib_dir is NULL, then NULL will be returned. */ gchar *gbr_find_lib_dir(const gchar * default_lib_dir) { gchar *prefix, *dir; prefix = gbr_find_prefix(NULL); if (prefix == NULL) { /* BinReloc not initialized. */ if (default_lib_dir != NULL) return g_strdup(default_lib_dir); else return NULL; } dir = g_build_filename(prefix, LIBDIR, NULL); g_free(prefix); return dir; } /** Locate the application's libexec folder. * * The path is generated by the following pseudo-code evaluation: * \code * prefix + "/libexec" * \endcode * * @param default_libexec_dir A default path which will used as fallback. * @return A string containing the libexec folder's path, which must be freed when * no longer necessary. If BinReloc is not initialized, or if the initialization * function failed, then a copy of default_libexec_dir will be returned. * If default_libexec_dir is NULL, then NULL will be returned. */ gchar *gbr_find_libexec_dir(const gchar * default_libexec_dir) { gchar *prefix, *dir; prefix = gbr_find_prefix(NULL); if (prefix == NULL) { /* BinReloc not initialized. */ if (default_libexec_dir != NULL) return g_strdup(default_libexec_dir); else return NULL; } dir = g_build_filename(prefix, "libexec", NULL); g_free(prefix); return dir; } /** Locate the application's configuration files folder. * * The path is generated by the following pseudo-code evaluation: * \code * prefix + "/etc" * \endcode * * @param default_etc_dir A default path which will used as fallback. * @return A string containing the etc folder's path, which must be freed when * no longer necessary. If BinReloc is not initialized, or if the initialization * function failed, then a copy of default_etc_dir will be returned. * If default_etc_dir is NULL, then NULL will be returned. */ gchar *gbr_find_etc_dir(const gchar * default_etc_dir) { gchar *prefix, *dir; prefix = gbr_find_prefix(NULL); if (prefix == NULL) { /* BinReloc not initialized. */ if (default_etc_dir != NULL) return g_strdup(default_etc_dir); else return NULL; } dir = g_build_filename(prefix, "etc", NULL); g_free(prefix); return dir; } G_END_DECLS #endif /* __BINRELOC_C__ */