Skip to content
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

bpo-39413: Implement os.unsetenv() on Windows #18163

Merged
merged 3 commits into from
Jan 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,9 @@ process and user.

.. availability:: most flavors of Unix, Windows.

.. versionchanged:: 3.9
The function is now also available on Windows.


.. _os-newstreams:

Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.9.rst
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ Exposed the Linux-specific :func:`os.pidfd_open` (:issue:`38692`) and
:data:`os.P_PIDFD` (:issue:`38713`) for process management with file
descriptors.

The :func:`os.unsetenv` function is now also available on Windows.
(Contributed by Victor Stinner in :issue:`39413`.)

poplib
------

Expand Down
43 changes: 35 additions & 8 deletions Lib/test/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,17 +953,44 @@ def test_environb(self):
value_str = value.decode(sys.getfilesystemencoding(), 'surrogateescape')
self.assertEqual(os.environ['bytes'], value_str)

@unittest.skipUnless(hasattr(os, 'putenv'), "Test needs os.putenv()")
@unittest.skipUnless(hasattr(os, 'unsetenv'), "Test needs os.unsetenv()")
def test_putenv_unsetenv(self):
name = "PYTHONTESTVAR"
value = "testvalue"
code = f'import os; print(repr(os.environ.get({name!r})))'

with support.EnvironmentVarGuard() as env:
env.pop(name, None)

os.putenv(name, value)
proc = subprocess.run([sys.executable, '-c', code], check=True,
stdout=subprocess.PIPE, text=True)
self.assertEqual(proc.stdout.rstrip(), repr(value))

os.unsetenv(name)
proc = subprocess.run([sys.executable, '-c', code], check=True,
stdout=subprocess.PIPE, text=True)
self.assertEqual(proc.stdout.rstrip(), repr(None))

# On OS X < 10.6, unsetenv() doesn't return a value (bpo-13415).
@support.requires_mac_ver(10, 6)
def test_unset_error(self):
@unittest.skipUnless(hasattr(os, 'putenv'), "Test needs os.putenv()")
@unittest.skipUnless(hasattr(os, 'unsetenv'), "Test needs os.unsetenv()")
def test_putenv_unsetenv_error(self):
# Empty variable name is invalid.
# "=" and null character are not allowed in a variable name.
for name in ('', '=name', 'na=me', 'name=', 'name\0', 'na\0me'):
self.assertRaises((OSError, ValueError), os.putenv, name, "value")
self.assertRaises((OSError, ValueError), os.unsetenv, name)

if sys.platform == "win32":
# an environment variable is limited to 32,767 characters
key = 'x' * 50000
self.assertRaises(ValueError, os.environ.__delitem__, key)
else:
# "=" is not allowed in a variable name
key = 'key='
self.assertRaises(OSError, os.environ.__delitem__, key)
# On Windows, an environment variable string ("name=value" string)
# is limited to 32,767 characters
longstr = 'x' * 32_768
self.assertRaises(ValueError, os.putenv, longstr, "1")
self.assertRaises(ValueError, os.putenv, "X", longstr)
self.assertRaises(ValueError, os.unsetenv, longstr)

def test_key_type(self):
missing = 'missingkey'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The :func:`os.unsetenv` function is now also available on Windows.
42 changes: 39 additions & 3 deletions Modules/clinic/posixmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 69 additions & 38 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -10083,23 +10083,9 @@ posix_putenv_dict_setitem(PyObject *name, PyObject *value)


#ifdef MS_WINDOWS
/*[clinic input]
os.putenv

name: unicode
value: unicode
/

Change or add an environment variable.
[clinic start generated code]*/

