diff --git a/src/win/process.c b/src/win/process.c index b2e73576..70c1c232 100644 --- a/src/win/process.c +++ b/src/win/process.c @@ -25,6 +25,8 @@ #include #include #include +#include +#include #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; } diff --git a/test/test-spawn.c b/test/test-spawn.c index c75f1ca2..0b404776 100644 --- a/test/test-spawn.c +++ b/test/test-spawn.c @@ -31,6 +31,7 @@ # include # endif # include +# include #else # include #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; }