[WIP] bpo-39413: os.unsetenv() uses _wputenv() on Windows#18115
[WIP] bpo-39413: os.unsetenv() uses _wputenv() on Windows#18115vstinner wants to merge 1 commit intopython:masterfrom vstinner:unsetenv_win3
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. |
Modules/posixmodule.c
Outdated
There was a problem hiding this comment.
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 argumentThere was a problem hiding this comment.
I noticed this surprising difference and I'm trying to understand what should be the correct behavior...
There was a problem hiding this comment.
Does it work with '=C'?
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
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.
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