| /* ********************************************************** |
| * Copyright (c) 2011-2013 Google, Inc. All rights reserved. |
| * Copyright (c) 2010 VMware, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of VMware, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| /* |
| * config.c - platform-independent app config routines |
| */ |
| |
| #include "configure.h" |
| |
| #ifndef PARAMS_IN_REGISTRY /* around whole file */ |
| |
| #include "globals.h" |
| #include "heap.h" |
| #ifdef WINDOWS |
| # include "ntdll.h" |
| #endif |
| |
| #include <string.h> |
| |
| /* DYNAMORIO_VAR_CONFIGDIR is searched first, and then these: */ |
| #ifdef UNIX |
| # define GLOBAL_CONFIG_DIR "/etc/dynamorio" |
| # define LOCAL_CONFIG_ENV "HOME" |
| # define LOCAL_CONFIG_SUBDIR ".dynamorio" |
| # define GLOBAL_CONFIG_SUBDIR "" |
| #else |
| # define LOCAL_CONFIG_ENV "USERPROFILE" |
| # define LOCAL_CONFIG_SUBDIR "dynamorio" |
| # define GLOBAL_CONFIG_SUBDIR "/config" |
| #endif |
| |
| /* We use separate file names to support apps with the same name |
| * that come in different arch flavors |
| */ |
| #define CFG_SFX_64 "config64" |
| #define CFG_SFX_32 "config32" |
| #ifdef X64 |
| # define CFG_SFX CFG_SFX_64 |
| #else |
| # define CFG_SFX CFG_SFX_32 |
| #endif |
| |
| /* no logfile is set up yet */ |
| #if defined(DEBUG) && defined(INTERNAL) |
| # define VERBOSE 0 |
| #else |
| # define VERBOSE 0 |
| #endif |
| |
| #if defined(NOT_DYNAMORIO_CORE) || defined(NOT_DYNAMORIO_CORE_PROPER) |
| /* for linking into linux preload we do use libc for now (xref i#46/PR 206369) */ |
| # undef ASSERT |
| # undef ASSERT_NOT_IMPLEMENTED |
| # undef ASSERT_NOT_TESTED |
| # undef ASSERT_NOT_REACHED |
| # undef FATAL_USAGE_ERROR |
| # undef USAGE_ERROR |
| # define ASSERT(x) /* nothing */ |
| # define ASSERT_NOT_IMPLEMENTED(x) /* nothing */ |
| # define ASSERT_NOT_TESTED(x) /* nothing */ |
| # define ASSERT_NOT_REACHED() /* nothing */ |
| # define FATAL_USAGE_ERROR(x, ...) /* nothing */ |
| # define USAGE_ERROR(x, ...) /* nothing */ |
| # ifdef WINDOWS |
| # if VERBOSE |
| extern void display_verbose_message(char *format, ...); |
| # define print_file(f, ...) display_verbose_message(__VA_ARGS__) |
| # else |
| # define print_file(...) /* nothing */ |
| # endif |
| # else |
| # define print_file(...) fprintf(__VA_ARGS__) |
| # endif |
| # undef STDERR |
| # define STDERR stderr |
| # undef our_snprintf |
| # ifdef WINDOWS |
| # define our_snprintf _snprintf |
| # else |
| # define our_snprintf snprintf |
| # endif |
| # undef DECLARE_NEVERPROT_VAR |
| # define DECLARE_NEVERPROT_VAR(var, val) var = (val) |
| #endif |
| |
| #ifdef DEBUG |
| DECLARE_NEVERPROT_VAR(static int infolevel, VERBOSE); |
| # define INFO(level, fmt, ...) do { \ |
| if (infolevel >= (level)) print_file(STDERR, "<"fmt">\n", __VA_ARGS__); \ |
| } while (0); |
| #else |
| # define INFO(level, fmt, ...) /* nothing */ |
| #endif |
| |
| /* we store values for each of these vars: */ |
| static const char *const config_var[] = { |
| DYNAMORIO_VAR_HOME, |
| DYNAMORIO_VAR_LOGDIR, |
| DYNAMORIO_VAR_OPTIONS, |
| DYNAMORIO_VAR_AUTOINJECT, |
| DYNAMORIO_VAR_UNSUPPORTED, |
| DYNAMORIO_VAR_RUNUNDER, |
| DYNAMORIO_VAR_CMDLINE, |
| DYNAMORIO_VAR_ONCRASH, |
| DYNAMORIO_VAR_SAFEMARKER, |
| DYNAMORIO_VAR_CACHE_ROOT, |
| DYNAMORIO_VAR_CACHE_SHARED, |
| }; |
| #ifdef WINDOWS |
| static const wchar_t *const w_config_var[] = { |
| L_DYNAMORIO_VAR_HOME, |
| L_DYNAMORIO_VAR_LOGDIR, |
| L_DYNAMORIO_VAR_OPTIONS, |
| L_DYNAMORIO_VAR_AUTOINJECT, |
| L_DYNAMORIO_VAR_UNSUPPORTED, |
| L_DYNAMORIO_VAR_RUNUNDER, |
| L_DYNAMORIO_VAR_CMDLINE, |
| L_DYNAMORIO_VAR_ONCRASH, |
| L_DYNAMORIO_VAR_SAFEMARKER, |
| L_DYNAMORIO_VAR_CACHE_ROOT, |
| L_DYNAMORIO_VAR_CACHE_SHARED, |
| }; |
| #endif |
| #define NUM_CONFIG_VAR (sizeof(config_var)/sizeof(config_var[0])) |
| |
| /* we want to read config values prior to setting up heap so all data |
| * is static |
| */ |
| typedef struct _config_val_t { |
| char val[MAX_CONFIG_VALUE]; |
| /* distinguish set to "" from never set */ |
| bool has_value; |
| /* identify which level: app-specific, default, from env */ |
| bool app_specific; |
| bool from_env; |
| } config_val_t; |
| |
| typedef struct _config_vals_t { |
| config_val_t vals[NUM_CONFIG_VAR]; |
| } config_vals_t; |
| |
| typedef struct _config_info_t { |
| char fname_app[MAXIMUM_PATH]; |
| char fname_default[MAXIMUM_PATH]; |
| /* Two modes: if query != NULL, we search for var with name==query |
| * and will fill in answer. if query == NULL, then we fill in v. |
| * Perhaps it would be worth the complexity to move fname_* to |
| * config_vals_t and reduce stack space of config_info_t (can't |
| * use heap very easily since used by preinject, injector, and DR). |
| */ |
| const char *query; |
| union { |
| struct { |
| config_val_t answer; |
| bool have_answer; |
| } q; |
| config_vals_t *v; |
| } u; |
| bool has_1config; |
| } config_info_t; |
| |
| static config_vals_t myvals; |
| static config_info_t config; |
| static bool config_initialized; |
| |
| #if !defined(NOT_DYNAMORIO_CORE) && !defined(NOT_DYNAMORIO_CORE_PROPER) |
| /* i#521: Re-reading the config takes long enough that we can't leave the data |
| * section unprotected while we do it. We initialize this pointer to a heap |
| * allocated config_vals_t struct and use that for doing re-reads. |
| */ |
| static config_info_t *config_reread_info; |
| static config_vals_t *config_reread_vals; |
| #endif /* !NOT_DYNAMORIO_CORE && !NOT_DYNAMORIO_CORE_PROPER */ |
| |
| const char * |
| my_getenv(IF_WINDOWS_ELSE_NP(const wchar_t *, const char *) var, char *buf, size_t bufsz) |
| { |
| #ifdef UNIX |
| return getenv(var); |
| #else |
| wchar_t wbuf[MAX_CONFIG_VALUE]; |
| if (env_get_value(var, wbuf, BUFFER_SIZE_BYTES(wbuf))) { |
| NULL_TERMINATE_BUFFER(wbuf); |
| snprintf(buf, bufsz, "%ls", wbuf); |
| buf[bufsz - 1] ='\0'; |
| return buf; |
| } |
| return NULL; |
| #endif |
| } |
| |
| const char * |
| get_config_val_ex(const char *var, bool *app_specific, bool *from_env) |
| { |
| uint i; |
| config_info_t *cfg = &config; |
| ASSERT(var != NULL); |
| ASSERT(cfg->u.v != NULL); |
| /* perf: we could stick the names in a hashtable */ |
| for (i = 0; i < NUM_CONFIG_VAR; i++) { |
| if (strstr(var, config_var[i]) == var) { |
| if (cfg->u.v->vals[i].has_value) { |
| if (app_specific != NULL) |
| *app_specific = cfg->u.v->vals[i].app_specific; |
| if (from_env != NULL) |
| *from_env = cfg->u.v->vals[i].from_env; |
| return (const char *) cfg->u.v->vals[i].val; |
| } else |
| return NULL; |
| } |
| } |
| return NULL; |
| } |
| |
| const char * |
| get_config_val(const char *var) |
| { |
| return get_config_val_ex(var, NULL, NULL); |
| } |
| |
| static void |
| set_config_from_env(config_info_t *cfg) |
| { |
| uint i; |
| char buf[MAX_CONFIG_VALUE]; |
| /* perf: we could stick the names in a hashtable */ |
| for (i = 0; i < NUM_CONFIG_VAR; i++) { |
| /* env vars set any unset vars (lower priority than config file) */ |
| if (!cfg->u.v->vals[i].has_value) { |
| const char *env = my_getenv(IF_WINDOWS_ELSE(w_config_var[i], config_var[i]), |
| buf, BUFFER_SIZE_BYTES(buf)); |
| if (env != NULL) { |
| strncpy(cfg->u.v->vals[i].val, env, |
| BUFFER_SIZE_ELEMENTS(cfg->u.v->vals[i].val)); |
| cfg->u.v->vals[i].has_value = true; |
| cfg->u.v->vals[i].app_specific = false; |
| cfg->u.v->vals[i].from_env = true; |
| INFO(1, "setting %s from env: \"%s\"", config_var[i], env); |
| } |
| } |
| } |
| } |
| |
| static void |
| process_config_line(char *line, config_info_t *cfg, bool app_specific, bool overwrite) |
| { |
| uint i; |
| ASSERT(line != NULL); |
| if (cfg->query != NULL) { |
| if (strstr(line, cfg->query) == line) { |
| char *val = strchr(line, '='); |
| if (val != NULL && |
| /* see comment below */ |
| val == line + strlen(cfg->query)) { |
| if (strlen(val + 1) >= BUFFER_SIZE_ELEMENTS(cfg->u.q.answer.val)) { |
| /* not FATAL so release build will continue */ |
| USAGE_ERROR("Config value for %s too long: truncating", cfg->query); |
| } |
| strncpy(cfg->u.q.answer.val, val + 1, |
| BUFFER_SIZE_ELEMENTS(cfg->u.q.answer.val)); |
| cfg->u.q.answer.app_specific = app_specific; |
| cfg->u.q.answer.from_env = false; |
| cfg->u.q.have_answer = true; |
| } |
| } |
| return; |
| } |
| /* perf: we could stick the names in a hashtable */ |
| for (i = 0; i < NUM_CONFIG_VAR; i++) { |
| if (strstr(line, config_var[i]) == line) { |
| char *val = strchr(line, '='); |
| if (val == NULL || |
| /* we don't have any vars that are prefixes of others so we |
| * can do a hard match on whole var. |
| * for parsing simplicity we don't allow whitespace before '='. |
| */ |
| val != line + strlen(config_var[i])) { |
| if (cfg->query == NULL) { /* only complain about this process */ |
| FATAL_USAGE_ERROR(ERROR_CONFIG_FILE_INVALID, 3, |
| get_application_name(), get_application_pid(), line); |
| ASSERT_NOT_REACHED(); |
| } |
| } else if (!cfg->u.v->vals[i].has_value || overwrite) { |
| if (strlen(val + 1) >= BUFFER_SIZE_ELEMENTS(cfg->u.v->vals[i].val)) { |
| /* not FATAL so release build will continue */ |
| USAGE_ERROR("Config value for %s too long: truncating", config_var[i]); |
| } |
| strncpy(cfg->u.v->vals[i].val, val + 1, |
| BUFFER_SIZE_ELEMENTS(cfg->u.v->vals[i].val)); |
| cfg->u.v->vals[i].has_value = true; |
| cfg->u.v->vals[i].app_specific = app_specific; |
| cfg->u.v->vals[i].from_env = false; |
| INFO(1, "setting %s from file: \"%s\"", config_var[i], |
| cfg->u.v->vals[i].val); |
| } |
| } |
| } |
| } |
| |
| static void |
| read_config_file(file_t f, config_info_t *cfg, bool app_specific, bool overwrite) |
| { |
| /* we are single-threaded for init, and config_reread() holds the |
| * options lock, but get_config_val_other() is called when child |
| * processes are created and thus other threads are around: so use |
| * a smaller buffer. these files are pretty small anyway. but, our |
| * buffer needs to hold at least one full line: we assume var name plus |
| * '=' plus newline chars < 128. |
| */ |
| # define BUFSIZE (MAX_CONFIG_VALUE+128) |
| char buf[BUFSIZE]; |
| |
| char *line, *newline = NULL; |
| ssize_t bufread = 0, bufwant; |
| ssize_t len; |
| |
| while (true) { |
| /* break file into lines */ |
| if (newline == NULL || newline == &BUFFER_LAST_ELEMENT(buf)) { |
| bufwant = BUFSIZE-1; |
| bufread = os_read(f, buf, bufwant); |
| ASSERT(bufread <= bufwant); |
| if (bufread <= 0) |
| break; |
| buf[bufread] = '\0'; |
| newline = strchr(buf, '\n'); |
| line = buf; |
| } else { |
| line = newline + 1; |
| newline = strchr(line, '\n'); |
| if (newline == NULL) { |
| /* shift 1st part of line to start of buf, then read in rest */ |
| /* the memory for the processed part can be reused */ |
| bufwant = line - buf; |
| ASSERT(bufwant <= bufread); |
| len = bufread - bufwant; /* what is left from last time */ |
| /* using memmove since strings can overlap */ |
| if (len > 0) |
| memmove(buf, line, len); |
| bufread = os_read(f, buf+len, bufwant); |
| ASSERT(bufread <= bufwant); |
| if (bufread <= 0) |
| break; |
| bufread += len; /* bufread is total in buf */ |
| buf[bufread] = '\0'; |
| newline = strchr(buf, '\n'); |
| line = buf; |
| } |
| } |
| /* buffer is big enough to hold at least one line */ |
| if (newline == NULL) { |
| /* only complain in debug build */ |
| USAGE_ERROR("Config file line \"%.20s...\" too long: truncating", line); |
| NULL_TERMINATE_BUFFER(buf); |
| newline = &BUFFER_LAST_ELEMENT(buf); |
| } |
| *newline = '\0'; |
| /* handle \r\n line endings */ |
| if (newline > line && *(newline-1) == '\r') |
| *(newline-1) = '\0'; |
| /* now we have one line */ |
| INFO(3, "config file line: \"%s\"", line); |
| /* we support blank lines and comments */ |
| if (line[0] == '\0' || line[0] == '#') |
| continue; |
| process_config_line(line, cfg, app_specific, overwrite); |
| if (cfg->query != NULL && cfg->u.q.have_answer) |
| break; |
| } |
| } |
| |
| static void |
| config_read(config_info_t *cfg, const char *appname_in, process_id_t pid, const char *sfx) |
| { |
| file_t f_app = INVALID_FILE, f_default = INVALID_FILE; |
| const char *local, *global; |
| const char *appname = appname_in; |
| char buf[MAXIMUM_PATH]; |
| bool check_global = true; |
| #ifdef WINDOWS |
| int retval; |
| #endif |
| ASSERT(cfg->query != NULL || cfg->u.v != NULL); |
| /* for now we only support config files by short name: we'll see |
| * whether we need to also support full paths |
| */ |
| if (appname == NULL) |
| appname = get_application_short_name(); |
| /* try in precedence order to find a config file. |
| * if app-specific exists, default at that level is also read to fill in any |
| * unspecified values. |
| * env vars are always read and used to fill in any unspecified values. |
| * Custom takes precedence over default local. |
| * if local exists, global is NOT read. |
| */ |
| local = my_getenv(L_IF_WIN(DYNAMORIO_VAR_CONFIGDIR), buf, BUFFER_SIZE_BYTES(buf)); |
| if (local == NULL) |
| local = my_getenv(L_IF_WIN(LOCAL_CONFIG_ENV), buf, BUFFER_SIZE_BYTES(buf)); |
| if (local != NULL) { |
| process_id_t pid_to_check = pid; |
| if (pid == 0 && cfg == &config) |
| pid_to_check = get_process_id(); |
| if (pid_to_check != 0) { |
| /* 1) <local>/appname.<pid>.1config |
| * only makes sense for main config for this process |
| */ |
| snprintf(cfg->fname_app, BUFFER_SIZE_ELEMENTS(cfg->fname_app), |
| "%s/%s/%s.%d.1%s", |
| local, LOCAL_CONFIG_SUBDIR, appname, pid_to_check, sfx); |
| NULL_TERMINATE_BUFFER(cfg->fname_app); |
| INFO(2, "trying config file %s", cfg->fname_app); |
| f_app = os_open(cfg->fname_app, OS_OPEN_READ); |
| if (f_app != INVALID_FILE) |
| cfg->has_1config = true; /* one-time file */ |
| } |
| /* 2) <local>/appname.config */ |
| if (f_app == INVALID_FILE) { |
| snprintf(cfg->fname_app, BUFFER_SIZE_ELEMENTS(cfg->fname_app), "%s/%s/%s.%s", |
| local, LOCAL_CONFIG_SUBDIR, appname, sfx); |
| NULL_TERMINATE_BUFFER(cfg->fname_app); |
| INFO(2, "trying config file %s", cfg->fname_app); |
| f_app = os_open(cfg->fname_app, OS_OPEN_READ); |
| } |
| /* 3) <local>/default.0config */ |
| if (f_default == INVALID_FILE) { |
| snprintf(cfg->fname_default, BUFFER_SIZE_ELEMENTS(cfg->fname_default), |
| "%s/%s/default.0%s", |
| local, LOCAL_CONFIG_SUBDIR, sfx); |
| NULL_TERMINATE_BUFFER(cfg->fname_default); |
| INFO(2, "trying config file %s", cfg->fname_default); |
| f_default = os_open(cfg->fname_default, OS_OPEN_READ); |
| } |
| } |
| #ifdef WINDOWS |
| /* on Windows the global dir is <installbase>/config/ */ |
| retval = get_parameter_from_registry(L_DYNAMORIO_VAR_HOME, |
| buf, BUFFER_SIZE_BYTES(buf)); |
| NULL_TERMINATE_BUFFER(buf); |
| check_global = IS_GET_PARAMETER_SUCCESS(retval); |
| global = buf; |
| #else |
| global = GLOBAL_CONFIG_DIR; |
| #endif |
| if (check_global) { |
| /* 4) <global>/appname.config */ |
| if (f_app == INVALID_FILE) { |
| snprintf(cfg->fname_app, BUFFER_SIZE_ELEMENTS(cfg->fname_app), |
| "%s%s/%s.%s", |
| global, GLOBAL_CONFIG_SUBDIR, appname, sfx); |
| NULL_TERMINATE_BUFFER(cfg->fname_app); |
| INFO(2, "trying config file %s", cfg->fname_app); |
| f_app = os_open(cfg->fname_app, OS_OPEN_READ); |
| } |
| /* 5) <global>/default.0config */ |
| if (f_default == INVALID_FILE) { |
| snprintf(cfg->fname_default, BUFFER_SIZE_ELEMENTS(cfg->fname_default), |
| "%s%s/default.0%s", global, GLOBAL_CONFIG_SUBDIR, sfx); |
| NULL_TERMINATE_BUFFER(cfg->fname_default); |
| INFO(2, "trying config file %s", cfg->fname_default); |
| f_default = os_open(cfg->fname_default, OS_OPEN_READ); |
| } |
| } |
| if (f_app != INVALID_FILE) { |
| INFO(1, "reading app config file %s", cfg->fname_app); |
| read_config_file(f_app, cfg, true, false); |
| os_close(f_app); |
| } else |
| INFO(1, "WARNING: no app config file found%s", ""); |
| if (f_default != INVALID_FILE) { |
| INFO(1, "reading default config file %s", cfg->fname_default); |
| read_config_file(f_default, cfg, false, false); |
| os_close(f_default); |
| } else |
| INFO(1, "no default config file found%s", ""); |
| /* 6) env vars fill in any still-unset values */ |
| if (appname_in == NULL) /* only consider env for cur process */ |
| set_config_from_env(cfg); |
| } |
| |
| /* up to caller to synchronize */ |
| /* no support for otherarch */ |
| void |
| config_reread(void) |
| { |
| #if !defined(NOT_DYNAMORIO_CORE) && !defined(NOT_DYNAMORIO_CORE_PROPER) |
| file_t f; |
| config_info_t *tmp_config; |
| |
| if (config_reread_vals != NULL) { |
| /* Re-reading the config file is reasonably fast, but not fast enough to |
| * leave the data section unprotected without hitting curiosities about |
| * datasec_not_prot. |
| */ |
| tmp_config = config_reread_info; |
| memcpy(config_reread_info, &config, sizeof(*config_reread_info)); |
| ASSERT(config_reread_info->u.v == &myvals); |
| memcpy(config_reread_vals, &myvals, sizeof(*config_reread_vals)); |
| config_reread_info->u.v = config_reread_vals; |
| } else { |
| SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT); |
| tmp_config = &config; |
| } |
| |
| if (tmp_config->fname_app[0] != '\0') { |
| f = os_open(tmp_config->fname_app, OS_OPEN_READ); |
| if (f != INVALID_FILE) { |
| INFO(3, "re-reading app config file %s", tmp_config->fname_app); |
| DODEBUG({ infolevel -= 2; }); |
| read_config_file(f, tmp_config, true, true); |
| DODEBUG({ infolevel += 2; }); |
| os_close(f); |
| } else |
| INFO(1, "WARNING: unable to re-read config file %s", |
| tmp_config->fname_app); |
| } |
| if (tmp_config->fname_default[0] != '\0') { |
| f = os_open(tmp_config->fname_default, OS_OPEN_READ); |
| if (f != INVALID_FILE) { |
| INFO(3, "re-reading default config file %s", |
| tmp_config->fname_default); |
| DODEBUG({ infolevel -= 2; }); |
| read_config_file(f, tmp_config, true, true); |
| DODEBUG({ infolevel += 2; }); |
| os_close(f); |
| } else |
| INFO(1, "WARNING: unable to re-read config file %s", |
| tmp_config->fname_default); |
| } |
| /* 6) env vars fill in any still-unset values */ |
| set_config_from_env(tmp_config); |
| |
| if (config_reread_vals != NULL) { |
| /* Unprotect the data section and copy the config results into the real |
| * config. Only the values should change, config_info_t should stay the |
| * same. |
| */ |
| SELF_UNPROTECT_DATASEC(DATASEC_RARELY_PROT); |
| memcpy(&myvals, config_reread_vals, sizeof(myvals)); |
| SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT); |
| } else { |
| SELF_PROTECT_DATASEC(DATASEC_RARELY_PROT); |
| } |
| #else |
| ASSERT_NOT_REACHED(); |
| #endif |
| } |
| |
| /* Since querying for other arch or other app typically asks just about |
| * one var in isolation, and is rare, rather than reading in the whole |
| * thing and requiring a 6K struct we read the files looking for just |
| * the var in question. |
| */ |
| static bool |
| get_config_val_other(const char *appname, process_id_t pid, const char *sfx, |
| const char *var, char *val, size_t valsz, |
| bool *app_specific, bool *from_env, bool *from_1config) |
| { |
| /* Can't use heap very easily since used by preinject, injector, and DR |
| * so we use a stack var. WARNING: this is about 1.5K, and config_read |
| * uses another 1.2K. |
| */ |
| config_info_t info; |
| memset(&info, 0, sizeof(info)); |
| info.query = var; |
| config_read(&info, appname, pid, sfx); |
| if (info.u.q.have_answer) { |
| if (valsz > BUFFER_SIZE_ELEMENTS(info.u.q.answer.val)) |
| valsz = BUFFER_SIZE_ELEMENTS(info.u.q.answer.val); |
| strncpy(val, info.u.q.answer.val, valsz); |
| val[valsz-1] = '\0'; |
| if (app_specific != NULL) |
| *app_specific = info.u.q.answer.app_specific; |
| if (from_env != NULL) |
| *from_env = info.u.q.answer.from_env; |
| if (from_1config != NULL) |
| *from_1config = info.has_1config; |
| } |
| return info.u.q.have_answer; |
| } |
| |
| bool |
| get_config_val_other_app(const char *appname, process_id_t pid, |
| dr_platform_t platform, |
| const char *var, char *val, size_t valsz, |
| bool *app_specific, bool *from_env, bool *from_1config) |
| { |
| const char *sfx;; |
| switch (platform) { |
| case DR_PLATFORM_DEFAULT: sfx = CFG_SFX; break; |
| case DR_PLATFORM_32BIT: sfx = CFG_SFX_32; break; |
| case DR_PLATFORM_64BIT: sfx = CFG_SFX_64; break; |
| default: |
| return false; /* invalid parms */ |
| } |
| return get_config_val_other(appname, pid, sfx, var, val, valsz, |
| app_specific, from_env, from_1config); |
| } |
| |
| bool |
| get_config_val_other_arch(const char *var, char *val, size_t valsz, |
| bool *app_specific, bool *from_env, bool *from_1config) |
| { |
| return get_config_val_other(NULL, 0, IF_X64_ELSE(CFG_SFX_32, CFG_SFX_64), |
| var, val, valsz, app_specific, from_env, from_1config); |
| } |
| |
| void |
| config_init(void) |
| { |
| config.u.v = &myvals; |
| config_read(&config, NULL, 0, CFG_SFX); |
| config_initialized = true; |
| |
| #ifndef NOT_DYNAMORIO_CORE_PROPER |
| /* i#1271: to avoid leaving a stale 1config file behind if this process |
| * crashes w/o a clean exit, we give up on re-reading the file and delete |
| * it now. It's an anonymous file anyway and not meant for manual updates. |
| * The user could override the dynamic_options by re-specifying in |
| * the option string, if desired, and re-create the 1config manually. |
| */ |
| if (config.has_1config) { |
| INFO(2, "deleting config file %s", config.fname_app); |
| os_delete_file(config.fname_app); |
| dynamo_options.dynamic_options = false; |
| } |
| /* we ignore otherarch having 1config */ |
| #endif |
| } |
| |
| #ifndef NOT_DYNAMORIO_CORE_PROPER |
| /* To support re-reading config, we need to heap allocate a config_vals_t array, |
| * which we can leave unprotected. |
| */ |
| void |
| config_heap_init(void) |
| { |
| config_reread_info = |
| (config_info_t *) global_heap_alloc(sizeof(*config_reread_info) |
| HEAPACCT(ACCT_OTHER)); |
| config_reread_vals = |
| (config_vals_t *) global_heap_alloc(sizeof(*config_reread_vals) |
| HEAPACCT(ACCT_OTHER)); |
| } |
| |
| void |
| config_heap_exit(void) |
| { |
| void *tmp; |
| if (config_reread_info != NULL) { |
| tmp = config_reread_info; |
| config_reread_info = NULL; |
| global_heap_free(tmp, sizeof(*config_reread_info) HEAPACCT(ACCT_OTHER)); |
| } |
| if (config_reread_vals != NULL) { |
| tmp = config_reread_vals; |
| config_reread_vals = NULL; |
| global_heap_free(tmp, sizeof(*config_reread_vals) HEAPACCT(ACCT_OTHER)); |
| } |
| } |
| #endif |
| |
| void |
| config_exit(void) |
| { |
| /* nothing -- so not called on fast exit */ |
| } |
| |
| /* Our parameters (option string, logdir, etc.) can be configured |
| * through files or environment variables. |
| * For the old registry-based scheme, define PARAMS_IN_REGISTRY. |
| * value is a buffer allocated by the caller to hold the |
| * resulting value. |
| */ |
| int |
| get_parameter_ex(const char *name, char *value, int maxlen, bool ignore_cache) |
| { |
| const char *val; |
| if (!config_initialized) |
| return GET_PARAMETER_FAILURE; |
| if (ignore_cache) |
| config_reread(); |
| val = get_config_val(name); |
| /* env var has top priority, then registry */ |
| if (val != NULL) { |
| strncpy(value, val, maxlen-1); |
| value[maxlen-1] = '\0'; /* if max no null */ |
| /* we do not return GET_PARAMETER_NOAPPSPECIFIC like PARAMS_IN_REGISTRY |
| * does: caller should use get_config_val_ex instead |
| */ |
| return GET_PARAMETER_SUCCESS; |
| } |
| return GET_PARAMETER_FAILURE; |
| } |
| |
| int |
| get_parameter(const char *name, char *value, int maxlen) |
| { |
| return get_parameter_ex(name, value, maxlen, false); |
| } |
| |
| int |
| get_unqualified_parameter(const char *name, char *value, int maxlen) |
| { |
| /* we don't use qualified names w/ our config files yet */ |
| return get_parameter(name, value, maxlen); |
| } |
| |
| #else /* !PARAMS_IN_REGISTRY around whole file */ |
| |
| void |
| config_init(void) |
| { |
| } |
| |
| void |
| config_exit(void) |
| { |
| } |
| |
| #endif /* !PARAMS_IN_REGISTRY around whole file */ |