/* EINA - EFL data type library * Copyright (C) 2011 Carsten Haitzler * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; * if not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #include #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_DLADDR # include #endif #ifdef HAVE_EVIL # include #endif #ifdef HAVE_ESCAPE # include #endif #include "eina_config.h" #include "eina_private.h" #include "eina_alloca.h" #include "eina_log.h" #include "eina_str.h" /* undefs EINA_ARG_NONULL() so NULL checks are not compiled out! */ #include "eina_safety_checks.h" #include "eina_prefix.h" #ifdef _WIN32 # define PSEP_C ';' # define DSEP_C '\\' # define DSEP_S "\\" #else # define PSEP_C ':' # define DSEP_C '/' # define DSEP_S "/" #endif /* _WIN32 */ /*============================================================================* * Local * *============================================================================*/ /** * @cond LOCAL */ struct _Eina_Prefix { char *exe_path; char *prefix_path; char *prefix_path_bin; char *prefix_path_data; char *prefix_path_lib; char *prefix_path_locale; unsigned char fallback : 1; unsigned char no_common_prefix : 1; unsigned char env_used : 1; }; #define STRDUP_REP(x, y) do { if (x) free(x); x = strdup(y); } while (0) #define IF_FREE_NULL(p) do { if (p) { free(p); p = NULL; } } while (0) #ifndef EINA_LOG_COLOR_DEFAULT #define EINA_LOG_COLOR_DEFAULT EINA_COLOR_CYAN #endif #ifdef ERR #undef ERR #endif #define ERR(...) EINA_LOG_DOM_ERR(_eina_prefix_log_dom, __VA_ARGS__) #ifdef WRN #undef WRN #endif #define WRN(...) EINA_LOG_DOM_WARN(_eina_prefix_log_dom, __VA_ARGS__) #ifdef INF #undef INF #endif #define INF(...) EINA_LOG_DOM_INFO(_eina_prefix_log_dom, __VA_ARGS__) #ifdef DBG #undef DBG #endif #define DBG(...) EINA_LOG_DOM_DBG(_eina_prefix_log_dom, __VA_ARGS__) static int _eina_prefix_log_dom = -1; static int _path_join(char *buf, int bufsize, const char *base, const char *extra) { return eina_str_join(buf, bufsize, DSEP_C, base, extra); } static int _path_join_multiple(char *buf, int bufsize, ...) { va_list ap; int used = 0; EINA_SAFETY_ON_NULL_RETURN_VAL(buf, -1); EINA_SAFETY_ON_TRUE_RETURN_VAL(bufsize < 1, -1); va_start(ap, bufsize); while (used < bufsize - 1) { const char *comp = va_arg(ap, const char *); int complen, seplen; if (!comp) break; seplen = (used == 0) ? 0 : 1; complen = strlen(comp); if (seplen + complen >= bufsize -1) { va_end(ap); buf[0] = '\0'; return -1; } if (used > 0) buf[used] = DSEP_C; memcpy(buf + used + seplen, comp, complen); used += complen + seplen; } va_end(ap); buf[used] = '\0'; return used; } static void _path_sep_fix(char *buf) { #ifdef _WIN32 for (; *buf != '\0'; buf++) { if (*buf == '/') *buf = DSEP_C; } #else (void)buf; #endif } static Eina_Bool _path_absolute_check(const char *path) { #ifdef _WIN32 return evil_path_is_absolute(path); #else return (path[0] == DSEP_C); #endif } static int _fallback(Eina_Prefix *pfx, const char *pkg_bin, const char *pkg_lib, const char *pkg_data, const char *pkg_locale, const char *envprefix) { char *p; STRDUP_REP(pfx->prefix_path, pkg_bin); if (!pfx->prefix_path) return 0; p = strrchr(pfx->prefix_path, DSEP_C); if (p) *p = 0; STRDUP_REP(pfx->prefix_path_bin, pkg_bin); STRDUP_REP(pfx->prefix_path_lib, pkg_lib); STRDUP_REP(pfx->prefix_path_data, pkg_data); STRDUP_REP(pfx->prefix_path_locale, pkg_locale); WRN("Could not determine its installed prefix for '%s'\n" " so am falling back on the compiled in default:\n" " %s\n" " implied by the following:\n" " bindir = %s\n" " libdir = %s\n" " datadir = %s\n" " localedir = %s\n" " Try setting the following environment variables:\n" " %s_PREFIX - points to the base prefix of install\n" " or the next 4 variables\n" " %s_BIN_DIR - provide a specific binary directory\n" " %s_LIB_DIR - provide a specific library directory\n" " %s_DATA_DIR - provide a specific data directory\n" " %s_LOCALE_DIR - provide a specific locale directory", envprefix, pfx->prefix_path, pkg_bin, pkg_lib, pkg_data, pkg_locale, envprefix, envprefix, envprefix, envprefix, envprefix); pfx->fallback = 1; return 1; } static int _try_proc(Eina_Prefix *pfx, void *symbol) { #ifndef _WIN32 FILE *f; char buf[4096]; f = fopen("/proc/self/maps", "rb"); if (!f) { WRN("Couldn't read /proc/self/maps to lookup symbol=%p", symbol); return 0; } DBG("Check /proc/self/maps for symbol=%p", symbol); while (fgets(buf, sizeof(buf), f)) { int len; char *p, mode[5] = ""; unsigned long ptr1 = 0, ptr2 = 0; len = strlen(buf); if (buf[len - 1] == '\n') { buf[len - 1] = 0; len--; } if (sscanf(buf, "%lx-%lx %4s", &ptr1, &ptr2, mode) == 3) { if (!strcmp(mode, "r-xp")) { if (((void *)ptr1 <= symbol) && (symbol < (void *)ptr2)) { p = strchr(buf, '/'); if (p) { if (len > 10) { if (!strcmp(buf + len - 10, " (deleted)")) buf[len - 10] = 0; } STRDUP_REP(pfx->exe_path, p); INF("Found %p in /proc/self/maps: %s (%s)", symbol, pfx->exe_path, buf); fclose(f); return 1; } else { DBG("Found %p in /proc/self/maps but not a file (%s)", symbol, buf); break; } } } } } fclose(f); WRN("Couldn't find symbol %p in a file in /proc/self/maps", symbol); return 0; #else return 0; (void)pfx; (void)symbol; #endif } static int _try_argv(Eina_Prefix *pfx, const char *argv0) { char *path, *p, *cp; int len, lenexe; char buf[PATH_MAX], buf2[PATH_MAX]; /* 1. is argv0 abs path? */ if (_path_absolute_check(argv0)) { if (access(argv0, X_OK) == 0) { INF("Executable argv0 is full path = %s", argv0); STRDUP_REP(pfx->exe_path, argv0); return 1; } WRN("Non executable argv0: %s", argv0); return 0; } /* 2. relative path */ if (strchr(argv0, DSEP_C)) { if (getcwd(buf2, sizeof(buf2))) { char joined[PATH_MAX]; _path_join(joined, sizeof(joined), buf2, argv0); if (realpath(joined, buf)) { if (access(buf, X_OK) == 0) { INF("Executable relative argv0=%s, cwd=%s, realpath=%s", argv0, buf2, buf); STRDUP_REP(pfx->exe_path, buf); return 1; } WRN("Non executable relative argv0=%s, cwd=%s, realpath=%s", argv0, buf2, buf); } else WRN("No realpath for argv0=%s, cwd=%s", argv0, buf2); } else WRN("Couldn't get current directory to lookup argv0=%s", argv0); } /* 3. argv0 no path - look in PATH */ path = getenv("PATH"); if (!path) { DBG("No env PATH to lookup argv0=%s", argv0); return 0; } p = path; cp = p; lenexe = strlen(argv0); while ((p = strchr(cp, PSEP_C))) { len = p - cp; if ((len == 0) || (len + lenexe + 2 >= (int)sizeof(buf2))) { cp = p + 1; continue; } strncpy(buf2, cp, len); buf2[len] = DSEP_C; strcpy(buf2 + len + 1, argv0); if (realpath(buf2, buf)) { if (access(buf, X_OK) == 0) { STRDUP_REP(pfx->exe_path, buf); INF("Path %s is executable", pfx->exe_path); return 1; } else DBG("Path not executable %s", buf); } else DBG("No realpath for argv0=%s in %.*s", argv0, len, cp); cp = p + 1; } /* 4. big problems. arg[0] != executable - weird execution */ WRN("Couldn't find argv0=%s in current directory or env PATH=%s", argv0, path); return 0; } static int _get_env_var(char **var, const char *envprefix, const char *envsuffix, const char *prefix, const char *dir) { char env[1024]; const char *s; snprintf(env, sizeof(env), "%s_%s_DIR", envprefix, envsuffix); s = getenv(env); if (s) { INF("Have prefix env %s = %s", env, s); STRDUP_REP(*var, s); return 1; } else if (prefix) { char buf[PATH_MAX]; _path_join(buf, sizeof(buf), prefix, dir); INF("Have %s_PREFIX = %s, use %s = %s", envprefix, prefix, env, buf); STRDUP_REP(*var, buf); return 1; } else { DBG("No env %s_PREFIX or %s for dir '%s'", envprefix, env, dir); return 0; } } static int _get_env_vars(Eina_Prefix *pfx, const char *envprefix, const char *bindir, const char *libdir, const char *datadir, const char *localedir) { char env[1024]; const char *prefix; int ret = 0; snprintf(env, sizeof(env), "%s_PREFIX", envprefix); if ((prefix = getenv(env))) STRDUP_REP(pfx->prefix_path, prefix); ret += _get_env_var(&pfx->prefix_path_bin, envprefix, "BIN", prefix, bindir); ret += _get_env_var(&pfx->prefix_path_lib, envprefix, "LIB", prefix, libdir); ret += _get_env_var(&pfx->prefix_path_data, envprefix, "DATA", prefix, datadir); ret += _get_env_var(&pfx->prefix_path_locale, envprefix, "LOCALE", prefix, localedir); return ret; } static int _common_prefix_find(const char *bin, const char *lib, const char *data, const char *locale) { const char *b = bin; const char *i = lib; const char *d = data; const char *o = locale; for (; (*b) && (*i) && (*d) && (*o); b++, i++, d++, o++) { if (*b != *i) break; if (*b != *d) break; if (*b != *o) break; } return b - bin; } /** * @endcond */ /*============================================================================* * Global * *============================================================================*/ /*============================================================================* * API * *============================================================================*/ EAPI Eina_Prefix * eina_prefix_new(const char *argv0, void *symbol, const char *envprefix, const char *sharedir, const char *magicsharefile, const char *pkg_bin, const char *pkg_lib, const char *pkg_data, const char *pkg_locale) { Eina_Prefix *pfx; char *p, buf[4096], *tmp, *magic = NULL; struct stat st; int prefixlen; const char *bindir = "bin"; const char *libdir = "lib"; const char *datadir = "share"; const char *localedir = "share"; Eina_Bool from_lib = EINA_FALSE, from_bin = EINA_FALSE; DBG("EINA PREFIX: argv0=%s, symbol=%p, magicsharefile=%s, envprefix=%s", argv0, symbol, magicsharefile, envprefix); DBG("EINA PREFIX: share=%s, bin=%s, lib=%s, data=%s, locale=%s", sharedir, pkg_bin, pkg_lib, pkg_data, pkg_locale); EINA_SAFETY_ON_NULL_RETURN_VAL(pkg_bin, NULL); EINA_SAFETY_ON_NULL_RETURN_VAL(pkg_lib, NULL); EINA_SAFETY_ON_NULL_RETURN_VAL(pkg_data, NULL); EINA_SAFETY_ON_NULL_RETURN_VAL(pkg_locale, NULL); pfx = calloc(1, sizeof(Eina_Prefix)); if (!pfx) return NULL; /* if provided with a share dir use datadir/sharedir as the share dir */ if (sharedir) { int len; len = _path_join(buf, sizeof(buf), datadir, sharedir); if (len > 0) { _path_sep_fix(buf + strlen(datadir) + strlen(DSEP_S)); tmp = alloca(len + 1); strcpy(tmp, buf); datadir = tmp; } } if (magicsharefile) { magic = alloca(strlen(magicsharefile)); strcpy(magic, magicsharefile); _path_sep_fix(magic); } /* look at compile-time package bin/lib/datadir etc. and figure out the * bin, lib and data dirs from these, if possible. i.e. * bin = /usr/local/bin * lib = /usr/local/lib * data = /usr/local/share/enlightenment * thus they all have a common prefix string of /usr/local/ and * bindir = bin * libdir = lib * datadir = share/enlightenment * this addresses things like libdir is lib64 or lib32 or other such * junk distributions like to do so then: * bin = /usr/local/bin * lib = /usr/local/lib64 * data = /usr/local/share/enlightenment * then * bindir = bin * libdir = lib64 * datadir = share/enlightennment * in theory this should also work with debians new multiarch style like * bindir = bin * libdir = lib/i386-linux-gnu * or * libdir = lib/x86_64-linux-gnu * all with a common prefix that can be relocated */ prefixlen = _common_prefix_find(pkg_bin, pkg_lib, pkg_data, pkg_locale); if (prefixlen > 0) { bindir = pkg_bin + prefixlen; libdir = pkg_lib + prefixlen; datadir = pkg_data + prefixlen; localedir = pkg_locale + prefixlen; DBG("Prefix common=%.*s, bin=%s, lib=%s, data=%s, locale=%s", prefixlen, pkg_bin, bindir, libdir, datadir, localedir); } /* 3. some galoot thought it awesome not to give us a common prefix at compile time * so fall back to the compile time directories. we are no longer relocatable */ else { STRDUP_REP(pfx->prefix_path_bin, pkg_bin); STRDUP_REP(pfx->prefix_path_lib, pkg_lib); STRDUP_REP(pfx->prefix_path_data, pkg_data); STRDUP_REP(pfx->prefix_path_locale, pkg_locale); pfx->no_common_prefix = 1; DBG("Can't work out a common prefix - compiled in fallback"); } /* if user provides env vars - then use that or also more specific sub * dirs for bin, lib, data and locale */ if ((envprefix) && (_get_env_vars(pfx, envprefix, bindir, libdir, datadir, localedir) > 0)) { pfx->env_used = 1; return pfx; } if (symbol) { #ifdef HAVE_DLADDR Dl_info info_dl; if (dladdr(symbol, &info_dl)) { if (info_dl.dli_fname) { if (_path_absolute_check(info_dl.dli_fname)) { INF("dladdr for symbol=%p: %s", symbol, info_dl.dli_fname); STRDUP_REP(pfx->exe_path, info_dl.dli_fname); from_lib = EINA_TRUE; } else WRN("dladdr for symbol=%p: %s is relative", symbol, info_dl.dli_fname); } else WRN("no dladdr filename for symbol=%p", symbol); } else WRN("no dladdr for symbol=%p", symbol); #endif if (!pfx->exe_path) _try_proc(pfx, symbol); /* no from_lib/from_bin as we're not sure it came from lib or bin! */ } /* no env var or symbol - examine process and possible argv0 */ if ((argv0) && (!pfx->exe_path)) { if (!_try_argv(pfx, argv0)) { WRN("Fallback - couldn't resolve based on argv0=%s", argv0); _fallback(pfx, pkg_bin, pkg_lib, pkg_data, pkg_locale, envprefix); return pfx; } from_bin = EINA_TRUE; } if (!pfx->exe_path) { WRN("Fallback - no variables, symbol or argv0 could be used."); _fallback(pfx, pkg_bin, pkg_lib, pkg_data, pkg_locale, envprefix); return pfx; } /* _exe_path is now a full absolute path TO this exe - figure out rest */ /* if * exe = /blah/whatever/bin/exe * or * exe = /blah/whatever/lib/libexe.so * then * prefix = /blah/whatever * bin_dir = /blah/whatever/bin * data_dir = /blah/whatever/share/enlightenment * lib_dir = /blah/whatever/lib * * new case - debian multiarch goop. * exe = /blah/whatever/lib/arch/libexe.so */ DBG("From exe %s figure out the rest", pfx->exe_path); p = strrchr(pfx->exe_path, DSEP_C); if (p) { p--; while (p >= pfx->exe_path) { if (*p == DSEP_C) { if (pfx->prefix_path) free(pfx->prefix_path); pfx->prefix_path = malloc(p - pfx->exe_path + 1); if (pfx->prefix_path) { Eina_Bool magic_found = EINA_FALSE; int checks_passed = 0; strncpy(pfx->prefix_path, pfx->exe_path, p - pfx->exe_path); pfx->prefix_path[p - pfx->exe_path] = 0; DBG("Have prefix = %s", pfx->prefix_path); /* bin */ _path_join(buf, sizeof(buf), pfx->prefix_path, bindir); STRDUP_REP(pfx->prefix_path_bin, buf); DBG("Have bin = %s", pfx->prefix_path_bin); if ((!from_bin) && (stat(buf, &st) == 0)) checks_passed++; /* lib */ _path_join(buf, sizeof(buf), pfx->prefix_path, libdir); STRDUP_REP(pfx->prefix_path_lib, buf); DBG("Have lib = %s", pfx->prefix_path_lib); if ((!from_lib) && (stat(buf, &st) == 0)) checks_passed++; /* locale */ _path_join(buf, sizeof(buf), pfx->prefix_path, localedir); STRDUP_REP(pfx->prefix_path_locale, buf); DBG("Have locale = %s", pfx->prefix_path_locale); if (stat(buf, &st) == 0) checks_passed++; /* check if magic file is there - then our guess is right */ if (!magic) DBG("No magic file"); else { DBG("Magic = %s", magic); _path_join_multiple(buf, sizeof(buf), pfx->prefix_path, datadir, magic, NULL); DBG("Check in %s", buf); if (stat(buf, &st) == 0) { checks_passed++; magic_found = EINA_TRUE; DBG("Magic path %s stat passed", buf); } else WRN("Missing magic path %s", buf); } if (((!magic) && (checks_passed > 0)) || ((magic) && (magic_found))) { _path_join(buf, sizeof(buf), pfx->prefix_path, datadir); STRDUP_REP(pfx->prefix_path_data, buf); } else { for (;p > pfx->exe_path; p--) { if (*p == DSEP_C) { p--; break; } } if (p > pfx->exe_path) { int newlen = p - pfx->exe_path; DBG("Go back one directory (%.*s)", newlen, pfx->exe_path); continue; } WRN("No Prefix path (exhausted search depth)"); _fallback(pfx, pkg_bin, pkg_lib, pkg_data, pkg_locale, envprefix); } } else { WRN("No Prefix path (alloc fail)"); _fallback(pfx, pkg_bin, pkg_lib, pkg_data, pkg_locale, envprefix); } return pfx; } p--; } } WRN("Final fallback"); _fallback(pfx, pkg_bin, pkg_lib, pkg_data, pkg_locale, envprefix); return pfx; } EAPI void eina_prefix_free(Eina_Prefix *pfx) { EINA_SAFETY_ON_NULL_RETURN(pfx); IF_FREE_NULL(pfx->exe_path); IF_FREE_NULL(pfx->prefix_path); IF_FREE_NULL(pfx->prefix_path_bin); IF_FREE_NULL(pfx->prefix_path_data); IF_FREE_NULL(pfx->prefix_path_lib); IF_FREE_NULL(pfx->prefix_path_locale); free(pfx); } EAPI const char * eina_prefix_get(Eina_Prefix *pfx) { EINA_SAFETY_ON_NULL_RETURN_VAL(pfx, ""); return pfx->prefix_path; } EAPI const char * eina_prefix_bin_get(Eina_Prefix *pfx) { EINA_SAFETY_ON_NULL_RETURN_VAL(pfx, ""); return pfx->prefix_path_bin; } EAPI const char * eina_prefix_lib_get(Eina_Prefix *pfx) { EINA_SAFETY_ON_NULL_RETURN_VAL(pfx, ""); return pfx->prefix_path_lib; } EAPI const char * eina_prefix_data_get(Eina_Prefix *pfx) { EINA_SAFETY_ON_NULL_RETURN_VAL(pfx, ""); return pfx->prefix_path_data; } EAPI const char * eina_prefix_locale_get(Eina_Prefix *pfx) { EINA_SAFETY_ON_NULL_RETURN_VAL(pfx, ""); return pfx->prefix_path_locale; } Eina_Bool eina_prefix_init(void) { _eina_prefix_log_dom = eina_log_domain_register("eina_prefix", EINA_LOG_COLOR_DEFAULT); if (_eina_prefix_log_dom < 0) { EINA_LOG_ERR("Could not register log domain: eina_prefix"); return EINA_FALSE; } return EINA_TRUE; } Eina_Bool eina_prefix_shutdown(void) { eina_log_domain_unregister(_eina_prefix_log_dom); _eina_prefix_log_dom = -1; return EINA_TRUE; }