2024-01-02 02:12:26 +08:00
|
|
|
/* Copyright (C) 1991-2024 Free Software Foundation, Inc.
|
1996-12-08 16:01:13 +08:00
|
|
|
This file is part of the GNU C Library.
|
1995-02-18 09:27:10 +08:00
|
|
|
|
1996-12-08 16:01:13 +08:00
|
|
|
The GNU C Library is free software; you can redistribute it and/or
|
2001-07-06 12:58:11 +08:00
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2.1 of the License, or (at your option) any later version.
|
1995-02-18 09:27:10 +08:00
|
|
|
|
1996-12-08 16:01:13 +08:00
|
|
|
The GNU C Library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
2001-07-06 12:58:11 +08:00
|
|
|
Lesser General Public License for more details.
|
1995-02-18 09:27:10 +08:00
|
|
|
|
2001-07-06 12:58:11 +08:00
|
|
|
You should have received a copy of the GNU Lesser General Public
|
2012-02-10 07:18:22 +08:00
|
|
|
License along with the GNU C Library; if not, see
|
Prefer https to http for gnu.org and fsf.org URLs
Also, change sources.redhat.com to sourceware.org.
This patch was automatically generated by running the following shell
script, which uses GNU sed, and which avoids modifying files imported
from upstream:
sed -ri '
s,(http|ftp)(://(.*\.)?(gnu|fsf|sourceware)\.org($|[^.]|\.[^a-z])),https\2,g
s,(http|ftp)(://(.*\.)?)sources\.redhat\.com($|[^.]|\.[^a-z]),https\2sourceware.org\4,g
' \
$(find $(git ls-files) -prune -type f \
! -name '*.po' \
! -name 'ChangeLog*' \
! -path COPYING ! -path COPYING.LIB \
! -path manual/fdl-1.3.texi ! -path manual/lgpl-2.1.texi \
! -path manual/texinfo.tex ! -path scripts/config.guess \
! -path scripts/config.sub ! -path scripts/install-sh \
! -path scripts/mkinstalldirs ! -path scripts/move-if-change \
! -path INSTALL ! -path locale/programs/charmap-kw.h \
! -path po/libc.pot ! -path sysdeps/gnu/errlist.c \
! '(' -name configure \
-execdir test -f configure.ac -o -f configure.in ';' ')' \
! '(' -name preconfigure \
-execdir test -f preconfigure.ac ';' ')' \
-print)
and then by running 'make dist-prepare' to regenerate files built
from the altered files, and then executing the following to cleanup:
chmod a+x sysdeps/unix/sysv/linux/riscv/configure
# Omit irrelevant whitespace and comment-only changes,
# perhaps from a slightly-different Autoconf version.
git checkout -f \
sysdeps/csky/configure \
sysdeps/hppa/configure \
sysdeps/riscv/configure \
sysdeps/unix/sysv/linux/csky/configure
# Omit changes that caused a pre-commit check to fail like this:
# remote: *** error: sysdeps/powerpc/powerpc64/ppc-mcount.S: trailing lines
git checkout -f \
sysdeps/powerpc/powerpc64/ppc-mcount.S \
sysdeps/unix/sysv/linux/s390/s390-64/syscall.S
# Omit change that caused a pre-commit check to fail like this:
# remote: *** error: sysdeps/sparc/sparc64/multiarch/memcpy-ultra3.S: last line does not end in newline
git checkout -f sysdeps/sparc/sparc64/multiarch/memcpy-ultra3.S
2019-09-07 13:40:42 +08:00
|
|
|
<https://www.gnu.org/licenses/>. */
|
1995-02-18 09:27:10 +08:00
|
|
|
|
stdlib: Make getenv thread-safe in more cases
Async-signal-safety is preserved, too. In fact, getenv is fully
reentrant and can be called from the malloc call in setenv
(if a replacement malloc uses getenv during its initialization).
This is relatively easy to implement because even before this change,
setenv, unsetenv, clearenv, putenv do not deallocate the environment
strings themselves as they are removed from the environment.
The main changes are:
* Use release stores for environment array updates, following
the usual pattern for safely publishing immutable data
(in this case, the environment strings).
* Do not deallocate the environment array. Instead, keep older
versions around and adopt an exponential resizing policy. This
results in an amortized constant space leak per active environment
variable, but there already is such a leak for the variable itself
(and that is even length-dependent, and includes no-longer used
values).
* Add a seqlock-like mechanism to retry getenv if a concurrent
unsetenv is observed. Without that, it is possible that
getenv returns NULL for a variable that is never unset. This
is visible on some AArch64 implementations with the newly
added stdlib/tst-getenv-unsetenv test case. The mechanism
is not a pure seqlock because it tolerates one write from
unsetenv. This avoids the need for a second copy of the
environ array that getenv can read from a signal handler
that happens to interrupt an unsetenv call.
No manual updates are included with this patch because environ
usage with execve, posix_spawn, system is still not thread-safe
relative unsetenv. The new process may end up with an environment
that misses entries that were never unset. This is the same issue
described above for getenv.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
2024-11-22 04:10:52 +08:00
|
|
|
#include <atomic.h>
|
|
|
|
#include <setenv.h>
|
1995-02-18 09:27:10 +08:00
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
stdlib: Make getenv thread-safe in more cases
Async-signal-safety is preserved, too. In fact, getenv is fully
reentrant and can be called from the malloc call in setenv
(if a replacement malloc uses getenv during its initialization).
This is relatively easy to implement because even before this change,
setenv, unsetenv, clearenv, putenv do not deallocate the environment
strings themselves as they are removed from the environment.
The main changes are:
* Use release stores for environment array updates, following
the usual pattern for safely publishing immutable data
(in this case, the environment strings).
* Do not deallocate the environment array. Instead, keep older
versions around and adopt an exponential resizing policy. This
results in an amortized constant space leak per active environment
variable, but there already is such a leak for the variable itself
(and that is even length-dependent, and includes no-longer used
values).
* Add a seqlock-like mechanism to retry getenv if a concurrent
unsetenv is observed. Without that, it is possible that
getenv returns NULL for a variable that is never unset. This
is visible on some AArch64 implementations with the newly
added stdlib/tst-getenv-unsetenv test case. The mechanism
is not a pure seqlock because it tolerates one write from
unsetenv. This avoids the need for a second copy of the
environ array that getenv can read from a signal handler
that happens to interrupt an unsetenv call.
No manual updates are included with this patch because environ
usage with execve, posix_spawn, system is still not thread-safe
relative unsetenv. The new process may end up with an environment
that misses entries that were never unset. This is the same issue
described above for getenv.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
2024-11-22 04:10:52 +08:00
|
|
|
struct environ_array *__environ_array_list;
|
|
|
|
environ_counter __environ_counter;
|
|
|
|
|
1995-02-18 09:27:10 +08:00
|
|
|
char *
|
2015-10-17 04:21:49 +08:00
|
|
|
getenv (const char *name)
|
1995-02-18 09:27:10 +08:00
|
|
|
{
|
stdlib: Make getenv thread-safe in more cases
Async-signal-safety is preserved, too. In fact, getenv is fully
reentrant and can be called from the malloc call in setenv
(if a replacement malloc uses getenv during its initialization).
This is relatively easy to implement because even before this change,
setenv, unsetenv, clearenv, putenv do not deallocate the environment
strings themselves as they are removed from the environment.
The main changes are:
* Use release stores for environment array updates, following
the usual pattern for safely publishing immutable data
(in this case, the environment strings).
* Do not deallocate the environment array. Instead, keep older
versions around and adopt an exponential resizing policy. This
results in an amortized constant space leak per active environment
variable, but there already is such a leak for the variable itself
(and that is even length-dependent, and includes no-longer used
values).
* Add a seqlock-like mechanism to retry getenv if a concurrent
unsetenv is observed. Without that, it is possible that
getenv returns NULL for a variable that is never unset. This
is visible on some AArch64 implementations with the newly
added stdlib/tst-getenv-unsetenv test case. The mechanism
is not a pure seqlock because it tolerates one write from
unsetenv. This avoids the need for a second copy of the
environ array that getenv can read from a signal handler
that happens to interrupt an unsetenv call.
No manual updates are included with this patch because environ
usage with execve, posix_spawn, system is still not thread-safe
relative unsetenv. The new process may end up with an environment
that misses entries that were never unset. This is the same issue
described above for getenv.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
2024-11-22 04:10:52 +08:00
|
|
|
while (true)
|
1998-06-08 21:37:25 +08:00
|
|
|
{
|
stdlib: Make getenv thread-safe in more cases
Async-signal-safety is preserved, too. In fact, getenv is fully
reentrant and can be called from the malloc call in setenv
(if a replacement malloc uses getenv during its initialization).
This is relatively easy to implement because even before this change,
setenv, unsetenv, clearenv, putenv do not deallocate the environment
strings themselves as they are removed from the environment.
The main changes are:
* Use release stores for environment array updates, following
the usual pattern for safely publishing immutable data
(in this case, the environment strings).
* Do not deallocate the environment array. Instead, keep older
versions around and adopt an exponential resizing policy. This
results in an amortized constant space leak per active environment
variable, but there already is such a leak for the variable itself
(and that is even length-dependent, and includes no-longer used
values).
* Add a seqlock-like mechanism to retry getenv if a concurrent
unsetenv is observed. Without that, it is possible that
getenv returns NULL for a variable that is never unset. This
is visible on some AArch64 implementations with the newly
added stdlib/tst-getenv-unsetenv test case. The mechanism
is not a pure seqlock because it tolerates one write from
unsetenv. This avoids the need for a second copy of the
environ array that getenv can read from a signal handler
that happens to interrupt an unsetenv call.
No manual updates are included with this patch because environ
usage with execve, posix_spawn, system is still not thread-safe
relative unsetenv. The new process may end up with an environment
that misses entries that were never unset. This is the same issue
described above for getenv.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
2024-11-22 04:10:52 +08:00
|
|
|
/* Used to deal with concurrent unsetenv. */
|
|
|
|
environ_counter start_counter = atomic_load_acquire (&__environ_counter);
|
|
|
|
|
|
|
|
/* We use relaxed MO for loading the string pointers because we
|
|
|
|
assume the strings themselves are immutable and that loads
|
|
|
|
through the string pointers carry a dependency. (This
|
|
|
|
depends on the the release MO store to __environ in
|
|
|
|
__add_to_environ.) Objects pointed to by pointers stored in
|
|
|
|
the __environ array are never modified or deallocated (except
|
|
|
|
perhaps if putenv is used, but then synchronization is the
|
|
|
|
responsibility of the applications). The backing store for
|
|
|
|
__environ is allocated zeroed. In summary, we can assume
|
|
|
|
that the pointers we observe are either valid or null, and
|
|
|
|
that only initialized string contents is visible. */
|
|
|
|
char **start_environ = atomic_load_relaxed (&__environ);
|
|
|
|
if (start_environ == NULL || name[0] == '\0')
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
size_t len = strlen (name);
|
|
|
|
for (char **ep = start_environ; ; ++ep)
|
|
|
|
{
|
|
|
|
char *entry = atomic_load_relaxed (ep);
|
|
|
|
if (entry == NULL)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* If there is a match, return that value. It was valid at
|
|
|
|
one point, so we can return it. */
|
|
|
|
if (name[0] == entry[0]
|
|
|
|
&& strncmp (name, entry, len) == 0 && entry[len] == '=')
|
|
|
|
return entry + len + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The variable was not found. This might be a false negative
|
|
|
|
because unsetenv has shuffled around entries. Check if it is
|
|
|
|
necessary to retry. */
|
|
|
|
|
|
|
|
/* See Hans Boehm, Can Seqlocks Get Along with Programming Language
|
|
|
|
Memory Models?, Section 4. This is necessary so that loads in
|
|
|
|
the loop above are not ordered past the counter check below. */
|
|
|
|
atomic_thread_fence_acquire ();
|
|
|
|
|
|
|
|
if (atomic_load_acquire (&__environ_counter) == start_counter)
|
|
|
|
/* If we reach this point and there was a concurrent
|
|
|
|
unsetenv call which removed the key we tried to find, the
|
|
|
|
NULL return value is valid. We can also try again, not
|
|
|
|
find the value, and then return NULL (assuming there are
|
|
|
|
no further concurrent unsetenv calls).
|
|
|
|
|
|
|
|
However, if getenv is called to find a value that is
|
|
|
|
present originally and not removed by any of the
|
|
|
|
concurrent unsetenv calls, we must not return NULL here.
|
|
|
|
|
|
|
|
If the counter did not change, there was at most one
|
|
|
|
write to the array in unsetenv while the scanning loop
|
|
|
|
above was running. This means that there are at most two
|
|
|
|
different versions of the array to consider. For the
|
|
|
|
sake of argument, we assume that each load can make an
|
|
|
|
independent choice which version to use. An arbitrary
|
|
|
|
number of unsetenv and setenv calls may have happened
|
|
|
|
since start of getenv. Lets write E[0], E[1], ... for
|
|
|
|
the original environment elements, a(0) < (1) < ... for a
|
|
|
|
sequence of increasing integers that are the indices of
|
|
|
|
the environment variables remaining after the removals, and
|
|
|
|
N[0], N[1], ... for the new variables added by setenv or
|
|
|
|
putenv. Then at the start of the last unsetenv call, the
|
|
|
|
environment contains
|
|
|
|
|
|
|
|
E[a(0)], E[a(1)], ..., N[0], N[1], ...
|
1995-02-18 09:27:10 +08:00
|
|
|
|
stdlib: Make getenv thread-safe in more cases
Async-signal-safety is preserved, too. In fact, getenv is fully
reentrant and can be called from the malloc call in setenv
(if a replacement malloc uses getenv during its initialization).
This is relatively easy to implement because even before this change,
setenv, unsetenv, clearenv, putenv do not deallocate the environment
strings themselves as they are removed from the environment.
The main changes are:
* Use release stores for environment array updates, following
the usual pattern for safely publishing immutable data
(in this case, the environment strings).
* Do not deallocate the environment array. Instead, keep older
versions around and adopt an exponential resizing policy. This
results in an amortized constant space leak per active environment
variable, but there already is such a leak for the variable itself
(and that is even length-dependent, and includes no-longer used
values).
* Add a seqlock-like mechanism to retry getenv if a concurrent
unsetenv is observed. Without that, it is possible that
getenv returns NULL for a variable that is never unset. This
is visible on some AArch64 implementations with the newly
added stdlib/tst-getenv-unsetenv test case. The mechanism
is not a pure seqlock because it tolerates one write from
unsetenv. This avoids the need for a second copy of the
environ array that getenv can read from a signal handler
that happens to interrupt an unsetenv call.
No manual updates are included with this patch because environ
usage with execve, posix_spawn, system is still not thread-safe
relative unsetenv. The new process may end up with an environment
that misses entries that were never unset. This is the same issue
described above for getenv.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
2024-11-22 04:10:52 +08:00
|
|
|
(the N[0], N[1], .... are optional.) Let's assume that
|
|
|
|
we are looking for the value E[j]. Then one of the
|
|
|
|
a(i) == j (otherwise we may return NULL here because
|
|
|
|
of a unsetenv for the value we are looking for). In the
|
|
|
|
discussion below it will become clear that the N[k] do
|
|
|
|
not actually matter.
|
|
|
|
|
|
|
|
The two versions of array we can choose from differ only
|
|
|
|
in one element, say E[a(i)]. There are two cases:
|
|
|
|
|
|
|
|
Case (A): E[a(i)] is an element being removed by unsetenv
|
|
|
|
(the target of the first write). We can see the original
|
|
|
|
version:
|
|
|
|
|
|
|
|
..., E[a(i-1)], E[a(i)], E[a(i+1)], ..., N[0], ...
|
|
|
|
-------
|
|
|
|
And the overwritten version:
|
|
|
|
|
|
|
|
..., E[a(i-1)], E[a(i+1)], E[a(i+1)], ..., N[0], ...
|
|
|
|
---------
|
|
|
|
|
|
|
|
(The valueE[a(i+1)] can be the terminating NULL.)
|
|
|
|
As discussed, we are not considering the removal of the
|
|
|
|
variable being searched for, so a(i) != j, and the
|
|
|
|
variable getenv is looking for is available in either
|
|
|
|
version, and we would have found it above.
|
|
|
|
|
|
|
|
Case (B): E[a(i)] is an element that has already been
|
|
|
|
moved forward and is now itself being overwritten with
|
|
|
|
its sucessor value E[a(i+1)]. The two versions of the
|
|
|
|
array look like this:
|
|
|
|
|
|
|
|
..., E[a(i-1)], E[a(i)], E[a(i)], E[a(i+1)], ..., N[0], ...
|
|
|
|
-------
|
|
|
|
And with the overwrite in place:
|
|
|
|
|
|
|
|
..., E[a(i-1)], E[a(i)], E[a(i+1)], E[a(i+1)], ..., N[0], ...
|
|
|
|
---------
|
|
|
|
|
|
|
|
The key observation here is that even in the second
|
|
|
|
version with the overwrite present, the scanning loop
|
|
|
|
will still encounter the overwritten value E[a(i)] in the
|
|
|
|
previous array element. This means that as long as the
|
|
|
|
E[j] is still present among the initial E[a(...)] (as we
|
|
|
|
assumed because there is no concurrent unsetenv for
|
|
|
|
E[j]), we encounter it while scanning here in getenv.
|
|
|
|
|
|
|
|
In summary, if there was at most one write, a negative
|
|
|
|
result is a true negative, and we can return NULL. This
|
|
|
|
is different from the seqlock paper, which retries if
|
|
|
|
there was any write at all. It avoids the need for a
|
|
|
|
second, unwritten copy for async-signal-safety. */
|
|
|
|
return NULL;
|
|
|
|
/* If there was one more write, retry. This will never happen
|
|
|
|
in a signal handler that interrupted unsetenv because the
|
|
|
|
suspended unsetenv call cannot change the counter value. */
|
|
|
|
}
|
1995-02-18 09:27:10 +08:00
|
|
|
}
|
2002-08-03 20:59:33 +08:00
|
|
|
libc_hidden_def (getenv)
|