static PyObject *
os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
/*[clinic end generated code: output=d29a567d6b2327d2 input=ba586581c2e6105f]*/
static PyObject*
win32_putenv(PyObject *name, PyObject *value)
{
const wchar_t *env;
Py_ssize_t size;

/* Search from index 1 because on Windows starting '=' is allowed for
defining hidden environment variables. */
if (PyUnicode_GET_LENGTH(name) == 0 ||
Expand All @@ -10108,36 +10094,68 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
return NULL;
}
PyObject *unicode = PyUnicode_FromFormat("%U=%U", name, value);
PyObject *unicode;
if (value != NULL) {
unicode = PyUnicode_FromFormat("%U=%U", name, value);
}
else {
unicode = PyUnicode_FromFormat("%U=", name);
}
if (unicode == NULL) {
return NULL;
}

env = PyUnicode_AsUnicodeAndSize(unicode, &size);
if (env == NULL)
goto error;
Py_ssize_t size;
/* PyUnicode_AsWideCharString() rejects embedded null characters */
wchar_t *env = PyUnicode_AsWideCharString(unicode, &size);
Py_DECREF(unicode);

if (env == NULL) {
return NULL;
}
if (size > _MAX_ENV) {
PyErr_Format(PyExc_ValueError,
"the environment variable is longer than %u characters",
_MAX_ENV);
goto error;
}
if (wcslen(env) != (size_t)size) {
PyErr_SetString(PyExc_ValueError, "embedded null character");
goto error;
PyMem_Free(env);
return NULL;
}

if (_wputenv(env)) {
/* _wputenv() and SetEnvironmentVariableW() update the environment in the
Process Environment Block (PEB). _wputenv() also updates CRT 'environ'
and '_wenviron' variables, whereas SetEnvironmentVariableW() does not.

Prefer _wputenv() to be compatible with C libraries using CRT
variables and CRT functions using these variables (ex: getenv()). */
int err = _wputenv(env);
PyMem_Free(env);

if (err) {
posix_error();
goto error;
return NULL;
}
Py_DECREF(unicode);

Py_RETURN_NONE;
}
#endif

error:
Py_DECREF(unicode);
return NULL;

#ifdef MS_WINDOWS
/*[clinic input]
os.putenv

name: unicode
value: unicode
/

Change or add an environment variable.
[clinic start generated code]*/

static PyObject *
os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
/*[clinic end generated code: output=d29a567d6b2327d2 input=ba586581c2e6105f]*/
{
return win32_putenv(name, value);
}
/* repeat !defined(MS_WINDOWS) to workaround an Argument Clinic issue */
#elif (defined(HAVE_SETENV) || defined(HAVE_PUTENV)) && !defined(MS_WINDOWS)
Expand Down Expand Up @@ -10186,7 +10204,23 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
#endif /* defined(HAVE_SETENV) || defined(HAVE_PUTENV) */


#ifdef HAVE_UNSETENV
#ifdef MS_WINDOWS
/*[clinic input]
os.unsetenv
name: unicode
/

Delete an environment variable.
[clinic start generated code]*/

static PyObject *
os_unsetenv_impl(PyObject *module, PyObject *name)
/*[clinic end generated code: output=54c4137ab1834f02 input=4d6a1747cc526d2f]*/
{
return win32_putenv(name, NULL);
}
/* repeat !defined(MS_WINDOWS) to workaround an Argument Clinic issue */
#elif defined(HAVE_UNSETENV) && !defined(MS_WINDOWS)
/*[clinic input]
os.unsetenv
name: FSConverter
Expand All @@ -10199,16 +10233,13 @@ static PyObject *
os_unsetenv_impl(PyObject *module, PyObject *name)
/*[clinic end generated code: output=54c4137ab1834f02 input=2bb5288a599c7107]*/
{
#ifndef HAVE_BROKEN_UNSETENV
int err;
#endif

#ifdef HAVE_BROKEN_UNSETENV
unsetenv(PyBytes_AS_STRING(name));
#else
err = unsetenv(PyBytes_AS_STRING(name));
if (err)
int err = unsetenv(PyBytes_AS_STRING(name));
if (err) {
return posix_error();
}
#endif

#ifdef PY_PUTENV_DICT
Expand Down