diff options
Diffstat (limited to 'hardinfo/binreloc.c')
-rw-r--r-- | hardinfo/binreloc.c | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/hardinfo/binreloc.c b/hardinfo/binreloc.c new file mode 100644 index 00000000..1d1acfe6 --- /dev/null +++ b/hardinfo/binreloc.c @@ -0,0 +1,672 @@ +/* + * BinReloc - a library for creating relocatable executables + * Written by: Hongli Lai <h.lai@chello.nl> + * http://autopackage.org/ + * + * This source code is public domain. You can relicense this code + * under whatever license you want. + * + * See http://autopackage.org/docs/binreloc/ for + * more information and how to use this. + */ + +#ifndef __BINRELOC_C__ +#define __BINRELOC_C__ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#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; + } + +#ifdef ARCH_x86_64 + dir = g_build_filename(prefix, "lib64", NULL); +#else + dir = g_build_filename(prefix, "lib", NULL); +#endif + + 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__ */ |