Skip to content

bpo-39406: Implement os.putenv() with setenv() if available #18095

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

Closed
wants to merge 2 commits into from
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
If ``setenv()`` C function is available, :func:`os.putenv` is now implemented
with ``setenv()`` instead of ``putenv()``, so Python doesn't have to handle the
environment variable memory. On Windows, :func:`os.putenv` is now implemented
with ``SetEnvironmentVariableW()``.
102 changes: 57 additions & 45 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -819,9 +819,18 @@ dir_fd_converter(PyObject *o, void *p)
}
}

#if !defined(MS_WINDOWS) && (defined(HAVE_PUTENV) && !defined(HAVE_SETENV))
# define PY_PUTENV_DICT
#endif

typedef struct {
PyObject *billion;
PyObject *posix_putenv_garbage;
#ifdef PY_PUTENV_DICT
/* putenv() requires to manage the memory manually: use a Python dict
object mapping name (bytes) to env (bytes) where env is a bytes string
like "name=value\0". */
PyObject *posix_putenv_dict;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why rename it? This increases the size of the diff.

#endif
PyObject *DirEntryType;
PyObject *ScandirIteratorType;
#if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM)
Expand Down Expand Up @@ -2105,7 +2114,9 @@ static int
_posix_clear(PyObject *module)
{
Py_CLEAR(_posixstate(module)->billion);
Py_CLEAR(_posixstate(module)->posix_putenv_garbage);
#ifdef PY_PUTENV_DICT
Py_CLEAR(_posixstate(module)->posix_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)
Expand All @@ -2130,7 +2141,9 @@ static int
_posix_traverse(PyObject *module, visitproc visit, void *arg)
{
Py_VISIT(_posixstate(module)->billion);
Py_VISIT(_posixstate(module)->posix_putenv_garbage);
#ifdef PY_PUTENV_DICT
Py_VISIT(_posixstate(module)->posix_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)
Expand Down Expand Up @@ -10047,21 +10060,22 @@ os_posix_fadvise_impl(PyObject *module, int fd, Py_off_t offset,
}
#endif /* HAVE_POSIX_FADVISE && !POSIX_FADVISE_AIX_BUG */

#ifdef HAVE_PUTENV

#ifdef PY_PUTENV_DICT
static void
posix_putenv_garbage_setitem(PyObject *name, PyObject *value)
posix_putenv_dict_setitem(PyObject *name, PyObject *value)
{
/* Install the first arg and newstr in posix_putenv_garbage;
/* Install the first arg and newstr in posix_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->posix_putenv_garbage, name, value))
if (PyDict_SetItem(_posixstate_global->posix_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
Expand All @@ -10079,9 +10093,6 @@ static PyObject *
os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
/*[clinic end generated code: output=d29a567d6b2327d2 input=ba586581c2e6105f]*/
{
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 @@ -10090,38 +10101,29 @@ 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);
if (unicode == NULL) {
return NULL;
}

env = PyUnicode_AsUnicodeAndSize(unicode, &size);
if (env == NULL)
goto error;
if (size > _MAX_ENV) {
PyErr_Format(PyExc_ValueError,
"the environment variable is longer than %u characters",
_MAX_ENV);
goto error;
/* PyUnicode_AsWideCharString() rejects embedded null characters */
wchar_t *name_str = PyUnicode_AsWideCharString(name, NULL);
if (name_str == NULL) {
return NULL;
}
if (wcslen(env) != (size_t)size) {
PyErr_SetString(PyExc_ValueError, "embedded null character");
goto error;
wchar_t *value_str = PyUnicode_AsWideCharString(value, NULL);
if (value_str == NULL) {
PyMem_Free(name_str);
return NULL;
}

if (_wputenv(env)) {
posix_error();
goto error;
BOOL ok = SetEnvironmentVariableW(name_str, value_str);
PyMem_Free(name_str);
PyMem_Free(value_str);

if (!ok) {
return PyErr_SetFromWindowsErr(0);
}

posix_putenv_garbage_setitem(name, unicode);
Py_RETURN_NONE;

error:
Py_DECREF(unicode);
return NULL;
}
#else /* MS_WINDOWS */
#elif defined(HAVE_PUTENV)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#elif defined(HAVE_PUTENV)
#elif defined(HAVE_SETENV) || defined(HAVE_PUTENV)

/*[clinic input]
os.putenv

Expand All @@ -10136,31 +10138,38 @@ static PyObject *
os_putenv_impl(PyObject *module, PyObject *name, PyObject *value)
/*[clinic end generated code: output=d29a567d6b2327d2 input=a97bc6152f688d31]*/
{
PyObject *bytes = NULL;
char *env;
const char *name_string = PyBytes_AS_STRING(name);
const char *value_string = PyBytes_AS_STRING(value);

if (strchr(name_string, '=') != NULL) {
PyErr_SetString(PyExc_ValueError, "illegal environment variable name");
return NULL;
}
bytes = PyBytes_FromFormat("%s=%s", name_string, value_string);

#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;
}

env = PyBytes_AS_STRING(bytes);
char *env = PyBytes_AS_STRING(bytes);
if (putenv(env)) {
Py_DECREF(bytes);
return posix_error();
}

posix_putenv_garbage_setitem(name, bytes);
#ifdef PY_PUTENV_DICT
posix_putenv_dict_setitem(name, bytes);
#endif
#endif /* !HAVE_SETENV */

Py_RETURN_NONE;
}
#endif /* MS_WINDOWS */
#endif /* HAVE_PUTENV */
#endif /* HAVE_PUTENV */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#endif /* HAVE_PUTENV */
#endif /* HAVE_SETENV || HAVE_PUTENV */



#ifdef HAVE_UNSETENV
Expand Down Expand Up @@ -10188,18 +10197,21 @@ os_unsetenv_impl(PyObject *module, PyObject *name)
return posix_error();
#endif

/* Remove the key from posix_putenv_garbage;
#ifdef PY_PUTENV_DICT
/* Remove the key from posix_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)->posix_putenv_garbage, name)) {
if (PyDict_DelItem(_posixstate(module)->posix_putenv_dict, name)) {
/* really not much we can do; just leak */
if (!PyErr_ExceptionMatches(PyExc_KeyError)) {
return NULL;
}
PyErr_Clear();
}
#endif /* PY_PUTENV_DICT */

Py_RETURN_NONE;
}
#endif /* HAVE_UNSETENV */
Expand Down Expand Up @@ -14496,10 +14508,10 @@ INITFUNC(void)
Py_INCREF(PyExc_OSError);
PyModule_AddObject(m, "error", PyExc_OSError);

#ifdef HAVE_PUTENV
#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)->posix_putenv_garbage = PyDict_New();
_posixstate(m)->posix_putenv_dict = PyDict_New();
#endif

#if defined(HAVE_WAITID) && !defined(__APPLE__)
Expand Down
17 changes: 3 additions & 14 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,6 @@ infodir
docdir
oldincludedir
includedir
runstatedir
localstatedir
sharedstatedir
sysconfdir
Expand Down Expand Up @@ -896,7 +895,6 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
Expand Down Expand Up @@ -1149,15 +1147,6 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;

-runstatedir | --runstatedir | --runstatedi | --runstated \
| --runstate | --runstat | --runsta | --runst | --runs \
| --run | --ru | --r)
ac_prev=runstatedir ;;
-runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
| --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
| --run=* | --ru=* | --r=*)
runstatedir=$ac_optarg ;;

-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
Expand Down Expand Up @@ -1295,7 +1284,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
libdir localedir mandir runstatedir
libdir localedir mandir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
Expand Down Expand Up @@ -1448,7 +1437,6 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
--runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
Expand Down Expand Up @@ -10303,6 +10291,7 @@ fi




if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
if test -n "$ac_tool_prefix"; then
# Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
Expand Down Expand Up @@ -11561,7 +11550,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \
pthread_condattr_setclock pthread_init pthread_kill putenv pwrite pwritev pwritev2 \
readlink readlinkat readv realpath renameat \
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid setenv seteuid \
setgid sethostname \
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
sched_get_priority_max sched_setaffinity sched_setscheduler sched_setparam \
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -3600,7 +3600,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \
pthread_condattr_setclock pthread_init pthread_kill putenv pwrite pwritev pwritev2 \
readlink readlinkat readv realpath renameat \
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid setenv seteuid \
setgid sethostname \
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
sched_get_priority_max sched_setaffinity sched_setscheduler sched_setparam \
Expand Down
3 changes: 3 additions & 0 deletions pyconfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,9 @@
/* 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

Expand Down