From c2d70945dce9cb09d5d7120d6e9ddf7faac8d230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20H=C3=B6gberg?= Date: Wed, 22 Nov 2017 13:19:57 +0100 Subject: Replace the libc environment with a thread-safe emulation putenv(3) and friends aren't thread-safe regardless of how you slice it; a global lock around all environment operations (like before) keeps things safe as far as our own operations go, but we have absolutely no control over what libc or a library dragged in by a driver/NIF does -- they're free to call getenv(3) or putenv(3) without honoring our lock. This commit solves this by setting up an "emulated" environment which can't be touched without going through our interfaces. Third-party libraries can still shoot themselves in the foot but benign uses of os:putenv/2 will no longer risk crashing the emulator. --- erts/emulator/sys/common/erl_osenv.c | 396 +++++++++++++++++++++++++ erts/emulator/sys/common/erl_osenv.h | 121 ++++++++ erts/emulator/sys/unix/erl_unix_sys.h | 2 +- erts/emulator/sys/unix/sys.c | 127 +------- erts/emulator/sys/unix/sys_drivers.c | 183 ++++-------- erts/emulator/sys/unix/sys_env.c | 133 +++++++++ erts/emulator/sys/win32/sys.c | 87 ++++-- erts/emulator/sys/win32/sys_env.c | 531 ++++++++++++++-------------------- 8 files changed, 989 insertions(+), 591 deletions(-) create mode 100644 erts/emulator/sys/common/erl_osenv.c create mode 100644 erts/emulator/sys/common/erl_osenv.h create mode 100644 erts/emulator/sys/unix/sys_env.c (limited to 'erts/emulator/sys') diff --git a/erts/emulator/sys/common/erl_osenv.c b/erts/emulator/sys/common/erl_osenv.c new file mode 100644 index 0000000000..9f54d1dff0 --- /dev/null +++ b/erts/emulator/sys/common/erl_osenv.c @@ -0,0 +1,396 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2017. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + */ + +#include "erl_osenv.h" + +#include "global.h" +#include "erl_alloc.h" +#include "erl_process.h" + +#define STACKBUF_SIZE (512) + +typedef struct __env_rbtnode_t { + struct __env_rbtnode_t *parent; + struct __env_rbtnode_t *left; + struct __env_rbtnode_t *right; + + int is_red; + + erts_osenv_data_t key; + erts_osenv_data_t value; +} env_rbtnode_t; + +#define ERTS_RBT_PREFIX env +#define ERTS_RBT_T env_rbtnode_t +#define ERTS_RBT_KEY_T erts_osenv_data_t +#define ERTS_RBT_FLAGS_T int +#define ERTS_RBT_INIT_EMPTY_TNODE(T) \ + do { \ + (T)->parent = NULL; \ + (T)->left = NULL; \ + (T)->right = NULL; \ + (T)->is_red = 0; \ + } while(0) +#define ERTS_RBT_IS_RED(T) ((T)->is_red) +#define ERTS_RBT_SET_RED(T) ((T)->is_red = 1) +#define ERTS_RBT_IS_BLACK(T) (!ERTS_RBT_IS_RED(T)) +#define ERTS_RBT_SET_BLACK(T) ((T)->is_red = 0) +#define ERTS_RBT_GET_FLAGS(T) ((T)->is_red) +#define ERTS_RBT_SET_FLAGS(T, F) ((T)->is_red = F) +#define ERTS_RBT_GET_PARENT(T) ((T)->parent) +#define ERTS_RBT_SET_PARENT(T, P) ((T)->parent = P) +#define ERTS_RBT_GET_RIGHT(T) ((T)->right) +#define ERTS_RBT_SET_RIGHT(T, R) ((T)->right = (R)) +#define ERTS_RBT_GET_LEFT(T) ((T)->left) +#define ERTS_RBT_SET_LEFT(T, L) ((T)->left = (L)) +#define ERTS_RBT_GET_KEY(T) ((T)->key) +#define ERTS_RBT_IS_LT(KX, KY) (compare_env_keys(KX, KY) < 0) +#define ERTS_RBT_IS_EQ(KX, KY) (compare_env_keys(KX, KY) == 0) +#define ERTS_RBT_WANT_FOREACH_DESTROY +#define ERTS_RBT_WANT_FOREACH +#define ERTS_RBT_WANT_REPLACE +#define ERTS_RBT_WANT_DELETE +#define ERTS_RBT_WANT_INSERT +#define ERTS_RBT_WANT_LOOKUP + +static int compare_env_keys(const erts_osenv_data_t a, const erts_osenv_data_t b); + +#include "erl_rbtree.h" + +static int compare_env_keys(const erts_osenv_data_t a, const erts_osenv_data_t b) { + int relation = sys_memcmp(a.data, b.data, MIN(a.length, b.length)); + + if(relation != 0) { + return relation; + } + + if(a.length < b.length) { + return -1; + } else if(a.length == b.length) { + return 0; + } else { + return 1; + } +} + +static void *convert_value_to_native(Eterm term, char *stackbuf, + int stackbuf_size, Sint *length) { + int encoding; + void *result; + + if(is_atom(term)) { + return NULL; + } + + encoding = erts_get_native_filename_encoding(); + *length = erts_native_filename_need(term, encoding); + + if(*length < 0) { + return NULL; + } else if(*length >= stackbuf_size) { + result = erts_alloc(ERTS_ALC_T_TMP, *length); + } else { + result = stackbuf; + } + + erts_native_filename_put(term, encoding, (byte*)result); + + return result; +} + +static void *convert_key_to_native(Eterm term, char *stackbuf, + int stackbuf_size, Sint *length) { + byte *name_iterator, *name_end; + void *result; + int encoding; + + result = convert_value_to_native(term, stackbuf, stackbuf_size, length); + + if(result == NULL || length == 0) { + return NULL; + } + + encoding = erts_get_native_filename_encoding(); + + name_iterator = (byte*)result; + name_end = &name_iterator[*length]; + +#ifdef __WIN32__ + /* Windows stores per-drive working directories as variables starting with + * '=', so we skip the first character to tolerate that. */ + name_iterator = erts_raw_env_next_char(name_iterator, encoding); +#endif + + while(name_iterator < name_end) { + if(erts_raw_env_char_is_7bit_ascii_char('=', name_iterator, encoding)) { + if(result != stackbuf) { + erts_free(ERTS_ALC_T_TMP, result); + } + + return NULL; + } + + name_iterator = erts_raw_env_next_char(name_iterator, encoding); + } + + return result; +} + +void erts_osenv_init(erts_osenv_t *env) { + env->variable_count = 0; + env->content_size = 0; + env->tree = NULL; +} + +static void destroy_foreach(env_rbtnode_t *node, void *_state) { + erts_free(ERTS_ALC_T_ENVIRONMENT, node); + (void)_state; +} + +void erts_osenv_clear(erts_osenv_t *env) { + env_rbt_foreach_destroy(&env->tree, &destroy_foreach, NULL); + erts_osenv_init(env); +} + +struct __env_merge { + int overwrite_existing; + erts_osenv_t *env; +}; + +static void merge_foreach(env_rbtnode_t *node, void *_state) { + struct __env_merge *state = (struct __env_merge*)(_state); + env_rbtnode_t *existing_node; + + existing_node = env_rbt_lookup(state->env->tree, node->key); + + if(existing_node == NULL || state->overwrite_existing) { + erts_osenv_put_native(state->env, &node->key, &node->value); + } +} + +void erts_osenv_merge(erts_osenv_t *env, const erts_osenv_t *with, int overwrite) { + struct __env_merge merge_state; + + merge_state.overwrite_existing = overwrite; + merge_state.env = env; + + env_rbt_foreach(with->tree, merge_foreach, &merge_state); +} + +struct __env_foreach_term { + erts_osenv_foreach_term_cb_t user_callback; + struct process *process; + void *user_state; +}; + +static void foreach_term_wrapper(env_rbtnode_t *node, void *_state) { + struct __env_foreach_term *state = (struct __env_foreach_term*)_state; + Eterm key, value; + + key = erts_convert_native_to_filename(state->process, + node->key.length, (byte*)node->key.data); + value = erts_convert_native_to_filename(state->process, + node->value.length, (byte*)node->value.data); + + state->user_callback(state->process, state->user_state, key, value); +} + +void erts_osenv_foreach_term(const erts_osenv_t *env, struct process *process, + void *state, erts_osenv_foreach_term_cb_t callback) { + struct __env_foreach_term wrapper_state; + + wrapper_state.user_callback = callback; + wrapper_state.user_state = state; + wrapper_state.process = process; + + env_rbt_foreach(env->tree, foreach_term_wrapper, &wrapper_state); +} + +int erts_osenv_get_term(const erts_osenv_t *env, Process *process, + Eterm key_term, Eterm *out_term) { + char key_stackbuf[STACKBUF_SIZE]; + erts_osenv_data_t key; + int result; + + key.data = convert_key_to_native(key_term, key_stackbuf, + STACKBUF_SIZE, &key.length); + result = -1; + + if(key.data != NULL) { + env_rbtnode_t *node; + + node = env_rbt_lookup(env->tree, key); + result = 0; + + if(node != NULL) { + (*out_term) = erts_convert_native_to_filename(process, + node->value.length, (byte*)node->value.data); + result = 1; + } + + if(key.data != key_stackbuf) { + erts_free(ERTS_ALC_T_TMP, key.data); + } + } + + return result; +} + +int erts_osenv_put_term(erts_osenv_t *env, Eterm key_term, Eterm value_term) { + char key_stackbuf[STACKBUF_SIZE], value_stackbuf[STACKBUF_SIZE]; + erts_osenv_data_t key, value; + int result; + + key.data = convert_key_to_native(key_term, key_stackbuf, + STACKBUF_SIZE, &key.length); + value.data = convert_value_to_native(value_term, value_stackbuf, + STACKBUF_SIZE, &value.length); + result = -1; + + if(value.data != NULL && key.data != NULL) { + result = erts_osenv_put_native(env, &key, &value); + } + + if(value.data != NULL && value.data != value_stackbuf) { + erts_free(ERTS_ALC_T_TMP, value.data); + } + + if(key.data != NULL && key.data != key_stackbuf) { + erts_free(ERTS_ALC_T_TMP, key.data); + } + + return result; +} + +int erts_osenv_unset_term(erts_osenv_t *env, Eterm key_term) { + char key_stackbuf[STACKBUF_SIZE]; + erts_osenv_data_t key; + int result; + + key.data = convert_key_to_native(key_term, key_stackbuf, + STACKBUF_SIZE, &key.length); + result = -1; + + if(key.data != NULL) { + result = erts_osenv_unset_native(env, &key); + + if(key.data != key_stackbuf) { + erts_free(ERTS_ALC_T_TMP, key.data); + } + } + + return result; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct __env_foreach_native { + erts_osenv_foreach_native_cb_t user_callback; + void *user_state; +}; + +static void foreach_native_wrapper(env_rbtnode_t *node, void *_state) { + struct __env_foreach_native *state = (struct __env_foreach_native*)_state; + + state->user_callback(state->user_state, &node->key, &node->value); +} + +void erts_osenv_foreach_native(const erts_osenv_t *env, void *state, + erts_osenv_foreach_native_cb_t callback) { + struct __env_foreach_native wrapper_state; + + wrapper_state.user_callback = callback; + wrapper_state.user_state = state; + + env_rbt_foreach(env->tree, foreach_native_wrapper, &wrapper_state); +} + +int erts_osenv_get_native(const erts_osenv_t *env, + const erts_osenv_data_t *key, + erts_osenv_data_t *value) { + env_rbtnode_t *node = env_rbt_lookup(env->tree, *key); + + if(node != NULL) { + if(value != NULL) { + if(node->value.length > value->length) { + return -1; + } + + sys_memcpy(value->data, node->value.data, node->value.length); + value->length = node->value.length; + } + + return 1; + } + + return 0; +} + +int erts_osenv_put_native(erts_osenv_t *env, const erts_osenv_data_t *key, + const erts_osenv_data_t *value) { + env_rbtnode_t *old_node, *new_node; + + new_node = erts_alloc(ERTS_ALC_T_ENVIRONMENT, sizeof(env_rbtnode_t) + + key->length + value->length); + + new_node->key.data = (char*)(&new_node[1]); + new_node->key.length = key->length; + new_node->value.data = &((char*)new_node->key.data)[key->length]; + new_node->value.length = value->length; + + sys_memcpy(new_node->key.data, key->data, key->length); + sys_memcpy(new_node->value.data, value->data, value->length); + + old_node = env_rbt_lookup(env->tree, *key); + + if(old_node != NULL) { + env->content_size -= old_node->value.length; + env->content_size -= old_node->key.length; + env_rbt_replace(&env->tree, old_node, new_node); + } else { + env_rbt_insert(&env->tree, new_node); + env->variable_count++; + } + + env->content_size += new_node->value.length; + env->content_size += new_node->key.length; + + if(old_node != NULL) { + erts_free(ERTS_ALC_T_ENVIRONMENT, old_node); + } + + return 1; +} + +int erts_osenv_unset_native(erts_osenv_t *env, const erts_osenv_data_t *key) { + env_rbtnode_t *old_node = env_rbt_lookup(env->tree, *key); + + if(old_node != NULL) { + env->content_size -= old_node->value.length; + env->content_size -= old_node->key.length; + env->variable_count -= 1; + + env_rbt_delete(&env->tree, old_node); + erts_free(ERTS_ALC_T_ENVIRONMENT, old_node); + return 1; + } + + return 0; +} diff --git a/erts/emulator/sys/common/erl_osenv.h b/erts/emulator/sys/common/erl_osenv.h new file mode 100644 index 0000000000..4777f2148a --- /dev/null +++ b/erts/emulator/sys/common/erl_osenv.h @@ -0,0 +1,121 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2017. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + */ + +/* This is a replacement for getenv(3) and friends, operating on instances so + * we can keep a common implementation for both the global and local (per-port) + * environments. + * + * The instances are not thread-safe on their own but unlike getenv(3) we're + * guaranteed to be the only user, so placing locks around all our accesses + * will suffice. + * + * Use erts_sys_rwlock_global_osenv to access the global environment. */ + +#ifndef __ERL_OSENV_H__ +#define __ERL_OSENV_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +typedef struct __erts_osenv_data_t erts_osenv_data_t; + +typedef struct __erts_osenv_t { + struct __env_rbtnode_t *tree; + int variable_count; + int content_size; +} erts_osenv_t; + +#include "sys.h" + +struct __erts_osenv_data_t { + Sint length; + void *data; +}; + +void erts_osenv_init(erts_osenv_t *env); +void erts_osenv_clear(erts_osenv_t *env); + +/* @brief Merges \c with into \c env + * + * @param overwrite Whether to overwrite existing entries or keep them as they + * are. */ +void erts_osenv_merge(erts_osenv_t *env, const erts_osenv_t *with, int overwrite); + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* @brief Copies env[key] into \c value + * + * @return 1 on success, 0 if the key couldn't be found, and -1 if the input + * was invalid. */ +int erts_osenv_get_term(const erts_osenv_t *env, struct process *process, + Eterm key, Eterm *value); + +/* @brief Copies \c value into \c env[key] + * + * @return 1 on success, -1 if the input was invalid. */ +int erts_osenv_put_term(erts_osenv_t *env, Eterm key, Eterm value); + +/* @brief Removes \c env[key] + * + * @return 1 on success, 0 if the key couldn't be found, and -1 if the input + * was invalid. */ +int erts_osenv_unset_term(erts_osenv_t *env, Eterm key); + +/* @brief Copies env[key] into \c value + * + * @param value [in,out] The buffer to copy the value into, may be NULL if you + * only wish to query presence. + * + * @return 1 on success, 0 if the key couldn't be found, and -1 if if the value + * didn't fit into the buffer. */ +int erts_osenv_get_native(const erts_osenv_t *env, const erts_osenv_data_t *key, + erts_osenv_data_t *value); + +/* @brief Copies \c value into \c env[key] + * + * @return 1 on success, -1 on failure. */ +int erts_osenv_put_native(erts_osenv_t *env, const erts_osenv_data_t *key, + const erts_osenv_data_t *value); + +/* @brief Removes \c key from the env. + * + * @return 1 on success, 0 if the key couldn't be found. */ +int erts_osenv_unset_native(erts_osenv_t *env, const erts_osenv_data_t *key); + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +typedef void (*erts_osenv_foreach_term_cb_t)(struct process *process, + void *state, Eterm key, Eterm value); + +typedef void (*erts_osenv_foreach_native_cb_t)(void *state, + const erts_osenv_data_t *key, + const erts_osenv_data_t *value); + +/* @brief Walks through all environment variables, calling \c callback for each + * one. It's unsafe to modify \c env within the callback. */ +void erts_osenv_foreach_term(const erts_osenv_t *env, struct process *process, + void *state, erts_osenv_foreach_term_cb_t callback); + +/* @copydoc erts_osenv_foreach_term */ +void erts_osenv_foreach_native(const erts_osenv_t *env, void *state, + erts_osenv_foreach_native_cb_t callback); + +#endif diff --git a/erts/emulator/sys/unix/erl_unix_sys.h b/erts/emulator/sys/unix/erl_unix_sys.h index b6f5b319ee..e367d565a7 100644 --- a/erts/emulator/sys/unix/erl_unix_sys.h +++ b/erts/emulator/sys/unix/erl_unix_sys.h @@ -133,7 +133,7 @@ #define ERTS_SYS_CONTINOUS_FD_NUMBERS -typedef void *GETENV_STATE; +void erts_sys_env_init(void); /* ** For the erl_timer_sup module. diff --git a/erts/emulator/sys/unix/sys.c b/erts/emulator/sys/unix/sys.c index 6315135151..189ca083d7 100644 --- a/erts/emulator/sys/unix/sys.c +++ b/erts/emulator/sys/unix/sys.c @@ -62,9 +62,6 @@ #include "erl_mseg.h" -extern char **environ; -erts_rwmtx_t environ_rwmtx; - #define MAX_VSIZE 16 /* Max number of entries allowed in an I/O * vector sock_sendv(). */ @@ -77,7 +74,7 @@ erts_rwmtx_t environ_rwmtx; #include "erl_check_io.h" #include "erl_cpu_topology.h" - +#include "erl_osenv.h" extern int driver_interrupt(int, int); extern void do_break(void); @@ -454,10 +451,10 @@ prepare_crash_dump(int secs) close(crashdump_companion_cube_fd); envsz = sizeof(env); - i = erts_sys_getenv__("ERL_CRASH_DUMP_NICE", env, &envsz); + i = erts_sys_explicit_8bit_getenv("ERL_CRASH_DUMP_NICE", env, &envsz); if (i >= 0) { int nice_val; - nice_val = i != 0 ? 0 : atoi(env); + nice_val = i != 1 ? 0 : atoi(env); if (nice_val > 39) { nice_val = 39; } @@ -749,34 +746,6 @@ void os_version(int *pMajor, int *pMinor, int *pBuild) { *pBuild = get_number(&release); /* Pointer to build number. */ } -void init_getenv_state(GETENV_STATE *state) -{ - erts_rwmtx_rlock(&environ_rwmtx); - *state = NULL; -} - -char *getenv_string(GETENV_STATE *state0) -{ - char **state = (char **) *state0; - char *cp; - - ERTS_LC_ASSERT(erts_lc_rwmtx_is_rlocked(&environ_rwmtx)); - - if (state == NULL) - state = environ; - - cp = *state++; - *state0 = (GETENV_STATE) state; - - return cp; -} - -void fini_getenv_state(GETENV_STATE *state) -{ - *state = NULL; - erts_rwmtx_runlock(&environ_rwmtx); -} - void erts_do_break_handling(void) { struct termios temp_mode; @@ -830,90 +799,6 @@ void sys_get_pid(char *buffer, size_t buffer_size){ erts_snprintf(buffer, buffer_size, "%lu",(unsigned long) p); } -int -erts_sys_putenv_raw(char *key, char *value) { - return erts_sys_putenv(key, value); -} -int -erts_sys_putenv(char *key, char *value) -{ - int res; - char *env; - Uint need = strlen(key) + strlen(value) + 2; - -#ifdef HAVE_COPYING_PUTENV - env = erts_alloc(ERTS_ALC_T_TMP, need); -#else - env = erts_alloc(ERTS_ALC_T_PUTENV_STR, need); - erts_atomic_add_nob(&sys_misc_mem_sz, need); -#endif - strcpy(env,key); - strcat(env,"="); - strcat(env,value); - erts_rwmtx_rwlock(&environ_rwmtx); - res = putenv(env); - erts_rwmtx_rwunlock(&environ_rwmtx); -#ifdef HAVE_COPYING_PUTENV - erts_free(ERTS_ALC_T_TMP, env); -#endif - return res; -} - -int -erts_sys_getenv__(char *key, char *value, size_t *size) -{ - int res; - char *orig_value = getenv(key); - if (!orig_value) - res = -1; - else { - size_t len = sys_strlen(orig_value); - if (len >= *size) { - *size = len + 1; - res = 1; - } - else { - *size = len; - sys_memcpy((void *) value, (void *) orig_value, len+1); - res = 0; - } - } - return res; -} - -int -erts_sys_getenv_raw(char *key, char *value, size_t *size) { - return erts_sys_getenv(key, value, size); -} - -/* - * erts_sys_getenv - * returns: - * -1, if environment key is not set with a value - * 0, if environment key is set and value fits into buffer size - * 1, if environment key is set but does not fit into buffer size - * size is set with the needed buffer size value - */ - -int -erts_sys_getenv(char *key, char *value, size_t *size) -{ - int res; - erts_rwmtx_rlock(&environ_rwmtx); - res = erts_sys_getenv__(key, value, size); - erts_rwmtx_runlock(&environ_rwmtx); - return res; -} - -int -erts_sys_unsetenv(char *key) -{ - int res; - erts_rwmtx_rwlock(&environ_rwmtx); - res = unsetenv(key); - erts_rwmtx_rwunlock(&environ_rwmtx); - return res; -} void sys_init_io(void) { } void erts_sys_alloc_init(void) { } @@ -1260,14 +1145,9 @@ erts_sys_main_thread(void) } } - void erl_sys_args(int* argc, char** argv) { - - erts_rwmtx_init(&environ_rwmtx, "environ", NIL, - ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC); - ASSERT(argc && argv); max_files = erts_check_io_max_files(); @@ -1275,4 +1155,5 @@ erl_sys_args(int* argc, char** argv) init_smp_sig_notify(); init_smp_sig_suspend(); + erts_sys_env_init(); } diff --git a/erts/emulator/sys/unix/sys_drivers.c b/erts/emulator/sys/unix/sys_drivers.c index 0228e1af54..b7ac89d89a 100644 --- a/erts/emulator/sys/unix/sys_drivers.c +++ b/erts/emulator/sys/unix/sys_drivers.c @@ -55,9 +55,6 @@ #include "erl_threads.h" -extern char **environ; -extern erts_rwmtx_t environ_rwmtx; - extern erts_atomic_t sys_misc_mem_sz; static Eterm forker_port; @@ -180,7 +177,7 @@ erl_sys_late_init(void) opts.read_write = 0; opts.hide_window = 0; opts.wd = NULL; - opts.envir = NULL; + erts_osenv_init(&opts.envir); opts.exit_status = 0; opts.overlapped_io = 0; opts.spawn_type = ERTS_SPAWN_ANY; @@ -443,85 +440,55 @@ static void close_pipes(int ifd[2], int ofd[2]) close(ofd[1]); } -static char **build_unix_environment(char *block) +struct __add_spawn_env_state { + struct iovec *iov; + int *iov_index; + + Sint32 *payload_size; + char *env_block; +}; + +static void add_spawn_env_block_foreach(void *_state, + const erts_osenv_data_t *key, + const erts_osenv_data_t *value) { - int i; - int j; - int len; - char *cp; - char **cpp; - char** old_env; - - ERTS_LC_ASSERT(erts_lc_rwmtx_is_rlocked(&environ_rwmtx)); - - cp = block; - len = 0; - while (*cp != '\0') { - cp += strlen(cp) + 1; - len++; - } - old_env = environ; - while (*old_env++ != NULL) { - len++; - } - - cpp = (char **) erts_alloc_fnf(ERTS_ALC_T_ENVIRONMENT, - sizeof(char *) * (len+1)); - if (cpp == NULL) { - return NULL; - } + struct __add_spawn_env_state *state; + struct iovec *iov; - cp = block; - len = 0; - while (*cp != '\0') { - cpp[len] = cp; - cp += strlen(cp) + 1; - len++; - } - - i = len; - for (old_env = environ; *old_env; old_env++) { - char* old = *old_env; - - for (j = 0; j < len; j++) { - char *s, *t; - - /* check if cpp[j] equals old - before the = sign, - i.e. - "TMPDIR=/tmp/" */ - s = cpp[j]; - t = old; - while (*s == *t && *s != '=') { - s++, t++; - } - if (*s == '=' && *t == '=') { - break; - } - } + state = (struct __add_spawn_env_state*)(_state); + iov = &state->iov[*state->iov_index]; - if (j == len) { /* New version not found */ - cpp[len++] = old; - } - } + iov->iov_base = state->env_block; - for (j = 0; j < i; ) { - size_t last = strlen(cpp[j])-1; - if (cpp[j][last] == '=' && strchr(cpp[j], '=') == cpp[j]+last) { - cpp[j] = cpp[--len]; - if (len < i) { - i--; - } else { - j++; - } - } - else { - j++; - } - } + sys_memcpy(state->env_block, key->data, key->length); + state->env_block += key->length; + *state->env_block++ = '='; + sys_memcpy(state->env_block, value->data, value->length); + state->env_block += value->length; + *state->env_block++ = '\0'; - cpp[len] = NULL; - return cpp; + iov->iov_len = state->env_block - (char*)iov->iov_base; + + (*state->payload_size) += iov->iov_len; + (*state->iov_index)++; +} + +static void *add_spawn_env_block(const erts_osenv_t *env, struct iovec *iov, + int *iov_index, Sint32 *payload_size) { + struct __add_spawn_env_state add_state; + char *env_block; + + env_block = erts_alloc(ERTS_ALC_T_TMP, env->content_size + + env->variable_count * sizeof("=\0")); + + add_state.iov = iov; + add_state.iov_index = iov_index; + add_state.env_block = env_block; + add_state.payload_size = payload_size; + + erts_osenv_foreach_native(env, &add_state, add_spawn_env_block_foreach); + + return env_block; } static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, @@ -531,7 +498,6 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, #define CMD_LINE_PREFIX_STR_SZ (sizeof(CMD_LINE_PREFIX_STR) - 1) int len; - char **new_environ; ErtsSysDriverData *dd; char *cmd_line; char wd_buff[MAXPATHLEN+1]; @@ -598,19 +564,7 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, memcpy((void *) (cmd_line + CMD_LINE_PREFIX_STR_SZ), (void *) name, len); cmd_line[CMD_LINE_PREFIX_STR_SZ + len] = '\0'; len = CMD_LINE_PREFIX_STR_SZ + len + 1; - } - - erts_rwmtx_rlock(&environ_rwmtx); - - if (opts->envir == NULL) { - new_environ = environ; - } else if ((new_environ = build_unix_environment(opts->envir)) == NULL) { - erts_rwmtx_runlock(&environ_rwmtx); - close_pipes(ifd, ofd); - erts_free(ERTS_ALC_T_TMP, (void *) cmd_line); - errno = ENOMEM; - return ERL_DRV_ERROR_ERRNO; - } +} if ((cwd = getcwd(wd_buff, MAXPATHLEN+1)) == NULL) { /* on some OSs this call opens a fd in the @@ -619,9 +573,6 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, int err = errno; close_pipes(ifd, ofd); erts_free(ERTS_ALC_T_TMP, (void *) cmd_line); - if (new_environ != environ) - erts_free(ERTS_ALC_T_ENVIRONMENT, (void *) new_environ); - erts_rwmtx_runlock(&environ_rwmtx); errno = err; return ERL_DRV_ERROR_ERRNO; } @@ -629,6 +580,7 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, wd = opts->wd; { + void *environment_block; struct iovec *io_vector; int iov_len = 5; char nullbuff[] = "\0"; @@ -641,10 +593,8 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, if (wd) iov_len++; - /* count number of elements in environment */ - while(new_environ[env_len] != NULL) - env_len++; - iov_len += 1 + env_len; /* num envs including size int */ + /* num envs including size int */ + iov_len += 1 + opts->envir.variable_count; /* count number of element in argument list */ if (opts->spawn_type == ERTS_SPAWN_EXECUTABLE) { @@ -661,10 +611,7 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, if (!io_vector) { close_pipes(ifd, ofd); - erts_rwmtx_runlock(&environ_rwmtx); erts_free(ERTS_ALC_T_TMP, (void *) cmd_line); - if (new_environ != environ) - erts_free(ERTS_ALC_T_ENVIRONMENT, (void *) new_environ); errno = ENOMEM; return ERL_DRV_ERROR_ERRNO; } @@ -699,16 +646,13 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, io_vector[i++].iov_len = 1; buffsz += io_vector[i-1].iov_len; + env_len = htonl(opts->envir.variable_count); io_vector[i].iov_base = (void*)&env_len; - env_len = htonl(env_len); io_vector[i++].iov_len = sizeof(env_len); buffsz += io_vector[i-1].iov_len; - for (j = 0; new_environ[j] != NULL; j++) { - io_vector[i].iov_base = new_environ[j]; - io_vector[i++].iov_len = strlen(new_environ[j]) + 1; - buffsz += io_vector[i-1].iov_len; - } + environment_block = add_spawn_env_block(&opts->envir, io_vector, &i, + &buffsz); /* only append arguments if this was a spawn_executable */ if (opts->spawn_type == ERTS_SPAWN_EXECUTABLE) { @@ -744,9 +688,6 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, int err = errno; close_pipes(ifd, ofd); erts_free(ERTS_ALC_T_TMP, io_vector); - if (new_environ != environ) - erts_free(ERTS_ALC_T_ENVIRONMENT, (void *) new_environ); - erts_rwmtx_runlock(&environ_rwmtx); erts_free(ERTS_ALC_T_TMP, (void *) cmd_line); errno = err; return ERL_DRV_ERROR_ERRNO; @@ -767,16 +708,12 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, driver_select(port_num, ofd[1], ERL_DRV_WRITE|ERL_DRV_USE, 1); } + erts_free(ERTS_ALC_T_TMP, environment_block); erts_free(ERTS_ALC_T_TMP, io_vector); } erts_free(ERTS_ALC_T_TMP, (void *) cmd_line); - if (new_environ != environ) - erts_free(ERTS_ALC_T_ENVIRONMENT, (void *) new_environ); - - erts_rwmtx_runlock(&environ_rwmtx); - dd = create_driver_data(port_num, ifd[0], ofd[1], opts->packet_bytes, DO_WRITE | DO_READ, opts->exit_status, 0, 0); @@ -1652,15 +1589,13 @@ static ErlDrvData forker_start(ErlDrvPort port_num, char* name, forker_port = erts_drvport2id(port_num); - res = erts_sys_getenv_raw("BINDIR", bindir, &bindirsz); - if (res != 0) { - if (res < 0) - erts_exit(1, - "Environment variable BINDIR is not set\n"); - if (res > 0) - erts_exit(1, - "Value of environment variable BINDIR is too large\n"); + res = erts_sys_explicit_8bit_getenv("BINDIR", bindir, &bindirsz); + if (res == 0) { + erts_exit(1, "Environment variable BINDIR is not set\n"); + } else if(res < 0) { + erts_exit(1, "Value of environment variable BINDIR is too large\n"); } + if (bindir[0] != DIR_SEPARATOR_CHAR) erts_exit(1, "Environment variable BINDIR does not contain an" diff --git a/erts/emulator/sys/unix/sys_env.c b/erts/emulator/sys/unix/sys_env.c new file mode 100644 index 0000000000..4d8301f985 --- /dev/null +++ b/erts/emulator/sys/unix/sys_env.c @@ -0,0 +1,133 @@ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include "erl_osenv.h" +#include "erl_alloc.h" + +#include "erl_thr_progress.h" + +static erts_osenv_t sysenv_global_env; +static erts_rwmtx_t sysenv_rwmtx; + +extern char **environ; + +static void import_initial_env(void); + +void erts_sys_env_init() { + erts_rwmtx_init(&sysenv_rwmtx, "environ", NIL, + ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC); + + erts_osenv_init(&sysenv_global_env); + import_initial_env(); +} + +const erts_osenv_t *erts_sys_rlock_global_osenv() { + erts_rwmtx_rlock(&sysenv_rwmtx); + return &sysenv_global_env; +} + +erts_osenv_t *erts_sys_rwlock_global_osenv() { + erts_rwmtx_rwlock(&sysenv_rwmtx); + return &sysenv_global_env; +} + +void erts_sys_rwunlock_global_osenv() { + erts_rwmtx_rwunlock(&sysenv_rwmtx); +} + +void erts_sys_runlock_global_osenv() { + erts_rwmtx_runlock(&sysenv_rwmtx); +} + +int erts_sys_explicit_8bit_putenv(char *key, char *value) { + erts_osenv_data_t env_key, env_value; + int result; + + env_key.length = sys_strlen(key); + env_key.data = key; + + env_value.length = sys_strlen(value); + env_value.data = value; + + { + erts_osenv_t *env = erts_sys_rwlock_global_osenv(); + result = erts_osenv_put_native(env, &env_key, &env_value); + erts_sys_rwunlock_global_osenv(); + } + + return result; +} + +int erts_sys_explicit_8bit_getenv(char *key, char *value, size_t *size) { + erts_osenv_data_t env_key, env_value; + int result; + + env_key.length = sys_strlen(key); + env_key.data = key; + + /* Reserve space for NUL termination. */ + env_value.length = *size - 1; + env_value.data = value; + + { + const erts_osenv_t *env = erts_sys_rlock_global_osenv(); + result = erts_osenv_get_native(env, &env_key, &env_value); + erts_sys_runlock_global_osenv(); + } + + if(result == 1) { + value[env_value.length] = '\0'; + } + + *size = env_value.length; + + return result; +} + +int erts_sys_explicit_host_getenv(char *key, char *value, size_t *size) { + char *orig_value; + size_t length; + + orig_value = getenv(key); + + if(orig_value == NULL) { + return 0; + } + + length = sys_strlen(orig_value); + + if (length >= *size) { + *size = length + 1; + return -1; + } + + sys_memcpy((void*)value, (void*)orig_value, length + 1); + *size = length; + + return 1; +} + +static void import_initial_env(void) { + char **environ_iterator, *environ_variable; + + environ_iterator = environ; + + while ((environ_variable = *(environ_iterator++)) != NULL) { + char *separator_index = strchr(environ_variable, '='); + + if (separator_index != NULL) { + erts_osenv_data_t env_key, env_value; + + env_key.length = separator_index - environ_variable; + env_key.data = environ_variable; + + env_value.length = sys_strlen(separator_index) - 1; + env_value.data = separator_index + 1; + + erts_osenv_put_native(&sysenv_global_env, &env_key, &env_value); + } + } +} diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c index 0598a12351..a1c630d68a 100644 --- a/erts/emulator/sys/win32/sys.c +++ b/erts/emulator/sys/win32/sys.c @@ -77,6 +77,7 @@ static int create_pipe(LPHANDLE, LPHANDLE, BOOL, BOOL); static int application_type(const wchar_t* originalName, wchar_t fullPath[MAX_PATH], BOOL search_in_path, BOOL handle_quotes, int *error_return); +static void *build_env_block(const erts_osenv_t *env); HANDLE erts_service_event; @@ -1190,7 +1191,6 @@ spawn_start(ErlDrvPort port_num, char* utf8_name, SysDriverOpts* opts) int ok; int neededSelects = 0; SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; - char* envir = opts->envir; int errno_return = -1; wchar_t *name; int len; @@ -1265,29 +1265,33 @@ spawn_start(ErlDrvPort port_num, char* utf8_name, SysDriverOpts* opts) name[i] = L'\0'; } DEBUGF(("Spawning \"%S\"\n", name)); - envir = win_build_environment(envir); /* Always a unicode environment */ - ok = create_child_process(name, - hChildStdin, - hChildStdout, - hChildStderr, - &dp->port_pid, - &pid, - opts->hide_window, - (LPVOID) envir, - (wchar_t *) opts->wd, - opts->spawn_type, - (wchar_t **) opts->argv, - &errno_return); - CloseHandle(hChildStdin); - CloseHandle(hChildStdout); - if (close_child_stderr && hChildStderr != INVALID_HANDLE_VALUE && - hChildStderr != 0) { - CloseHandle(hChildStderr); - } - erts_free(ERTS_ALC_T_TMP, name); - - if (envir != NULL) { - erts_free(ERTS_ALC_T_ENVIRONMENT, envir); + + { + void *environment_block = build_env_block(&opts->envir); + + ok = create_child_process(name, + hChildStdin, + hChildStdout, + hChildStderr, + &dp->port_pid, + &pid, + opts->hide_window, + environment_block, + (wchar_t *) opts->wd, + opts->spawn_type, + (wchar_t **) opts->argv, + &errno_return); + + CloseHandle(hChildStdin); + CloseHandle(hChildStdout); + + if (close_child_stderr && hChildStderr != INVALID_HANDLE_VALUE && + hChildStderr != 0) { + CloseHandle(hChildStderr); + } + + erts_free(ERTS_ALC_T_TMP, environment_block); + erts_free(ERTS_ALC_T_TMP, name); } if (!ok) { @@ -1338,6 +1342,41 @@ spawn_start(ErlDrvPort port_num, char* utf8_name, SysDriverOpts* opts) return retval; } +struct __build_env_state { + WCHAR *next_variable; +}; + +static void build_env_foreach(void *_state, const erts_osenv_data_t *key, + const erts_osenv_data_t *value) +{ + struct __build_env_state *state = (struct __build_env_state*)(_state); + + sys_memcpy(state->next_variable, key->data, key->length); + state->next_variable += (int)key->length / sizeof(WCHAR); + *state->next_variable++ = L'='; + + sys_memcpy(state->next_variable, value->data, value->length); + state->next_variable += (int)value->length / sizeof(WCHAR); + *state->next_variable++ = L'\0'; +} + +/* Builds an environment block suitable for CreateProcessW. */ +static void *build_env_block(const erts_osenv_t *env) { + struct __build_env_state build_state; + WCHAR *env_block; + + env_block = erts_alloc(ERTS_ALC_T_TMP, env->content_size + + (env->variable_count * sizeof(L"=\0") + sizeof(L'\0'))); + + build_state.next_variable = env_block; + + erts_osenv_foreach_native(env, &build_state, build_env_foreach); + + (*build_state.next_variable) = L'\0'; + + return env_block; +} + static int create_file_thread(AsyncIo* aio, int mode) { diff --git a/erts/emulator/sys/win32/sys_env.c b/erts/emulator/sys/win32/sys_env.c index 5792816267..c78161b344 100644 --- a/erts/emulator/sys/win32/sys_env.c +++ b/erts/emulator/sys/win32/sys_env.c @@ -1,319 +1,212 @@ -/* - * %CopyrightBegin% - * - * Copyright Ericsson AB 2002-2016. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * %CopyrightEnd% - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include "sys.h" -#include "erl_sys_driver.h" -#include "erl_alloc.h" - -static WCHAR *merge_environment(WCHAR *current, WCHAR *add); -static WCHAR *arg_to_env(WCHAR **arg); -static WCHAR **env_to_arg(WCHAR *env); -static WCHAR **find_arg(WCHAR **arg, WCHAR *str); -static int compare(const void *a, const void *b); - -static erts_rwmtx_t environ_rwmtx; - -void -erts_sys_env_init(void) -{ - erts_rwmtx_init(&environ_rwmtx, "environ", NIL, - ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC); -} - -int -erts_sys_putenv_raw(char *key, char *value) -{ - int res; - erts_rwmtx_rwlock(&environ_rwmtx); - res = (SetEnvironmentVariable((LPCTSTR) key, - (LPCTSTR) value) ? 0 : 1); - erts_rwmtx_rwunlock(&environ_rwmtx); - return res; -} - -int -erts_sys_putenv(char *key, char *value) -{ - int res; - WCHAR *wkey = (WCHAR *) key; - WCHAR *wvalue = (WCHAR *) value; - erts_rwmtx_rwlock(&environ_rwmtx); - res = (SetEnvironmentVariableW(wkey, - wvalue) ? 0 : 1); - erts_rwmtx_rwunlock(&environ_rwmtx); - return res; -} - -int -erts_sys_getenv(char *key, char *value, size_t *size) -{ - size_t req_size = 0; - int res = 0; - DWORD new_size; - WCHAR *wkey = (WCHAR *) key; - WCHAR *wvalue = (WCHAR *) value; - DWORD wsize = *size / (sizeof(WCHAR) / sizeof(char)); - - SetLastError(0); - erts_rwmtx_rlock(&environ_rwmtx); - new_size = GetEnvironmentVariableW(wkey, - wvalue, - (DWORD) wsize); - res = !new_size && GetLastError() == ERROR_ENVVAR_NOT_FOUND ? -1 : 0; - erts_rwmtx_runlock(&environ_rwmtx); - if (res < 0) - return res; - res = new_size > wsize ? 1 : 0; - *size = new_size * (sizeof(WCHAR) / sizeof(char)); - return res; -} -int -erts_sys_getenv__(char *key, char *value, size_t *size) -{ - size_t req_size = 0; - int res = 0; - DWORD new_size; - - SetLastError(0); - new_size = GetEnvironmentVariable((LPCTSTR) key, - (LPTSTR) value, - (DWORD) *size); - res = !new_size && GetLastError() == ERROR_ENVVAR_NOT_FOUND ? -1 : 0; - if (res < 0) - return res; - res = new_size > *size ? 1 : 0; - *size = new_size; - return res; -} - -int -erts_sys_getenv_raw(char *key, char *value, size_t *size) -{ - int res; - erts_rwmtx_rlock(&environ_rwmtx); - res = erts_sys_getenv__(key, value, size); - erts_rwmtx_runlock(&environ_rwmtx); - return res; -} - -void init_getenv_state(GETENV_STATE *state) -{ - erts_rwmtx_rlock(&environ_rwmtx); - state->environment_strings = GetEnvironmentStringsW(); - state->next_string = state->environment_strings; -} - -char *getenv_string(GETENV_STATE *state) -{ - ERTS_LC_ASSERT(erts_lc_rwmtx_is_rlocked(&environ_rwmtx)); - if (state->next_string[0] == L'\0') { - return NULL; - } else { - WCHAR *res = state->next_string; - state->next_string += wcslen(res) + 1; - return (char *) res; - } -} - -void fini_getenv_state(GETENV_STATE *state) -{ - FreeEnvironmentStringsW(state->environment_strings); - state->environment_strings = state->next_string = NULL; - erts_rwmtx_runlock(&environ_rwmtx); -} - -int erts_sys_unsetenv(char *key) -{ - int res = 0; - WCHAR *wkey = (WCHAR *) key; - - SetLastError(0); - erts_rwmtx_rlock(&environ_rwmtx); - GetEnvironmentVariableW(wkey, - NULL, - 0); - if (GetLastError() != ERROR_ENVVAR_NOT_FOUND) { - res = (SetEnvironmentVariableW(wkey, - NULL) ? 0 : 1); - } - erts_rwmtx_runlock(&environ_rwmtx); - return res; -} - -char* -win_build_environment(char* new_env) -{ - if (new_env == NULL) { - return NULL; - } else { - WCHAR *tmp, *merged, *tmp_new; - - tmp_new = (WCHAR *) new_env; - - erts_rwmtx_rlock(&environ_rwmtx); - tmp = GetEnvironmentStringsW(); - merged = merge_environment(tmp, tmp_new); - - FreeEnvironmentStringsW(tmp); - erts_rwmtx_runlock(&environ_rwmtx); - return (char *) merged; - } -} - -static WCHAR * -merge_environment(WCHAR *old, WCHAR *add) -{ - WCHAR **a_arg = env_to_arg(add); - WCHAR **c_arg = env_to_arg(old); - WCHAR *ret; - int i, j; - - for(i = 0; c_arg[i] != NULL; ++i) - ; - - for(j = 0; a_arg[j] != NULL; ++j) - ; - - c_arg = erts_realloc(ERTS_ALC_T_TMP, - c_arg, (i+j+1) * sizeof(WCHAR *)); - - for(j = 0; a_arg[j] != NULL; ++j){ - WCHAR **tmp; - WCHAR *current = a_arg[j]; - WCHAR *eq_p = wcschr(current,L'='); - int unset = (eq_p!=NULL && eq_p[1]==L'\0'); - - if ((tmp = find_arg(c_arg, current)) != NULL) { - if (!unset) { - *tmp = current; - } else { - *tmp = c_arg[--i]; - c_arg[i] = NULL; - } - } else if (!unset) { - c_arg[i++] = current; - c_arg[i] = NULL; - } - } - ret = arg_to_env(c_arg); - erts_free(ERTS_ALC_T_TMP, c_arg); - erts_free(ERTS_ALC_T_TMP, a_arg); - return ret; -} - -static WCHAR** -find_arg(WCHAR **arg, WCHAR *str) -{ - WCHAR *tmp; - int len; - - if ((tmp = wcschr(str, L'=')) != NULL) { - tmp++; - len = tmp - str; - while (*arg != NULL){ - if (_wcsnicmp(*arg, str, len) == 0){ - return arg; - } - ++arg; - } - } - return NULL; -} - -static int -compare(const void *a, const void *b) -{ - WCHAR *s1 = *((WCHAR **) a); - WCHAR *s2 = *((WCHAR **) b); - WCHAR *e1 = wcschr(s1,L'='); - WCHAR *e2 = wcschr(s2,L'='); - int ret; - int len; - - if(!e1) - e1 = s1 + wcslen(s1); - if(!e2) - e2 = s2 + wcslen(s2); - - if((e1 - s1) > (e2 - s2)) - len = (e2 - s2); - else - len = (e1 - s1); - - ret = _wcsnicmp(s1,s2,len); - if (ret == 0) - return ((e1 - s1) - (e2 - s2)); - else - return ret; -} - -static WCHAR** -env_to_arg(WCHAR *env) -{ - WCHAR **ret; - WCHAR *tmp; - int i; - int num_strings = 0; - - for(tmp = env; *tmp != '\0'; tmp += wcslen(tmp)+1) { - ++num_strings; - } - ret = erts_alloc(ERTS_ALC_T_TMP, sizeof(WCHAR *) * (num_strings + 1)); - i = 0; - for(tmp = env; *tmp != '\0'; tmp += wcslen(tmp)+1){ - ret[i++] = tmp; - } - ret[i] = NULL; - return ret; -} - -static WCHAR * -arg_to_env(WCHAR **arg) -{ - WCHAR *block; - WCHAR *ptr; - int i; - int totlen = 1; /* extra '\0' */ - - for(i = 0; arg[i] != NULL; ++i) { - totlen += wcslen(arg[i])+1; - } - - /* sort the environment vector */ - qsort(arg, i, sizeof(WCHAR *), &compare); - - if (totlen == 1){ - block = erts_alloc(ERTS_ALC_T_ENVIRONMENT, 2 * sizeof(WCHAR)); - block[0] = block[1] = '\0'; - } else { - block = erts_alloc(ERTS_ALC_T_ENVIRONMENT, totlen * sizeof(WCHAR)); - ptr = block; - for(i=0; arg[i] != NULL; ++i){ - wcscpy(ptr, arg[i]); - ptr += wcslen(ptr)+1; - } - *ptr = '\0'; - } - return block; -} +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2002-2017. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * %CopyrightEnd% + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include "erl_sys_driver.h" +#include "erl_alloc.h" + +static erts_osenv_t sysenv_global_env; +static erts_rwmtx_t sysenv_rwmtx; + +static void import_initial_env(void); + +void erts_sys_env_init() { + erts_rwmtx_init(&sysenv_rwmtx, "environ", NIL, + ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC); + + erts_osenv_init(&sysenv_global_env); + import_initial_env(); +} + +const erts_osenv_t *erts_sys_rlock_global_osenv() { + erts_rwmtx_rlock(&sysenv_rwmtx); + return &sysenv_global_env; +} + +erts_osenv_t *erts_sys_rwlock_global_osenv() { + erts_rwmtx_rwlock(&sysenv_rwmtx); + return &sysenv_global_env; +} + +void erts_sys_runlock_global_osenv() { + erts_rwmtx_runlock(&sysenv_rwmtx); +} + +void erts_sys_rwunlock_global_osenv() { + erts_rwmtx_rwunlock(&sysenv_rwmtx); +} + +int erts_sys_explicit_host_getenv(char *key, char *value, size_t *size) { + size_t new_size = GetEnvironmentVariableA(key, value, (DWORD)*size); + + if(new_size == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + return 0; + } else if(new_size > *size) { + return -1; + } + + *size = new_size; + return 1; +} + +int erts_sys_explicit_8bit_putenv(char *key, char *value) { + WCHAR *wide_key, *wide_value; + int key_length, value_length; + int result; + + /* Note that we do *NOT* honor the filename encoding flags (+fnu/+fnl) + * here; the previous implementation used SetEnvironmentVariableA and + * things may break if we step away from that. */ + + key_length = MultiByteToWideChar(CP_ACP, 0, key, -1, NULL, 0); + value_length = MultiByteToWideChar(CP_ACP, 0, value, -1, NULL, 0); + + /* Report "not found" if either string isn't convertible. */ + if(key_length == 0 || value_length == 0) { + return 0; + } + + wide_key = erts_alloc(ERTS_ALC_T_TMP, key_length * sizeof(WCHAR)); + wide_value = erts_alloc(ERTS_ALC_T_TMP, value_length * sizeof(WCHAR)); + + MultiByteToWideChar(CP_ACP, 0, key, -1, wide_key, key_length); + MultiByteToWideChar(CP_ACP, 0, value, -1, wide_value, value_length); + + { + erts_osenv_data_t env_key, env_value; + erts_osenv_t *env; + + env = erts_sys_rwlock_global_osenv(); + + /* -1 to exclude the NUL terminator. */ + env_key.length = (key_length - 1) * sizeof(WCHAR); + env_key.data = wide_key; + + env_value.length = (value_length - 1) * sizeof(WCHAR); + env_value.data = wide_value; + + result = erts_osenv_put_native(env, &env_key, &env_value); + erts_sys_rwunlock_global_osenv(); + } + + erts_free(ERTS_ALC_T_TMP, wide_key); + erts_free(ERTS_ALC_T_TMP, wide_value); + + return result; +} + +int erts_sys_explicit_8bit_getenv(char *key, char *value, size_t *size) { + erts_osenv_data_t env_key, env_value; + int key_length, value_length, result; + WCHAR *wide_key, *wide_value; + + key_length = MultiByteToWideChar(CP_ACP, 0, key, -1, NULL, 0); + + /* Report "not found" if the string isn't convertible. */ + if(key_length == 0) { + return 0; + } + + wide_key = erts_alloc(ERTS_ALC_T_TMP, key_length * sizeof(WCHAR)); + MultiByteToWideChar(CP_ACP, 0, key, -1, wide_key, key_length); + + /* We assume that the worst possible size is twice the output buffer width, + * as we could theoretically be on a code page that requires surrogates. */ + value_length = (*size) * 2; + wide_value = erts_alloc(ERTS_ALC_T_TMP, value_length * sizeof(WCHAR)); + + { + const erts_osenv_t *env = erts_sys_rlock_global_osenv(); + + /* -1 to exclude the NUL terminator. */ + env_key.length = (key_length - 1) * sizeof(WCHAR); + env_key.data = wide_key; + + env_value.length = value_length * sizeof(WCHAR); + env_value.data = wide_value; + + result = erts_osenv_get_native(env, &env_key, &env_value); + erts_sys_runlock_global_osenv(); + } + + if(result == 1 && env_value.length > 0) { + /* This function doesn't NUL-terminate if the provided size is >= 0, + * so we pass (*size - 1) to reserve space for it and then do it + * manually. */ + *size = WideCharToMultiByte(CP_ACP, 0, env_value.data, + env_value.length / sizeof(WCHAR), value, *size - 1, NULL, NULL); + + if(*size == 0) { + if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + result = -1; + } else { + result = 0; + } + } + } else { + *size = 0; + } + + if(*size > 0) { + value[*size] = '\0'; + } + + erts_free(ERTS_ALC_T_TMP, wide_key); + erts_free(ERTS_ALC_T_TMP, wide_value); + + return result; +} + +static void import_initial_env(void) { + WCHAR *environment_block, *current_variable; + + environment_block = GetEnvironmentStringsW(); + current_variable = environment_block; + + while(wcslen(current_variable) > 0) { + WCHAR *separator_index = wcschr(current_variable, L'='); + + /* We tolerate environment variables starting with '=' as the per-drive + * working directories are stored this way. */ + if(separator_index == current_variable) { + separator_index = wcschr(separator_index + 1, L'='); + } + + if(separator_index != NULL && separator_index != current_variable) { + erts_osenv_data_t env_key, env_value; + + env_key.length = (separator_index - current_variable) * sizeof(WCHAR); + env_key.data = current_variable; + + env_value.length = (wcslen(separator_index) - 1) * sizeof(WCHAR); + env_value.data = separator_index + 1; + + erts_osenv_put_native(&sysenv_global_env, &env_key, &env_value); + } + + current_variable += wcslen(current_variable) + 1; + } + + FreeEnvironmentStringsW(environment_block); +} -- cgit v1.2.3