windows: sort environment variables before calling CreateProcess
This commit is contained in:
parent
2ce14cfab4
commit
8db42383ad
@ -25,6 +25,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <limits.h>
|
||||
#include <malloc.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "uv.h"
|
||||
#include "internal.h"
|
||||
@ -36,14 +38,27 @@
|
||||
|
||||
|
||||
typedef struct env_var {
|
||||
const char* narrow;
|
||||
const WCHAR* wide;
|
||||
size_t len; /* including null or '=' */
|
||||
DWORD value_len;
|
||||
int supplied;
|
||||
const WCHAR* const wide;
|
||||
const WCHAR* const wide_eq;
|
||||
const size_t len; /* including null or '=' */
|
||||
} env_var_t;
|
||||
|
||||
#define E_V(str) { str "=", L##str, sizeof(str), 0, 0 }
|
||||
#define E_V(str) { L##str, L##str L"=", sizeof(str) }
|
||||
|
||||
static const env_var_t required_vars[] = { /* keep me sorted */
|
||||
E_V("HOMEDRIVE"),
|
||||
E_V("HOMEPATH"),
|
||||
E_V("LOGONSERVER"),
|
||||
E_V("PATH"),
|
||||
E_V("SYSTEMDRIVE"),
|
||||
E_V("SYSTEMROOT"),
|
||||
E_V("TEMP"),
|
||||
E_V("USERDOMAIN"),
|
||||
E_V("USERNAME"),
|
||||
E_V("USERPROFILE"),
|
||||
E_V("WINDIR"),
|
||||
};
|
||||
static size_t n_required_vars = ARRAY_SIZE(required_vars);
|
||||
|
||||
|
||||
static HANDLE uv_global_job_handle_;
|
||||
@ -587,25 +602,56 @@ error:
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* If we learn that people are passing in huge environment blocks
|
||||
* then we should probably qsort() the array and then bsearch()
|
||||
* to see if it contains this variable. But there are ownership
|
||||
* issues associated with that solution; this is the caller's
|
||||
* char**, and modifying it is rude.
|
||||
*/
|
||||
static void check_required_vars_contains_var(env_var_t* required, int count,
|
||||
const char* var) {
|
||||
int i;
|
||||
for (i = 0; i < count; ++i) {
|
||||
if (_strnicmp(required[i].narrow, var, required[i].len) == 0) {
|
||||
required[i].supplied = 1;
|
||||
return;
|
||||
int env_strncmp(const wchar_t* a, int na, const wchar_t* b) {
|
||||
wchar_t* a_eq;
|
||||
wchar_t* b_eq;
|
||||
wchar_t* A;
|
||||
wchar_t* B;
|
||||
int nb;
|
||||
int r;
|
||||
|
||||
if (na < 0) {
|
||||
a_eq = wcschr(a, L'=');
|
||||
assert(a_eq);
|
||||
na = (int)(long)(a_eq - a);
|
||||
} else {
|
||||
na--;
|
||||
}
|
||||
b_eq = wcschr(b, L'=');
|
||||
assert(b_eq);
|
||||
nb = b_eq - b;
|
||||
|
||||
A = alloca((na+1) * sizeof(wchar_t));
|
||||
B = alloca((nb+1) * sizeof(wchar_t));
|
||||
|
||||
r = LCMapStringW(LOCALE_INVARIANT, LCMAP_UPPERCASE, a, na, A, na);
|
||||
assert(r==na);
|
||||
A[na] = L'\0';
|
||||
r = LCMapStringW(LOCALE_INVARIANT, LCMAP_UPPERCASE, b, nb, B, nb);
|
||||
assert(r==nb);
|
||||
B[nb] = L'\0';
|
||||
|
||||
while (1) {
|
||||
wchar_t AA = *A++;
|
||||
wchar_t BB = *B++;
|
||||
if (AA < BB) {
|
||||
return -1;
|
||||
} else if (AA > BB) {
|
||||
return 1;
|
||||
} else if (!AA && !BB) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int qsort_wcscmp(const void *a, const void *b) {
|
||||
wchar_t* astr = *(wchar_t* const*)a;
|
||||
wchar_t* bstr = *(wchar_t* const*)b;
|
||||
return env_strncmp(astr, -1, bstr);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The way windows takes environment variables is different than what C does;
|
||||
* Windows wants a contiguous block of null-terminated strings, terminated
|
||||
@ -626,93 +672,143 @@ int make_program_env(char* env_block[], WCHAR** dst_ptr) {
|
||||
WCHAR* dst;
|
||||
WCHAR* ptr;
|
||||
char** env;
|
||||
size_t env_len = 1; /* room for closing null */
|
||||
size_t env_len = 0;
|
||||
int len;
|
||||
size_t i;
|
||||
DWORD var_size;
|
||||
size_t env_block_count = 1; /* 1 for null-terminator */
|
||||
WCHAR* dst_copy;
|
||||
WCHAR** ptr_copy;
|
||||
WCHAR** env_copy;
|
||||
DWORD* required_vars_value_len = alloca(n_required_vars * sizeof(DWORD*));
|
||||
|
||||
env_var_t required_vars[] = {
|
||||
E_V("SYSTEMROOT"),
|
||||
E_V("SYSTEMDRIVE"),
|
||||
E_V("TEMP"),
|
||||
E_V("HOMEDRIVE"),
|
||||
E_V("HOMEPATH"),
|
||||
E_V("USERDOMAIN"),
|
||||
E_V("USERNAME"),
|
||||
E_V("USERPROFILE"),
|
||||
E_V("WINDIR"),
|
||||
E_V("PATH"),
|
||||
E_V("LOGONSERVER"),
|
||||
};
|
||||
|
||||
/* first pass: determine size in UTF-16 */
|
||||
for (env = env_block; *env; env++) {
|
||||
int len;
|
||||
check_required_vars_contains_var(required_vars,
|
||||
ARRAY_SIZE(required_vars),
|
||||
*env);
|
||||
|
||||
len = MultiByteToWideChar(CP_UTF8,
|
||||
0,
|
||||
*env,
|
||||
-1,
|
||||
NULL,
|
||||
0);
|
||||
if (len <= 0) {
|
||||
return GetLastError();
|
||||
if (strchr(*env, '=')) {
|
||||
len = MultiByteToWideChar(CP_UTF8,
|
||||
0,
|
||||
*env,
|
||||
-1,
|
||||
NULL,
|
||||
0);
|
||||
if (len <= 0) {
|
||||
return GetLastError();
|
||||
}
|
||||
env_len += len;
|
||||
env_block_count++;
|
||||
}
|
||||
|
||||
env_len += len;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(required_vars); ++i) {
|
||||
if (!required_vars[i].supplied) {
|
||||
/* second pass: copy to UTF-16 environment block */
|
||||
dst_copy = _malloca(env_len * sizeof(WCHAR));
|
||||
if (!dst_copy) {
|
||||
return ERROR_OUTOFMEMORY;
|
||||
}
|
||||
env_copy = alloca(env_block_count * sizeof(WCHAR*));
|
||||
|
||||
ptr = dst_copy;
|
||||
ptr_copy = env_copy;
|
||||
for (env = env_block; *env; env++) {
|
||||
if (strchr(*env, '=')) {
|
||||
len = MultiByteToWideChar(CP_UTF8,
|
||||
0,
|
||||
*env,
|
||||
-1,
|
||||
ptr,
|
||||
(int) (env_len - (ptr - dst_copy)));
|
||||
if (len <= 0) {
|
||||
DWORD err = GetLastError();
|
||||
_freea(dst_copy);
|
||||
return err;
|
||||
}
|
||||
*ptr_copy++ = ptr;
|
||||
ptr += len;
|
||||
}
|
||||
}
|
||||
*ptr_copy = NULL;
|
||||
assert(env_len == ptr - dst_copy);
|
||||
|
||||
/* sort our (UTF-16) copy */
|
||||
qsort(env_copy, env_block_count-1, sizeof(wchar_t*), qsort_wcscmp);
|
||||
|
||||
/* third pass: check for required variables */
|
||||
for (ptr_copy = env_copy, i = 0; i < n_required_vars; ) {
|
||||
int cmp;
|
||||
if (!*ptr_copy) {
|
||||
cmp = -1;
|
||||
} else {
|
||||
cmp = env_strncmp(required_vars[i].wide_eq,
|
||||
required_vars[i].len,
|
||||
*ptr_copy);
|
||||
}
|
||||
if (cmp < 0) {
|
||||
/* missing required var */
|
||||
var_size = GetEnvironmentVariableW(required_vars[i].wide, NULL, 0);
|
||||
required_vars[i].value_len = var_size;
|
||||
required_vars_value_len[i] = var_size;
|
||||
if (var_size != 0) {
|
||||
env_len += required_vars[i].len;
|
||||
env_len += var_size;
|
||||
}
|
||||
i++;
|
||||
} else {
|
||||
ptr_copy++;
|
||||
if (cmp == 0)
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
dst = malloc(env_len * sizeof(WCHAR));
|
||||
/* final pass: copy, in sort order, and inserting required variables */
|
||||
dst = malloc((1+env_len) * sizeof(WCHAR));
|
||||
if (!dst) {
|
||||
_freea(dst_copy);
|
||||
return ERROR_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
ptr = dst;
|
||||
|
||||
for (env = env_block; *env; env++, ptr += len) {
|
||||
len = MultiByteToWideChar(CP_UTF8,
|
||||
0,
|
||||
*env,
|
||||
-1,
|
||||
ptr,
|
||||
(int) (env_len - (ptr - dst)));
|
||||
if (len <= 0) {
|
||||
free(dst);
|
||||
return GetLastError();
|
||||
for (ptr = dst, ptr_copy = env_copy, i = 0;
|
||||
*ptr_copy || i < n_required_vars;
|
||||
ptr += len) {
|
||||
int cmp;
|
||||
if (i >= n_required_vars) {
|
||||
cmp = 1;
|
||||
} else if (!*ptr_copy) {
|
||||
cmp = -1;
|
||||
} else {
|
||||
cmp = env_strncmp(required_vars[i].wide_eq,
|
||||
required_vars[i].len,
|
||||
*ptr_copy);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(required_vars); ++i) {
|
||||
if (!required_vars[i].supplied && required_vars[i].value_len!=0) {
|
||||
wcscpy(ptr, required_vars[i].wide);
|
||||
ptr += required_vars[i].len - 1;
|
||||
*ptr++ = L'=';
|
||||
var_size = GetEnvironmentVariableW(required_vars[i].wide,
|
||||
ptr,
|
||||
required_vars[i].value_len);
|
||||
if (var_size == 0) {
|
||||
uv_fatal_error(GetLastError(), "GetEnvironmentVariableW");
|
||||
if (cmp < 0) {
|
||||
/* missing required var */
|
||||
len = required_vars_value_len[i];
|
||||
if (len) {
|
||||
wcscpy(ptr, required_vars[i].wide_eq);
|
||||
ptr += required_vars[i].len;
|
||||
var_size = GetEnvironmentVariableW(required_vars[i].wide,
|
||||
ptr,
|
||||
(int) (env_len - (ptr - dst)));
|
||||
if (var_size != len-1) { /* race condition? */
|
||||
uv_fatal_error(GetLastError(), "GetEnvironmentVariableW");
|
||||
}
|
||||
}
|
||||
ptr += required_vars[i].value_len;
|
||||
i++;
|
||||
} else {
|
||||
/* copy var from env_block */
|
||||
DWORD r;
|
||||
len = wcslen(*ptr_copy) + 1;
|
||||
r = wmemcpy_s(ptr, (env_len - (ptr - dst)), *ptr_copy, len);
|
||||
assert(!r);
|
||||
ptr_copy++;
|
||||
if (cmp == 0)
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Terminate with an extra NULL. */
|
||||
assert(env_len == (ptr - dst));
|
||||
*ptr = L'\0';
|
||||
|
||||
_freea(dst_copy);
|
||||
*dst_ptr = dst;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
# include <basetyps.h>
|
||||
# endif
|
||||
# include <shellapi.h>
|
||||
# include <wchar.h>
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
@ -897,45 +898,110 @@ TEST_IMPL(environment_creation) {
|
||||
"SYSTEM=ROOT", /* substring of a supplied var name */
|
||||
"SYSTEMROOTED=OMG", /* supplied var name is a substring */
|
||||
"TEMP=C:\\Temp",
|
||||
"INVALID",
|
||||
"BAZ=QUX",
|
||||
"B_Z=QUX",
|
||||
"B\xe2\x82\xacZ=QUX",
|
||||
"B\xf0\x90\x80\x82Z=QUX",
|
||||
"B\xef\xbd\xa1Z=QUX",
|
||||
"B\xf0\xa3\x91\x96Z=QUX",
|
||||
"BAZ", /* repeat, invalid variable */
|
||||
NULL
|
||||
};
|
||||
|
||||
WCHAR expected[512];
|
||||
WCHAR* ptr = expected;
|
||||
WCHAR* wenvironment[] = {
|
||||
L"BAZ=QUX",
|
||||
L"B_Z=QUX",
|
||||
L"B\x20acZ=QUX",
|
||||
L"B\xd800\xdc02Z=QUX",
|
||||
L"B\xd84d\xdc56Z=QUX",
|
||||
L"B\xff61Z=QUX",
|
||||
L"FOO=BAR",
|
||||
L"SYSTEM=ROOT", /* substring of a supplied var name */
|
||||
L"SYSTEMROOTED=OMG", /* supplied var name is a substring */
|
||||
L"TEMP=C:\\Temp",
|
||||
};
|
||||
WCHAR* from_env[] = {
|
||||
/* list should be kept in sync with list
|
||||
* in process.c, minus variables in wenvironment */
|
||||
L"HOMEDRIVE",
|
||||
L"HOMEPATH",
|
||||
L"LOGONSERVER",
|
||||
L"PATH",
|
||||
L"USERDOMAIN",
|
||||
L"USERNAME",
|
||||
L"USERPROFILE",
|
||||
L"SYSTEMDRIVE",
|
||||
L"SYSTEMROOT",
|
||||
L"WINDIR",
|
||||
/* test for behavior in the absence of a
|
||||
* required-environment variable: */
|
||||
L"ZTHIS_ENV_VARIABLE_DOES_NOT_EXIST",
|
||||
};
|
||||
int found_in_loc_env[ARRAY_SIZE(wenvironment)] = {0};
|
||||
int found_in_usr_env[ARRAY_SIZE(from_env)] = {0};
|
||||
WCHAR *expected[ARRAY_SIZE(from_env)];
|
||||
int result;
|
||||
WCHAR* str;
|
||||
WCHAR* prev;
|
||||
WCHAR* env;
|
||||
|
||||
for (i = 0; i < sizeof(environment) / sizeof(environment[0]) - 1; i++) {
|
||||
ptr += uv_utf8_to_utf16(environment[i],
|
||||
ptr,
|
||||
expected + sizeof(expected) - ptr);
|
||||
for (i = 0; i < ARRAY_SIZE(from_env); i++) {
|
||||
/* copy expected additions to environment locally */
|
||||
size_t len = GetEnvironmentVariableW(from_env[i], NULL, 0);
|
||||
if (len == 0) {
|
||||
found_in_usr_env[i] = 1;
|
||||
str = malloc(1 * sizeof(WCHAR));
|
||||
*str = 0;
|
||||
expected[i] = str;
|
||||
} else {
|
||||
size_t name_len = wcslen(from_env[i]);
|
||||
str = malloc((name_len+1+len) * sizeof(WCHAR));
|
||||
wmemcpy(str, from_env[i], name_len);
|
||||
expected[i] = str;
|
||||
str += name_len;
|
||||
*str++ = L'=';
|
||||
GetEnvironmentVariableW(from_env[i], str, len);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(ptr, L"SYSTEMROOT=", sizeof(L"SYSTEMROOT="));
|
||||
ptr += sizeof(L"SYSTEMROOT=")/sizeof(WCHAR) - 1;
|
||||
ptr += GetEnvironmentVariableW(L"SYSTEMROOT",
|
||||
ptr,
|
||||
expected + sizeof(expected) - ptr);
|
||||
++ptr;
|
||||
|
||||
memcpy(ptr, L"SYSTEMDRIVE=", sizeof(L"SYSTEMDRIVE="));
|
||||
ptr += sizeof(L"SYSTEMDRIVE=")/sizeof(WCHAR) - 1;
|
||||
ptr += GetEnvironmentVariableW(L"SYSTEMDRIVE",
|
||||
ptr,
|
||||
expected + sizeof(expected) - ptr);
|
||||
++ptr;
|
||||
*ptr = '\0';
|
||||
|
||||
result = make_program_env(environment, &env);
|
||||
ASSERT(result == 0);
|
||||
|
||||
for (str = env; *str; str += wcslen(str) + 1) {
|
||||
wprintf(L"%s\n", str);
|
||||
for (str = env, prev = NULL; *str; prev = str, str += wcslen(str) + 1) {
|
||||
int found = 0;
|
||||
#if 0
|
||||
_cputws(str);
|
||||
putchar('\n');
|
||||
#endif
|
||||
for (i = 0; i < ARRAY_SIZE(wenvironment) && !found; i++) {
|
||||
if (!wcscmp(str, wenvironment[i])) {
|
||||
ASSERT(!found_in_loc_env[i]);
|
||||
found_in_loc_env[i] = 1;
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < ARRAY_SIZE(expected) && !found; i++) {
|
||||
if (!wcscmp(str, expected[i])) {
|
||||
ASSERT(!found_in_usr_env[i]);
|
||||
found_in_usr_env[i] = 1;
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
if (prev) { /* verify sort order -- requires Vista */
|
||||
#if _WIN32_WINNT >= 0x0600
|
||||
ASSERT(CompareStringOrdinal(prev, -1, str, -1, TRUE) == 1);
|
||||
#endif
|
||||
}
|
||||
ASSERT(found); /* verify that we expected this variable */
|
||||
}
|
||||
|
||||
ASSERT(wcscmp(expected, env) == 0);
|
||||
/* verify that we found all expected variables */
|
||||
for (i = 0; i < ARRAY_SIZE(wenvironment); i++) {
|
||||
ASSERT(found_in_loc_env[i]);
|
||||
}
|
||||
for (i = 0; i < ARRAY_SIZE(expected); i++) {
|
||||
ASSERT(found_in_usr_env[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user