Skip to content

Commit 338b6c9

Browse files
committed
Fix getenv()/putenv() glitches on Windows
We fix some wrong assumptions regarding `GetEnvironmentVariableW()`s return value; although the comment got it right, the implementation did not. We also attempt to improve our `putenv()` implementation. The problem that the underlying `_putenv()` is not thread-safe is no longer an issue, since there are mutexes in place. This fixes the ZTS part of bug #66265. However, calling `_putenv()` after `SetEnvironmentVariable()` causes issues regarding empty strings as variable values, because `_putenv()` does not support that (instead, it removes the variable from the environment). Therefore we swap both calls.
1 parent 56ab608 commit 338b6c9

File tree

2 files changed

+34
-44
lines changed

2 files changed

+34
-44
lines changed

ext/gettext/tests/bug66267.phpt renamed to ext/gettext/tests/bug66265.phpt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@
55
if (!extension_loaded("gettext")) {
66
die("skip\n");
77
}
8-
if (PHP_ZTS) {
9-
/* this is supposed to fail on the TS build at least on Windows,
10-
should be even XFAIL till it's fixed there */
11-
die("skip NTS only");
12-
}
138
if (substr(PHP_OS, 0, 3) != 'WIN') {
149
$loc = ["de_DE", "fr_FR", "en_US"];
1510
foreach($loc as $l) {

ext/standard/basic_functions.c

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -775,29 +775,30 @@ PHP_FUNCTION(getenv)
775775
}
776776

777777
SetLastError(0);
778-
/*If the given buffer is not large enough to hold the data, the return value is
778+
/* If the given buffer is not large enough to hold the data, the return value is
779779
the buffer size, in characters, required to hold the string and its terminating
780780
null character. We use this return value to alloc the final buffer. */
781781
size = GetEnvironmentVariableW(keyw, &dummybuf, 0);
782-
if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
782+
switch (size) {
783+
case 0:
783784
/* The environment variable doesn't exist. */
785+
ZEND_ASSERT(GetLastError() == ERROR_ENVVAR_NOT_FOUND);
784786
free(keyw);
785787
RETURN_FALSE;
786-
}
787-
788-
if (size == 0) {
788+
case 1:
789789
/* env exists, but it is empty */
790790
free(keyw);
791791
RETURN_EMPTY_STRING();
792792
}
793793

794-
valw = emalloc((size + 1) * sizeof(wchar_t));
794+
valw = emalloc(size * sizeof(wchar_t));
795795
size = GetEnvironmentVariableW(keyw, valw, size);
796796
if (size == 0) {
797-
/* has been removed between the two calls */
798-
free(keyw);
799-
efree(valw);
800-
RETURN_EMPTY_STRING();
797+
/* has been removed between the two calls */
798+
ZEND_ASSERT(GetLastError() == ERROR_ENVVAR_NOT_FOUND);
799+
free(keyw);
800+
efree(valw);
801+
RETURN_FALSE;
801802
} else {
802803
ptr = php_win32_cp_w_to_any(valw);
803804
RETVAL_STRING(ptr);
@@ -841,7 +842,6 @@ PHP_FUNCTION(putenv)
841842
#ifdef PHP_WIN32
842843
char *value = NULL;
843844
int equals = 0;
844-
int error_code;
845845
#endif
846846

847847
ZEND_PARSE_PARAMETERS_START(1, 1)
@@ -896,38 +896,33 @@ PHP_FUNCTION(putenv)
896896
unsetenv(pe.putenv_string);
897897
}
898898
if (!p || putenv(pe.putenv_string) == 0) { /* success */
899-
#else
900-
# ifndef PHP_WIN32
899+
#elif !defined(PHP_WIN32)
901900
if (putenv(pe.putenv_string) == 0) { /* success */
902-
# else
903-
wchar_t *keyw, *valw = NULL;
904-
905-
keyw = php_win32_cp_any_to_w(pe.key);
906-
if (value) {
907-
valw = php_win32_cp_any_to_w(value);
908-
}
909-
/* valw may be NULL, but the failed conversion still needs to be checked. */
910-
if (!keyw || !valw && value) {
911-
efree(pe.putenv_string);
912-
efree(pe.key);
913-
free(keyw);
914-
free(valw);
915-
RETURN_FALSE;
916-
}
901+
#else
902+
wchar_t *keyw, *valw = NULL;
917903

918-
error_code = SetEnvironmentVariableW(keyw, valw);
904+
keyw = php_win32_cp_any_to_w(pe.key);
905+
if (value) {
906+
valw = php_win32_cp_any_to_w(value);
907+
}
908+
/* valw may be NULL, but the failed conversion still needs to be checked. */
909+
if (!keyw || !valw && value) {
910+
efree(pe.putenv_string);
911+
efree(pe.key);
912+
free(keyw);
913+
free(valw);
914+
RETURN_FALSE;
915+
}
919916

920-
if (error_code != 0
921-
# ifndef ZTS
922-
/* We need both SetEnvironmentVariable and _putenv here as some
923-
dependency lib could use either way to read the environment.
924-
Obviously the CRT version will be useful more often. But
925-
generally, doing both brings us on the safe track at least
926-
in NTS build. */
927-
&& _wputenv_s(keyw, valw ? valw : L"") == 0
928-
# endif
917+
/* While we prefer SetEnvironmentVariable, we also call putenv here as
918+
some dependency lib could use getenv to read the environment, and there
919+
is at least a chance that the lib sees the putenv changes.
920+
For best compatibility with other systems, we have to call _putenv first,
921+
because that cannot assign an empty string, while SetEnvironmentVariable
922+
can, so at least GetEnvironmentVariable will return the proper result. */
923+
if (_wputenv_s(keyw, valw ? valw : L"") == 0
924+
&& SetEnvironmentVariableW(keyw, valw) != 0
929925
) { /* success */
930-
# endif
931926
#endif
932927
zend_hash_str_add_mem(&BG(putenv_ht), pe.key, pe.key_len, &pe, sizeof(putenv_entry));
933928
#ifdef HAVE_TZSET

0 commit comments

Comments
 (0)