Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign upbpo-39413: os.unsetenv() uses _wputenv() on Windows #18115
Conversation
This comment has been minimized.
This comment has been minimized.
This PR is a follow-up of PR #18107 which was a implementation in pure Python. @serhiy-storchaka wrote that he would prefer an implementation in C: so here you have. |
@@ -10126,6 +10126,8 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value) | |||
goto error; | |||
} | |||
|
|||
/* Use _wputenv() rather than SetEnvironmentVariableW(): _wputenv() |
This comment has been minimized.
This comment has been minimized.
eryksun
Jan 22, 2020
Contributor
The search for "=" (line 10102) should begin at index 0, not index 1. Windows allows variable names that begin with 0, but ucrt does not allow it. For example:
>>> os.putenv('spam=', 'eggs')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: illegal environment variable name
>>> os.putenv('=spam', 'eggs')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument
This comment has been minimized.
This comment has been minimized.
vstinner
Jan 22, 2020
Author
Member
I noticed this surprising difference and I'm trying to understand what should be the correct behavior...
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
eryksun
Jan 22, 2020
•
Contributor
Python initializes nt.environ
from C _wenviron
, so it follows the long-standing convention of MSVC to exclude hidden variables that begin with "=" and to disallow setting them. We still set them for per-drive working directories (e.g. "=Z:"), but we set them in the process environment only, with SetEnvironmentVariableW
. For example, see win32_wchdir
in this module. The drive variables are supposed to be a hidden implementation detail. CMD's set
doesn't show them, except due to a parsing bug if we run set ""
or set "
.
This comment has been minimized.
This comment has been minimized.
eryksun
Jan 22, 2020
•
Contributor
If you want to allow setting them, then Python should initialize nt.environ
from WINAPI GetEnvironmentStringsW
instead of _wenviron
(or at least also from GetEnvironmentStringsW
, just for the hidden variables). Then if a name begins with "=", call SetEnvironmentVariableW
instead of _wputenv
.
This comment has been minimized.
This comment has been minimized.
vstinner
Jan 22, 2020
Author
Member
I rewrote my PR to only check for "=" in os.putenv(). os.unsetenv() doesn't check the variable name: just report _wputenv() error. Example:
>>> os.unsetenv("key=")
>>> os.unsetenv("=key")
OSError: [Errno 22] Invalid argument
>>> os.unsetenv("=c:")
OSError: [Errno 22] Invalid argument
By the way, why does os.putenv() explicitly rejects "key=" variable name?
This comment has been minimized.
This comment has been minimized.
serhiy-storchaka
Jan 22, 2020
Member
What about os.unsetenv("key=value")
?
By the way, why does os.putenv() explicitly rejects "key=" variable name?
Because in "key==value" what is a key and what is a value? key=="key" and value=="=value"
or key=="key=" and value=="value"
?
This comment has been minimized.
This comment has been minimized.
vstinner
Jan 22, 2020
Author
Member
Windows is so weird. The official documentation says:
The name of an environment variable cannot include an equal sign (=).
I tested: I can set a variable with name "zlong=" (the name contains "="). It is shown by the set
command for example:
>>> import os
>>> os.putenv("zlong=", "1")
>>> os.putenv("zlon=g", "2")
>>> os.system("set")
(...)
zlon=g=2
zlong==1
This comment has been minimized.
This comment has been minimized.
eryksun
Jan 22, 2020
Contributor
The posix_putenv_dict_setitem
call below is unnecessary with _wputenv
. It copies the string before adding it to _wenviron
, i.e. it does not comply with POSIX putenv
.
This comment has been minimized.
This comment has been minimized.
eryksun
Jan 22, 2020
Contributor
I checked back to even Python 2.7 w/ msvcr90.dll, and even that old version of the CRT copies the string that's passed to putenv
(via calls to _calloc_crt
and strcpy_s
), which I verified by noting the result from _calloc_crt
and cross-checking with the address returned by getenv
. The CRT didn't used to copy the string back in the 1990s, but that's ancient history.
os.unsetenv() uses _wputenv() rather than SetEnvironmentVariableW(): _wputenv() updates the CRT, whereas SetEnvironmentVariableW() does not. Replace also lambda functions with regular functions to get named functions, to ease debug.
os_putenv_impl(PyObject *module, PyObject *name, PyObject *value) | ||
/*[clinic end generated code: output=d29a567d6b2327d2 input=ba586581c2e6105f]*/ | ||
static PyObject* | ||
py_win_putenv(PyObject *name, PyObject *value) |
This comment has been minimized.
This comment has been minimized.
eryksun
Jan 22, 2020
Contributor
Is py_win_
the naming convention from now on -- as opposed to win32_
?
/* Search from index 1 because on Windows starting '=' is allowed for | ||
defining hidden environment variables. */ | ||
if (PyUnicode_GET_LENGTH(name) == 0 || | ||
PyUnicode_FindChar(name, '=', 1, PyUnicode_GET_LENGTH(name), 1) != -1) |
This comment has been minimized.
This comment has been minimized.
eryksun
Jan 22, 2020
Contributor
Again, this search should start at index 0, so that it consistently raises ValueError
, instead of OSError
if a name begins with "=".
This comment has been minimized.
This comment has been minimized.
Some notes on Windows environment variables:
|
This comment has been minimized.
This comment has been minimized.
There are also "secure" (or "safe"?) (but not thread-safe) |
This comment has been minimized.
This comment has been minimized.
I created https://bugs.python.org/issue39420 to track this issue. |
This comment has been minimized.
This comment has been minimized.
I'm thinking aloud. If we switch Python to use the native API ( To support "=" at the beginning of the variable name, one option would be to use the native API ( I tested
|
This comment has been minimized.
This comment has been minimized.
The consequence for this is that in Vista and later
"=" is rejected by
CMD actually implements this all on its own. And it has a parsing bug that leads to displaying the 'hidden' variables if you run
Python is right. |
This comment has been minimized.
This comment has been minimized.
Oh right, nice :-)
|
This comment has been minimized.
This comment has been minimized.
Do you mean |
This comment has been minimized.
This comment has been minimized.
int _wputenv(
const wchar_t *envstring
); https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv?view=vs-2019#syntax |
This comment has been minimized.
This comment has been minimized.
GetEnvironmentStringsA() and GetEnvironmentStringsW() result also contain variables with name starting with "=", like It seems like internally, Windows environment is a list of "name=value\0" strings. Variable name and value seem to be stored together as a single string. Moreover, after _wputenv("victor=secret"):
... in short, it doesn't seem possible to distinguish if "=" character belongs to the name or the value, except for the special case of name starting with "=". SetEnvironmentVariableW() allows a name starting with "=", but "=" is not allowed elsewhere in the same. SetEnvironmentVariableW("=a=b", "c") fails for example (WinError 87). The problem comes from _wputenv() API which takes a single argument. |
This comment has been minimized.
This comment has been minimized.
I failed to find CRT source code in my VS 2017 Community install. VC\crt\src directory is missing. But I found ReactOS implementation which gives me an idea on how it can be implemented and are the constraints: https://doxygen.reactos.org/df/def/putenv_8c.html#adea7d1267d47562084ac102de11a11fb
|
This comment has been minimized.
This comment has been minimized.
A valid environment block is also terminated by a final null character.
These are correct.
The The
There is no problem with |
This comment has been minimized.
This comment has been minimized.
Look in "%ProgramFiles(x86)%\Windows Kits\10\Source\10.0.[version]\ucrt\env". |
This comment has been minimized.
This comment has been minimized.
The ReactOS implementation of RtlQueryEnvironmentVariable_U looks correct to me, so I think the bug is in Vista's new In the loop body, they first increment |
vstinner commentedJan 22, 2020
•
edited by bedevere-bot
os.unsetenv() uses _wputenv() rather than SetEnvironmentVariableW():
_wputenv() updates the CRT, whereas SetEnvironmentVariableW() does
not.
Replace also lambda functions with regular functions to get named
functions, to ease debug.
https://bugs.python.org/issue39413