From b8d1262e8afe7b907b4a394a191739571092acdb Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 24 Jan 2020 14:05:48 +0100 Subject: bpo-39395: putenv() and unsetenv() always available (GH-18135) The os.putenv() and os.unsetenv() functions are now always available. On non-Windows platforms, Python now requires setenv() and unsetenv() functions to build. Remove putenv_dict from posixmodule.c: it's not longer needed. --- Doc/library/os.rst | 43 +++++------ Doc/whatsnew/3.9.rst | 8 +++ Lib/os.py | 32 ++------- Lib/test/test_os.py | 4 -- Lib/test/test_posix.py | 1 - .../Build/2020-01-23-03-05-13.bpo-39395.RoArIZ.rst | 2 + .../2020-01-23-03-05-41.bpo-39395.4dda42.rst | 2 + Modules/clinic/posixmodule.c.h | 10 +-- Modules/posixmodule.c | 83 ++-------------------- configure | 6 +- configure.ac | 6 +- pyconfig.h.in | 9 --- 12 files changed, 52 insertions(+), 154 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2020-01-23-03-05-13.bpo-39395.RoArIZ.rst create mode 100644 Misc/NEWS.d/next/Library/2020-01-23-03-05-41.bpo-39395.4dda42.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index de3e560..f59423c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -111,9 +111,9 @@ process and user. to the environment made after this time are not reflected in ``os.environ``, except for changes made by modifying ``os.environ`` directly. - If the platform supports the :func:`putenv` function, this mapping may be used - to modify the environment as well as query the environment. :func:`putenv` will - be called automatically when the mapping is modified. + This mapping may be used to modify the environment as well as query the + environment. :func:`putenv` will be called automatically when the mapping + is modified. On Unix, keys and values use :func:`sys.getfilesystemencoding` and ``'surrogateescape'`` error handler. Use :data:`environb` if you would like @@ -130,14 +130,10 @@ process and user. cause memory leaks. Refer to the system documentation for :c:func:`putenv`. - If :func:`putenv` is not provided, a modified copy of this mapping may be - passed to the appropriate process-creation functions to cause child processes - to use a modified environment. - - If the platform supports the :func:`unsetenv` function, you can delete items in - this mapping to unset environment variables. :func:`unsetenv` will be called - automatically when an item is deleted from ``os.environ``, and when - one of the :meth:`pop` or :meth:`clear` methods is called. + You can delete items in this mapping to unset environment variables. + :func:`unsetenv` will be called automatically when an item is deleted from + ``os.environ``, and when one of the :meth:`pop` or :meth:`clear` methods is + called. .. data:: environb @@ -439,17 +435,18 @@ process and user. changes to the environment affect subprocesses started with :func:`os.system`, :func:`popen` or :func:`fork` and :func:`execv`. - .. availability:: most flavors of Unix, Windows. + Assignments to items in ``os.environ`` are automatically translated into + corresponding calls to :func:`putenv`; however, calls to :func:`putenv` + don't update ``os.environ``, so it is actually preferable to assign to items + of ``os.environ``. .. note:: On some platforms, including FreeBSD and Mac OS X, setting ``environ`` may - cause memory leaks. Refer to the system documentation for putenv. + cause memory leaks. Refer to the system documentation for :c:func:`putenv`. - When :func:`putenv` is supported, assignments to items in ``os.environ`` are - automatically translated into corresponding calls to :func:`putenv`; however, - calls to :func:`putenv` don't update ``os.environ``, so it is actually - preferable to assign to items of ``os.environ``. + .. versionchanged:: 3.9 + The function is now always available. .. function:: setegid(egid) @@ -638,15 +635,13 @@ process and user. environment affect subprocesses started with :func:`os.system`, :func:`popen` or :func:`fork` and :func:`execv`. - When :func:`unsetenv` is supported, deletion of items in ``os.environ`` is - automatically translated into a corresponding call to :func:`unsetenv`; however, - calls to :func:`unsetenv` don't update ``os.environ``, so it is actually - preferable to delete items of ``os.environ``. - - .. availability:: most flavors of Unix, Windows. + Deletion of items in ``os.environ`` is automatically translated into a + corresponding call to :func:`unsetenv`; however, calls to :func:`unsetenv` + don't update ``os.environ``, so it is actually preferable to delete items of + ``os.environ``. .. versionchanged:: 3.9 - The function is now also available on Windows. + The function is now always available and is also available on Windows. .. _os-newstreams: diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst index 751562e..a4c4266 100644 --- a/Doc/whatsnew/3.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -227,6 +227,10 @@ descriptors. The :func:`os.unsetenv` function is now also available on Windows. (Contributed by Victor Stinner in :issue:`39413`.) +The :func:`os.putenv` and :func:`os.unsetenv` functions are now always +available. +(Contributed by Victor Stinner in :issue:`39395`.) + poplib ------ @@ -331,6 +335,10 @@ Build and C API Changes Python 3.0, it has been ignored and unused. (Contributed by Jeroen Demeyer in :issue:`36974`.) +* On non-Windows platforms, the :c:func:`setenv` and :c:func:`unsetenv` + functions are now required to build Python. + (Contributed by Victor Stinner in :issue:`39395`.) + Deprecated ========== diff --git a/Lib/os.py b/Lib/os.py index ca418ed..7ae1026 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -654,17 +654,15 @@ def get_exec_path(env=None): return path_list.split(pathsep) -# Change environ to automatically call putenv(), unsetenv if they exist. +# Change environ to automatically call putenv() and unsetenv() from _collections_abc import MutableMapping class _Environ(MutableMapping): - def __init__(self, data, encodekey, decodekey, encodevalue, decodevalue, putenv, unsetenv): + def __init__(self, data, encodekey, decodekey, encodevalue, decodevalue): self.encodekey = encodekey self.decodekey = decodekey self.encodevalue = encodevalue self.decodevalue = decodevalue - self.putenv = putenv - self.unsetenv = unsetenv self._data = data def __getitem__(self, key): @@ -678,12 +676,12 @@ class _Environ(MutableMapping): def __setitem__(self, key, value): key = self.encodekey(key) value = self.encodevalue(value) - self.putenv(key, value) + putenv(key, value) self._data[key] = value def __delitem__(self, key): encodedkey = self.encodekey(key) - self.unsetenv(encodedkey) + unsetenv(encodedkey) try: del self._data[encodedkey] except KeyError: @@ -712,22 +710,6 @@ class _Environ(MutableMapping): self[key] = value return self[key] -try: - _putenv = putenv -except NameError: - _putenv = lambda key, value: None -else: - if "putenv" not in __all__: - __all__.append("putenv") - -try: - _unsetenv = unsetenv -except NameError: - _unsetenv = lambda key: _putenv(key, "") -else: - if "unsetenv" not in __all__: - __all__.append("unsetenv") - def _createenviron(): if name == 'nt': # Where Env Var Names Must Be UPPERCASE @@ -755,8 +737,7 @@ def _createenviron(): data = environ return _Environ(data, encodekey, decode, - encode, decode, - _putenv, _unsetenv) + encode, decode) # unicode environ environ = _createenviron() @@ -781,8 +762,7 @@ if supports_bytes_environ: # bytes environ environb = _Environ(environ._data, _check_bytes, bytes, - _check_bytes, bytes, - _putenv, _unsetenv) + _check_bytes, bytes) del _check_bytes def getenvb(key, default=None): diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index dbdc00c..9e3a169 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -953,8 +953,6 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol): 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" @@ -975,8 +973,6 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol): # On OS X < 10.6, unsetenv() doesn't return a value (bpo-13415). @support.requires_mac_ver(10, 6) - @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. diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 4df882b..fad26d8 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -969,7 +969,6 @@ class PosixTester(unittest.TestCase): self.assertEqual(type(k), item_type) self.assertEqual(type(v), item_type) - @unittest.skipUnless(hasattr(os, "putenv"), "requires os.putenv()") def test_putenv(self): with self.assertRaises(ValueError): os.putenv('FRUIT\0VEGETABLE', 'cabbage') diff --git a/Misc/NEWS.d/next/Build/2020-01-23-03-05-13.bpo-39395.RoArIZ.rst b/Misc/NEWS.d/next/Build/2020-01-23-03-05-13.bpo-39395.RoArIZ.rst new file mode 100644 index 0000000..aa2146a --- /dev/null +++ b/Misc/NEWS.d/next/Build/2020-01-23-03-05-13.bpo-39395.RoArIZ.rst @@ -0,0 +1,2 @@ +On non-Windows platforms, the :c:func:`setenv` and :c:func:`unsetenv` functions +are now required to build Python. diff --git a/Misc/NEWS.d/next/Library/2020-01-23-03-05-41.bpo-39395.4dda42.rst b/Misc/NEWS.d/next/Library/2020-01-23-03-05-41.bpo-39395.4dda42.rst new file mode 100644 index 0000000..cf71370 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-01-23-03-05-41.bpo-39395.4dda42.rst @@ -0,0 +1,2 @@ +The :func:`os.putenv` and :func:`os.unsetenv` functions are now always +available. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 0f5995e..48dd7a7 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -6082,7 +6082,7 @@ exit: #endif /* defined(MS_WINDOWS) */ -#if ((defined(HAVE_SETENV) || defined(HAVE_PUTENV)) && !defined(MS_WINDOWS)) +#if !defined(MS_WINDOWS) PyDoc_STRVAR(os_putenv__doc__, "putenv($module, name, value, /)\n" @@ -6123,7 +6123,7 @@ exit: return return_value; } -#endif /* ((defined(HAVE_SETENV) || defined(HAVE_PUTENV)) && !defined(MS_WINDOWS)) */ +#endif /* !defined(MS_WINDOWS) */ #if defined(MS_WINDOWS) @@ -6161,7 +6161,7 @@ exit: #endif /* defined(MS_WINDOWS) */ -#if (defined(HAVE_UNSETENV) && !defined(MS_WINDOWS)) +#if !defined(MS_WINDOWS) PyDoc_STRVAR(os_unsetenv__doc__, "unsetenv($module, name, /)\n" @@ -6193,7 +6193,7 @@ exit: return return_value; } -#endif /* (defined(HAVE_UNSETENV) && !defined(MS_WINDOWS)) */ +#endif /* !defined(MS_WINDOWS) */ PyDoc_STRVAR(os_strerror__doc__, "strerror($module, code, /)\n" @@ -8809,4 +8809,4 @@ exit: #ifndef OS__REMOVE_DLL_DIRECTORY_METHODDEF #define OS__REMOVE_DLL_DIRECTORY_METHODDEF #endif /* !defined(OS__REMOVE_DLL_DIRECTORY_METHODDEF) */ -/*[clinic end generated code: output=0348cbdff48691e3 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=5d99f90cead7c0e1 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 3a8e6aa..b71eddf 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -819,22 +819,8 @@ dir_fd_converter(PyObject *o, void *p) } } -/* Windows _wputenv() and setenv() copy the arguments and so don't require - the caller to manage the variable memory. Only Unix putenv() requires - putenv_dict. */ -#if defined(HAVE_PUTENV) && !defined(MS_WINDOWS) && !defined(HAVE_SETENV) -# define PY_PUTENV_DICT -#endif - typedef struct { PyObject *billion; -#ifdef PY_PUTENV_DICT - /* putenv() requires that the caller manages the environment variable - memory. Use a Python dictionary for that: name => env, where env is a - string like "name=value". On Windows, dict keys and values are Unicode - strings. On Unix, they are bytes strings. */ - PyObject *putenv_dict; -#endif PyObject *DirEntryType; PyObject *ScandirIteratorType; #if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM) @@ -2118,9 +2104,6 @@ static int _posix_clear(PyObject *module) { Py_CLEAR(_posixstate(module)->billion); -#ifdef PY_PUTENV_DICT - Py_CLEAR(_posixstate(module)->putenv_dict); -#endif Py_CLEAR(_posixstate(module)->DirEntryType); Py_CLEAR(_posixstate(module)->ScandirIteratorType); #if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM) @@ -2145,9 +2128,6 @@ static int _posix_traverse(PyObject *module, visitproc visit, void *arg) { Py_VISIT(_posixstate(module)->billion); -#ifdef PY_PUTENV_DICT - Py_VISIT(_posixstate(module)->putenv_dict); -#endif Py_VISIT(_posixstate(module)->DirEntryType); Py_VISIT(_posixstate(module)->ScandirIteratorType); #if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM) @@ -10065,23 +10045,6 @@ os_posix_fadvise_impl(PyObject *module, int fd, Py_off_t offset, #endif /* HAVE_POSIX_FADVISE && !POSIX_FADVISE_AIX_BUG */ -#ifdef PY_PUTENV_DICT -static void -posix_putenv_dict_setitem(PyObject *name, PyObject *value) -{ - /* Install the first arg and newstr in putenv_dict; - * this will cause previous value to be collected. This has to - * happen after the real putenv() call because the old value - * was still accessible until then. */ - if (PyDict_SetItem(_posixstate_global->putenv_dict, name, value)) - /* really not much we can do; just leak */ - PyErr_Clear(); - else - Py_DECREF(value); -} -#endif /* PY_PUTENV_DICT */ - - #ifdef MS_WINDOWS static PyObject* win32_putenv(PyObject *name, PyObject *value) @@ -10157,8 +10120,7 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value) { 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) +#else /*[clinic input] os.putenv @@ -10181,27 +10143,12 @@ os_putenv_impl(PyObject *module, PyObject *name, PyObject *value) return NULL; } -#ifdef HAVE_SETENV if (setenv(name_string, value_string, 1)) { return posix_error(); } -#else - PyObject *bytes = PyBytes_FromFormat("%s=%s", name_string, value_string); - if (bytes == NULL) { - return NULL; - } - - char *env = PyBytes_AS_STRING(bytes); - if (putenv(env)) { - Py_DECREF(bytes); - return posix_error(); - } - - posix_putenv_dict_setitem(name, bytes); -#endif Py_RETURN_NONE; } -#endif /* defined(HAVE_SETENV) || defined(HAVE_PUTENV) */ +#endif /* !defined(MS_WINDOWS) */ #ifdef MS_WINDOWS @@ -10219,8 +10166,7 @@ os_unsetenv_impl(PyObject *module, PyObject *name) { return win32_putenv(name, NULL); } -/* repeat !defined(MS_WINDOWS) to workaround an Argument Clinic issue */ -#elif defined(HAVE_UNSETENV) && !defined(MS_WINDOWS) +#else /*[clinic input] os.unsetenv name: FSConverter @@ -10242,24 +10188,9 @@ os_unsetenv_impl(PyObject *module, PyObject *name) } #endif -#ifdef PY_PUTENV_DICT - /* Remove the key from putenv_dict; - * this will cause it to be collected. This has to - * happen after the real unsetenv() call because the - * old value was still accessible until then. - */ - if (PyDict_DelItem(_posixstate(module)->putenv_dict, name)) { - /* really not much we can do; just leak */ - if (!PyErr_ExceptionMatches(PyExc_KeyError)) { - return NULL; - } - PyErr_Clear(); - } -#endif - Py_RETURN_NONE; } -#endif /* HAVE_UNSETENV */ +#endif /* !MS_WINDOWS */ /*[clinic input] @@ -14553,12 +14484,6 @@ INITFUNC(void) Py_INCREF(PyExc_OSError); PyModule_AddObject(m, "error", PyExc_OSError); -#ifdef PY_PUTENV_DICT - /* Save putenv() parameters as values here, so we can collect them when they - * get re-set with another call for the same key. */ - _posixstate(m)->putenv_dict = PyDict_New(); -#endif - #if defined(HAVE_WAITID) && !defined(__APPLE__) waitid_result_desc.name = MODNAME ".waitid_result"; PyObject *WaitidResultType = (PyObject *)PyStructSequence_NewType(&waitid_result_desc); diff --git a/configure b/configure index e966836..85120e4 100755 --- a/configure +++ b/configure @@ -11548,9 +11548,9 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \ memrchr mbrtowc mkdirat mkfifo \ madvise mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \ posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \ - pthread_condattr_setclock pthread_init pthread_kill putenv pwrite pwritev pwritev2 \ + pthread_condattr_setclock pthread_init pthread_kill pwrite pwritev pwritev2 \ readlink readlinkat readv realpath renameat \ - sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid setenv seteuid \ + sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \ setgid sethostname \ setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \ sched_get_priority_max sched_setaffinity sched_setscheduler sched_setparam \ @@ -11558,7 +11558,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \ sigaction sigaltstack sigfillset siginterrupt sigpending sigrelse \ sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy strsignal symlinkat sync \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ - truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \ + truncate uname unlinkat utimensat utimes waitid waitpid wait3 wait4 \ wcscoll wcsftime wcsxfrm wmemcmp writev _getpty rtpSpawn do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` diff --git a/configure.ac b/configure.ac index 36c165b..ab8e1b7 100644 --- a/configure.ac +++ b/configure.ac @@ -3598,9 +3598,9 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \ memrchr mbrtowc mkdirat mkfifo \ madvise mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \ posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \ - pthread_condattr_setclock pthread_init pthread_kill putenv pwrite pwritev pwritev2 \ + pthread_condattr_setclock pthread_init pthread_kill pwrite pwritev pwritev2 \ readlink readlinkat readv realpath renameat \ - sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid setenv seteuid \ + sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \ setgid sethostname \ setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \ sched_get_priority_max sched_setaffinity sched_setscheduler sched_setparam \ @@ -3608,7 +3608,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \ sigaction sigaltstack sigfillset siginterrupt sigpending sigrelse \ sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy strsignal symlinkat sync \ sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \ - truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \ + truncate uname unlinkat utimensat utimes waitid waitpid wait3 wait4 \ wcscoll wcsftime wcsxfrm wmemcmp writev _getpty rtpSpawn) # Force lchmod off for Linux. Linux disallows changing the mode of symbolic diff --git a/pyconfig.h.in b/pyconfig.h.in index 1918fab..b560213 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -802,9 +802,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_PTY_H -/* Define to 1 if you have the `putenv' function. */ -#undef HAVE_PUTENV - /* Define to 1 if you have the `pwrite' function. */ #undef HAVE_PWRITE @@ -895,9 +892,6 @@ /* Define to 1 if you have the `setegid' function. */ #undef HAVE_SETEGID -/* Define to 1 if you have the `setenv' function. */ -#undef HAVE_SETENV - /* Define to 1 if you have the `seteuid' function. */ #undef HAVE_SETEUID @@ -1266,9 +1260,6 @@ /* Define to 1 if you have the `unlinkat' function. */ #undef HAVE_UNLINKAT -/* Define to 1 if you have the `unsetenv' function. */ -#undef HAVE_UNSETENV - /* Define if you have a useable wchar_t type defined in wchar.h; useable means wchar_t must be an unsigned type with at least 16 bits. (see Include/unicodeobject.h). */ -- cgit v0.12