/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2002-2011. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights 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_smp_rwmtx_t environ_rwmtx;
void
erts_sys_env_init(void)
{
erts_smp_rwmtx_init(&environ_rwmtx, "environ");
}
int
erts_sys_putenv_raw(char *key, char *value)
{
int res;
erts_smp_rwmtx_rwlock(&environ_rwmtx);
res = (SetEnvironmentVariable((LPCTSTR) key,
(LPCTSTR) value) ? 0 : 1);
erts_smp_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_smp_rwmtx_rwlock(&environ_rwmtx);
res = (SetEnvironmentVariableW(wkey,
wvalue) ? 0 : 1);
erts_smp_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_smp_rwmtx_rlock(&environ_rwmtx);
new_size = GetEnvironmentVariableW(wkey,
wvalue,
(DWORD) wsize);
res = !new_size && GetLastError() == ERROR_ENVVAR_NOT_FOUND ? -1 : 0;
erts_smp_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_smp_rwmtx_rlock(&environ_rwmtx);
res = erts_sys_getenv__(key, value, size);
erts_smp_rwmtx_runlock(&environ_rwmtx);
return res;
}
void init_getenv_state(GETENV_STATE *state)
{
erts_smp_rwmtx_rlock(&environ_rwmtx);
state->environment_strings = GetEnvironmentStringsW();
state->next_string = state->environment_strings;
}
char *getenv_string(GETENV_STATE *state)
{
ERTS_SMP_LC_ASSERT(erts_smp_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_smp_rwmtx_runlock(&environ_rwmtx);
}
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_smp_rwmtx_rlock(&environ_rwmtx);
tmp = GetEnvironmentStringsW();
merged = merge_environment(tmp, tmp_new);
FreeEnvironmentStringsW(tmp);
erts_smp_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;
}