-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] bpo-39413: os.unsetenv() uses _wputenv() on Windows #18115
Conversation
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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed this surprising difference and I'm trying to understand what should be the correct behavior...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it work with '=C'
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 "
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Fixed in commit 0852c7d.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this search should start at index 0, so that it consistently raises ValueError
, instead of OSError
if a name begins with "=".
Some notes on Windows environment variables:
|
There are also "secure" (or "safe"?) (but not thread-safe) |
I created https://bugs.python.org/issue39420 to track this issue. |
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
|
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. |
Oh right, nice :-)
|
Do you mean |
int _wputenv(
const wchar_t *envstring
); https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-wputenv?view=vs-2019#syntax |
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. |
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
|
A valid environment block is also terminated by a final null character.
These are correct.
The The
There is no problem with |
Look in "%ProgramFiles(x86)%\Windows Kits\10\Source\10.0.[version]\ucrt\env". |
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 |
Handling environment variables on Windows is more complex than what I expected. I start by reverting my previous change, to move back to the previous status quo: PR #18124. I would prefer to restart from the previous state, and try to handle all issues at once. |
This PR has some issues. I wrote PR #18163 which is simpler and so should be easier to review. |
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