* [PATCH v2 00/13] getenv/environ thread safety
@ 2024-07-28 19:02 Florian Weimer
2024-07-28 19:02 ` [PATCH v2 01/13] stdlib: Make getenv thread-safe in more cases Florian Weimer
` (12 more replies)
0 siblings, 13 replies; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:02 UTC (permalink / raw)
To: libc-alpha
This patch series implements thread-safe getenv, and environment
snapshotting for the execve and posix_spawn function families.
The execve snapshotting is not ideal because it requires stack
allocation. I have a more complicated version that uses an mmap
fallback for large allocations and avoids the vfork memory leak.
But I hope we won't need it.
Some of the concurrency tests have run for many hours on AArch64 and
POWER systems. The series has also been built with
build-many-glibcs.py.
Thanks,
Florian
Florian Weimer (13):
stdlib: Make getenv thread-safe in more cases
Linux: Assume __NR_execveat is defined in fexecve
Linux: Introduce __do_fexecve in fexecve
Linux: Error handling fixes for fexecve
posix: Consolidate execve family of functions
posix: Introduce the __arch_spawni function
stdlib: Add internal __getenvarray function
support: Add support_count_maps
posix: New tst-vfork-mappings test case
posix: Improve thread safety of functions using environ with execveat
posix: Implement environ snapshotting in __spawni
posix: Acquire the environment lock during fork
manual: Updates for thread-safe getenv
NEWS | 5 +
manual/argp.texi | 20 +-
manual/conf.texi | 2 +-
manual/filesys.texi | 4 +-
manual/getopt.texi | 6 +-
manual/intro.texi | 17 +-
manual/locale.texi | 9 +-
manual/macros.texi | 4 -
manual/memory.texi | 18 +-
manual/message.texi | 37 ++-
manual/pattern.texi | 64 +++--
manual/process.texi | 4 +-
manual/socket.texi | 145 ++++++-----
manual/startup.texi | 122 +++++++--
manual/sysinfo.texi | 2 +-
manual/syslog.texi | 18 +-
manual/time.texi | 150 ++++++-----
posix/Makefile | 2 +
posix/execve.c | 13 +-
posix/execveat.c | 33 ++-
posix/fexecve.c | 13 +-
posix/fork.c | 9 +
posix/spawn_int.h | 7 +
posix/spawni.c | 103 +++++++-
posix/tst-fexecve.c | 14 ++
posix/tst-vfork-mappings.c | 195 +++++++++++++++
stdlib/Makefile | 31 ++-
stdlib/environ_single_threaded_no_snapshot.c | 40 +++
stdlib/getenv.c | 140 ++++++++++-
stdlib/getenvarray.c | 64 +++++
stdlib/putenv.c | 2 +
stdlib/setenv.c | 232 +++++++++++++-----
stdlib/setenv.h | 98 ++++++++
.../execveat.c => stdlib/tst-environ-execl.c | 29 ++-
stdlib/tst-environ-fork.c | 38 +++
.../tst-environ-posix_spawn.c | 25 +-
stdlib/tst-environ-snapshot-skeleton.c | 218 ++++++++++++++++
stdlib/tst-environ.c | 13 +-
stdlib/tst-getenv-signal.c | 94 +++++++
stdlib/tst-getenv-thread.c | 61 +++++
stdlib/tst-getenv-unsetenv.c | 75 ++++++
support/Makefile | 2 +
support/support.h | 5 +
support/support_count_maps.c | 51 ++++
support/tst-support_count_maps.c | 52 ++++
sysdeps/generic/arch-execveat.h | 25 ++
sysdeps/generic/arch-spawni.h | 27 ++
.../mach/hurd/{execveat.c => arch-execveat.h} | 11 +-
sysdeps/mach/hurd/{spawni.c => arch-spawni.h} | 12 +-
sysdeps/mach/hurd/fexecve.c | 47 ----
sysdeps/unix/sysv/linux/arch-execveat.h | 78 ++++++
.../sysv/linux/{spawni.c => arch-spawni.h} | 18 +-
sysdeps/unix/sysv/linux/fexecve.c | 69 ------
sysdeps/unix/sysv/linux/syscalls.list | 1 -
54 files changed, 1985 insertions(+), 589 deletions(-)
create mode 100644 posix/tst-vfork-mappings.c
create mode 100644 stdlib/environ_single_threaded_no_snapshot.c
create mode 100644 stdlib/getenvarray.c
create mode 100644 stdlib/setenv.h
rename sysdeps/unix/sysv/linux/execveat.c => stdlib/tst-environ-execl.c (56%)
create mode 100644 stdlib/tst-environ-fork.c
rename sysdeps/mach/hurd/execve.c => stdlib/tst-environ-posix_spawn.c (61%)
create mode 100644 stdlib/tst-environ-snapshot-skeleton.c
create mode 100644 stdlib/tst-getenv-signal.c
create mode 100644 stdlib/tst-getenv-thread.c
create mode 100644 stdlib/tst-getenv-unsetenv.c
create mode 100644 support/support_count_maps.c
create mode 100644 support/tst-support_count_maps.c
create mode 100644 sysdeps/generic/arch-execveat.h
create mode 100644 sysdeps/generic/arch-spawni.h
rename sysdeps/mach/hurd/{execveat.c => arch-execveat.h} (90%)
rename sysdeps/mach/hurd/{spawni.c => arch-spawni.h} (99%)
delete mode 100644 sysdeps/mach/hurd/fexecve.c
create mode 100644 sysdeps/unix/sysv/linux/arch-execveat.h
rename sysdeps/unix/sysv/linux/{spawni.c => arch-spawni.h} (96%)
delete mode 100644 sysdeps/unix/sysv/linux/fexecve.c
base-commit: 32328a5a1461ff88c0b1e04954e9c68b3fa7f56d
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 01/13] stdlib: Make getenv thread-safe in more cases
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
@ 2024-07-28 19:02 ` Florian Weimer
2024-10-16 17:30 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 02/13] Linux: Assume __NR_execveat is defined in fexecve Florian Weimer
` (11 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:02 UTC (permalink / raw)
To: libc-alpha
Async-signal-safety is preserved, too.
---
stdlib/Makefile | 7 ++
stdlib/getenv.c | 138 +++++++++++++++++++--
stdlib/putenv.c | 2 +
stdlib/setenv.c | 225 +++++++++++++++++++++++++----------
stdlib/setenv.h | 73 ++++++++++++
stdlib/tst-environ.c | 13 +-
stdlib/tst-getenv-signal.c | 94 +++++++++++++++
stdlib/tst-getenv-thread.c | 61 ++++++++++
stdlib/tst-getenv-unsetenv.c | 75 ++++++++++++
9 files changed, 605 insertions(+), 83 deletions(-)
create mode 100644 stdlib/setenv.h
create mode 100644 stdlib/tst-getenv-signal.c
create mode 100644 stdlib/tst-getenv-thread.c
create mode 100644 stdlib/tst-getenv-unsetenv.c
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 8b0ac63ddb..697b547540 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -275,6 +275,9 @@ tests := \
tst-canon-bz26341 \
tst-cxa_atexit \
tst-environ \
+ tst-getenv-signal \
+ tst-getenv-thread \
+ tst-getenv-unsetenv \
tst-getrandom \
tst-labs \
tst-limits \
@@ -623,3 +626,7 @@ $(objpfx)tst-setcontext3.out: tst-setcontext3.sh $(objpfx)tst-setcontext3
$(evaluate-test)
$(objpfx)tst-qsort5: $(libm)
+
+$(objpfx)tst-getenv-signal: $(shared-thread-library)
+$(objpfx)tst-getenv-thread: $(shared-thread-library)
+$(objpfx)tst-getenv-unsetenv: $(shared-thread-library)
diff --git a/stdlib/getenv.c b/stdlib/getenv.c
index bea69e0f40..ad7f0d00c8 100644
--- a/stdlib/getenv.c
+++ b/stdlib/getenv.c
@@ -15,24 +15,140 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
-#include <stdlib.h>
+#include <atomic.h>
+#include <setenv.h>
#include <string.h>
#include <unistd.h>
+struct environ_array *__environ_array_list;
+environ_counter __environ_counter;
+
char *
getenv (const char *name)
{
- if (__environ == NULL || name[0] == '\0')
- return NULL;
-
- size_t len = strlen (name);
- for (char **ep = __environ; *ep != NULL; ++ep)
+ while (true)
{
- if (name[0] == (*ep)[0]
- && strncmp (name, *ep, len) == 0 && (*ep)[len] == '=')
- return *ep + len + 1;
- }
+ /* Used to deal with concurrent unsetenv. */
+ environ_counter start_counter = atomic_load_acquire (&__environ_counter);
+
+ /* We use relaxed MO here because we assume that loads carry a
+ dependency, matching the release MO store to __environ in
+ __add_to_environ. Objects pointed to by pointers stored in the
+ __environ array are never 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. String stores into the environment
+ array use release MO, so we can assume the loads here see the
+ initialized contents (againt except for putenv). */
+ 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 remaing 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], ...
- return NULL;
+ (the N[k] 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. */
+ return NULL;
+ /* Otherwise retry. This will never happen in a signal handler
+ that interrupted unsetenv because the suspended unsetenv call
+ cannot change the counter value. */
+ }
}
libc_hidden_def (getenv)
diff --git a/stdlib/putenv.c b/stdlib/putenv.c
index b234ce0612..c61beb226f 100644
--- a/stdlib/putenv.c
+++ b/stdlib/putenv.c
@@ -23,6 +23,8 @@
# include <config.h>
#endif
+#include <setenv.h>
+
#if _LIBC || HAVE_STDLIB_H
# include <stdlib.h>
#endif
diff --git a/stdlib/setenv.c b/stdlib/setenv.c
index e2164371ad..6bd0596b79 100644
--- a/stdlib/setenv.c
+++ b/stdlib/setenv.c
@@ -19,6 +19,9 @@
# include <config.h>
#endif
+#include <assert.h>
+#include <setenv.h>
+
/* Pacify GCC; see the commentary about VALLEN below. This is needed
at least through GCC 4.9.2. Pacify GCC for the entire file, as
there seems to be no way to pacify GCC selectively, only for the
@@ -100,25 +103,51 @@ static void *known_values;
#endif
+/* Allocate a new environment array and put it o the
+ __environ_array_list. Returns NULL on memory allocation
+ failure. */
+static struct environ_array *
+__environ_new_array (size_t required_size)
+{
+ /* No backing array yet, or insufficient room. */
+ size_t new_size;
+ if (__environ_array_list == NULL
+ || __environ_array_list->allocated * 2 < required_size)
+ /* Add some unused space for future growth. */
+ new_size = required_size + 16;
+ else
+ new_size = __environ_array_list->allocated * 2;
+
+ size_t new_size_in_bytes;
+ if (__builtin_mul_overflow (new_size, sizeof (char *),
+ &new_size_in_bytes)
+ || __builtin_add_overflow (new_size_in_bytes,
+ offsetof (struct environ_array,
+ array),
+ &new_size_in_bytes))
+ {
+ __set_errno (ENOMEM);
+ return NULL;
+ }
-/* If this variable is not a null pointer we allocated the current
- environment. */
-static char **last_environ;
-
+ /* Zero-initialize everything, so that getenv can only
+ observe valid or null pointers. */
+ struct environ_array *target_array = calloc (1, new_size_in_bytes);
+ if (target_array == NULL)
+ return NULL;
+ target_array->allocated = new_size;
+ assert (new_size >= target_array->allocated);
+
+ /* Put it onto the list. */
+ target_array->next = __environ_array_list;
+ __environ_array_list = target_array;
+ return target_array;
+}
-/* This function is used by `setenv' and `putenv'. The difference between
- the two functions is that for the former must create a new string which
- is then placed in the environment, while the argument of `putenv'
- must be used directly. This is all complicated by the fact that we try
- to reuse values once generated for a `setenv' call since we can never
- free the strings. */
int
__add_to_environ (const char *name, const char *value, const char *combined,
int replace)
{
- char **ep;
- size_t size;
-
/* Compute lengths before locking, so that the critical section is
less of a performance bottleneck. VALLEN is needed only if
COMBINED is null (unfortunately GCC is not smart enough to deduce
@@ -133,45 +162,80 @@ __add_to_environ (const char *name, const char *value, const char *combined,
LOCK;
/* We have to get the pointer now that we have the lock and not earlier
- since another thread might have created a new environment. */
- ep = __environ;
+ since another thread might have created a new environment. */
+ char **start_environ = atomic_load_relaxed (&__environ);
+ char **ep = start_environ;
- size = 0;
+ /* This gets written to __environ in the end. */
+ char **result_environ = start_environ;
+
+ /* Size of the environment if *ep == NULL. */
if (ep != NULL)
- {
- for (; *ep != NULL; ++ep)
- if (!strncmp (*ep, name, namelen) && (*ep)[namelen] == '=')
- break;
- else
- ++size;
- }
+ for (; *ep != NULL; ++ep)
+ if (strncmp (*ep, name, namelen) == 0 && (*ep)[namelen] == '=')
+ break;
- if (ep == NULL || __builtin_expect (*ep == NULL, 1))
+ if (ep == NULL || __glibc_likely (*ep == NULL))
{
- char **new_environ;
-
- /* We allocated this space; we can extend it. Avoid using the raw
- reallocated pointer to avoid GCC -Wuse-after-free. */
- uintptr_t ip_last_environ = (uintptr_t)last_environ;
- new_environ = (char **) realloc (last_environ,
- (size + 2) * sizeof (char *));
- if (new_environ == NULL)
+ /* The scanning loop above reached the end of the environment.
+ Add a new string to it. */
+ replace = true;
+
+ /* + 2 for the new entry and the terminating NULL. */
+ size_t required_size = (ep - start_environ) + 2;
+ if (__environ_is_from_array_list (start_environ)
+ && required_size <= __environ_array_list->allocated)
+ /* The __environ array is ours, and we have room in it. We
+ can use ep as-is. Add a null terminator in case current
+ usage is less than previous usage. */
+ ep[1] = NULL;
+ else
{
- UNLOCK;
- return -1;
- }
-
- if ((uintptr_t)__environ != ip_last_environ)
- memcpy ((char *) new_environ, (char *) __environ,
- size * sizeof (char *));
-
- new_environ[size] = NULL;
- new_environ[size + 1] = NULL;
- ep = new_environ + size;
+ /* We cannot use __environ as is and need to copy over the
+ __environ contents into an array managed via
+ __environ_array_list. */
+
+ struct environ_array *target_array;
+ if (__environ_array_list != NULL
+ && required_size <= __environ_array_list->allocated)
+ /* Existing array has enough room. Contents is copied below. */
+ target_array = __environ_array_list;
+ else
+ {
+ /* Allocate a new array. */
+ target_array = __environ_new_array (required_size);
+ if (target_array == NULL)
+ {
+ UNLOCK;
+ return -1;
+ }
+ }
- last_environ = __environ = new_environ;
+ /* Copy over the __environ array contents. This is a
+ backwards copy if __environ points into
+ target_array->array, and it does not clobber values still
+ needing copying. This handles the case start_environ
+ == ep == NULL, too. */
+ size_t i;
+ for (i = 0; start_environ + i < ep; ++i)
+ /* Regular store because unless there has been direct
+ manipulation of the environment, target_array is still
+ a private copy. */
+ target_array->array[i] = atomic_load_relaxed (start_environ + i);
+
+ /* This is the new place where we should add the element. */
+ ep = target_array->array + i;
+
+ /* Add the null terminator in case there was a pointer there
+ previously. */
+ ep[1] = NULL;
+
+ /* And __environ should be repointed to our array. */
+ result_environ = &target_array->array[0];
+ }
}
- if (*ep == NULL || replace)
+
+ if (replace || *ep == NULL)
{
char *np;
@@ -213,9 +277,15 @@ __add_to_environ (const char *name, const char *value, const char *combined,
#endif
}
- *ep = np;
+ /* Use release MO so that loads are sufficient to observe the
+ pointer contents because the CPU carries the dependency for
+ us. This also acts as a thread fence, making getenv
+ async-signal-safe. */
+ atomic_store_release (ep, np);
+ atomic_store_release (&__environ, result_environ);
}
+
UNLOCK;
return 0;
@@ -249,18 +319,40 @@ unsetenv (const char *name)
LOCK;
- ep = __environ;
+ ep = atomic_load_relaxed (&__environ);
if (ep != NULL)
- while (*ep != NULL)
+ while (true)
{
- if (!strncmp (*ep, name, len) && (*ep)[len] == '=')
+ char *entry = atomic_load_relaxed (ep);
+ if (entry == NULL)
+ break;
+ if (!strncmp (entry, name, len) && entry[len] == '=')
{
/* Found it. Remove this pointer by moving later ones back. */
char **dp = ep;
- do
- dp[0] = dp[1];
- while (*dp++);
+ while (true)
+ {
+ char *next_value = atomic_load_relaxed (dp + 1);
+ /* This store overwrites a value that has been
+ removed, or that has already been written to a
+ previous value. Release MO so that this store does
+ not get reordered before the counter update in the
+ previous loop iteration. */
+ atomic_store_release (dp, next_value);
+ /* Release store synchronizes with acquire loads in
+ getenv. Non-atomic update because there is just
+ one writer due to the lock.
+
+ See discussion of the counter check in getenv for
+ an explanation why this is sufficient synchronization. */
+ atomic_store_release (&__environ_counter,
+ atomic_load_relaxed (&__environ_counter)
+ + 1);
+ if (next_value == NULL)
+ break;
+ ++dp;
+ }
/* Continue the loop in case NAME appears again. */
}
else
@@ -279,19 +371,20 @@ int
clearenv (void)
{
LOCK;
-
- if (__environ == last_environ && __environ != NULL)
+ char **start_environ = atomic_load_relaxed (&__environ);
+ if (__environ_is_from_array_list (start_environ))
{
- /* We allocated this environment so we can free it. */
- free (__environ);
- last_environ = NULL;
+ /* Store null pointers to avoid strange effects when the array
+ is reused in setenv. */
+ for (char **ep = start_environ; *ep != NULL; ++ep)
+ atomic_store_relaxed(ep, NULL);
+ /* Update the counter similar to unsetenv, so that the writes in
+ setenv do not need to update the counter. */
+ atomic_store_release (&__environ_counter,
+ atomic_load_relaxed (&__environ_counter) + 1);
}
-
- /* Clear the environment pointer removes the whole environment. */
- __environ = NULL;
-
+ atomic_store_relaxed (&__environ, NULL);
UNLOCK;
-
return 0;
}
#ifdef _LIBC
@@ -301,6 +394,14 @@ __libc_setenv_freemem (void)
/* Remove all traces. */
clearenv ();
+ /* Clear all backing arrays. */
+ while (__environ_array_list != NULL)
+ {
+ void *ptr = __environ_array_list;
+ __environ_array_list = __environ_array_list->next;
+ free (ptr);
+ }
+
/* Now remove the search tree. */
__tdestroy (known_values, free);
known_values = NULL;
diff --git a/stdlib/setenv.h b/stdlib/setenv.h
new file mode 100644
index 0000000000..3919943f87
--- /dev/null
+++ b/stdlib/setenv.h
@@ -0,0 +1,73 @@
+/* Common declarations for the setenv/getenv family of functions.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#ifndef _SETENV_H
+#define _SETENV_H
+
+#include <atomic.h>
+#include <stdbool.h>
+
+/* We use an exponential sizing policy for environment arrays. The
+ arrays are not deallocating during the lifetime of the process.
+ This adds between one and two additional pointers per active
+ environemnt entry, on top of what is used by setenv to keep track
+ of environment values used before. */
+struct environ_array
+{
+ struct environ_array *next; /* Previously used environment array. */
+ size_t allocated; /* Number of allocated array elments. */
+ char *array[]; /* The actual environment array. */
+};
+
+/* After initialization, and until the user resets environ (perhaps by
+ calling clearenv), &__environ[0] == &environ_array_list->array[0]. */
+extern struct environ_array *__environ_array_list attribute_hidden;
+
+/* Returns true if EP (which should be an __environ value) is a
+ pointer managed by setenv. */
+static inline _Bool
+__environ_is_from_array_list (char **ep)
+{
+ struct environ_array *eal = atomic_load_relaxed (&__environ_array_list);
+ return eal != NULL && &eal->array[0] == ep;
+}
+
+/* Counter for detecting concurrent modification in unsetenv.
+ Ideally, this should be a 64-bit counter that cannot wrap around,
+ but given that counter wrapround is probably impossible to hit
+ (2**32 operations in unsetenv concurrently with getenv), using
+ <atomic_wide_counter.h> seems unnecessary. */
+#if __HAVE_64B_ATOMICS
+typedef uint64_t environ_counter;
+#else
+typedef uint32_t environ_counter;
+#endif
+
+/* Updated by unsetenv to detect multiple overwrites in getenv. */
+extern environ_counter __environ_counter attribute_hidden;
+
+/* This function is used by `setenv' and `putenv'. The difference between
+ the two functions is that for the former must create a new string which
+ is then placed in the environment, while the argument of `putenv'
+ must be used directly. This is all complicated by the fact that we try
+ to reuse values once generated for a `setenv' call since we can never
+ free the strings. */
+int __add_to_environ (const char *name, const char *value,
+ const char *combines, int replace) attribute_hidden;
+
+#endif /* _SETENV_H */
diff --git a/stdlib/tst-environ.c b/stdlib/tst-environ.c
index bab8873f08..aff191bb4d 100644
--- a/stdlib/tst-environ.c
+++ b/stdlib/tst-environ.c
@@ -20,6 +20,7 @@
#include <stdlib.h>
#include <string.h>
#include <libc-diag.h>
+#include <support/check.h>
#define VAR "FOOBAR"
@@ -50,11 +51,7 @@ do_test (void)
/* Getting this value should now be possible. */
valp = getenv (VAR);
- if (valp == NULL || strcmp (valp, "one") != 0)
- {
- puts ("getenv #2 failed");
- result = 1;
- }
+ TEST_COMPARE_STRING (valp, "one");
/* Try to replace without the replace flag set. This should fail. */
if (setenv (VAR, "two", 0) != 0)
@@ -65,11 +62,7 @@ do_test (void)
/* The value shouldn't have changed. */
valp = getenv (VAR);
- if (valp == NULL || strcmp (valp, "one") != 0)
- {
- puts ("getenv #3 failed");
- result = 1;
- }
+ TEST_COMPARE_STRING (valp, "one");
/* Now replace the value using putenv. */
if (putenv (putenv_val) != 0)
diff --git a/stdlib/tst-getenv-signal.c b/stdlib/tst-getenv-signal.c
new file mode 100644
index 0000000000..bd35f886b5
--- /dev/null
+++ b/stdlib/tst-getenv-signal.c
@@ -0,0 +1,94 @@
+/* Test getenv from a signal handler interrupting environent updates.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xthread.h>
+#include <support/xsignal.h>
+
+/* Set to false by the main thread after doing all the setenv
+ calls. */
+static bool running = true;
+
+/* Used to synchronize the start of signal sending. */
+static pthread_barrier_t barrier;
+
+/* Identity of the main thread. */
+static pthread_t main_thread;
+
+/* Send SIGUSR1 signals to main_thread. */
+static void *
+signal_thread (void *ignored)
+{
+ xpthread_barrier_wait (&barrier);
+ while (__atomic_load_n (&running, __ATOMIC_RELAXED))
+ xpthread_kill (main_thread, SIGUSR1);
+ return NULL;
+}
+
+/* Call getenv from a signal handler. */
+static void
+signal_handler (int signo)
+{
+ TEST_COMPARE_STRING (getenv ("unset_variable"), NULL);
+ char *value = getenv ("set_variable");
+ TEST_VERIFY (strncmp (value, "value", strlen ("value")) == 0);
+}
+
+static int
+do_test (void)
+{
+ /* Added to the environment using putenv. */
+ char *variables[30];
+ for (int i = 0; i < array_length (variables); ++i)
+ variables[i] = xasprintf ("v%d=%d", i, i);
+
+ xsignal (SIGUSR1, signal_handler);
+ TEST_COMPARE (setenv ("set_variable", "value", 1), 0);
+ xraise (SIGUSR1);
+ main_thread = pthread_self ();
+ xpthread_barrier_init (&barrier, NULL, 2);
+ pthread_t thr = xpthread_create (NULL, signal_thread, NULL);
+ xpthread_barrier_wait (&barrier);
+
+ for (int i = 0; i < array_length (variables); ++i)
+ {
+ char buf[30];
+ TEST_COMPARE (setenv ("temporary_variable", "1", 1), 0);
+ snprintf (buf, sizeof (buf), "V%d", i);
+ TEST_COMPARE (setenv (buf, buf + 1, 1), 0);
+ TEST_COMPARE (putenv (variables[i]), 0);
+ snprintf (buf, sizeof (buf), "value%d", i);
+ TEST_COMPARE (setenv ("set_variable", buf, 1), 0);
+ TEST_COMPARE (unsetenv ("temporary_variable"), 0);
+ }
+
+ __atomic_store_n (&running, false, __ATOMIC_RELAXED);
+ xpthread_join (thr);
+ xpthread_barrier_destroy (&barrier);
+
+ for (int i = 0; i < array_length (variables); ++i)
+ free (variables[i]);
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdlib/tst-getenv-thread.c b/stdlib/tst-getenv-thread.c
new file mode 100644
index 0000000000..e5a79c5227
--- /dev/null
+++ b/stdlib/tst-getenv-thread.c
@@ -0,0 +1,61 @@
+/* Test getenv with concurrent setenv.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/xthread.h>
+
+/* Set to false by the main thread after doing all the setenv
+ calls. */
+static bool running = true;
+
+/* Used to synchronize the start of the getenv thread. */
+static pthread_barrier_t barrier;
+
+/* Invoke getenv for a nonexisting environment variable in a loop.
+ This checks that concurrent setenv does not invalidate the
+ environment array while getenv reads it. */
+static void *
+getenv_thread (void *ignored)
+{
+ xpthread_barrier_wait (&barrier);
+ while (__atomic_load_n (&running, __ATOMIC_RELAXED))
+ TEST_VERIFY (getenv ("unset_variable") == NULL);
+ return NULL;
+}
+
+static int
+do_test (void)
+{
+ xpthread_barrier_init (&barrier, NULL, 2);
+ pthread_t thr = xpthread_create (NULL, getenv_thread, NULL);
+ xpthread_barrier_wait (&barrier);
+ for (int i = 0; i < 1000; ++i)
+ {
+ char buf[30];
+ snprintf (buf, sizeof (buf), "V%d", i);
+ TEST_COMPARE (setenv (buf, buf + 1, 1), 0);
+ }
+ __atomic_store_n (&running, false, __ATOMIC_RELAXED);
+ xpthread_join (thr);
+ xpthread_barrier_destroy (&barrier);
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/stdlib/tst-getenv-unsetenv.c b/stdlib/tst-getenv-unsetenv.c
new file mode 100644
index 0000000000..4d42b5fd69
--- /dev/null
+++ b/stdlib/tst-getenv-unsetenv.c
@@ -0,0 +1,75 @@
+/* Test getenv with concurrent unsetenv.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xthread.h>
+
+/* Used to synchronize the start of each test iteration. */
+static pthread_barrier_t barrier;
+
+/* Number of iterations. */
+enum { iterations = 10000 };
+
+/* Check that even with concurrent unsetenv, a variable that is known
+ to be there is found. */
+static void *
+getenv_thread (void *ignored)
+{
+ for (int i = 0; i < iterations; ++i)
+ {
+ xpthread_barrier_wait (&barrier);
+ TEST_COMPARE_STRING (getenv ("variable"), "value");
+ xpthread_barrier_wait (&barrier);
+ }
+ return NULL;
+}
+
+static int
+do_test (void)
+{
+ xpthread_barrier_init (&barrier, NULL, 2);
+ pthread_t thr = xpthread_create (NULL, getenv_thread, NULL);
+
+ char *variables[50];
+ for (int i = 0; i < array_length (variables); ++i)
+ variables[i] = xasprintf ("V%d", i);
+
+ for (int i = 0; i < iterations; ++i)
+ {
+ clearenv ();
+ for (int j = 0; j < array_length (variables); ++j)
+ TEST_COMPARE (setenv (variables[j], variables[j] + 1, 1), 0);
+ TEST_COMPARE (setenv ("variable", "value", 1), 0);
+ xpthread_barrier_wait (&barrier);
+ /* Test runs. */
+ for (int j = 0; j < array_length (variables); ++j)
+ TEST_COMPARE (unsetenv (variables[j]), 0);
+ xpthread_barrier_wait (&barrier);
+ }
+ xpthread_join (thr);
+ xpthread_barrier_destroy (&barrier);
+ for (int i = 0; i < array_length (variables); ++i)
+ free (variables[i]);
+ return 0;
+}
+
+#include <support/test-driver.c>
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 02/13] Linux: Assume __NR_execveat is defined in fexecve
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
2024-07-28 19:02 ` [PATCH v2 01/13] stdlib: Make getenv thread-safe in more cases Florian Weimer
@ 2024-07-28 19:02 ` Florian Weimer
2024-10-15 19:15 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 03/13] Linux: Introduce __do_fexecve " Florian Weimer
` (10 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:02 UTC (permalink / raw)
To: libc-alpha
It is available on all architectures.
---
sysdeps/unix/sysv/linux/fexecve.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/sysdeps/unix/sysv/linux/fexecve.c b/sysdeps/unix/sysv/linux/fexecve.c
index a62a757ec3..e0e5566fd7 100644
--- a/sysdeps/unix/sysv/linux/fexecve.c
+++ b/sysdeps/unix/sysv/linux/fexecve.c
@@ -39,13 +39,11 @@ fexecve (int fd, char *const argv[], char *const envp[])
return -1;
}
-#ifdef __NR_execveat
/* Avoid implicit array coercion in syscall macros. */
INLINE_SYSCALL (execveat, 5, fd, "", &argv[0], &envp[0], AT_EMPTY_PATH);
-# ifndef __ASSUME_EXECVEAT
+#ifndef __ASSUME_EXECVEAT
if (errno != ENOSYS)
return -1;
-# endif
#endif
#ifndef __ASSUME_EXECVEAT
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 03/13] Linux: Introduce __do_fexecve in fexecve
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
2024-07-28 19:02 ` [PATCH v2 01/13] stdlib: Make getenv thread-safe in more cases Florian Weimer
2024-07-28 19:02 ` [PATCH v2 02/13] Linux: Assume __NR_execveat is defined in fexecve Florian Weimer
@ 2024-07-28 19:02 ` Florian Weimer
2024-10-15 19:51 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 04/13] Linux: Error handling fixes for fexecve Florian Weimer
` (9 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:02 UTC (permalink / raw)
To: libc-alpha
Call the execve system call directly, in preparation of
wrapping environ access in the *execve* family of function.
Make handling of errno more explicit on the fallback path
by using INTERNAL_SYSCALL_CALL.
---
sysdeps/unix/sysv/linux/fexecve.c | 53 ++++++++++++++++---------------
1 file changed, 28 insertions(+), 25 deletions(-)
diff --git a/sysdeps/unix/sysv/linux/fexecve.c b/sysdeps/unix/sysv/linux/fexecve.c
index e0e5566fd7..46c8170092 100644
--- a/sysdeps/unix/sysv/linux/fexecve.c
+++ b/sysdeps/unix/sysv/linux/fexecve.c
@@ -27,6 +27,33 @@
#include <sys/syscall.h>
#include <kernel-features.h>
+static int
+__do_fexecve (int fd, char *const argv[], char *const envp[])
+{
+ /* Avoid implicit array coercion in syscall macros. */
+ int err = -INTERNAL_SYSCALL_CALL (execveat, fd, "", &argv[0], &envp[0],
+ AT_EMPTY_PATH);
+
+#ifndef __ASSUME_EXECVEAT
+ if (err == ENOSYS)
+ {
+ /* We use the /proc filesystem to get the information. If it is not
+ mounted we fail. We do not need the return value. */
+ struct fd_to_filename filename;
+ const char *path = __fd_to_filename (fd, &filename);
+ err = -INTERNAL_SYSCALL_CALL (execve, path, &argv[0], &envp[0]);
+
+ /* We come here only if the 'execve' call fails. Determine whether
+ /proc is mounted. If not we return ENOSYS. */
+ if (INTERNAL_SYSCALL_CALL (faccessat, AT_FDCWD, "/proc/self/fd", 0)
+ == -ENOENT)
+ err = ENOSYS;
+ }
+#endif /* __ASSUME_EXECVEAT */
+
+ __set_errno (err);
+ return -1;
+}
/* Execute the file FD refers to, overlaying the running program image.
ARGV and ENVP are passed to the new program, as for `execve'. */
@@ -39,29 +66,5 @@ fexecve (int fd, char *const argv[], char *const envp[])
return -1;
}
- /* Avoid implicit array coercion in syscall macros. */
- INLINE_SYSCALL (execveat, 5, fd, "", &argv[0], &envp[0], AT_EMPTY_PATH);
-#ifndef __ASSUME_EXECVEAT
- if (errno != ENOSYS)
- return -1;
-#endif
-
-#ifndef __ASSUME_EXECVEAT
- /* We use the /proc filesystem to get the information. If it is not
- mounted we fail. We do not need the return value. */
- struct fd_to_filename filename;
- __execve (__fd_to_filename (fd, &filename), argv, envp);
-
- int save = errno;
-
- /* We come here only if the 'execve' call fails. Determine whether
- /proc is mounted. If not we return ENOSYS. */
- struct __stat64_t64 st;
- if (__stat64_time64 ("/proc/self/fd", &st) != 0 && errno == ENOENT)
- save = ENOSYS;
-
- __set_errno (save);
-#endif
-
- return -1;
+ return __do_fexecve (fd, argv, envp);
}
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 04/13] Linux: Error handling fixes for fexecve
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
` (2 preceding siblings ...)
2024-07-28 19:02 ` [PATCH v2 03/13] Linux: Introduce __do_fexecve " Florian Weimer
@ 2024-07-28 19:02 ` Florian Weimer
2024-10-15 19:57 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 05/13] posix: Consolidate execve family of functions Florian Weimer
` (8 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:02 UTC (permalink / raw)
To: libc-alpha
Do not check argv and envp for null, for consistency with
execveat and execve. An invalid descriptor should result in
EBADF, not EINVAL.
In the fallback code, if the file descriptor is not valid,
return EBADF, not ENOENT.
---
posix/tst-fexecve.c | 14 ++++++++++++++
sysdeps/unix/sysv/linux/fexecve.c | 20 +++++++++++++++-----
2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/posix/tst-fexecve.c b/posix/tst-fexecve.c
index 9c528fe8d9..c8e772afb6 100644
--- a/posix/tst-fexecve.c
+++ b/posix/tst-fexecve.c
@@ -82,6 +82,20 @@ do_test (void)
close (fd);
#endif
+ /* Now fd is not open, so fexecve is expected to fail with EBADF. */
+ static const char *const argv[] = {
+ "/bin/sh", "-c", "false", NULL
+ };
+ errno = -1;
+ TEST_COMPARE (fexecve (fd, (char **const) argv, environ), -1);
+ TEST_COMPARE (errno, EBADF);
+
+ /* Likewise for negative descriptors. */
+ TEST_VERIFY (AT_FDCWD != -1);
+ errno = -1;
+ TEST_COMPARE (fexecve (-1, (char **const) argv, environ), -1);
+ TEST_COMPARE (errno, EBADF);
+
return ret;
}
diff --git a/sysdeps/unix/sysv/linux/fexecve.c b/sysdeps/unix/sysv/linux/fexecve.c
index 46c8170092..c2303735de 100644
--- a/sysdeps/unix/sysv/linux/fexecve.c
+++ b/sysdeps/unix/sysv/linux/fexecve.c
@@ -45,9 +45,18 @@ __do_fexecve (int fd, char *const argv[], char *const envp[])
/* We come here only if the 'execve' call fails. Determine whether
/proc is mounted. If not we return ENOSYS. */
- if (INTERNAL_SYSCALL_CALL (faccessat, AT_FDCWD, "/proc/self/fd", 0)
- == -ENOENT)
- err = ENOSYS;
+ if (err == ENOENT)
+ {
+ if (INTERNAL_SYSCALL_CALL (faccessat, AT_FDCWD, "/proc/self/fd", 0)
+ == -ENOENT)
+ err = ENOSYS;
+ else if (INTERNAL_SYSCALL_CALL (faccessat, AT_FDCWD, path, 0)
+ == -ENOENT)
+ /* The file descriptor does not exist, but /proc is mounted. */
+ err = EBADF;
+ /* Otherwise ENOENT comes from an invalid program
+ interpreter or some other property of the file. */
+ }
}
#endif /* __ASSUME_EXECVEAT */
@@ -60,9 +69,10 @@ __do_fexecve (int fd, char *const argv[], char *const envp[])
int
fexecve (int fd, char *const argv[], char *const envp[])
{
- if (fd < 0 || argv == NULL || envp == NULL)
+ /* Avoid unexpected behavior of execveat with AT_FDCWD. */
+ if (fd < 0)
{
- __set_errno (EINVAL);
+ __set_errno (EBADF);
return -1;
}
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 05/13] posix: Consolidate execve family of functions
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
` (3 preceding siblings ...)
2024-07-28 19:02 ` [PATCH v2 04/13] Linux: Error handling fixes for fexecve Florian Weimer
@ 2024-07-28 19:02 ` Florian Weimer
2024-10-16 13:40 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 06/13] posix: Introduce the __arch_spawni function Florian Weimer
` (7 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:02 UTC (permalink / raw)
To: libc-alpha
Ports only need to implement __arch_execveat going forward.
Generic code implements execve, fexecve, execveat on top of it.
The __arch_execveat/execveat split makes it easy to add
generic code to the execveat implementation, e.g., for environ
handling.
The Linux implementation prefers the execve system call if
available and keeps emulation for fexecve-style execveat,
for maximum compatibility.
---
posix/execve.c | 13 +----
posix/execveat.c | 12 ++---
posix/fexecve.c | 13 ++---
.../hurd/execve.c => generic/arch-execveat.h} | 21 +++------
.../mach/hurd/{execveat.c => arch-execveat.h} | 11 ++---
sysdeps/mach/hurd/fexecve.c | 47 -------------------
.../sysv/linux/{fexecve.c => arch-execveat.h} | 38 +++++++--------
sysdeps/unix/sysv/linux/execveat.c | 32 -------------
sysdeps/unix/sysv/linux/syscalls.list | 1 -
9 files changed, 40 insertions(+), 148 deletions(-)
rename sysdeps/{mach/hurd/execve.c => generic/arch-execveat.h} (61%)
rename sysdeps/mach/hurd/{execveat.c => arch-execveat.h} (90%)
delete mode 100644 sysdeps/mach/hurd/fexecve.c
rename sysdeps/unix/sysv/linux/{fexecve.c => arch-execveat.h} (73%)
delete mode 100644 sysdeps/unix/sysv/linux/execveat.c
diff --git a/posix/execve.c b/posix/execve.c
index c8f08658cb..6d6cefaebe 100644
--- a/posix/execve.c
+++ b/posix/execve.c
@@ -16,24 +16,15 @@
<https://www.gnu.org/licenses/>. */
#include <errno.h>
-#include <stddef.h>
+#include <fcntl.h>
#include <unistd.h>
-
/* Replace the current process, executing PATH with arguments ARGV and
environment ENVP. ARGV and ENVP are terminated by NULL pointers. */
int
__execve (const char *path, char *const argv[], char *const envp[])
{
- if (path == NULL || argv == NULL || envp == NULL)
- {
- __set_errno (EINVAL);
- return -1;
- }
-
- __set_errno (ENOSYS);
- return -1;
+ return __execveat (AT_FDCWD, path, argv, envp, 0);
}
-stub_warning (execve)
weak_alias (__execve, execve)
diff --git a/posix/execveat.c b/posix/execveat.c
index bddc4d5fb3..110177ea54 100644
--- a/posix/execveat.c
+++ b/posix/execveat.c
@@ -20,6 +20,8 @@
#include <stddef.h>
#include <unistd.h>
+#include <arch-execveat.h>
+
/* Replace the current process, executing PATH relative to difrd with
arguments argv and environment envp.
argv and envp are terminated by NULL pointers. */
@@ -27,15 +29,7 @@ int
__execveat (int dirfd, const char *path, char *const argv[], char *const envp[],
int flags)
{
- if (path == NULL || argv == NULL || envp == NULL)
- {
- __set_errno (EINVAL);
- return -1;
- }
-
- __set_errno (ENOSYS);
- return -1;
+ return __arch_execveat (dirfd, path, argv, envp, flags);
}
-stub_warning (execveat)
weak_alias (__execveat, execveat)
diff --git a/posix/fexecve.c b/posix/fexecve.c
index 04e59f97ad..3f632b3ea3 100644
--- a/posix/fexecve.c
+++ b/posix/fexecve.c
@@ -16,23 +16,20 @@
<https://www.gnu.org/licenses/>. */
#include <errno.h>
-#include <stddef.h>
+#include <fcntl.h>
#include <unistd.h>
-
/* Execute the file FD refers to, overlaying the running program image.
ARGV and ENVP are passed to the new program, as for `execve'. */
int
fexecve (int fd, char *const argv[], char *const envp[])
{
- if (fd < 0 || argv == NULL || envp == NULL)
+ /* Avoid unexpected behavior of execveat with AT_FDCWD. */
+ if (fd < 0)
{
- __set_errno (EINVAL);
+ __set_errno (EBADF);
return -1;
}
- __set_errno (ENOSYS);
- return -1;
+ return __execveat (fd, "", argv, envp, AT_EMPTY_PATH);
}
-
-stub_warning (fexecve)
diff --git a/sysdeps/mach/hurd/execve.c b/sysdeps/generic/arch-execveat.h
similarity index 61%
rename from sysdeps/mach/hurd/execve.c
rename to sysdeps/generic/arch-execveat.h
index 11e3f34b39..cd064df28f 100644
--- a/sysdeps/mach/hurd/execve.c
+++ b/sysdeps/generic/arch-execveat.h
@@ -1,4 +1,5 @@
-/* Copyright (C) 1991-2024 Free Software Foundation, Inc.
+/* Architecture-specific execveat implementation. Stub version.
+ Copyright (C) 2024 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
@@ -15,18 +16,10 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
-#include <unistd.h>
-#include <hurd.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-/* Replace the current process, executing FILE_NAME with arguments ARGV and
- environment ENVP. ARGV and ENVP are terminated by NULL pointers. */
-int
-__execve (const char *file_name, char *const argv[], char *const envp[])
+static int
+__arch_execveat (int dirfd, const char *path, char *const argv,
+ char *const envp, int flags)
{
- return __execveat (AT_FDCWD, file_name, argv, envp, 0);
+ __set_errno (ENOSYS);
+ return -1;
}
-
-weak_alias (__execve, execve)
diff --git a/sysdeps/mach/hurd/execveat.c b/sysdeps/mach/hurd/arch-execveat.h
similarity index 90%
rename from sysdeps/mach/hurd/execveat.c
rename to sysdeps/mach/hurd/arch-execveat.h
index f24dd7b62f..999779cbd5 100644
--- a/sysdeps/mach/hurd/execveat.c
+++ b/sysdeps/mach/hurd/arch-execveat.h
@@ -1,4 +1,5 @@
-/* Copyright (C) 1991-2024 Free Software Foundation, Inc.
+/* Execute program relative to a directory file descriptor. Hurd version.
+ Copyright (C) 1991-2024 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
@@ -25,9 +26,9 @@
/* Replace the current process, executing FILE_NAME with arguments ARGV and
environment ENVP. ARGV and ENVP are terminated by NULL pointers. */
-int
-__execveat (int dirfd, const char *file_name, char *const argv[],
- char *const envp[], int flags)
+static int
+__arch_execveat (int dirfd, const char *file_name, char *const argv[],
+ char *const envp[], int flags)
{
error_t err;
char *concat_name = NULL;
@@ -88,5 +89,3 @@ __execveat (int dirfd, const char *file_name, char *const argv[],
return __hurd_fail (err);
}
-
-weak_alias (__execveat, execveat)
diff --git a/sysdeps/mach/hurd/fexecve.c b/sysdeps/mach/hurd/fexecve.c
deleted file mode 100644
index 50472d5ca5..0000000000
--- a/sysdeps/mach/hurd/fexecve.c
+++ /dev/null
@@ -1,47 +0,0 @@
-/* Copyright (C) 1993-2024 Free Software Foundation, Inc.
- This file is part of the GNU C Library.
-
- The GNU C Library is free software; you can redistribute it and/or
- 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.
-
- 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
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, see
- <https://www.gnu.org/licenses/>. */
-
-#include <unistd.h>
-#include <hurd.h>
-#include <hurd/fd.h>
-#include <errno.h>
-
-/* Execute the file FD refers to, overlaying the running program image. */
-
-int
-fexecve (int fd, char *const argv[], char *const envp[])
-{
- file_t file;
- error_t err;
- enum retry_type doretry;
- char retryname[1024];
-
- err = HURD_DPORT_USE (fd,
- __dir_lookup (port, "", O_EXEC, 0, &doretry, retryname, &file));
-
- if (! err && (doretry != FS_RETRY_NORMAL || retryname[0] != '\0'))
- err = EGRATUITOUS;
- if (err)
- return __hurd_fail(err);
-
- err = _hurd_exec_paths (__mach_task_self (), file, NULL, NULL, argv, envp);
- if (! err)
- err = EGRATUITOUS;
-
- __mach_port_deallocate (__mach_task_self (), file);
- return __hurd_fail (err);
-}
diff --git a/sysdeps/unix/sysv/linux/fexecve.c b/sysdeps/unix/sysv/linux/arch-execveat.h
similarity index 73%
rename from sysdeps/unix/sysv/linux/fexecve.c
rename to sysdeps/unix/sysv/linux/arch-execveat.h
index c2303735de..b6bc286829 100644
--- a/sysdeps/unix/sysv/linux/fexecve.c
+++ b/sysdeps/unix/sysv/linux/arch-execveat.h
@@ -1,4 +1,5 @@
-/* Copyright (C) 1994-2024 Free Software Foundation, Inc.
+/* Execute program relative to a directory file descriptor. Linux version.
+ Copyright (C) 1994-2024 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
@@ -15,17 +16,11 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
-#include <errno.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <unistd.h>
#include <fcntl.h>
-#include <sys/stat.h>
-
#include <fd_to_filename.h>
-#include <sysdep.h>
-#include <sys/syscall.h>
#include <kernel-features.h>
+#include <sys/syscall.h>
+#include <sysdep.h>
static int
__do_fexecve (int fd, char *const argv[], char *const envp[])
@@ -64,17 +59,20 @@ __do_fexecve (int fd, char *const argv[], char *const envp[])
return -1;
}
-/* Execute the file FD refers to, overlaying the running program image.
- ARGV and ENVP are passed to the new program, as for `execve'. */
-int
-fexecve (int fd, char *const argv[], char *const envp[])
+static int
+__arch_execveat (int dirfd, const char *path, char *const *argv,
+ char *const *envp, int flags)
{
- /* Avoid unexpected behavior of execveat with AT_FDCWD. */
- if (fd < 0)
- {
- __set_errno (EBADF);
- return -1;
- }
+ /* Use execve if possible, to preserve the historic system call
+ profile. */
+ if (dirfd == AT_FDCWD && flags == 0)
+ return INLINE_SYSCALL_CALL (execve, path, argv, envp, flags);
+
+ /* Provide emulation for an fexecve-style execveat call. */
+ if (dirfd >= 0 && flags == AT_EMPTY_PATH && path[0] == '\0')
+ return __do_fexecve (dirfd, argv, envp);
- return __do_fexecve (fd, argv, envp);
+ /* Otherwise perform the system call without emulation. */
+ return INLINE_SYSCALL_CALL (execveat, dirfd, path, &argv[0], &envp[0],
+ flags);
}
diff --git a/sysdeps/unix/sysv/linux/execveat.c b/sysdeps/unix/sysv/linux/execveat.c
deleted file mode 100644
index e1d68b9043..0000000000
--- a/sysdeps/unix/sysv/linux/execveat.c
+++ /dev/null
@@ -1,32 +0,0 @@
-/* Execute program relative to a directory file descriptor.
- Copyright (C) 2021-2024 Free Software Foundation, Inc.
- This file is part of the GNU C Library.
-
- The GNU C Library is free software; you can redistribute it and/or
- 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.
-
- 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
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, see
- <https://www.gnu.org/licenses/>. */
-
-#include <fd_to_filename.h>
-#include <sysdep.h>
-#include <unistd.h>
-
-/* Execute the file FD refers to, overlaying the running program image.
- ARGV and ENVP are passed to the new program, as for 'execve'. */
-int
-execveat (int dirfd, const char *path, char *const argv[], char *const envp[],
- int flags)
-{
- /* Avoid implicit array coercion in syscall macros. */
- return INLINE_SYSCALL_CALL (execveat, dirfd, path, &argv[0], &envp[0],
- flags);
-}
diff --git a/sysdeps/unix/sysv/linux/syscalls.list b/sysdeps/unix/sysv/linux/syscalls.list
index 9ac42c3436..14bbb5fe6e 100644
--- a/sysdeps/unix/sysv/linux/syscalls.list
+++ b/sysdeps/unix/sysv/linux/syscalls.list
@@ -9,7 +9,6 @@ delete_module EXTRA delete_module 3 delete_module
epoll_create1 EXTRA epoll_create1 i:i epoll_create1
epoll_ctl EXTRA epoll_ctl i:iiip epoll_ctl
eventfd EXTRA eventfd2 i:ii eventfd
-execve - execve i:spp __execve execve
flock - flock i:ii __flock flock
fsconfig EXTRA fsconfig i:iUsNi fsconfig
fsmount EXTRA fsmount i:iUU fsmount
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 06/13] posix: Introduce the __arch_spawni function
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
` (4 preceding siblings ...)
2024-07-28 19:02 ` [PATCH v2 05/13] posix: Consolidate execve family of functions Florian Weimer
@ 2024-07-28 19:02 ` Florian Weimer
2024-10-29 20:29 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 07/13] stdlib: Add internal __getenvarray function Florian Weimer
` (6 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:02 UTC (permalink / raw)
To: libc-alpha
And move the definition of SPAWN_ERROR to spawn_int.h.
The separate __arch_spawni function allows centralized
implementation of preparatory code in posix/spawni.c, which is no
longer a stub after this commit.
---
posix/spawn_int.h | 7 +++++
posix/spawni.c | 15 ++---------
sysdeps/generic/arch-spawni.h | 27 +++++++++++++++++++
sysdeps/mach/hurd/{spawni.c => arch-spawni.h} | 12 ++++-----
.../sysv/linux/{spawni.c => arch-spawni.h} | 18 ++++---------
5 files changed, 47 insertions(+), 32 deletions(-)
create mode 100644 sysdeps/generic/arch-spawni.h
rename sysdeps/mach/hurd/{spawni.c => arch-spawni.h} (99%)
rename sysdeps/unix/sysv/linux/{spawni.c => arch-spawni.h} (96%)
diff --git a/posix/spawn_int.h b/posix/spawn_int.h
index 2cdb55b4d6..9d0de483fe 100644
--- a/posix/spawn_int.h
+++ b/posix/spawn_int.h
@@ -78,6 +78,13 @@ struct __spawn_action
#define SPAWN_XFLAGS_TRY_SHELL 0x2
#define SPAWN_XFLAGS_RET_PIDFD 0x4
+/* The Unix standard contains a long explanation of the way to signal
+ an error after the fork() was successful. Since no new wait status
+ was wanted there is no way to signal an error using one of the
+ available methods. The committee chose to signal an error by a
+ normal program exit with the exit code 127. */
+#define SPAWN_ERROR 127
+
extern int __posix_spawn_file_actions_realloc (posix_spawn_file_actions_t *
file_actions)
attribute_hidden;
diff --git a/posix/spawni.c b/posix/spawni.c
index 52ff5f1966..ef47dd9ea4 100644
--- a/posix/spawni.c
+++ b/posix/spawni.c
@@ -19,15 +19,7 @@
#include <errno.h>
#include <spawn.h>
#include "spawn_int.h"
-
-
-/* The Unix standard contains a long explanation of the way to signal
- an error after the fork() was successful. Since no new wait status
- was wanted there is no way to signal an error using one of the
- available methods. The committee chose to signal an error by a
- normal program exit with the exit code 127. */
-#define SPAWN_ERROR 127
-
+#include <arch-spawni.h>
/* Spawn a new process executing PATH with the attributes describes in *ATTRP.
Before running the process perform the actions described in FILE-ACTIONS. */
@@ -37,8 +29,5 @@ __spawni (pid_t *pid, const char *file,
const posix_spawnattr_t *attrp, char *const argv[],
char *const envp[], int xflags)
{
- __set_errno (ENOSYS);
- return -1;
+ return __arch_spawni (pid, file, file_actions, attrp, argv, envp, xflags);
}
-
-stub_warning (__spawni)
diff --git a/sysdeps/generic/arch-spawni.h b/sysdeps/generic/arch-spawni.h
new file mode 100644
index 0000000000..ad225d642d
--- /dev/null
+++ b/sysdeps/generic/arch-spawni.h
@@ -0,0 +1,27 @@
+/* Architecture-specific __spawni implementation. Stub version.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+static int
+__arch_spawni (pid_t *pid, const char *file,
+ const posix_spawn_file_actions_t *file_actions,
+ const posix_spawnattr_t *attrp, char *const argv[],
+ char *const envp[], int xflags)
+{
+ __set_errno (ENOSYS);
+ return -1;
+}
diff --git a/sysdeps/mach/hurd/spawni.c b/sysdeps/mach/hurd/arch-spawni.h
similarity index 99%
rename from sysdeps/mach/hurd/spawni.c
rename to sysdeps/mach/hurd/arch-spawni.h
index a0d2e28c8e..6c1b9e98ad 100644
--- a/sysdeps/mach/hurd/spawni.c
+++ b/sysdeps/mach/hurd/arch-spawni.h
@@ -36,12 +36,12 @@
/* Spawn a new process executing PATH with the attributes describes in *ATTRP.
Before running the process perform the actions described in FILE-ACTIONS. */
-int
-__spawni (pid_t *pid, const char *file,
- const posix_spawn_file_actions_t *file_actions,
- const posix_spawnattr_t *attrp,
- char *const argv[], char *const envp[],
- int xflags)
+static int
+__arch_spawni (pid_t *pid, const char *file,
+ const posix_spawn_file_actions_t *file_actions,
+ const posix_spawnattr_t *attrp,
+ char *const argv[], char *const envp[],
+ int xflags)
{
pid_t new_pid;
char *path, *p, *name;
diff --git a/sysdeps/unix/sysv/linux/spawni.c b/sysdeps/unix/sysv/linux/arch-spawni.h
similarity index 96%
rename from sysdeps/unix/sysv/linux/spawni.c
rename to sysdeps/unix/sysv/linux/arch-spawni.h
index f57e92815e..8caf74f7f5 100644
--- a/sysdeps/unix/sysv/linux/spawni.c
+++ b/sysdeps/unix/sysv/linux/arch-spawni.h
@@ -47,14 +47,6 @@
child has either exec'ed successfully or exited. */
-/* The Unix standard contains a long explanation of the way to signal
- an error after the fork() was successful. Since no new wait status
- was wanted there is no way to signal an error using one of the
- available methods. The committee chose to signal an error by a
- normal program exit with the exit code 127. */
-#define SPAWN_ERROR 127
-
-
struct posix_spawn_args
{
internal_sigset_t oldmask;
@@ -483,11 +475,11 @@ __spawnix (int *pid, const char *file,
/* Spawn a new process executing PATH with the attributes describes in *ATTRP.
Before running the process perform the actions described in FILE-ACTIONS. */
-int
-__spawni (pid_t * pid, const char *file,
- const posix_spawn_file_actions_t * acts,
- const posix_spawnattr_t * attrp, char *const argv[],
- char *const envp[], int xflags)
+static int
+__arch_spawni (pid_t * pid, const char *file,
+ const posix_spawn_file_actions_t * acts,
+ const posix_spawnattr_t * attrp, char *const argv[],
+ char *const envp[], int xflags)
{
/* It uses __execvpex to avoid run ENOEXEC in non compatibility mode (it
will be handled by maybe_script_execute). */
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 07/13] stdlib: Add internal __getenvarray function
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
` (5 preceding siblings ...)
2024-07-28 19:02 ` [PATCH v2 06/13] posix: Introduce the __arch_spawni function Florian Weimer
@ 2024-07-28 19:02 ` Florian Weimer
2024-10-30 16:21 ` Adhemerval Zanella Netto
2024-07-28 19:03 ` [PATCH v2 08/13] support: Add support_count_maps Florian Weimer
` (5 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:02 UTC (permalink / raw)
To: libc-alpha
---
stdlib/Makefile | 1 +
stdlib/getenvarray.c | 64 ++++++++++++++++++++++++++++++++++++++++++++
stdlib/setenv.h | 7 +++++
3 files changed, 72 insertions(+)
create mode 100644 stdlib/getenvarray.c
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 697b547540..e1f1848439 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -79,6 +79,7 @@ routines := \
getcontext \
getentropy \
getenv \
+ getenvarray \
getrandom \
getsubopt \
jrand48 \
diff --git a/stdlib/getenvarray.c b/stdlib/getenvarray.c
new file mode 100644
index 0000000000..cac308fb32
--- /dev/null
+++ b/stdlib/getenvarray.c
@@ -0,0 +1,64 @@
+/* Obtain a consistent copy of the environ array.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <atomic.h>
+#include <setenv.h>
+#include <unistd.h>
+
+size_t
+__getenvarray (char **result, size_t length)
+{
+ while (true)
+ {
+ /* The snapshotting approach follows getenv. Duplicates are
+ filtered out explicitly. */
+ environ_counter start_counter = atomic_load_acquire (&__environ_counter);
+ char **start_environ = atomic_load_relaxed (&__environ);
+ if (start_environ == NULL)
+ return 0;
+
+ const char *previous = NULL;
+ size_t count = 0;
+ for (char *const *ep = start_environ; ; ++ep)
+ {
+ char *entry = atomic_load_relaxed (ep);
+ if (entry == NULL)
+ break;
+ if (entry == previous)
+ /* Filter out a duplicate created by concurrent unsetenv. */
+ continue;
+ if (count < length)
+ result[count] = entry;
+ ++count;
+ previous = entry;
+ }
+
+ atomic_thread_fence_acquire ();
+
+ if (atomic_load_acquire (&__environ_counter) != start_counter)
+ /* Retry if there has been more than one write to the array
+ from within unsetenv. Writes in setenv/putenv do not
+ matter because they do not change the key, so they cannot
+ introduce duplicates. */
+ continue;
+
+ if (count < length)
+ result[count] = NULL;
+ return count;
+ }
+}
diff --git a/stdlib/setenv.h b/stdlib/setenv.h
index 3919943f87..69fa9367ed 100644
--- a/stdlib/setenv.h
+++ b/stdlib/setenv.h
@@ -47,6 +47,13 @@ __environ_is_from_array_list (char **ep)
return eal != NULL && &eal->array[0] == ep;
}
+/* Make a copy of the environ array. Writes up to LENGTH pointers to
+ *RESULT, including a terminating null pointer if there is room.
+ *Returns the total number of non-null elements in the environ
+ *array. This number can be larger than LENGTH. */
+size_t __getenvarray (char **result, size_t length)
+ __attr_access ((__write_only__, 1, 2)) attribute_hidden;
+
/* Counter for detecting concurrent modification in unsetenv.
Ideally, this should be a 64-bit counter that cannot wrap around,
but given that counter wrapround is probably impossible to hit
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 08/13] support: Add support_count_maps
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
` (6 preceding siblings ...)
2024-07-28 19:02 ` [PATCH v2 07/13] stdlib: Add internal __getenvarray function Florian Weimer
@ 2024-07-28 19:03 ` Florian Weimer
2024-11-27 13:07 ` Adhemerval Zanella Netto
2024-07-28 19:03 ` [PATCH v2 09/13] posix: New tst-vfork-mappings test case Florian Weimer
` (4 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:03 UTC (permalink / raw)
To: libc-alpha
---
support/Makefile | 2 ++
support/support.h | 5 +++
support/support_count_maps.c | 51 +++++++++++++++++++++++++++++++
support/tst-support_count_maps.c | 52 ++++++++++++++++++++++++++++++++
4 files changed, 110 insertions(+)
create mode 100644 support/support_count_maps.c
create mode 100644 support/tst-support_count_maps.c
diff --git a/support/Makefile b/support/Makefile
index aa57207bdc..bc35faa325 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -53,6 +53,7 @@ libsupport-routines = \
support_chroot \
support_copy_file \
support_copy_file_range \
+ support_count_maps \
support_create_timer \
support_descriptor_supports_holes \
support_descriptors \
@@ -319,6 +320,7 @@ tests = \
tst-support-process_state \
tst-support_blob_repeat \
tst-support_capture_subprocess \
+ tst-support_count_maps \
tst-support_descriptors \
tst-support_format_dns_packet \
tst-support_quote_blob \
diff --git a/support/support.h b/support/support.h
index ba21ec9b5a..5769e8c5f3 100644
--- a/support/support.h
+++ b/support/support.h
@@ -96,6 +96,11 @@ int support_descriptor_supports_holes (int fd);
WHY_MSG as part of the diagnostic. */
void support_need_proc (const char *why_msg);
+/* Counts the number of mappings in the process address space.
+ Currently implemented using /proc/self/maps on Linux, so it needs
+ that to exist. */
+size_t support_count_maps (void);
+
/* Error-checking wrapper functions which terminate the process on
error. */
diff --git a/support/support_count_maps.c b/support/support_count_maps.c
new file mode 100644
index 0000000000..2a6dd9fee3
--- /dev/null
+++ b/support/support_count_maps.c
@@ -0,0 +1,51 @@
+/* Count the number of mappings.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <support/support.h>
+#include <support/check.h>
+#include <support/xstdio.h>
+#include <errno.h>
+
+size_t
+support_count_maps (void)
+{
+ FILE *fp = fopen ("/proc/self/maps", "r");
+ if (fp == NULL)
+ {
+ if (errno == ENOENT)
+ FAIL_UNSUPPORTED ("support_count_maps: test needs /proc/self/maps");
+ else
+ FAIL_EXIT1 ("cannot open /proc/self/maps: %m");
+ }
+ size_t count = 0;
+ while (true)
+ {
+ switch (getc_unlocked (fp))
+ {
+ case EOF:
+ /* Error will be reported by xfclose below. */
+ goto out;
+ case '\n':
+ ++count;
+ break;
+ }
+ }
+ out:
+ xfclose (fp);
+ return count;
+}
diff --git a/support/tst-support_count_maps.c b/support/tst-support_count_maps.c
new file mode 100644
index 0000000000..e904a00cd4
--- /dev/null
+++ b/support/tst-support_count_maps.c
@@ -0,0 +1,52 @@
+/* Test counting the number of mappings.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <support/support.h>
+#include <support/check.h>
+#include <support/xstdio.h>
+#include <sys/mman.h>
+#include <support/xunistd.h>
+
+static int
+do_test (void)
+{
+ long int page_size = xsysconf (_SC_PAGESIZE);
+
+ size_t initial = support_count_maps ();
+ printf ("info: initial number of maps: %zu\n", initial);
+
+ void *ptr = xmmap (NULL, 2 * page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
+ size_t one_mapping = support_count_maps ();
+ printf ("info: new number of maps: %zu\n", one_mapping);
+ TEST_COMPARE (initial + 1, one_mapping);
+
+ /* The mapping gets split due to the protection flags change. */
+ xmprotect (ptr, page_size, PROT_READ);
+ size_t two_mappings = support_count_maps ();
+ TEST_COMPARE (initial + 2, two_mappings);
+
+ xmunmap (ptr, 2 * page_size);
+ size_t final = support_count_maps ();
+ TEST_COMPARE (initial, final);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 09/13] posix: New tst-vfork-mappings test case
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
` (7 preceding siblings ...)
2024-07-28 19:03 ` [PATCH v2 08/13] support: Add support_count_maps Florian Weimer
@ 2024-07-28 19:03 ` Florian Weimer
2024-11-27 14:07 ` Adhemerval Zanella Netto
2024-07-28 19:03 ` [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat Florian Weimer
` (3 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:03 UTC (permalink / raw)
To: libc-alpha
It checks if vfork followed by execve leaves additional memory mappings
behind.
---
posix/Makefile | 2 +
posix/tst-vfork-mappings.c | 195 +++++++++++++++++++++++++++++++++++++
2 files changed, 197 insertions(+)
create mode 100644 posix/tst-vfork-mappings.c
diff --git a/posix/Makefile b/posix/Makefile
index 2c598cd20a..ed3c9d0659 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -319,6 +319,7 @@ tests := \
tst-sysconf-empty-chroot \
tst-truncate \
tst-truncate64 \
+ tst-vfork-mappings \
tst-vfork1 \
tst-vfork2 \
tst-wait3 \
@@ -635,6 +636,7 @@ $(objpfx)runptests.o: $(objpfx)ptestcases.h
$(objpfx)tst-getopt-cancel: $(shared-thread-library)
$(objpfx)tst-_Fork: $(shared-thread-library)
+$(objpfx)tst-vfork-mappings: $(shared-thread-library)
test-xfail-annexc = yes
$(objpfx)annexc.out: $(objpfx)annexc
diff --git a/posix/tst-vfork-mappings.c b/posix/tst-vfork-mappings.c
new file mode 100644
index 0000000000..fcab423383
--- /dev/null
+++ b/posix/tst-vfork-mappings.c
@@ -0,0 +1,195 @@
+/* Test that vfork, *exec* does not accumulate mappings.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+#include <sys/mman.h>
+#include <spawn.h>
+#include <unistd.h>
+
+/* 0 for direct calls, 1 for using an empheral thread. */
+static int use_thread;
+
+/* Wrapper used by test_one to invoke the callback on a new thread. */
+static void *
+thread_wrapper (void *action)
+{
+ ((void (*) (void)) action) ();
+ return NULL;
+}
+
+static void
+test_one (const char *label, void (*action) (void))
+{
+ printf ("info: testing %s%s\n",
+ label, use_thread ? " (threading mode)" : "");
+ size_t before = support_count_maps ();
+
+ /* Create mappings which should prevent merging of any persistant
+ new mappings, so that the count actually goes up in case of a
+ leak. */
+ void *gap_maps[50];
+
+ for (int i = 0; i < array_length (gap_maps); )
+ {
+ gap_maps[i++] = xmmap (NULL, 1, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
+ gap_maps[i++] = xmmap (NULL, 1, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
+ if (use_thread)
+ xpthread_join (xpthread_create (NULL, thread_wrapper, action));
+ else
+ action ();
+ }
+
+ for (int i = 0; i < array_length (gap_maps); ++i)
+ xmunmap (gap_maps[i], 1);
+
+ size_t afterwards = support_count_maps ();
+ if (afterwards > before + 1)
+ {
+ printf ("error: %s: additional maps created: %zu (%zu -> %zu)\n",
+ label, afterwards - before, before, afterwards);
+ support_record_failure ();
+ }
+}
+
+static void *
+dummy_thread (void *ignored)
+{
+ return NULL;
+}
+
+static void
+do_system (void)
+{
+ TEST_COMPARE (system ("exit"), 0);
+}
+
+static void
+do_vfork_execl (void)
+{
+ pid_t ret = vfork ();
+ if (ret == 0)
+ {
+ execl ("/bin/true", "true", NULL);
+ _exit (1);
+ }
+ if (ret < 0)
+ FAIL_EXIT1 ("vfork");
+ int status;
+ xwaitpid (ret, &status, 0);
+ TEST_COMPARE (status, 0);
+}
+
+static void
+do_vfork_execlp (void)
+{
+ pid_t ret = vfork ();
+ if (ret == 0)
+ {
+ execlp ("true", "true", NULL);
+ _exit (1);
+ }
+ if (ret < 0)
+ FAIL_EXIT1 ("vfork");
+ int status;
+ xwaitpid (ret, &status, 0);
+ TEST_COMPARE (status, 0);
+}
+
+static void
+do_posix_spawn (void)
+{
+ pid_t pid;
+ char *const argv[] = { NULL };
+ TEST_COMPARE (posix_spawn (&pid, "/bin/true", NULL, NULL, argv, environ), 0);
+ int status;
+ xwaitpid (pid, &status, 0);
+ TEST_COMPARE (status, 0);
+}
+
+static void
+do_posix_spawnp (void)
+{
+ pid_t pid;
+ char *const argv[] = { NULL };
+ TEST_COMPARE (posix_spawnp (&pid, "true", NULL, NULL, argv, environ), 0);
+ int status;
+ xwaitpid (pid, &status, 0);
+ TEST_COMPARE (status, 0);
+}
+
+static int
+do_test (void)
+{
+ xpthread_join (xpthread_create (NULL, dummy_thread, NULL));
+
+ for (int do_pass = 0; do_pass < 4; ++do_pass)
+ {
+ printf ("info: pass %d\n", do_pass);
+ switch (do_pass)
+ {
+ case 1:
+ /* Switch on multi-threaded mode. */
+ xpthread_join (xpthread_create (NULL, dummy_thread, NULL));
+ break;
+ case 2:
+ /* Stuff the environment with some values. Aim for
+ consuming at least one page of pointers. */
+ for (int i = 0; i <= 4096 / sizeof (char *) ; ++i)
+ {
+ char buf[20];
+ snprintf (buf, sizeof (buf), "V%d", i);
+ TEST_COMPARE (setenv (buf, buf + 1, 1), 0);
+ }
+ break;
+ case 3:
+ /* Stuff the environment with even more values. Aim for three
+ pages (for small page sizes). */
+ for (int i = 0; i <= 2 * 4096 / sizeof (char *); ++i)
+ {
+ char buf[20];
+ snprintf (buf, sizeof (buf), "U%d", i);
+ TEST_COMPARE (setenv (buf, buf + 1, 1), 0);
+ }
+ break;
+ }
+
+ for (use_thread = 0; use_thread < 2; ++use_thread)
+ {
+ if (do_pass >= 3)
+ /* With many environment variables, bash becomes rather
+ slow, so skip testing it. */
+ test_one ("system", do_system);
+ test_one ("vfork/execl", do_vfork_execl);
+ test_one ("vfork/execlp", do_vfork_execlp);
+ test_one ("posix_spawn", do_posix_spawn);
+ test_one ("posix_spawnp", do_posix_spawnp);
+ }
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
` (8 preceding siblings ...)
2024-07-28 19:03 ` [PATCH v2 09/13] posix: New tst-vfork-mappings test case Florian Weimer
@ 2024-07-28 19:03 ` Florian Weimer
2024-11-27 20:01 ` Adhemerval Zanella Netto
2024-07-28 19:03 ` [PATCH v2 11/13] posix: Implement environ snapshotting in __spawni Florian Weimer
` (2 subsequent siblings)
12 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:03 UTC (permalink / raw)
To: libc-alpha
The environment snapshot is stored on the stack, for easy
memory management if vfork is involved.
---
posix/execveat.c | 27 ++-
stdlib/Makefile | 18 +-
stdlib/environ_single_threaded_no_snapshot.c | 40 ++++
stdlib/setenv.h | 24 +-
stdlib/tst-environ-execl.c | 35 +++
stdlib/tst-environ-snapshot-skeleton.c | 218 +++++++++++++++++++
6 files changed, 354 insertions(+), 8 deletions(-)
create mode 100644 stdlib/environ_single_threaded_no_snapshot.c
create mode 100644 stdlib/tst-environ-execl.c
create mode 100644 stdlib/tst-environ-snapshot-skeleton.c
diff --git a/posix/execveat.c b/posix/execveat.c
index 110177ea54..2cd275d02a 100644
--- a/posix/execveat.c
+++ b/posix/execveat.c
@@ -21,15 +21,38 @@
#include <unistd.h>
#include <arch-execveat.h>
+#include <stdlib/setenv.h>
/* Replace the current process, executing PATH relative to difrd with
arguments argv and environment envp.
argv and envp are terminated by NULL pointers. */
int
-__execveat (int dirfd, const char *path, char *const argv[], char *const envp[],
+__execveat (int dirfd, const char *path, char *const *argv, char *const *envp,
int flags)
{
- return __arch_execveat (dirfd, path, argv, envp, flags);
+ if (envp == NULL || envp != __environ
+ || !__environ_is_from_array_list ((char **) envp)
+ || __environ_single_threaded_no_snapshot (envp))
+ return __arch_execveat (dirfd, path, argv, envp, flags);
+
+ size_t env_size = ENVIRON_STACK_SNAPSHOT_SIZE;
+
+ while (true)
+ {
+ /* Using an on-stack buffer is not ideal, but it is the most
+ practical way to avoid memory leaks with vfork: New mappings
+ created before the execveat call will be preserved in the
+ original process before the vfork call even if the execve at
+ call succeeds and replaces the current process. */
+ ++env_size; /* Make room for the null terminator. */
+ char *env_copy[env_size];
+ size_t env_count = __getenvarray (env_copy, env_size);
+ if (env_count < env_size)
+ return __arch_execveat (dirfd, path, argv, env_copy, flags);
+
+ /* Retry with larger buffer. */
+ env_size = env_count;
+ }
}
weak_alias (__execveat, execveat)
diff --git a/stdlib/Makefile b/stdlib/Makefile
index e1f1848439..498ec15e91 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -72,6 +72,7 @@ routines := \
drand48 \
drand48-iter \
drand48_r \
+ environ_single_threaded_no_snapshot \
erand48 \
erand48_r \
exit \
@@ -374,11 +375,20 @@ tests-container := \
tst-system \
#tests-container
+# These tests re-exec themselves, so they need to be linked
+# statically, or use harcoded paths.
+tests-with-reexec := \
+ tst-environ-execl \
+ # tests-with-reexec
+tests += $(tests-with-reexec)
+
ifeq ($(build-hardcoded-path-in-tests),yes)
tests += \
tst-empty-env \
# tests
-endif
+else # !$(build-hardcoded-path-in-tests)
+tests-static += $(tests-with-reexec)
+endif # !$(build-hardcoded-path-in-tests)
LDLIBS-test-atexit-race = $(shared-thread-library)
LDLIBS-test-at_quick_exit-race = $(shared-thread-library)
@@ -631,3 +641,9 @@ $(objpfx)tst-qsort5: $(libm)
$(objpfx)tst-getenv-signal: $(shared-thread-library)
$(objpfx)tst-getenv-thread: $(shared-thread-library)
$(objpfx)tst-getenv-unsetenv: $(shared-thread-library)
+# See above for $(tests-with-reexec).
+ifeq ($(build-hardcoded-path-in-tests),yes)
+$(objpfx)tst-environ-execl: $(shared-thread-library)
+else
+$(objpfx)tst-environ-execl: $(static-thread-library)
+endif
diff --git a/stdlib/environ_single_threaded_no_snapshot.c b/stdlib/environ_single_threaded_no_snapshot.c
new file mode 100644
index 0000000000..6dc1d22ea2
--- /dev/null
+++ b/stdlib/environ_single_threaded_no_snapshot.c
@@ -0,0 +1,40 @@
+/* Check for environ snapshots in single-threaded case.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <setenv.h>
+#include <single-thread.h>
+#include <tls.h>
+
+bool
+__environ_single_threaded_no_snapshot (char *const *envp)
+{
+ if (!SINGLE_THREAD_P)
+ return false;
+
+ const char *previous = NULL;
+ for (char *const *ep = envp; *ep != NULL; ++ep)
+ {
+ if (*ep == previous)
+ /* Must perform a snapshot to remove the duplicate. */
+ return false;
+ previous = *ep;
+ }
+ /* No duplicates, no snapshot needed because no interference
+ from other threads is possible. */
+ return true;
+}
diff --git a/stdlib/setenv.h b/stdlib/setenv.h
index 69fa9367ed..3e530f0bc0 100644
--- a/stdlib/setenv.h
+++ b/stdlib/setenv.h
@@ -19,8 +19,10 @@
#ifndef _SETENV_H
#define _SETENV_H
-#include <atomic.h>
-#include <stdbool.h>
+/* Not actually usable in rtld, but it is required for symbol discovery. */
+#if IS_IN (libc) || IS_IN (rtld)
+# include <atomic.h>
+# include <stdbool.h>
/* We use an exponential sizing policy for environment arrays. The
arrays are not deallocating during the lifetime of the process.
@@ -59,11 +61,11 @@ size_t __getenvarray (char **result, size_t length)
but given that counter wrapround is probably impossible to hit
(2**32 operations in unsetenv concurrently with getenv), using
<atomic_wide_counter.h> seems unnecessary. */
-#if __HAVE_64B_ATOMICS
+# if __HAVE_64B_ATOMICS
typedef uint64_t environ_counter;
-#else
+# else
typedef uint32_t environ_counter;
-#endif
+# endif
/* Updated by unsetenv to detect multiple overwrites in getenv. */
extern environ_counter __environ_counter attribute_hidden;
@@ -77,4 +79,16 @@ extern environ_counter __environ_counter attribute_hidden;
int __add_to_environ (const char *name, const char *value,
const char *combines, int replace) attribute_hidden;
+/* Returns true if the environ pointer ENVP (which should be equal to
+ environ) does not need snapshotting because execution is
+ single-threaded and there are no duplicates in the array. */
+bool __environ_single_threaded_no_snapshot (char *const *envp)
+ attribute_hidden;
+
+#endif /* IS_IN (libc) */
+
+/* This seems to cover the majority of situations on the first
+ pass and requires only a moderate amount of stack size. */
+enum { ENVIRON_STACK_SNAPSHOT_SIZE = 120 };
+
#endif /* _SETENV_H */
diff --git a/stdlib/tst-environ-execl.c b/stdlib/tst-environ-execl.c
new file mode 100644
index 0000000000..b028a94c7d
--- /dev/null
+++ b/stdlib/tst-environ-execl.c
@@ -0,0 +1,35 @@
+/* Test environment snapshots with execl.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#define PROGRAM "tst-environ-execl"
+#include "tst-environ-snapshot-skeleton.c"
+
+static pid_t
+create_process (void)
+{
+ pid_t pid = vfork ();
+ if (pid < 0)
+ FAIL_EXIT1 ("vfork: %m");
+ if (pid == 0)
+ {
+ execl (self_path, self_path, "verify", NULL);
+ printf ("error: execl failed: %m\n");
+ _exit (17);
+ }
+ return pid;
+}
diff --git a/stdlib/tst-environ-snapshot-skeleton.c b/stdlib/tst-environ-snapshot-skeleton.c
new file mode 100644
index 0000000000..8548e0a9e0
--- /dev/null
+++ b/stdlib/tst-environ-snapshot-skeleton.c
@@ -0,0 +1,218 @@
+/* Test skeleton for environment snapshots.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+/* The actual test including this file needs to define the macro
+ PROGRAM as a string literal, the name of the test program, before
+ including this file.
+
+ It laso needs to define a function
+
+ static pid_t create_process (void);
+
+ after including this file, describing how the subprocess with the
+ environment snapshot is created. */
+
+#include <array_length.h>
+#include <setenv.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+#include <unistd.h>
+
+static pid_t create_process (void);
+
+/* Absolute path to the executable. */
+static char *self_path;
+
+/* Used to synchronize the start of each test iteration. */
+static pthread_barrier_t barrier;
+
+/* These are the variables set by the main thread. This needs to
+ exceed the stack reservation for the snapshot, to exercise that
+ code path. Variables before start_variable are not removed in the
+ unsetenv loop. */
+enum
+ {
+ on_stack_count = ENVIRON_STACK_SNAPSHOT_SIZE,
+ start_variable = 50,
+ };
+static char *variables[start_variable + on_stack_count];
+
+/* Number of iterations. */
+enum { iterations = 5000 };
+
+/* Check that even with concurrent unsetenv, a variable that is known
+ to be there is found. */
+static void *
+test_thread (void *ignored)
+{
+ for (int i = 0; i < iterations; ++i)
+ {
+ xpthread_barrier_wait (&barrier);
+ pid_t pid = create_process ();
+ int status;
+ xwaitpid (pid, &status, 0);
+ TEST_COMPARE (status, 0);
+ xpthread_barrier_wait (&barrier);
+ }
+ return NULL;
+}
+
+/* Called to verify the contents of the environment. */
+static void
+verify_environ (void)
+{
+ TEST_COMPARE_STRING (getenv ("variable"), "value");
+ char *changing = getenv ("changing");
+ if (strcmp (changing, "value-1") != 0)
+ TEST_COMPARE_STRING (changing, "value-2");
+
+ bool seen[array_length (variables)] = { 0, };
+ bool seen_changing = false;
+ bool seen_variable = false;
+ char **ep = environ;
+ for (int i = 0; i < start_variable; ++i)
+ {
+ char expected[20];
+ snprintf (expected, sizeof (expected), "V%d=%d", i, i);
+ TEST_COMPARE_STRING (ep[i], expected);
+ if (ep[i] == NULL)
+ break;
+ }
+
+ if (*ep == NULL)
+ return;
+
+ for (ep += start_variable; *ep != NULL; ++ep)
+ {
+ if (**ep == 'V')
+ {
+ char *eq = strchr (*ep, '=');
+ TEST_VERIFY (eq != NULL);
+ if (eq != NULL)
+ {
+ int idx = atoi (eq + 1);
+ TEST_VERIFY (idx >= 0);
+ TEST_VERIFY (idx < array_length (variables));
+ if (idx >= 0 && idx < array_length (variables))
+ {
+ char expected[20];
+ snprintf (expected, sizeof (expected), "V%d=%d", idx, idx);
+ TEST_COMPARE_STRING (*ep, expected);
+ if (seen[idx])
+ {
+ printf ("error: duplicate: %s\n", *ep);
+ support_record_failure ();
+ }
+ seen[idx] = true;
+ }
+ }
+ }
+ else if (strcmp (*ep, "changing=value-1") == 0
+ || strcmp (*ep, "changing=value-2") == 0)
+ {
+ if (seen_changing)
+ {
+ printf ("error: duplicate: %s\n", *ep);
+ support_record_failure ();
+ }
+ seen_changing = true;
+ }
+ else
+ {
+ TEST_COMPARE_STRING (*ep, "variable=value");
+ if (seen_variable)
+ {
+ printf ("error: duplicate: %s\n", *ep);
+ support_record_failure ();
+ }
+ seen_variable = true;
+ }
+ }
+}
+
+/* In the prepare function, verify the environment contents after a
+ self-exec. */
+#define PREPARE do_prepare
+static void
+do_prepare (int argc, char *argv[])
+{
+ if (argc == 2 && strcmp (argv[1], "verify") == 0)
+ {
+ verify_environ ();
+
+ /* Pass errors through to the other process. */
+ _exit (support_record_failure_is_failed () ? 1 : 0);
+ }
+}
+
+static int
+do_test (void)
+{
+ self_path = xasprintf ("%s/stdlib/%s", support_objdir_root, PROGRAM);
+ xpthread_barrier_init (&barrier, NULL, 2);
+ pthread_t thr = xpthread_create (NULL, test_thread, NULL);
+
+ for (int i = 0; i < array_length (variables); ++i)
+ variables[i] = xasprintf ("V%d", i);
+
+ for (int i = 0; i < iterations; ++i)
+ {
+ int variable_count = array_length (variables);
+ if ((iterations % 2) == 0)
+ /* Use the on-stack code path in __environ_snapshot_get. */
+ variable_count = on_stack_count - 1;
+
+ clearenv ();
+ for (int j = 0; j < variable_count; ++j)
+ {
+ /* Add the changing variable somewhere in the middle. */
+ if (j == start_variable + 5)
+ TEST_COMPARE (setenv ("changing", "value-2", 1), 0);
+ TEST_COMPARE (setenv (variables[j], variables[j] + 1, 1), 0);
+ }
+ TEST_COMPARE (setenv ("variable", "value", 1), 0);
+ xpthread_barrier_wait (&barrier);
+
+ /* Leave a certain amount of variables in place. */
+ for (int j = start_variable; j < variable_count; ++j)
+ {
+ /* Every few iterations, change the variable. */
+ if ((j % 4) == 0)
+ TEST_COMPARE (setenv ("changing",
+ ((j / 4) % 2) == 0 ? "value-1" : "value-2",
+ 1), 0);
+ TEST_COMPARE (unsetenv (variables[j]), 0);
+ }
+
+ xpthread_barrier_wait (&barrier);
+ }
+ xpthread_join (thr);
+ xpthread_barrier_destroy (&barrier);
+ for (int i = 0; i < array_length (variables); ++i)
+ free (variables[i]);
+ free (self_path);
+ return 0;
+}
+
+#include <support/test-driver.c>
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 11/13] posix: Implement environ snapshotting in __spawni
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
` (9 preceding siblings ...)
2024-07-28 19:03 ` [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat Florian Weimer
@ 2024-07-28 19:03 ` Florian Weimer
2024-07-28 19:03 ` [PATCH v2 12/13] posix: Acquire the environment lock during fork Florian Weimer
2024-07-28 19:03 ` [PATCH v2 13/13] manual: Updates for thread-safe getenv Florian Weimer
12 siblings, 0 replies; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:03 UTC (permalink / raw)
To: libc-alpha
This keeps stack usage bounded if environ is used with the
functions in the posix_spawn family.
---
posix/spawni.c | 94 +++++++++++++++++++++++++++++++-
stdlib/Makefile | 3 +
stdlib/tst-environ-posix_spawn.c | 31 +++++++++++
3 files changed, 127 insertions(+), 1 deletion(-)
create mode 100644 stdlib/tst-environ-posix_spawn.c
diff --git a/posix/spawni.c b/posix/spawni.c
index ef47dd9ea4..02e3ad0c8d 100644
--- a/posix/spawni.c
+++ b/posix/spawni.c
@@ -20,6 +20,29 @@
#include <spawn.h>
#include "spawn_int.h"
#include <arch-spawni.h>
+#include <stdlib/setenv.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+/* Used to keep track of the memory mapping for the environment
+ snapshot. */
+struct spawni_environ_snapshot
+{
+ char **data;
+ size_t size; /* In bytes. */
+};
+
+static void
+__spawni_environ_snapshot_unmap (void *arg)
+{
+ struct spawni_environ_snapshot *ses = arg;
+ if (ses->data != NULL)
+ {
+ int save_errno = errno;
+ __munmap (ses->data, ses->size);
+ __set_errno (save_errno);
+ }
+}
/* Spawn a new process executing PATH with the attributes describes in *ATTRP.
Before running the process perform the actions described in FILE-ACTIONS. */
@@ -29,5 +52,74 @@ __spawni (pid_t *pid, const char *file,
const posix_spawnattr_t *attrp, char *const argv[],
char *const envp[], int xflags)
{
- return __arch_spawni (pid, file, file_actions, attrp, argv, envp, xflags);
+ if (envp == NULL || envp != __environ
+ || !__environ_is_from_array_list ((char **) envp)
+ || __environ_single_threaded_no_snapshot (envp))
+ return __arch_spawni (pid, file, file_actions, attrp, argv, envp, xflags);
+
+ /* Perform the environment snapshot. It is beneficial to do this
+ here, and not in execveat, because it is possible to use an
+ mmap-backed array to avoid a huge stack allocation. Memory
+ management is easier because the allocation happens before vfork
+ and can be undone completely before the function returns. */
+
+ size_t env_size;
+ {
+ char *on_stack[ENVIRON_STACK_SNAPSHOT_SIZE];
+ env_size = __getenvarray (on_stack, ENVIRON_STACK_SNAPSHOT_SIZE);
+ if (env_size < ENVIRON_STACK_SNAPSHOT_SIZE)
+ return __arch_spawni (pid, file, file_actions, attrp, argv,
+ on_stack, xflags);
+ }
+
+ int ret;
+
+ /* Use mmap for async-signal-safety. Async-cancel-safety has
+ historically not been provided, so do not disable asynchronous
+ cancellation. As a result, there are memory leaks. */
+ struct spawni_environ_snapshot ses = { NULL, 0 };
+ __libc_cleanup_push (__spawni_environ_snapshot_unmap, &ses);
+
+ while (true)
+ {
+ /* Make room for the null terminator. */
+ ++env_size;
+
+ /* Create the allocation. */
+ {
+ size_t size = env_size * sizeof (char *);
+ void *ptr = __mmap (NULL, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (ptr == MAP_FAILED)
+ {
+ ret = -1;
+ break;
+ }
+ ses.data = ptr;
+ ses.size = size;
+ }
+
+ /* Try to get a snapshot. */
+ size_t env_count = __getenvarray (ses.data, env_size);
+ if (env_count < env_size)
+ {
+ ret = __arch_spawni (pid, file, file_actions, attrp, argv,
+ ses.data, xflags);
+ break;
+ }
+
+ /* The environment grew. Unmap the allocation that turned out
+ to be too small. Make a copy to avoid spurious unmaps with
+ asynchronous cancellation. */
+ struct spawni_environ_snapshot copy = ses;
+ ses.data = NULL;
+ __spawni_environ_snapshot_unmap (©);
+
+ /* Try again with the updated size. */
+ env_size = env_count;
+ }
+
+ __libc_cleanup_pop (1);
+ return ret;
}
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 498ec15e91..5934b683b1 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -379,6 +379,7 @@ tests-container := \
# statically, or use harcoded paths.
tests-with-reexec := \
tst-environ-execl \
+ tst-environ-posix_spawn \
# tests-with-reexec
tests += $(tests-with-reexec)
@@ -644,6 +645,8 @@ $(objpfx)tst-getenv-unsetenv: $(shared-thread-library)
# See above for $(tests-with-reexec).
ifeq ($(build-hardcoded-path-in-tests),yes)
$(objpfx)tst-environ-execl: $(shared-thread-library)
+$(objpfx)tst-environ-posix_spawn: $(shared-thread-library)
else
$(objpfx)tst-environ-execl: $(static-thread-library)
+$(objpfx)tst-environ-posix_spawn: $(static-thread-library)
endif
diff --git a/stdlib/tst-environ-posix_spawn.c b/stdlib/tst-environ-posix_spawn.c
new file mode 100644
index 0000000000..4791e19f1f
--- /dev/null
+++ b/stdlib/tst-environ-posix_spawn.c
@@ -0,0 +1,31 @@
+/* Test environment snapshots with posix_spawn.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#define PROGRAM "tst-environ-posix_spawn"
+#include "tst-environ-snapshot-skeleton.c"
+
+#include <spawn.h>
+
+static pid_t
+create_process (void)
+{
+ char *argv[] = { self_path, (char *) "verify", NULL };
+ pid_t pid;
+ return posix_spawn (&pid, self_path, NULL, NULL, argv, environ);
+ return pid;
+}
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 12/13] posix: Acquire the environment lock during fork
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
` (10 preceding siblings ...)
2024-07-28 19:03 ` [PATCH v2 11/13] posix: Implement environ snapshotting in __spawni Florian Weimer
@ 2024-07-28 19:03 ` Florian Weimer
2024-07-28 19:03 ` [PATCH v2 13/13] manual: Updates for thread-safe getenv Florian Weimer
12 siblings, 0 replies; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:03 UTC (permalink / raw)
To: libc-alpha
This makes setenv, unsetenv etc. usable after fork with the
builtin-malloc (avoid deadlocks). Deadlocks may still occur
with replacement mallocs. If this turns out to be a problem,
a bit of refactoring should allow calling malloc without
the environment lock in unsetenv.
---
posix/fork.c | 9 +++++++++
stdlib/Makefile | 2 ++
stdlib/getenv.c | 2 ++
stdlib/setenv.c | 7 ++-----
stdlib/setenv.h | 4 ++++
stdlib/tst-environ-fork.c | 38 ++++++++++++++++++++++++++++++++++++++
6 files changed, 57 insertions(+), 5 deletions(-)
create mode 100644 stdlib/tst-environ-fork.c
diff --git a/posix/fork.c b/posix/fork.c
index 298765a1ff..75d0910a94 100644
--- a/posix/fork.c
+++ b/posix/fork.c
@@ -25,6 +25,7 @@
#include <stdio-lock.h>
#include <sys/single_threaded.h>
#include <unwind-link.h>
+#include <stdlib/setenv.h>
static void
fresetlockfiles (void)
@@ -64,6 +65,10 @@ __libc_fork (void)
_IO_list_lock ();
+ /* The setenv function acquires this lock, and then the malloc
+ locks. Use the same lock order here. */
+ __libc_lock_lock (__environ_lock);
+
/* Acquire malloc locks. This needs to come last because fork
handlers may use malloc, and the libio list lock has an
indirect malloc dependency as well (via the getdelim
@@ -87,6 +92,8 @@ __libc_fork (void)
/* Release malloc locks. */
call_function_static_weak (__malloc_fork_unlock_child);
+ __libc_lock_unlock (__environ_lock);
+
/* Reset the file list. These are recursive mutexes. */
fresetlockfiles ();
@@ -119,6 +126,8 @@ __libc_fork (void)
/* Release malloc locks, parent process variant. */
call_function_static_weak (__malloc_fork_unlock_parent);
+ __libc_lock_unlock (__environ_lock);
+
/* We execute this even if the 'fork' call failed. */
_IO_list_unlock ();
}
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 5934b683b1..5613c34bcb 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -277,6 +277,7 @@ tests := \
tst-canon-bz26341 \
tst-cxa_atexit \
tst-environ \
+ tst-environ-fork \
tst-getenv-signal \
tst-getenv-thread \
tst-getenv-unsetenv \
@@ -639,6 +640,7 @@ $(objpfx)tst-setcontext3.out: tst-setcontext3.sh $(objpfx)tst-setcontext3
$(objpfx)tst-qsort5: $(libm)
+$(objpfx)tst-environ-fork: $(shared-thread-library)
$(objpfx)tst-getenv-signal: $(shared-thread-library)
$(objpfx)tst-getenv-thread: $(shared-thread-library)
$(objpfx)tst-getenv-unsetenv: $(shared-thread-library)
diff --git a/stdlib/getenv.c b/stdlib/getenv.c
index ad7f0d00c8..c581ecf951 100644
--- a/stdlib/getenv.c
+++ b/stdlib/getenv.c
@@ -19,7 +19,9 @@
#include <setenv.h>
#include <string.h>
#include <unistd.h>
+#include <libc-lock.h>
+__libc_lock_define_initialized (, __environ_lock);
struct environ_array *__environ_array_list;
environ_counter __environ_counter;
diff --git a/stdlib/setenv.c b/stdlib/setenv.c
index 6bd0596b79..a97f6de4d4 100644
--- a/stdlib/setenv.c
+++ b/stdlib/setenv.c
@@ -55,11 +55,8 @@ extern char **environ;
#endif
#if _LIBC
-/* This lock protects against simultaneous modifications of `environ'. */
-# include <libc-lock.h>
-__libc_lock_define_initialized (static, envlock)
-# define LOCK __libc_lock_lock (envlock)
-# define UNLOCK __libc_lock_unlock (envlock)
+# define LOCK __libc_lock_lock (__environ_lock)
+# define UNLOCK __libc_lock_unlock (__environ_lock)
#else
# define LOCK
# define UNLOCK
diff --git a/stdlib/setenv.h b/stdlib/setenv.h
index 3e530f0bc0..1a184594ce 100644
--- a/stdlib/setenv.h
+++ b/stdlib/setenv.h
@@ -22,8 +22,12 @@
/* Not actually usable in rtld, but it is required for symbol discovery. */
#if IS_IN (libc) || IS_IN (rtld)
# include <atomic.h>
+# include <libc-lock.h>
# include <stdbool.h>
+/* This lock protects against simultaneous modifications of `environ'. */
+__libc_lock_define (extern, __environ_lock attribute_hidden);
+
/* We use an exponential sizing policy for environment arrays. The
arrays are not deallocating during the lifetime of the process.
This adds between one and two additional pointers per active
diff --git a/stdlib/tst-environ-fork.c b/stdlib/tst-environ-fork.c
new file mode 100644
index 0000000000..0e25b324e7
--- /dev/null
+++ b/stdlib/tst-environ-fork.c
@@ -0,0 +1,38 @@
+/* Test environment snapshots with fork.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ 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.
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+/* This test uses the process-creation skeleton, but does not performa
+ re-exec, so the use of PROGRAM is a bit misleading here. */
+
+#define PROGRAM "tst-environ-fork"
+#include "tst-environ-snapshot-skeleton.c"
+
+static pid_t
+create_process (void)
+{
+ pid_t pid = xfork ();
+ if (pid == 0)
+ {
+ verify_environ ();
+ /* Verify that it is possible to obtain the environment lock. */
+ TEST_COMPARE (setenv ("unrelated", "unrelated-set", 1), 0);
+ TEST_COMPARE_STRING (getenv ("unrelated"), "unrelated-set");
+ _exit (0);
+ }
+ return pid;
+}
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* [PATCH v2 13/13] manual: Updates for thread-safe getenv
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
` (11 preceding siblings ...)
2024-07-28 19:03 ` [PATCH v2 12/13] posix: Acquire the environment lock during fork Florian Weimer
@ 2024-07-28 19:03 ` Florian Weimer
12 siblings, 0 replies; 39+ messages in thread
From: Florian Weimer @ 2024-07-28 19:03 UTC (permalink / raw)
To: libc-alpha
---
NEWS | 5 ++
manual/argp.texi | 20 +++---
manual/conf.texi | 2 +-
manual/filesys.texi | 4 +-
manual/getopt.texi | 6 +-
manual/intro.texi | 17 +----
manual/locale.texi | 9 +--
manual/macros.texi | 4 --
manual/memory.texi | 18 +++---
manual/message.texi | 37 +++++------
manual/pattern.texi | 64 +++++++++----------
manual/process.texi | 4 +-
manual/socket.texi | 145 +++++++++++++++++++++---------------------
manual/startup.texi | 122 ++++++++++++++++++++++++++++-------
manual/sysinfo.texi | 2 +-
manual/syslog.texi | 18 +++---
manual/time.texi | 150 +++++++++++++++++++++-----------------------
17 files changed, 333 insertions(+), 294 deletions(-)
diff --git a/NEWS b/NEWS
index d488874aba..026be0c167 100644
--- a/NEWS
+++ b/NEWS
@@ -89,6 +89,11 @@ Major new features:
of large writes. This behaviour is controlled by a new tunable
x86_memset_non_temporal_threshold.
+* The getenv function is now thread-safe and async-signal-safe, provided
+ that the environ array is only manipulated using the function setenv,
+ unsetenv, and clearenv. If putenv is used, the safety properties of
+ getenv depend on how the string into the environment is updated.
+
Deprecated and removed features, and other changes affecting compatibility:
* Architectures which use a 32-bit seconds-since-epoch field in struct
diff --git a/manual/argp.texi b/manual/argp.texi
index 0023441812..b09e0b9880 100644
--- a/manual/argp.texi
+++ b/manual/argp.texi
@@ -35,7 +35,7 @@ needed in @code{main}.
@deftypefun {error_t} argp_parse (const struct argp *@var{argp}, int @var{argc}, char **@var{argv}, unsigned @var{flags}, int *@var{arg_index}, void *@var{input})
@standards{GNU, argp.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:argpbuf} @mtslocale{} @mtsenv{}}@asunsafe{@ascuheap{} @ascuintl{} @asulock{} @asucorrupt{}}@acunsafe{@acsmem{} @aculock{} @acucorrupt{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:argpbuf} @mtslocale{}}@asunsafe{@ascuheap{} @ascuintl{} @asulock{} @asucorrupt{}}@acunsafe{@acsmem{} @aculock{} @acucorrupt{}}}
@c Optionally alloca()tes standard help options, initializes the parser,
@c then parses individual args in a loop, and then finalizes.
@c parser_init
@@ -56,12 +56,12 @@ needed in @code{main}.
@c group_parse dup
@c parser_parse_opt
@c group_parse dup
-@c argp_error dup @mtasurace:argpbuf @mtsenv @mtslocale @ascuheap @ascuintl @asucorrupt @acsmem @acucorrupt @aculock
-@c dgettext (bad key error) dup @mtsenv @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
+@c argp_error dup @mtasurace:argpbuf @mtslocale @ascuheap @ascuintl @asucorrupt @acsmem @acucorrupt @aculock
+@c dgettext (bad key error) dup @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
@c parser_finalize
@c group_parse
@c fprintf dup @mtslocale @asucorrupt @aculock @acucorrupt [no @ascuheap @acsmem]
-@c dgettext dup @mtsenv @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
+@c dgettext dup @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
@c arg_state_help
@c free dup @ascuhelp @acsmem
The @code{argp_parse} function parses the arguments in @var{argv}, of
@@ -666,7 +666,7 @@ parser function. @xref{Argp Parsing State}.
@cindex usage messages, in argp
@deftypefun void argp_usage (const struct argp_state *@var{state})
@standards{GNU, argp.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:argpbuf} @mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @ascuintl{} @asucorrupt{}}@acunsafe{@acsmem{} @acucorrupt{} @aculock{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:argpbuf} @mtslocale{}}@asunsafe{@ascuheap{} @ascuintl{} @asucorrupt{}}@acunsafe{@acsmem{} @acucorrupt{} @aculock{}}}
@c Just calls argp_state_help with stderr and ARGP_HELP_STD_USAGE.
Outputs the standard usage message for the argp parser referred to by
@var{state} to @code{@var{state}->err_stream} and terminates the program
@@ -676,7 +676,7 @@ with @code{exit (argp_err_exit_status)}. @xref{Argp Global Variables}.
@cindex syntax error messages, in argp
@deftypefun void argp_error (const struct argp_state *@var{state}, const char *@var{fmt}, @dots{})
@standards{GNU, argp.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:argpbuf} @mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @ascuintl{} @asucorrupt{}}@acunsafe{@acsmem{} @acucorrupt{} @aculock{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:argpbuf} @mtslocale{}}@asunsafe{@ascuheap{} @ascuintl{} @asucorrupt{}}@acunsafe{@acsmem{} @acucorrupt{} @aculock{}}}
@c Lock stream, vasprintf the formatted message into a buffer, print the
@c buffer prefixed by the short program name (in libc,
@c argp_short_program_name is a macro that expands to
@@ -713,7 +713,7 @@ for options, bad phase of the moon, etc.
@deftypefun void argp_state_help (const struct argp_state *@var{state}, FILE *@var{stream}, unsigned @var{flags})
@standards{GNU, argp.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:argpbuf} @mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @ascuintl{} @asucorrupt{}}@acunsafe{@acsmem{} @acucorrupt{} @aculock{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:argpbuf} @mtslocale{}}@asunsafe{@ascuheap{} @ascuintl{} @asucorrupt{}}@acunsafe{@acsmem{} @acucorrupt{} @aculock{}}}
@c Just calls _help with the short program name and optionally exit.
@c The main problems in _help, besides the usual issues with stream I/O
@c and translation, are the use of a static buffer (uparams, thus
@@ -721,11 +721,11 @@ for options, bad phase of the moon, etc.
@c from the environment for ARGP_HELP_FMT, accessing the locale object
@c multiple times.
-@c _help @mtsenv @mtasurace:argpbuf @mtslocale @ascuheap @ascuintl @asucorrupt @acsmem @acucorrupt @aculock
+@c _help @mtasurace:argpbuf @mtslocale @ascuheap @ascuintl @asucorrupt @acsmem @acucorrupt @aculock
@c dgettext @ascuintl
@c flockfile @aculock
@c funlockfile @aculock
-@c fill_in_uparams @mtsenv @mtasurace:argpbuf @mtslocale @asucorrupt @ascuheap @aculock @acucorrupt @acsmem
+@c fill_in_uparams @mtasurace:argpbuf @mtslocale @asucorrupt @ascuheap @aculock @acucorrupt @acsmem
@c argp_failure dup (status = errnum = 0)
@c atoi dup @mtslocale
@c argp_hol @ascuheap @acsmem
@@ -1066,7 +1066,7 @@ program options, argp offers the @code{argp_help} interface.
@deftypefun void argp_help (const struct argp *@var{argp}, FILE *@var{stream}, unsigned @var{flags}, char *@var{name})
@standards{GNU, argp.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:argpbuf} @mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @ascuintl{} @asucorrupt{}}@acunsafe{@acsmem{} @acucorrupt{} @aculock{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:argpbuf} @mtslocale{}}@asunsafe{@ascuheap{} @ascuintl{} @asucorrupt{}}@acunsafe{@acsmem{} @acucorrupt{} @aculock{}}}
@c Just calls _help.
This outputs a help message for the argp parser @var{argp} to
@var{stream}. The type of messages printed will be determined by
diff --git a/manual/conf.texi b/manual/conf.texi
index be680e0692..c522d56955 100644
--- a/manual/conf.texi
+++ b/manual/conf.texi
@@ -272,7 +272,7 @@ constants are declared in the header file @file{unistd.h}.
@deftypefun {long int} sysconf (int @var{parameter})
@standards{POSIX.1, unistd.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@asulock{} @ascuheap{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@asulock{} @ascuheap{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
@c Some parts of the implementation open /proc and /sys files and dirs
@c to collect system details, using fd and stream I/O depending on the
@c case. The returned max value may change over time for NPROCS,
diff --git a/manual/filesys.texi b/manual/filesys.texi
index 47d929744e..40c2e53e06 100644
--- a/manual/filesys.texi
+++ b/manual/filesys.texi
@@ -150,7 +150,7 @@ this function is deprecated.
@vindex PWD
@deftypefun {char *} get_current_dir_name (void)
@standards{GNU, unistd.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{} @acsfd{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{} @acsfd{}}}
@c Besides getcwd, which this function calls as a fallback, it calls
@c getenv, with the potential thread-safety issues that brings about.
The @code{get_current_dir_name} function is basically equivalent to
@@ -3443,7 +3443,7 @@ never less than @code{25}.
@deftypefun {char *} tempnam (const char *@var{dir}, const char *@var{prefix})
@standards{SVID, stdio.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
@c There's no way (short of being setuid) to avoid getenv("TMPDIR"),
@c even with a non-NULL dir.
@c
diff --git a/manual/getopt.texi b/manual/getopt.texi
index b4c0b15ac2..5c87100a46 100644
--- a/manual/getopt.texi
+++ b/manual/getopt.texi
@@ -55,7 +55,7 @@ option argument, for those options that accept arguments.
@deftypefun int getopt (int @var{argc}, char *const *@var{argv}, const char *@var{options})
@standards{POSIX.2, unistd.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:getopt} @mtsenv{}}@asunsafe{@ascuheap{} @ascuintl{} @asulock{} @asucorrupt{}}@acunsafe{@acsmem{} @aculock{} @acucorrupt{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:getopt}}@asunsafe{@ascuheap{} @ascuintl{} @asulock{} @asucorrupt{}}@acunsafe{@acsmem{} @aculock{} @acucorrupt{}}}
@c Swapping elements of passed-in argv may be partial in case of
@c cancellation. Gettext brings about a whole lot of AS and AC safety
@c issues. The getopt API involves returning values in the
@@ -237,7 +237,7 @@ was seen.
@deftypefun int getopt_long (int @var{argc}, char *const *@var{argv}, const char *@var{shortopts}, const struct option *@var{longopts}, int *@var{indexptr})
@standards{GNU, getopt.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:getopt} @mtsenv{}}@asunsafe{@ascuheap{} @ascuintl{} @asulock{} @asucorrupt{}}@acunsafe{@acsmem{} @aculock{} @acucorrupt{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:getopt}}@asunsafe{@ascuheap{} @ascuintl{} @asulock{} @asucorrupt{}}@acunsafe{@acsmem{} @aculock{} @acucorrupt{}}}
@c Same issues as getopt.
Decode options from the vector @var{argv} (whose length is @var{argc}).
The argument @var{shortopts} describes the short options to accept, just as
@@ -292,7 +292,7 @@ getopt functionality there is one more function available.
@deftypefun int getopt_long_only (int @var{argc}, char *const *@var{argv}, const char *@var{shortopts}, const struct option *@var{longopts}, int *@var{indexptr})
@standards{GNU, getopt.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:getopt} @mtsenv{}}@asunsafe{@ascuheap{} @ascuintl{} @asulock{} @asucorrupt{}}@acunsafe{@acsmem{} @aculock{} @acucorrupt{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:getopt}}@asunsafe{@ascuheap{} @ascuintl{} @asulock{} @asucorrupt{}}@acunsafe{@acsmem{} @aculock{} @acucorrupt{}}}
@c Same issues as getopt.
The @code{getopt_long_only} function is equivalent to the
diff --git a/manual/intro.texi b/manual/intro.texi
index 879c1b38d9..2a0f882184 100644
--- a/manual/intro.texi
+++ b/manual/intro.texi
@@ -433,7 +433,7 @@ Functions marked with @code{i18n} may call internationalization
functions of the @code{gettext} family and will be only as safe as those
functions. This note is thus equivalent to:
-@sampsafety{@mtsafe{@mtsenv{}}@asunsafe{@asucorrupt{} @ascuheap{} @ascudlopen{}}@acunsafe{@acucorrupt{}}}
+@sampsafety{@mtsafe{}@asunsafe{@asucorrupt{} @ascuheap{} @ascudlopen{}}@acunsafe{@acucorrupt{}}}
@item @code{timer}
@@ -732,21 +732,6 @@ constant in these contexts, which makes the former safe.
@c because of the unexpected locale changes.
-@item @code{env}
-@cindex env
-
-Functions marked with @code{env} as an MT-Safety issue access the
-environment with @code{getenv} or similar, without any guards to ensure
-safety in the presence of concurrent modifications.
-
-We do not mark these functions as MT- or AS-Unsafe, however, because
-functions that modify the environment are all marked with
-@code{const:env} and regarded as unsafe. Being unsafe, the latter are
-not to be called when multiple threads are running or asynchronous
-signals are enabled, and so the environment can be considered
-effectively constant in these contexts, which makes the former safe.
-
-
@item @code{hostid}
@cindex hostid
diff --git a/manual/locale.texi b/manual/locale.texi
index 1b3f97839b..9e20295fc3 100644
--- a/manual/locale.texi
+++ b/manual/locale.texi
@@ -222,23 +222,21 @@ The symbols in this section are defined in the header file @file{locale.h}.
@deftypefun {char *} setlocale (int @var{category}, const char *@var{locale})
@standards{ISO, locale.h}
-@safety{@prelim{}@mtunsafe{@mtasuconst{:@mtslocale{}} @mtsenv{}}@asunsafe{@asuinit{} @asulock{} @ascuheap{} @asucorrupt{}}@acunsafe{@acuinit{} @acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
+@safety{@prelim{}@mtunsafe{@mtasuconst{:@mtslocale{}}}@asunsafe{@asuinit{} @asulock{} @ascuheap{} @asucorrupt{}}@acunsafe{@acuinit{} @acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
@c Uses of the global locale object are unguarded in functions that
@c ought to be MT-Safe, so we're ruling out the use of this function
@c once threads are started. It takes a write lock itself, but it may
@c return a pointer loaded from the global locale object after releasing
@c the lock, or before taking it.
-@c setlocale @mtasuconst:@mtslocale @mtsenv @asuinit @ascuheap @asulock @asucorrupt @acucorrupt @acsmem @acsfd @aculock
+@c setlocale @mtasuconst:@mtslocale @asuinit @ascuheap @asulock @asucorrupt @acucorrupt @acsmem @acsfd @aculock
@c libc_rwlock_wrlock @asulock @aculock
@c libc_rwlock_unlock @aculock
-@c getenv LOCPATH @mtsenv
@c malloc @ascuheap @acsmem
@c free @ascuheap @acsmem
@c new_composite_name ok
@c setdata ok
@c setname ok
-@c _nl_find_locale @mtsenv @asuinit @ascuheap @asulock @asucorrupt @acucorrupt @acsmem @acsfd @aculock
-@c getenv LC_ALL and LANG @mtsenv
+@c _nl_find_locale @asuinit @ascuheap @asulock @asucorrupt @acucorrupt @acsmem @acsfd @aculock
@c _nl_load_locale_from_archive @ascuheap @acucorrupt @acsmem @acsfd
@c sysconf _SC_PAGE_SIZE ok
@c _nl_normalize_codeset @ascuheap @acsmem
@@ -301,7 +299,6 @@ The symbols in this section are defined in the header file @file{locale.h}.
@c (libc_once-initializes gconv_cache and gconv_path_envvar; they're
@c never modified afterwards)
@c __gconv_load_cache @ascuheap @acsmem @acsfd
-@c getenv GCONV_PATH @mtsenv
@c open_not_cancel @acsfd
@c __fxstat64 ok
@c close_not_cancel_no_status ok
diff --git a/manual/macros.texi b/manual/macros.texi
index 579da3fb81..584f26602f 100644
--- a/manual/macros.texi
+++ b/manual/macros.texi
@@ -125,10 +125,6 @@ const\comments\
@macro mtslocale {comments}
locale\comments\
@end macro
-@c Function accesses the assumed-constant environment.
-@macro mtsenv {comments}
-env\comments\
-@end macro
@c Function accesses the assumed-constant hostid.
@macro mtshostid {comments}
hostid\comments\
diff --git a/manual/memory.texi b/manual/memory.texi
index 3710d7ec66..c444f0f0e5 100644
--- a/manual/memory.texi
+++ b/manual/memory.texi
@@ -415,14 +415,12 @@ this function is in @file{stdlib.h}.
@c fastbin_index ok
@c fastbin ok
@c catomic_compare_and_exhange_val_acq ok
-@c malloc_printerr dup @mtsenv
+@c malloc_printerr dup
@c if we get to it, we're toast already, undefined behavior must have
@c been invoked before
-@c libc_message @mtsenv [no leaks with cancellation disabled]
+@c libc_message [no leaks with cancellation disabled]
@c FATAL_PREPARE ok
@c pthread_setcancelstate disable ok
-@c libc_secure_getenv @mtsenv
-@c getenv @mtsenv
@c open_not_cancel_2 dup @acsfd
@c strchrnul ok
@c WRITEV_FOR_FATAL ok
@@ -1096,7 +1094,7 @@ systems that do not support @w{ISO C11}.
@standards{BSD, stdlib.h}
@safety{@prelim{}@mtunsafe{@mtuinit{}}@asunsafe{@asuinit{} @asulock{}}@acunsafe{@acuinit{} @aculock{} @acsfd{} @acsmem{}}}
@c __libc_valloc @mtuinit @asuinit @asulock @aculock @acsfd @acsmem
-@c ptmalloc_init (once) @mtsenv @asulock @aculock @acsfd @acsmem
+@c ptmalloc_init (once) @asulock @aculock @acsfd @acsmem
@c _dl_addr @asucorrupt? @aculock
@c __rtld_lock_lock_recursive (dl_load_lock) @asucorrupt? @aculock
@c _dl_find_dso_for_object ok, iterates over dl_ns and its _ns_loaded objs
@@ -1105,7 +1103,6 @@ systems that do not support @w{ISO C11}.
@c _dl_addr_inside_object ok
@c determine_info ok
@c __rtld_lock_unlock_recursive (dl_load_lock) @aculock
-@c *_environ @mtsenv
@c next_env_entry ok
@c strcspn dup ok
@c __libc_mallopt dup @mtasuconst:mallopt [setting mp_]
@@ -1147,7 +1144,7 @@ interface, defined in @file{malloc.h}.
@deftypefun int mallopt (int @var{param}, int @var{value})
@safety{@prelim{}@mtunsafe{@mtuinit{} @mtasuconst{:mallopt}}@asunsafe{@asuinit{} @asulock{}}@acunsafe{@acuinit{} @aculock{}}}
@c __libc_mallopt @mtuinit @mtasuconst:mallopt @asuinit @asulock @aculock
-@c ptmalloc_init (once) dup @mtsenv @asulock @aculock @acsfd @acsmem
+@c ptmalloc_init (once) dup @asulock @aculock @acsfd @acsmem
@c mutex_lock (main_arena->mutex) @asulock @aculock
@c malloc_consolidate dup ok
@c set_max_fast ok
@@ -1456,7 +1453,7 @@ space's data segment).
@c that collect the possibly inconsistent data.
@c __libc_mallinfo2 @mtuinit @mtasuconst:mallopt @asuinit @asulock @aculock
-@c ptmalloc_init (once) dup @mtsenv @asulock @aculock @acsfd @acsmem
+@c ptmalloc_init (once) dup @asulock @aculock @acsfd @acsmem
@c mutex_lock dup @asulock @aculock
@c int_mallinfo @mtasuconst:mallopt [mp_ access on main_arena]
@c malloc_consolidate dup ok
@@ -1556,13 +1553,12 @@ penalties for the program if the debugging mode is not enabled.
@deftypefun void mtrace (void)
@standards{GNU, mcheck.h}
-@safety{@prelim{}@mtunsafe{@mtsenv{} @mtasurace{:mtrace} @mtuinit{}}@asunsafe{@asuinit{} @ascuheap{} @asucorrupt{} @asulock{}}@acunsafe{@acuinit{} @acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:mtrace} @mtuinit{}}@asunsafe{@asuinit{} @ascuheap{} @asucorrupt{} @asulock{}}@acunsafe{@acuinit{} @acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
@c Like the mcheck hooks, these are not designed with thread safety in
@c mind, because the hook pointers are temporarily modified without
@c regard to other threads, signals or cancellation.
-@c mtrace @mtuinit @mtasurace:mtrace @mtsenv @asuinit @ascuheap @asucorrupt @acuinit @acucorrupt @aculock @acsfd @acsmem
-@c __libc_secure_getenv dup @mtsenv
+@c mtrace @mtuinit @mtasurace:mtrace @asuinit @ascuheap @asucorrupt @acuinit @acucorrupt @aculock @acsfd @acsmem
@c malloc dup @ascuheap @acsmem
@c fopen dup @ascuheap @asulock @aculock @acsmem @acsfd
@c fcntl dup ok
diff --git a/manual/message.texi b/manual/message.texi
index ef68693fd1..2cff798346 100644
--- a/manual/message.texi
+++ b/manual/message.texi
@@ -85,11 +85,10 @@ are defined/declared in the @file{nl_types.h} header file.
@deftypefun nl_catd catopen (const char *@var{cat_name}, int @var{flag})
@standards{X/Open, nl_types.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
-@c catopen @mtsenv @ascuheap @acsmem
+@safety{@prelim{}@mtsafe{}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
+@c catopen @ascuheap @acsmem
@c strchr ok
@c setlocale(,NULL) ok
-@c getenv @mtsenv
@c strlen ok
@c alloca ok
@c stpcpy ok
@@ -831,7 +830,7 @@ not part of the C library they can be found in a separate library named
@deftypefun {char *} gettext (const char *@var{msgid})
@standards{GNU, libintl.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
@c Wrapper for dcgettext.
The @code{gettext} function searches the currently selected message
catalogs for a string which is equal to @var{msgid}. If there is such a
@@ -879,7 +878,7 @@ information.
@deftypefun {char *} dgettext (const char *@var{domainname}, const char *@var{msgid})
@standards{GNU, libintl.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
@c Wrapper for dcgettext.
The @code{dgettext} function acts just like the @code{gettext}
function. It only takes an additional first argument @var{domainname}
@@ -894,9 +893,9 @@ anachronism. The returned string must never be modified.
@deftypefun {char *} dcgettext (const char *@var{domainname}, const char *@var{msgid}, int @var{category})
@standards{GNU, libintl.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
-@c dcgettext @mtsenv @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
-@c dcigettext @mtsenv @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
+@safety{@prelim{}@mtsafe{}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@c dcgettext @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
+@c dcigettext @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
@c libc_rwlock_rdlock @asulock @aculock
@c current_locale_name ok [protected from @mtslocale]
@c tfind ok
@@ -911,16 +910,15 @@ anachronism. The returned string must never be modified.
@c strchr ok
@c stpcpy ok
@c category_to_name ok
-@c guess_category_value @mtsenv
-@c getenv @mtsenv
+@c guess_category_value
@c current_locale_name dup ok [protected from @mtslocale by dcigettext]
@c strcmp ok
@c ENABLE_SECURE ok
-@c _nl_find_domain @mtsenv @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
+@c _nl_find_domain @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
@c libc_rwlock_rdlock dup @asulock @aculock
@c _nl_make_l10nflist dup @ascuheap @acsmem
@c libc_rwlock_unlock dup ok
-@c _nl_load_domain @mtsenv @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
+@c _nl_load_domain @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
@c libc_lock_lock_recursive @aculock
@c libc_lock_unlock_recursive @aculock
@c open->open_not_cancel_2 @acsfd
@@ -937,7 +935,7 @@ anachronism. The returned string must never be modified.
@c hash_string dup ok
@c free dup @ascuheap @acsmem
@c libc_rwlock_init ok
-@c _nl_find_msg dup @mtsenv @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
+@c _nl_find_msg dup @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
@c libc_rwlock_fini ok
@c EXTRACT_PLURAL_EXPRESSION @ascuheap @acsmem
@c strstr dup ok
@@ -952,16 +950,15 @@ anachronism. The returned string must never be modified.
@c _nl_explode_name dup @ascuheap @acsmem
@c libc_rwlock_wrlock dup @asulock @aculock
@c free dup @asulock @aculock @acsfd @acsmem
-@c _nl_find_msg @mtsenv @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
-@c _nl_load_domain dup @mtsenv @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
+@c _nl_find_msg @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
+@c _nl_load_domain dup @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsfd @acsmem
@c strlen ok
@c hash_string ok
@c W ok
@c SWAP ok
@c bswap_32 ok
@c strcmp ok
-@c get_output_charset @mtsenv @ascuheap @acsmem
-@c getenv dup @mtsenv
+@c get_output_charset @ascuheap @acsmem
@c strlen dup ok
@c malloc dup @ascuheap @acsmem
@c memcpy dup ok
@@ -1272,7 +1269,7 @@ purpose.
@deftypefun {char *} ngettext (const char *@var{msgid1}, const char *@var{msgid2}, unsigned long int @var{n})
@standards{GNU, libintl.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
@c Wrapper for dcngettext.
The @code{ngettext} function is similar to the @code{gettext} function
as it finds the message catalogs in the same way. But it takes two
@@ -1296,7 +1293,7 @@ Please note that the numeric value @var{n} has to be passed to the
@deftypefun {char *} dngettext (const char *@var{domain}, const char *@var{msgid1}, const char *@var{msgid2}, unsigned long int @var{n})
@standards{GNU, libintl.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
@c Wrapper for dcngettext.
The @code{dngettext} is similar to the @code{dgettext} function in the
way the message catalog is selected. The difference is that it takes
@@ -1306,7 +1303,7 @@ parameters are handled in the same way @code{ngettext} handles them.
@deftypefun {char *} dcngettext (const char *@var{domain}, const char *@var{msgid1}, const char *@var{msgid2}, unsigned long int @var{n}, int @var{category})
@standards{GNU, libintl.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
@c Wrapper for dcigettext.
The @code{dcngettext} is similar to the @code{dcgettext} function in the
way the message catalog is selected. The difference is that it takes
diff --git a/manual/pattern.texi b/manual/pattern.texi
index 250fa1e265..9de0842c14 100644
--- a/manual/pattern.texi
+++ b/manual/pattern.texi
@@ -27,29 +27,25 @@ declared in @file{fnmatch.h}.
@deftypefun int fnmatch (const char *@var{pattern}, const char *@var{string}, int @var{flags})
@standards{POSIX.2, fnmatch.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
-@c fnmatch @mtsenv @mtslocale @ascuheap @acsmem
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
+@c fnmatch @mtslocale @ascuheap @acsmem
@c strnlen dup ok
@c mbsrtowcs
@c memset dup ok
@c malloc dup @ascuheap @acsmem
@c mbsinit dup ok
@c free dup @ascuheap @acsmem
-@c FCT = internal_fnwmatch @mtsenv @mtslocale @ascuheap @acsmem
+@c FCT = internal_fnwmatch @mtslocale @ascuheap @acsmem
@c FOLD @mtslocale
@c towlower @mtslocale
-@c EXT @mtsenv @mtslocale @ascuheap @acsmem
+@c EXT @mtslocale @ascuheap @acsmem
@c STRLEN = wcslen dup ok
-@c getenv @mtsenv
@c malloc dup @ascuheap @acsmem
@c MEMPCPY = wmempcpy dup ok
-@c FCT dup @mtsenv @mtslocale @ascuheap @acsmem
+@c FCT dup @mtslocale @ascuheap @acsmem
@c STRCAT = wcscat dup ok
@c free dup @ascuheap @acsmem
-@c END @mtsenv
-@c getenv @mtsenv
@c MEMCHR = wmemchr dup ok
-@c getenv @mtsenv
@c IS_CHAR_CLASS = is_char_class @mtslocale
@c wctype @mtslocale
@c BTOWC ok
@@ -385,8 +381,8 @@ This is a GNU extension.
@deftypefun int glob (const char *@var{pattern}, int @var{flags}, int (*@var{errfunc}) (const char *@var{filename}, int @var{error-code}), glob_t *@var{vector-ptr})
@standards{POSIX.2, glob.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:utent} @mtsenv{} @mtascusig{:ALRM} @mtascutimer{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
-@c glob @mtasurace:utent @mtsenv @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@safety{@prelim{}@mtunsafe{@mtasurace{:utent} @mtascusig{:ALRM} @mtascutimer{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@c glob @mtasurace:utent @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c strlen dup ok
@c strchr dup ok
@c malloc dup @ascuheap @acsmem
@@ -396,7 +392,6 @@ This is a GNU extension.
@c globfree dup @asucorrupt @ascuheap @acucorrupt @acsmem
@c glob_pattern_p ok
@c glob_pattern_type dup ok
-@c getenv dup @mtsenv
@c GET_LOGIN_NAME_MAX ok
@c getlogin_r dup @mtasurace:utent @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c GETPW_R_SIZE_MAX ok
@@ -409,7 +404,7 @@ This is a GNU extension.
@c S_ISDIR dup ok
@c strdup dup @ascuheap @acsmem
@c glob_pattern_type ok
-@c glob_in_dir @mtsenv @mtslocale @asucorrupt @ascuheap @acucorrupt @acsfd @acsmem
+@c glob_in_dir @mtslocale @asucorrupt @ascuheap @acucorrupt @acsfd @acsmem
@c strlen dup ok
@c glob_pattern_type dup ok
@c malloc dup @ascuheap @acsmem
@@ -425,7 +420,7 @@ This is a GNU extension.
@c readdir64 ok [protected by exclusive use of the stream]
@c REAL_DIR_ENTRY ok
@c DIRENT_MIGHT_BE_DIR ok
-@c fnmatch dup @mtsenv @mtslocale @ascuheap @acsmem
+@c fnmatch dup @mtslocale @ascuheap @acsmem
@c DIRENT_MIGHT_BE_SYMLINK ok
@c link_exists_p ok
@c link_exists2_p ok
@@ -509,7 +504,7 @@ is encountered @code{glob} @emph{can} fail.
@deftypefun int glob64 (const char *@var{pattern}, int @var{flags}, int (*@var{errfunc}) (const char *@var{filename}, int @var{error-code}), glob64_t *@var{vector-ptr})
@standards{GNU, glob.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:utent} @mtsenv{} @mtascusig{:ALRM} @mtascutimer{} @mtslocale{}}@asunsafe{@ascudlopen{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:utent} @mtascusig{:ALRM} @mtascutimer{} @mtslocale{}}@asunsafe{@ascudlopen{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
@c Same code as glob, but with glob64_t #defined as glob_t.
The @code{glob64} function was added as part of the Large File Summit
extensions but is not part of the original LFS proposal. The reason for
@@ -1622,7 +1617,7 @@ the function @code{regerror} to turn it into an error message string.
@deftypefun size_t regerror (int @var{errcode}, const regex_t *restrict @var{compiled}, char *restrict @var{buffer}, size_t @var{length})
@standards{POSIX.2, regex.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
@c regerror calls gettext, strcmp and mempcpy or memcpy.
This function produces an error message string for the error code
@var{errcode}, and stores the string in @var{length} bytes of memory
@@ -1787,20 +1782,19 @@ the beginning of the vector.
@deftypefun int wordexp (const char *@var{words}, wordexp_t *@var{word-vector-ptr}, int @var{flags})
@standards{POSIX.2, wordexp.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:utent} @mtasuconst{:@mtsenv{}} @mtsenv{} @mtascusig{:ALRM} @mtascutimer{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuintl{} @ascuheap{} @asucorrupt{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
-@c wordexp @mtasurace:utent @mtasuconst:@mtsenv @mtsenv @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asucorrupt @asulock @acucorrupt @aculock @acsfd @acsmem
+@safety{@prelim{}@mtunsafe{@mtasurace{:utent} @mtascusig{:ALRM} @mtascutimer{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuintl{} @ascuheap{} @asucorrupt{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@c wordexp @mtasurace:utent @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asucorrupt @asulock @acucorrupt @aculock @acsfd @acsmem
@c w_newword ok
@c wordfree dup @asucorrupt @ascuheap @acucorrupt @acsmem
@c calloc dup @ascuheap @acsmem
-@c getenv dup @mtsenv
@c strcpy dup ok
@c parse_backslash @ascuheap @acsmem
@c w_addchar dup @ascuheap @acsmem
-@c parse_dollars @mtasuconst:@mtsenv @mtslocale @mtsenv @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c parse_dollars @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c w_addchar dup @ascuheap @acsmem
-@c parse_arith @mtasuconst:@mtsenv @mtslocale @mtsenv @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c parse_arith @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c w_newword dup ok
-@c parse_dollars dup @mtasuconst:@mtsenv @mtslocale @mtsenv @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c parse_dollars dup @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c parse_backtick dup @ascuplugin @ascuheap @aculock @acsfd @acsmem
@c parse_qtd_backslash dup @ascuheap @acsmem
@c eval_expr @mtslocale
@@ -1842,7 +1836,7 @@ the beginning of the vector.
@c free dup @ascuheap @acsmem
@c kill dup ok
@c free dup @ascuheap @acsmem
-@c parse_param @mtasuconst:@mtsenv @mtslocale @mtsenv @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c parse_param @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c reads from __libc_argc and __libc_argv without guards
@c w_newword dup ok
@c isalpha dup @mtslocale^^
@@ -1860,14 +1854,13 @@ the beginning of the vector.
@c stpcpy dup ok
@c w_addword dup @ascuheap @acsmem
@c strdup dup @ascuheap @acsmem
-@c getenv dup @mtsenv
-@c parse_dollars dup @mtasuconst:@mtsenv @mtslocale @mtsenv @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
-@c parse_tilde dup @mtslocale @mtsenv @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
-@c fnmatch dup @mtsenv @mtslocale @ascuheap @acsmem
+@c parse_dollars dup @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c parse_tilde dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c fnmatch dup @mtslocale @ascuheap @acsmem
@c mempcpy dup ok
@c _ dup @ascuintl
@c fxprintf dup @aculock
-@c setenv dup @mtasuconst:@mtsenv @ascuheap @asulock @acucorrupt @aculock @acsmem
+@c setenv dup @ascuheap @asulock @acucorrupt @aculock @acsmem
@c strspn dup ok
@c strcspn dup ok
@c parse_backtick @ascuplugin @ascuheap @aculock @acsfd @acsmem
@@ -1877,8 +1870,8 @@ the beginning of the vector.
@c parse_qtd_backslash dup @ascuheap @acsmem
@c parse_backslash dup @ascuheap @acsmem
@c w_addchar dup @ascuheap @acsmem
-@c parse_dquote @mtasuconst:@mtsenv @mtslocale @mtsenv @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
-@c parse_dollars dup @mtasuconst:@mtsenv @mtslocale @mtsenv @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c parse_dquote @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c parse_dollars dup @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c parse_backtick dup @ascuplugin @ascuheap @aculock @acsfd @acsmem
@c parse_qtd_backslash dup @ascuheap @acsmem
@c w_addchar dup @ascuheap @acsmem
@@ -1888,10 +1881,9 @@ the beginning of the vector.
@c free dup @ascuheap @acsmem
@c parse_squote dup @ascuheap @acsmem
@c w_addchar dup @ascuheap @acsmem
-@c parse_tilde @mtslocale @mtsenv @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c parse_tilde @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c strchr dup ok
@c w_addchar dup @ascuheap @acsmem
-@c getenv dup @mtsenv
@c w_addstr dup @ascuheap @acsmem
@c strlen dup ok
@c w_addmem dup @ascuheap @acsmem
@@ -1901,17 +1893,17 @@ the beginning of the vector.
@c getuid dup ok
@c getpwuid_r dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c getpwnam_r dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
-@c parse_glob @mtasurace:utent @mtasuconst:@mtsenv @mtsenv @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c parse_glob @mtasurace:utent @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c strchr dup ok
-@c parse_dollars dup @mtasuconst:@mtsenv @mtslocale @mtsenv @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c parse_dollars dup @mtslocale @ascudlopen @ascuplugin @ascuintl @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c parse_qtd_backslash @ascuheap @acsmem
@c w_addchar dup @ascuheap @acsmem
@c parse_backslash dup @ascuheap @acsmem
@c w_addchar dup @ascuheap @acsmem
@c w_addword dup @ascuheap @acsmem
@c w_newword dup ok
-@c do_parse_glob @mtasurace:utent @mtsenv @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @aculock @acsfd @acsmem
-@c glob dup @mtasurace:utent @mtsenv @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @aculock @acsfd @acsmem [auto glob_t avoids @asucorrupt @acucorrupt]
+@c do_parse_glob @mtasurace:utent @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @aculock @acsfd @acsmem
+@c glob dup @mtasurace:utent @mtascusig:ALRM @mtascutimer @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @aculock @acsfd @acsmem [auto glob_t avoids @asucorrupt @acucorrupt]
@c w_addstr dup @ascuheap @acsmem
@c w_addchar dup @ascuheap @acsmem
@c globfree dup @ascuheap @acsmem [auto glob_t avoids @asucorrupt @acucorrupt]
diff --git a/manual/process.texi b/manual/process.texi
index 8254e5ee86..78e355899d 100644
--- a/manual/process.texi
+++ b/manual/process.texi
@@ -477,7 +477,7 @@ the @code{environ} variable.
@deftypefun int execvp (const char *@var{filename}, char *const @var{argv}@t{[]})
@standards{POSIX.1, unistd.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
The @code{execvp} function is similar to @code{execv}, except that it
searches the directories listed in the @code{PATH} environment variable
(@pxref{Standard Environment}) to find the full file name of a
@@ -490,7 +490,7 @@ to run the commands that users type.
@deftypefun int execlp (const char *@var{filename}, const char *@var{arg0}, @dots{})
@standards{POSIX.1, unistd.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
+@safety{@prelim{}@mtsafe{}@asunsafe{@ascuheap{}}@acunsafe{@acsmem{}}}
This function is like @code{execl}, except that it performs the same
file name searching as the @code{execvp} function.
@end deftypefun
diff --git a/manual/socket.texi b/manual/socket.texi
index 8708cbb07c..b46ea147e1 100644
--- a/manual/socket.texi
+++ b/manual/socket.texi
@@ -1287,19 +1287,18 @@ need to save it across calls. You can also use @code{getaddrinfo} and
@deftypefun {struct hostent *} gethostbyname (const char *@var{name})
@standards{BSD, netdb.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:hostbyname} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
-@c gethostbyname @mtasurace:hostbyname @mtsenv @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
+@safety{@prelim{}@mtunsafe{@mtasurace{:hostbyname} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
+@c gethostbyname @mtasurace:hostbyname @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
@c libc_lock_lock dup @asulock @aculock
@c malloc dup @ascuheap @acsmem
-@c nss_hostname_digits_dots @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c res_maybe_init(!preinit) @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c nss_hostname_digits_dots @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c res_maybe_init(!preinit) @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c res_iclose @acsuheap @acsmem @acsfd
@c close_not_cancel_no_status dup @acsfd
@c free dup @acsuheap @acsmem
-@c res_vinit @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c res_vinit @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c res_randomid ok
@c getpid dup ok
-@c getenv dup @mtsenv
@c strncpy dup ok
@c fopen dup @ascuheap @asulock @acsmem @acsfd @aculock
@c fsetlocking dup ok [no concurrent uses]
@@ -1334,8 +1333,8 @@ need to save it across calls. You can also use @code{getaddrinfo} and
@c gethostname dup ok
@c strcpy dup ok
@c rawmemchr dup ok
-@c res_ninit @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c res_vinit dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c res_ninit @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c res_vinit dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c isdigit dup @mtslocale
@c isxdigit dup @mtslocale
@c strlen dup ok
@@ -1347,7 +1346,7 @@ need to save it across calls. You can also use @code{getaddrinfo} and
@c strcpy dup ok
@c memcpy dup ok
@c strchr dup ok
-@c gethostbyname_r dup @mtsenv @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
+@c gethostbyname_r dup @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
@c realloc dup @ascuheap @acsmem
@c free dup @ascuheap @acsmem
@c libc_lock_unlock dup @aculock
@@ -1358,12 +1357,12 @@ named @var{name}. If the lookup fails, it returns a null pointer.
@deftypefun {struct hostent *} gethostbyname2 (const char *@var{name}, int @var{af})
@standards{IPv6 Basic API, netdb.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:hostbyname2} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
-@c gethostbyname2 @mtasurace:hostbyname2 @mtsenv @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
+@safety{@prelim{}@mtunsafe{@mtasurace{:hostbyname2} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
+@c gethostbyname2 @mtasurace:hostbyname2 @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
@c libc_lock_lock dup @asulock @aculock
@c malloc dup @ascuheap @acsmem
-@c nss_hostname_digits_dots dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c gethostbyname2_r dup @mtsenv @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
+@c nss_hostname_digits_dots dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c gethostbyname2_r dup @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
@c realloc dup @ascuheap @acsmem
@c free dup @ascuheap @acsmem
@c libc_lock_unlock dup @aculock
@@ -1375,11 +1374,11 @@ allows the caller to specify the desired address family (e.g.@:
@deftypefun {struct hostent *} gethostbyaddr (const void *@var{addr}, socklen_t @var{length}, int @var{format})
@standards{BSD, netdb.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:hostbyaddr} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
-@c gethostbyaddr @mtasurace:hostbyaddr @mtsenv @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
+@safety{@prelim{}@mtunsafe{@mtasurace{:hostbyaddr} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
+@c gethostbyaddr @mtasurace:hostbyaddr @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
@c libc_lock_lock dup @asulock @aculock
@c malloc dup @ascuheap @acsmem
-@c gethostbyaddr_r dup @mtsenv @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
+@c gethostbyaddr_r dup @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
@c realloc dup @ascuheap @acsmem
@c free dup @ascuheap @acsmem
@c libc_lock_unlock dup @aculock
@@ -1431,12 +1430,11 @@ used in this context.
@deftypefun int gethostbyname_r (const char *restrict @var{name}, struct hostent *restrict @var{result_buf}, char *restrict @var{buf}, size_t @var{buflen}, struct hostent **restrict @var{result}, int *restrict @var{h_errnop})
@standards{GNU, netdb.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
-@c gethostbyname_r @mtsenv @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
-@c nss_hostname_digits_dots dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c nscd_gethostbyname_r @mtsenv @ascuheap @acsfd @acsmem
-@c nscd_gethst_r @mtsenv @ascuheap @acsfd @acsmem
-@c getenv dup @mtsenv
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
+@c gethostbyname_r @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
+@c nss_hostname_digits_dots dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c nscd_gethostbyname_r @ascuheap @acsfd @acsmem
+@c nscd_gethst_r @ascuheap @acsfd @acsmem
@c nscd_get_map_ref dup @ascuheap @acsfd @acsmem
@c nscd_cache_search dup ok
@c memcpy dup ok
@@ -1446,11 +1444,10 @@ used in this context.
@c close_not_cancel_no_status dup @acsfd
@c nscd_drop_map_ref dup @ascuheap @acsmem
@c nscd_unmap dup @ascuheap @acsmem
-@c res_maybe_init(!preinit) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c res_hconf_init @mtsenv @mtslocale @asucorrupt @ascuheap @aculock @acucorrupt @acsmem [no @asuinit:reshconf @acuinit:reshconf, conditionally called]
-@c res_hconf.c:do_init @mtsenv @mtslocale @asucorrupt @ascuheap @aculock @acucorrupt @acsmem
+@c res_maybe_init(!preinit) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c res_hconf_init @mtslocale @asucorrupt @ascuheap @aculock @acucorrupt @acsmem [no @asuinit:reshconf @acuinit:reshconf, conditionally called]
+@c res_hconf.c:do_init @mtslocale @asucorrupt @ascuheap @aculock @acucorrupt @acsmem
@c memset dup ok
-@c getenv dup @mtsenv
@c fopen dup @ascuheap @asulock @acsmem @acsfd @aculock
@c fsetlocking dup ok [no concurrent uses]
@c fgets_unlocked dup ok [no concurrent uses]
@@ -1555,13 +1552,13 @@ gethostname (char *host)
@deftypefun int gethostbyname2_r (const char *@var{name}, int @var{af}, struct hostent *restrict @var{result_buf}, char *restrict @var{buf}, size_t @var{buflen}, struct hostent **restrict @var{result}, int *restrict @var{h_errnop})
@standards{GNU, netdb.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
-@c gethostbyname2_r @mtsenv @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
-@c nss_hostname_digits_dots dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c nscd_gethostbyname2_r @mtsenv @ascuheap @asulock @aculock @acsfd @acsmem
-@c nscd_gethst_r dup @mtsenv @ascuheap @asulock @aculock @acsfd @acsmem
-@c res_maybe_init(!preinit) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c res_hconf_init dup @mtsenv @mtslocale @asucorrupt @ascuheap @aculock @acucorrupt @acsmem [no @asuinit:reshconf @acuinit:reshconf, conditionally called]
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
+@c gethostbyname2_r @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
+@c nss_hostname_digits_dots dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c nscd_gethostbyname2_r @ascuheap @asulock @aculock @acsfd @acsmem
+@c nscd_gethst_r dup @ascuheap @asulock @aculock @acsfd @acsmem
+@c res_maybe_init(!preinit) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c res_hconf_init dup @mtslocale @asucorrupt @ascuheap @aculock @acucorrupt @acsmem [no @asuinit:reshconf @acuinit:reshconf, conditionally called]
@c nss_hosts_lookup2 dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c *fct.l -> _nss_*_gethostbyname2_r @ascuplugin
@c nss_next2 dup @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@@ -1573,13 +1570,13 @@ allows the caller to specify the desired address family (e.g.@:
@deftypefun int gethostbyaddr_r (const void *@var{addr}, socklen_t @var{length}, int @var{format}, struct hostent *restrict @var{result_buf}, char *restrict @var{buf}, size_t @var{buflen}, struct hostent **restrict @var{result}, int *restrict @var{h_errnop})
@standards{GNU, netdb.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
-@c gethostbyaddr_r @mtsenv @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
+@c gethostbyaddr_r @mtslocale @ascudlopen @ascuplugin @asucorrupt @ascuheap @asulock @aculock @acucorrupt @acsmem @acsfd
@c memcmp dup ok
-@c nscd_gethostbyaddr_r @mtsenv @ascuheap @asulock @aculock @acsfd @acsmem
-@c nscd_gethst_r dup @mtsenv @ascuheap @asulock @aculock @acsfd @acsmem
-@c res_maybe_init(!preinit) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c res_hconf_init dup @mtsenv @mtslocale @asucorrupt @ascuheap @aculock @acucorrupt @acsmem [no @asuinit:reshconf @acuinit:reshconf, conditionally called]
+@c nscd_gethostbyaddr_r @ascuheap @asulock @aculock @acsfd @acsmem
+@c nscd_gethst_r dup @ascuheap @asulock @aculock @acsfd @acsmem
+@c res_maybe_init(!preinit) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c res_hconf_init dup @mtslocale @asucorrupt @ascuheap @aculock @acucorrupt @acsmem [no @asuinit:reshconf @acuinit:reshconf, conditionally called]
@c nss_hosts_lookup2 dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c *fct.l -> _nss_*_gethostbyaddr_r @ascuplugin
@c nss_next2 dup @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@@ -1609,11 +1606,11 @@ when using these functions because they are not reentrant.
@deftypefun void sethostent (int @var{stayopen})
@standards{BSD, netdb.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:hostent} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
-@c sethostent @mtasurace:hostent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@safety{@prelim{}@mtunsafe{@mtasurace{:hostent} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@c sethostent @mtasurace:hostent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c libc_lock_lock dup @asulock @aculock
-@c nss_setent(nss_hosts_lookup2) @mtasurace:hostent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
-@c res_maybe_init(!preinit) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c nss_setent(nss_hosts_lookup2) @mtasurace:hostent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c res_maybe_init(!preinit) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c set_h_errno dup ok
@c setup(nss_hosts_lookup2) @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c *lookup_fct = nss_hosts_lookup2 dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@@ -1635,20 +1632,20 @@ reopening the database for each call.
@deftypefun {struct hostent *} gethostent (void)
@standards{BSD, netdb.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:hostent} @mtasurace{:hostentbuf} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
-@c gethostent @mtasurace:hostent @mtasurace:hostentbuf @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@safety{@prelim{}@mtunsafe{@mtasurace{:hostent} @mtasurace{:hostentbuf} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@c gethostent @mtasurace:hostent @mtasurace:hostentbuf @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c libc_lock_lock dup @asulock @aculock
-@c nss_getent(gethostent_r) @mtasurace:hostent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c nss_getent(gethostent_r) @mtasurace:hostent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c malloc dup @ascuheap @acsmem
-@c *func = gethostent_r dup @mtasurace:hostent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c *func = gethostent_r dup @mtasurace:hostent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c realloc dup @ascuheap @acsmem
@c free dup @ascuheap @acsmem
@c libc_lock_unlock dup @aculock
@c
-@c gethostent_r @mtasurace:hostent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c gethostent_r @mtasurace:hostent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c libc_lock_lock dup @asulock @aculock
-@c nss_getent_r(nss_hosts_lookup2) @mtasurace:hostent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
-@c res_maybe_init(!preinit) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c nss_getent_r(nss_hosts_lookup2) @mtasurace:hostent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c res_maybe_init(!preinit) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c setup(nss_hosts_lookup2) dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c *fct.f @mtasurace:hostent @ascuplugin
@c nss_next2 dup @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@@ -1662,11 +1659,11 @@ returns a null pointer if there are no more entries.
@deftypefun void endhostent (void)
@standards{BSD, netdb.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:hostent} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
-@c endhostent @mtasurace:hostent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@safety{@prelim{}@mtunsafe{@mtasurace{:hostent} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@c endhostent @mtasurace:hostent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c libc_lock_lock @asulock @aculock
-@c nss_endent(nss_hosts_lookup2) @mtasurace:hostent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
-@c res_maybe_init(!preinit) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c nss_endent(nss_hosts_lookup2) @mtasurace:hostent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c res_maybe_init(!preinit) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c setup(nss_passwd_lookup2) dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c *fct.f @mtasurace:hostent @ascuplugin
@c nss_next2 dup @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@@ -3526,18 +3523,18 @@ copy the information if you need to save it.
@deftypefun {struct netent *} getnetbyname (const char *@var{name})
@standards{BSD, netdb.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:netbyname} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
-@c getnetbyname =~ getpwuid @mtasurace:netbyname @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@safety{@prelim{}@mtunsafe{@mtasurace{:netbyname} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@c getnetbyname =~ getpwuid @mtasurace:netbyname @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c libc_lock_lock dup @asulock @aculock
@c malloc dup @ascuheap @acsmem
-@c getnetbyname_r dup @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c getnetbyname_r dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c realloc dup @ascuheap @acsmem
@c free dup @ascuheap @acsmem
@c libc_lock_unlock dup @aculock
@c
-@c getnetbyname_r =~ getpwuid_r @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c getnetbyname_r =~ getpwuid_r @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c no nscd support
-@c res_maybe_init(!preinit) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c res_maybe_init(!preinit) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c nss_networks_lookup2 =~ nss_passwd_lookup2 @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c *fct.l -> _nss_*_getnetbyname_r @ascuplugin
@c nss_next2 dup @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@@ -3576,11 +3573,11 @@ functions because they are not reentrant.
@deftypefun void setnetent (int @var{stayopen})
@standards{BSD, netdb.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:netent} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
-@c setnetent @mtasurace:netent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@safety{@prelim{}@mtunsafe{@mtasurace{:netent} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@c setnetent @mtasurace:netent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c libc_lock_lock dup @asulock @aculock
-@c nss_setent(nss_networks_lookup2) @mtasurace:netent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
-@c res_maybe_init(!preinit) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c nss_setent(nss_networks_lookup2) @mtasurace:netent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c res_maybe_init(!preinit) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c setup(nss_networks_lookup2) @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c *lookup_fct = nss_networks_lookup2 dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c nss_lookup dup @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@@ -3598,20 +3595,20 @@ reopening the database for each call.
@deftypefun {struct netent *} getnetent (void)
@standards{BSD, netdb.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:netent} @mtasurace{:netentbuf} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
-@c getnetent @mtasurace:netent @mtasurace:netentbuf @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@safety{@prelim{}@mtunsafe{@mtasurace{:netent} @mtasurace{:netentbuf} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@c getnetent @mtasurace:netent @mtasurace:netentbuf @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c libc_lock_lock dup @asulock @aculock
-@c nss_getent(getnetent_r) @mtasurace:netent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c nss_getent(getnetent_r) @mtasurace:netent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c malloc dup @ascuheap @acsmem
-@c *func = getnetent_r dup @mtasurace:netent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c *func = getnetent_r dup @mtasurace:netent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c realloc dup @ascuheap @acsmem
@c free dup @ascuheap @acsmem
@c libc_lock_unlock dup @aculock
@c
-@c getnetent_r @mtasurace:netent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c getnetent_r @mtasurace:netent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c libc_lock_lock dup @asulock @aculock
-@c nss_getent_r(nss_networks_lookup2) @mtasurace:netent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
-@c res_maybe_init(!preinit) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c nss_getent_r(nss_networks_lookup2) @mtasurace:netent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c res_maybe_init(!preinit) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c setup(nss_networks_lookup2) dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c *fct.f @mtasurace:servent @ascuplugin
@c nss_next2 dup @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@@ -3624,11 +3621,11 @@ returns a null pointer if there are no more entries.
@deftypefun void endnetent (void)
@standards{BSD, netdb.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:netent} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
-@c endnetent @mtasurace:netent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@safety{@prelim{}@mtunsafe{@mtasurace{:netent} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsfd{} @acsmem{}}}
+@c endnetent @mtasurace:netent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c libc_lock_lock @asulock @aculock
-@c nss_endent(nss_networks_lookup2) @mtasurace:netent @mtsenv @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
-@c res_maybe_init(!preinit) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c nss_endent(nss_networks_lookup2) @mtasurace:netent @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
+@c res_maybe_init(!preinit) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c setup(nss_networks_lookup2) dup @mtslocale @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
@c *fct.f @mtasurace:netent @ascuplugin
@c nss_next2 dup @ascudlopen @ascuplugin @ascuheap @asulock @acucorrupt @aculock @acsfd @acsmem
diff --git a/manual/startup.texi b/manual/startup.texi
index 747beed4d9..8fd8754208 100644
--- a/manual/startup.texi
+++ b/manual/startup.texi
@@ -92,9 +92,14 @@ int main (int @var{argc}, char *@var{argv}[], char *@var{envp}[])
The first two arguments are just the same. The third argument
@var{envp} gives the program's environment; it is the same as the value
-of @code{environ}. @xref{Environment Variables}. POSIX.1 does not
-allow this three-argument form, so to be portable it is best to write
-@code{main} to take two arguments, and use the value of @code{environ}.
+of @code{environ}. The @var{envp} array is subject to the same
+restrictions as the @code{environ} variable. @xref{Environment
+Variables}.
+
+POSIX.1 does not allow this three-argument form, so to be portable it is
+best to write @code{main} to take two arguments, and use the value of
+@code{environ}.
+
@menu
* Argument Syntax:: By convention, options start with a hyphen.
@@ -326,20 +331,31 @@ functions can be safely used in multi-threaded programs.
@deftypefun {char *} getenv (const char *@var{name})
@standards{ISO, stdlib.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@assafe{}@acsafe{}}
-@c Unguarded access to __environ.
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
This function returns a string that is the value of the environment
variable @var{name}. You must not modify this string. In some non-Unix
systems not using @theglibc{}, it might be overwritten by subsequent
calls to @code{getenv} (but not by any other library function). If the
environment variable @var{name} is not defined, the value is a null
pointer.
+
+In @theglibc{} implementation, if the variable was last set using the
+@code{setenv} function, the string returned by @code{getenv} will not
+change or be deallocated even if the environment is subsequently
+modified using @code{setenv}, @code{putenv}, or @code{unsetenv}. If the
+variable was set using @code{putenv} or with a direct @code{environ}
+array update, @code{getenv} returns a pointer into the
+application-supplied string, and the behavior depends on what the
+application does with that string.
+
+While the @code{getenv} function is thread-safe, threads may not observe
+environment variable updates in the same order as they happened on other
+threads.
@end deftypefun
@deftypefun {char *} secure_getenv (const char *@var{name})
@standards{GNU, stdlib.h}
-@safety{@prelim{}@mtsafe{@mtsenv{}}@assafe{}@acsafe{}}
-@c Calls getenv unless secure mode is enabled.
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
This function is similar to @code{getenv}, but it returns a null
pointer if the environment is untrusted. This happens when the
program file has SUID or SGID bits set. General-purpose libraries
@@ -352,13 +368,13 @@ This function is a GNU extension.
@deftypefun int putenv (char *@var{string})
@standards{SVID, stdlib.h}
-@safety{@prelim{}@mtunsafe{@mtasuconst{:@mtsenv{}}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{}}}
-@c putenv @mtasuconst:@mtsenv @ascuheap @asulock @acucorrupt @aculock @acsmem
+@safety{@mtsafe{}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{}}}
+@c putenv @ascuheap @asulock @acucorrupt @aculock @acsmem
@c strchr dup ok
@c strndup dup @ascuheap @acsmem
-@c add_to_environ dup @mtasuconst:@mtsenv @ascuheap @asulock @acucorrupt @aculock @acsmem
+@c add_to_environ dup @ascuheap @asulock @acucorrupt @aculock @acsmem
@c free dup @ascuheap @acsmem
-@c unsetenv dup @mtasuconst:@mtsenv @asulock @aculock
+@c unsetenv dup @asulock @aculock
The @code{putenv} function adds or removes definitions from the environment.
If the @var{string} is of the form @samp{@var{name}=@var{value}}, the
definition is added to the environment. Otherwise, the @var{string} is
@@ -376,6 +392,12 @@ reflect automatically in the environment. This also requires that
variable is removed from the environment. The same applies of course to
dynamically allocated variables which are freed later.
+Modification of a string installed into the environment using
+@code{putenv} can impact the thread-safety and async-signal-safety of
+@code{getenv}. Simple modifications of an environment variable, for
+example single-byte changes to the value part (such as changing a
+@samp{0} to a @samp{1}), do not impact safety.
+
This function is part of the extended Unix interface. You should define
@var{_XOPEN_SOURCE} before including any header.
@end deftypefun
@@ -383,9 +405,9 @@ This function is part of the extended Unix interface. You should define
@deftypefun int setenv (const char *@var{name}, const char *@var{value}, int @var{replace})
@standards{BSD, stdlib.h}
-@safety{@prelim{}@mtunsafe{@mtasuconst{:@mtsenv{}}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{}}}
-@c setenv @mtasuconst:@mtsenv @ascuheap @asulock @acucorrupt @aculock @acsmem
-@c add_to_environ @mtasuconst:@mtsenv @ascuheap @asulock @acucorrupt @aculock @acsmem
+@safety{@prelim{}@mtsafe{}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{}}}
+@c setenv @ascuheap @asulock @acucorrupt @aculock @acsmem
+@c add_to_environ @ascuheap @asulock @acucorrupt @aculock @acsmem
@c strlen dup ok
@c libc_lock_lock @asulock @aculock
@c strncmp dup ok
@@ -423,8 +445,8 @@ the Unix standard.
@deftypefun int unsetenv (const char *@var{name})
@standards{BSD, stdlib.h}
-@safety{@prelim{}@mtunsafe{@mtasuconst{:@mtsenv{}}}@asunsafe{@asulock{}}@acunsafe{@aculock{}}}
-@c unsetenv @mtasuconst:@mtsenv @asulock @aculock
+@safety{@prelim{}@mtsafe{}@asunsafe{@asulock{}}@acunsafe{@aculock{}}}
+@c unsetenv @asulock @aculock
@c strchr dup ok
@c strlen dup ok
@c libc_lock_lock @asulock @aculock
@@ -440,6 +462,15 @@ The function returns @code{-1} if @var{name} is a null pointer, points to
an empty string, or points to a string containing a @code{=} character.
It returns @code{0} if the call succeeded.
+In @theglibc{} implementation, this function does not actually
+deallocate the storage for the environment variable. This ensures that
+@code{getenv} is thread-safe and the returned strings can be used
+without restriction (unless @code{putenv} was used to set these
+variables). If storage for changed environment variables needs to be
+reclaimed, applications must use @code{putenv} to set environment
+variables and manage storage reclamation themselves. Storage for
+variables set using @code{setenv} cannot be reclaimed.
+
This function was originally part of the BSD library but is now part of
the Unix standard. The BSD version had no return value, though.
@end deftypefun
@@ -452,11 +483,7 @@ to enable writing standard compliant Fortran environments.
@deftypefun int clearenv (void)
@standards{GNU, stdlib.h}
-@safety{@prelim{}@mtunsafe{@mtasuconst{:@mtsenv{}}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{}}}
-@c clearenv @mtasuconst:@mtsenv @ascuheap @asulock @aculock @acsmem
-@c libc_lock_lock @asulock @aculock
-@c free dup @ascuheap @acsmem
-@c libc_lock_unlock @aculock
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
The @code{clearenv} function removes all entries from the environment.
Using @code{putenv} and @code{setenv} new entries can be added again
later.
@@ -479,10 +506,61 @@ strings appear in the environment is not significant, but the same
@var{name} must not appear more than once. The last element of the
array is a null pointer.
-This variable is declared in the header file @file{unistd.h}.
+The @code{environ} variable is defined by POSIX, but the declaration in
+@file{unistd.h} is a GNU extension.
If you just want to get the value of an environment variable, use
@code{getenv}.
+
+Direct writes to @code{environ} can impact the thread safety and
+async-signal-safety of functionality that uses the process environment.
+It is recommended to update @code{environ} using the functions
+@code{setenv}, @code{putenv}, @code{unsetenv}, @code{clearenv} only, as
+described above.
+
+The @code{environ} array can be passed to functions in the @code{execve}
+and @code{posix_spawn} families. @xref{Executing a File}.
+
+As a GNU extension, if @code{environ} is used with these execution
+functions, @theglibc{} ensures that a copy of the environment variables
+is passed to the subprocess, without data races, even if the environment
+is concurrently modified using the environment-updating functions
+describe above.
+
+@strong{Note:} If the @code{environ} pointer is passed to
+@code{execve}-style functions in multi-threaded programs, these
+functions may need additional stack space to store a shallow copy of the
+@code{environ} array. This does not apply to single-threaded programs
+or the functions in @code{posix_spawn} family.
+
+In this context, @emph{concurrently modified} means that the invocations
+of the environment-modifying functions are not in a happens-before
+relationship with the @code{execve} or @code{posix_spawn} calls. The
+copy of the @code{environ} array passed to the new process has the
+following properties:
+
+@itemize @bullet
+@item
+If an environment variable is not modified or unset by the concurrent
+calls, it retains its value.
+
+@item
+If a variable is unset by concurrent calls, it is unspecified whether it
+is present in the copy.
+
+@item
+If a variable is set by concurrent calls, it is unspecified whether it
+retains its original value (if any), or one of the set values.
+
+@item
+Variables are not duplicated if the original array did not contain any
+duplicates.
+
+@item
+Even if the variable modifications obey a happens-before relationship
+among themselves, the copy may not correspond to any intermediate state
+consistent with that happens-before relationship.
+@end itemize
@end deftypevar
Unix systems, and @gnusystems{}, pass the initial value of
diff --git a/manual/sysinfo.texi b/manual/sysinfo.texi
index 1ca77adc7e..2b1be40cbe 100644
--- a/manual/sysinfo.texi
+++ b/manual/sysinfo.texi
@@ -177,7 +177,7 @@ The specifics of this function are analogous to @code{sethostname}, above.
@deftypefun {long int} gethostid (void)
@standards{BSD, unistd.h}
-@safety{@prelim{}@mtsafe{@mtshostid{} @mtsenv{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
+@safety{@prelim{}@mtsafe{@mtshostid{} @mtslocale{}}@asunsafe{@ascudlopen{} @ascuplugin{} @asucorrupt{} @ascuheap{} @asulock{}}@acunsafe{@aculock{} @acucorrupt{} @acsmem{} @acsfd{}}}
@c On HURD, calls _hurd_get_host_config and strtol. On Linux, open
@c HOSTIDFILE, reads an int32_t and closes; if that fails, it calls
@c gethostname and gethostbyname_r to use the h_addr.
diff --git a/manual/syslog.texi b/manual/syslog.texi
index 02f84d6e6f..0e71fc0677 100644
--- a/manual/syslog.texi
+++ b/manual/syslog.texi
@@ -285,11 +285,11 @@ The symbols referred to in this section are declared in the file
@c syslog() is implemented as a call to vsyslog().
@deftypefun void syslog (int @var{facility_priority}, const char *@var{format}, @dots{})
@standards{BSD, syslog.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
-@c syslog @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
+@c syslog @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
@c va_start dup ok
-@c vsyslog_chk @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
-@c syslog(INTERNALLOG) dup @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@c vsyslog_chk @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@c syslog(INTERNALLOG) dup @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
@c open_memstream @ascuheap @acsmem
@c stpcpy dup ok
@c getpid dup ok
@@ -297,8 +297,8 @@ The symbols referred to in this section are declared in the file
@c fsetlocking [no @mtasurace:stream @asulock for exclusive stream]
@c fprintf @mtslocale @ascuheap @acsmem [no @asucorrupt @aculock @acucorrupt on temp memstream]
@c time dup ok
-@c localtime_r dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c strftime_l(C) dup @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@c localtime_r dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c strftime_l(C) dup @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
@c ftell dup ok [no @asucorrupt @aculock @acucorrupt on temp memstream]
@c fputs_unlocked dup ok [no @mtasurace:stream @asucorrupt @acucorrupt on temp memstream]
@c putc_unlocked dup ok [no @mtasurace:stream @asucorrupt @acucorrupt on temp memstream]
@@ -444,9 +444,9 @@ syslog (LOG_MAKEPRI(LOG_LOCAL1, LOG_ERROR),
@deftypefun void vsyslog (int @var{facility_priority}, const char *@var{format}, va_list @var{arglist})
@standards{BSD, syslog.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
-@c vsyslog @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
-@c vsyslog_chk dup @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
+@c vsyslog @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@c vsyslog_chk dup @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
This is functionally identical to @code{syslog}, with the BSD style variable
length argument.
diff --git a/manual/time.texi b/manual/time.texi
index 6b1080db06..03b400646d 100644
--- a/manual/time.texi
+++ b/manual/time.texi
@@ -1100,10 +1100,10 @@ has a geographical format. @xref{Time Zone State}.
@deftypefun {struct tm *} localtime (const time_t *@var{time})
@standards{ISO, time.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:tmbuf} @mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@safety{@prelim{}@mtunsafe{@mtasurace{:tmbuf} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
@c Calls tz_convert with a static buffer.
-@c localtime @mtasurace:tmbuf @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c tz_convert dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c localtime @mtasurace:tmbuf @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tz_convert dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
The @code{localtime} function converts the simple time pointed to by
@var{time} to broken-down time representation, expressed relative to the
user's specified time zone.
@@ -1130,24 +1130,22 @@ all threads. A variant function avoids this problem.
@deftypefun {struct tm *} localtime_r (const time_t *@var{time}, struct tm *@var{resultp})
@standards{POSIX.1c, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
-@c localtime_r @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c tz_convert(use_localtime) @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@c localtime_r @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tz_convert(use_localtime) @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c libc_lock_lock dup @asulock @aculock
-@c tzset_internal @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzset_internal @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c always called with tzset_lock held
@c sets static is_initialized before initialization;
@c reads and sets old_tz; sets tz_rules.
@c some of the issues only apply on the first call.
@c subsequent calls only trigger these when called by localtime;
@c otherwise, they're ok.
-@c getenv dup @mtsenv
@c strcmp dup ok
@c strdup @ascuheap
-@c tzfile_read @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzfile_read @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c memcmp dup ok
@c strstr dup ok
-@c getenv dup @mtsenv
@c asprintf dup @mtslocale @ascuheap @acsmem
@c stat64 dup ok
@c fopen dup @ascuheap @asulock @acsmem @acsfd @aculock
@@ -1176,9 +1174,8 @@ all threads. A variant function avoids this problem.
@c double-underscore-prefixed name, this interface violation could
@c be regarded as undefined behavior.
@c strlen ok
-@c tzset_parse_tz @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzset_parse_tz @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c sscanf dup @mtslocale @ascuheap @acsmem
-@c isalnum dup @mtsenv
@c tzstring @ascuheap @acsmem
@c reads and changes tzstring_list without synchronization, but
@c only called with tzset_lock held (save for interface violations)
@@ -1187,20 +1184,20 @@ all threads. A variant function avoids this problem.
@c strcpy dup ok
@c isdigit dup @mtslocale
@c compute_offset ok
-@c tzfile_default @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzfile_default @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c sets tzname, timezone, types, zone_names, rule_*off, etc; no guards
@c strlen dup ok
-@c tzfile_read dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzfile_read dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c mempcpy dup ok
@c compute_tzname_max ok [if guarded by tzset_lock]
@c iterates over zone_names; no guards
@c free dup @ascuheap @acsmem
@c strtoul dup @mtslocale
@c update_vars dup ok
-@c tzfile_compute(use_localtime) @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzfile_compute(use_localtime) @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c sets tzname; no guards. with !use_localtime, as in gmtime, it's ok
@c tzstring dup @acsuheap @acsmem
-@c tzset_parse_tz dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzset_parse_tz dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c offtime dup ok
@c tz_compute dup ok
@c strcmp dup ok
@@ -1227,9 +1224,9 @@ object the result was written into, i.e., it returns @var{resultp}.
@deftypefun {struct tm *} gmtime (const time_t *@var{time})
@standards{ISO, time.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:tmbuf} @mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
-@c gmtime @mtasurace:tmbuf @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c tz_convert dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@safety{@prelim{}@mtunsafe{@mtasurace{:tmbuf} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@c gmtime @mtasurace:tmbuf @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tz_convert dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
This function is similar to @code{localtime}, except that the broken-down
time is expressed as UTC rather than relative to a local time zone.
The broken-down time's @code{tm_gmtoff} is 0, and its
@@ -1243,15 +1240,15 @@ is placed in a static variable. A thread-safe replacement is also provided for
@deftypefun {struct tm *} gmtime_r (const time_t *@var{time}, struct tm *@var{resultp})
@standards{POSIX.1c, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
@c You'd think tz_convert could avoid some safety issues with
@c !use_localtime, but no such luck: tzset_internal will always bring
@c about all possible AS and AC problems when it's first called.
@c Calling any of localtime,gmtime_r once would run the initialization
@c and avoid the heap, mem and fd issues in gmtime* in subsequent calls,
@c but the unsafe locking would remain.
-@c gmtime_r @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c tz_convert(gmtime_r) dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c gmtime_r @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tz_convert(gmtime_r) dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
This function is similar to @code{localtime_r}, except that it converts
just like @code{gmtime} the given time as UTC.
@@ -1262,8 +1259,8 @@ object the result was written into, i.e., it returns @var{resultp}.
@deftypefun time_t mktime (struct tm *@var{brokentime})
@standards{ISO, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
-@c mktime @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@c mktime @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c passes a static localtime_offset to mktime_internal; it is read
@c once, used as an initial guess, and updated at the end, but not
@c used except as a guess for subsequent calls, so it should be safe.
@@ -1273,11 +1270,11 @@ object the result was written into, i.e., it returns @var{resultp}.
@c call to an external function, and a conditional change of the
@c variable before the external call, so refraining from allocating a
@c local variable at the first load would be a very bad optimization.
-@c tzset dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c mktime_internal(localtime_r) @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzset dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c mktime_internal(localtime_r) @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c ydhms_diff ok
-@c ranged_convert(localtime_r) @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c *convert = localtime_r dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c ranged_convert(localtime_r) @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c *convert = localtime_r dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c time_t_avg dup ok
@c guess_time_tm dup ok
@c ydhms_diff dup ok
@@ -1311,7 +1308,7 @@ members. @xref{Time Zone State}.
@deftypefun time_t timelocal (struct tm *@var{brokentime})
@standards{???, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
@c Alias to mktime.
@code{timelocal} is functionally identical to @code{mktime}, but more
@@ -1325,13 +1322,13 @@ available. @code{timelocal} is rather rare.
@deftypefun time_t timegm (struct tm *@var{brokentime})
@standards{ISO, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
-@c timegm @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@c timegm @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c gmtime_offset triggers the same caveats as localtime_offset in mktime.
@c although gmtime_r, as called by mktime, might save some issues,
@c tzset calls tzset_internal with always, which forces
@c reinitialization, so all issues may arise.
-@c tzset dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzset dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c mktime_internal(gmtime_r) @asulock @aculock
@c ..gmtime_r @asulock @aculock
@c ... dup ok
@@ -1364,10 +1361,10 @@ strings. These functions are declared in the header file @file{time.h}.
@deftypefun size_t strftime (char *@var{s}, size_t @var{size}, const char *@var{template}, const struct tm *@var{brokentime})
@standards{ISO, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
-@c strftime @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
-@c strftime_l @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
-@c strftime_internal @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
+@c strftime @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@c strftime_l @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@c strftime_internal @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
@c add ok
@c memset_zero dup ok
@c memset_space dup ok
@@ -1387,7 +1384,7 @@ strings. These functions are declared in the header file @file{time.h}.
@c ISDIGIT ok
@c STRLEN ok
@c strlen dup ok
-@c strftime_internal dup @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@c strftime_internal dup @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
@c TOUPPER dup ok
@c nl_get_era_entry @ascuheap @asulock @acsmem @aculock
@c nl_init_era_entries @ascuheap @asulock @acsmem @aculock
@@ -1414,12 +1411,12 @@ strings. These functions are declared in the header file @file{time.h}.
@c memset dup ok
@c memset_zero ok
@c memset dup ok
-@c mktime dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c mktime dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c iso_week_days ok
@c isleap ok
-@c tzset dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c localtime_r dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c gmtime_r dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzset dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c localtime_r dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c gmtime_r dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c tm_diff ok
This function is similar to the @code{sprintf} function (@pxref{Formatted
Input}), but the conversion specifications that can appear in the format
@@ -1750,7 +1747,7 @@ For an example of @code{strftime}, see @ref{Time Functions Example}.
@deftypefun size_t strftime_l (char *restrict @var{s}, size_t @var{size}, const char *restrict @var{template}, const struct tm *@var{brokentime}, locale_t @var{locale})
@standards{POSIX.1, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
The @code{strftime_l} function is equivalent to the @code{strftime}
function except that it operates in @var{locale} rather than in
the current locale.
@@ -1758,10 +1755,10 @@ the current locale.
@deftypefun size_t wcsftime (wchar_t *@var{s}, size_t @var{size}, const wchar_t *@var{template}, const struct tm *@var{brokentime})
@standards{ISO, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
-@c wcsftime @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
-@c wcsftime_l @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
-@c wcsftime_internal @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@asucorrupt{} @ascuheap{} @asulock{} @ascudlopen{}}@acunsafe{@acucorrupt{} @aculock{} @acsmem{} @acsfd{}}}
+@c wcsftime @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@c wcsftime_l @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@c wcsftime_internal @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
@c add ok
@c memset_zero dup ok
@c memset_space dup ok
@@ -1782,7 +1779,7 @@ the current locale.
@c ISDIGIT ok
@c STRLEN ok
@c wcslen dup ok
-@c wcsftime_internal dup @mtsenv @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
+@c wcsftime_internal dup @mtslocale @asucorrupt @ascuheap @asulock @ascudlopen @acucorrupt @aculock @acsmem @acsfd
@c TOUPPER dup ok
@c nl_get_era_entry dup @ascuheap @asulock @acsmem @aculock
@c DO_NUMBER ok
@@ -1798,12 +1795,12 @@ the current locale.
@c wmemset dup ok
@c memset_zero ok
@c wmemset dup ok
-@c mktime dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c mktime dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c iso_week_days ok
@c isleap ok
-@c tzset dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c localtime_r dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c gmtime_r dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzset dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c localtime_r dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c gmtime_r dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c tm_diff ok
The @code{wcsftime} function is equivalent to the @code{strftime}
function with the difference that it operates on wide character
@@ -1886,9 +1883,9 @@ Programs should instead use @code{strftime} or even @code{sprintf}.
@deftypefun {Deprecated function} {char *} ctime (const time_t *@var{time})
@standards{ISO, time.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:tmbuf} @mtasurace{:asctime} @mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
-@c ctime @mtasurace:tmbuf @mtasurace:asctime @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c localtime dup @mtasurace:tmbuf @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@safety{@prelim{}@mtunsafe{@mtasurace{:tmbuf} @mtasurace{:asctime} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@c ctime @mtasurace:tmbuf @mtasurace:asctime @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c localtime dup @mtasurace:tmbuf @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c asctime dup @mtasurace:asctime @mtslocale
The @code{ctime} function is similar to @code{asctime}, except that you
specify the calendar time argument as a @code{time_t} simple time value
@@ -1911,9 +1908,9 @@ Programs should instead use @code{strftime} or even @code{sprintf}.
@deftypefun {Deprecated function} {char *} ctime_r (const time_t *@var{time}, char *@var{buffer})
@standards{???, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
-@c ctime_r @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c localtime_r dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@c ctime_r @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c localtime_r dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c asctime_r dup @mtslocale
This function is similar to @code{ctime}, but places the result in the
string pointed to by @var{buffer}, and the time zone state is not
@@ -1962,9 +1959,9 @@ which is defined and implemented in terms of calls to @code{strptime}.
@deftypefun {char *} strptime (const char *@var{s}, const char *@var{fmt}, struct tm *@var{tp})
@standards{XPG4, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
-@c strptime @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c strptime_internal @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@c strptime @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c strptime_internal @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c memset dup ok
@c ISSPACE ok
@c isspace_l dup ok
@@ -1973,11 +1970,11 @@ which is defined and implemented in terms of calls to @code{strptime}.
@c strlen dup ok
@c strncasecmp_l dup ok
@c strcmp dup ok
-@c recursive @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c strptime_internal dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c recursive @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c strptime_internal dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c get_number ok
@c ISSPACE dup ok
-@c localtime_r dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c localtime_r dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c nl_select_era_entry @ascuheap @asulock @acsmem @aculock
@c nl_init_era_entries dup @ascuheap @asulock @acsmem @aculock
@c get_alt_number dup @ascuheap @asulock @acsmem @aculock
@@ -2406,9 +2403,9 @@ in a @code{time_t} variable.
@deftypefun {struct tm *} getdate (const char *@var{string})
@standards{Unix98, time.h}
-@safety{@prelim{}@mtunsafe{@mtasurace{:getdate} @mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
-@c getdate @mtasurace:getdate @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c getdate_r dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@safety{@prelim{}@mtunsafe{@mtasurace{:getdate} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@c getdate @mtasurace:getdate @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c getdate_r dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
The interface to @code{getdate} is the simplest possible for a function
to parse a string and return the value. @var{string} is the input
string and the result is returned in a statically-allocated variable.
@@ -2519,9 +2516,8 @@ any arbitrary file and chances are high that with some bogus input
@deftypefun int getdate_r (const char *@var{string}, struct tm *@var{tp})
@standards{GNU, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
-@c getdate_r @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c getenv dup @mtsenv
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@c getdate_r @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c stat64 dup ok
@c access dup ok
@c fopen dup @ascuheap @asulock @acsmem @acsfd @aculock
@@ -2532,17 +2528,17 @@ any arbitrary file and chances are high that with some bogus input
@c fclose dup @ascuheap @asulock @aculock @acsmem @acsfd
@c memcpy dup ok
@c getline dup @ascuheap @acsmem [no @asucorrupt @aculock @acucorrupt, exclusive]
-@c strptime dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c strptime dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c feof_unlocked dup ok
@c free dup @ascuheap @acsmem
@c ferror_unlocked dup dup ok
@c time dup ok
-@c localtime_r dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
-@c first_wday @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c localtime_r dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c first_wday @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c memset dup ok
-@c mktime dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c mktime dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c check_mday ok
-@c mktime dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c mktime dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
The @code{getdate_r} function is the reentrant counterpart of
@code{getdate}. It does not use the global variable @code{getdate_err}
to signal an error, but instead returns an error code. The same error
@@ -2815,10 +2811,10 @@ Programs should instead use the @code{tm_gmtoff} and
@deftypefun void tzset (void)
@standards{POSIX.1, time.h}
-@safety{@prelim{}@mtsafe{@mtsenv{} @mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
-@c tzset @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@safety{@prelim{}@mtsafe{@mtslocale{}}@asunsafe{@ascuheap{} @asulock{}}@acunsafe{@aculock{} @acsmem{} @acsfd{}}}
+@c tzset @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c libc_lock_lock dup @asulock @aculock
-@c tzset_internal dup @mtsenv @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
+@c tzset_internal dup @mtslocale @ascuheap @asulock @aculock @acsmem @acsfd
@c libc_lock_unlock dup @aculock
The @code{tzset} function initializes the state variables from
the value of the @env{TZ} environment variable.
--
2.45.2
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 02/13] Linux: Assume __NR_execveat is defined in fexecve
2024-07-28 19:02 ` [PATCH v2 02/13] Linux: Assume __NR_execveat is defined in fexecve Florian Weimer
@ 2024-10-15 19:15 ` Adhemerval Zanella Netto
0 siblings, 0 replies; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-10-15 19:15 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
On 28/07/24 16:02, Florian Weimer wrote:
> It is available on all architectures.
LGTM, thanks.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> ---
> sysdeps/unix/sysv/linux/fexecve.c | 4 +---
> 1 file changed, 1 insertion(+), 3 deletions(-)
>
> diff --git a/sysdeps/unix/sysv/linux/fexecve.c b/sysdeps/unix/sysv/linux/fexecve.c
> index a62a757ec3..e0e5566fd7 100644
> --- a/sysdeps/unix/sysv/linux/fexecve.c
> +++ b/sysdeps/unix/sysv/linux/fexecve.c
> @@ -39,13 +39,11 @@ fexecve (int fd, char *const argv[], char *const envp[])
> return -1;
> }
>
> -#ifdef __NR_execveat
> /* Avoid implicit array coercion in syscall macros. */
> INLINE_SYSCALL (execveat, 5, fd, "", &argv[0], &envp[0], AT_EMPTY_PATH);
> -# ifndef __ASSUME_EXECVEAT
> +#ifndef __ASSUME_EXECVEAT
> if (errno != ENOSYS)
> return -1;
> -# endif
> #endif
>
> #ifndef __ASSUME_EXECVEAT
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 03/13] Linux: Introduce __do_fexecve in fexecve
2024-07-28 19:02 ` [PATCH v2 03/13] Linux: Introduce __do_fexecve " Florian Weimer
@ 2024-10-15 19:51 ` Adhemerval Zanella Netto
0 siblings, 0 replies; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-10-15 19:51 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
On 28/07/24 16:02, Florian Weimer wrote:
> Call the execve system call directly, in preparation of
> wrapping environ access in the *execve* family of function.
>
> Make handling of errno more explicit on the fallback path
> by using INTERNAL_SYSCALL_CALL.
LGTM, thanks.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> ---
> sysdeps/unix/sysv/linux/fexecve.c | 53 ++++++++++++++++---------------
> 1 file changed, 28 insertions(+), 25 deletions(-)
>
> diff --git a/sysdeps/unix/sysv/linux/fexecve.c b/sysdeps/unix/sysv/linux/fexecve.c
> index e0e5566fd7..46c8170092 100644
> --- a/sysdeps/unix/sysv/linux/fexecve.c
> +++ b/sysdeps/unix/sysv/linux/fexecve.c
> @@ -27,6 +27,33 @@
> #include <sys/syscall.h>
> #include <kernel-features.h>
>
> +static int
> +__do_fexecve (int fd, char *const argv[], char *const envp[])
> +{
> + /* Avoid implicit array coercion in syscall macros. */
> + int err = -INTERNAL_SYSCALL_CALL (execveat, fd, "", &argv[0], &envp[0],
> + AT_EMPTY_PATH);
> +
> +#ifndef __ASSUME_EXECVEAT
> + if (err == ENOSYS)
> + {
> + /* We use the /proc filesystem to get the information. If it is not
> + mounted we fail. We do not need the return value. */
> + struct fd_to_filename filename;
> + const char *path = __fd_to_filename (fd, &filename);
> + err = -INTERNAL_SYSCALL_CALL (execve, path, &argv[0], &envp[0]);
> +
> + /* We come here only if the 'execve' call fails. Determine whether
> + /proc is mounted. If not we return ENOSYS. */
> + if (INTERNAL_SYSCALL_CALL (faccessat, AT_FDCWD, "/proc/self/fd", 0)
> + == -ENOENT)
> + err = ENOSYS;
Ok, replace stat with faccessat (I had to remind myself that only faccessat2
accepts a flag).
> + }
> +#endif /* __ASSUME_EXECVEAT */
> +
> + __set_errno (err);
> + return -1;
> +}
>
> /* Execute the file FD refers to, overlaying the running program image.
> ARGV and ENVP are passed to the new program, as for `execve'. */
> @@ -39,29 +66,5 @@ fexecve (int fd, char *const argv[], char *const envp[])
> return -1;
> }
>
> - /* Avoid implicit array coercion in syscall macros. */
> - INLINE_SYSCALL (execveat, 5, fd, "", &argv[0], &envp[0], AT_EMPTY_PATH);
> -#ifndef __ASSUME_EXECVEAT
> - if (errno != ENOSYS)
> - return -1;
> -#endif
> -
> -#ifndef __ASSUME_EXECVEAT
> - /* We use the /proc filesystem to get the information. If it is not
> - mounted we fail. We do not need the return value. */
> - struct fd_to_filename filename;
> - __execve (__fd_to_filename (fd, &filename), argv, envp);
> -
> - int save = errno;
> -
> - /* We come here only if the 'execve' call fails. Determine whether
> - /proc is mounted. If not we return ENOSYS. */
> - struct __stat64_t64 st;
> - if (__stat64_time64 ("/proc/self/fd", &st) != 0 && errno == ENOENT)
> - save = ENOSYS;
> -
> - __set_errno (save);
> -#endif
> -
> - return -1;
> + return __do_fexecve (fd, argv, envp);
> }
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 04/13] Linux: Error handling fixes for fexecve
2024-07-28 19:02 ` [PATCH v2 04/13] Linux: Error handling fixes for fexecve Florian Weimer
@ 2024-10-15 19:57 ` Adhemerval Zanella Netto
0 siblings, 0 replies; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-10-15 19:57 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
On 28/07/24 16:02, Florian Weimer wrote:
> Do not check argv and envp for null, for consistency with
> execveat and execve. An invalid descriptor should result in
> EBADF, not EINVAL.
>
> In the fallback code, if the file descriptor is not valid,
> return EBADF, not ENOENT.
LGTM, thanks.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> ---
> posix/tst-fexecve.c | 14 ++++++++++++++
> sysdeps/unix/sysv/linux/fexecve.c | 20 +++++++++++++++-----
> 2 files changed, 29 insertions(+), 5 deletions(-)
>
> diff --git a/posix/tst-fexecve.c b/posix/tst-fexecve.c
> index 9c528fe8d9..c8e772afb6 100644
> --- a/posix/tst-fexecve.c
> +++ b/posix/tst-fexecve.c
> @@ -82,6 +82,20 @@ do_test (void)
> close (fd);
> #endif
>
> + /* Now fd is not open, so fexecve is expected to fail with EBADF. */
> + static const char *const argv[] = {
> + "/bin/sh", "-c", "false", NULL
> + };
> + errno = -1;
> + TEST_COMPARE (fexecve (fd, (char **const) argv, environ), -1);
> + TEST_COMPARE (errno, EBADF);
> +
> + /* Likewise for negative descriptors. */
> + TEST_VERIFY (AT_FDCWD != -1);
> + errno = -1;
> + TEST_COMPARE (fexecve (-1, (char **const) argv, environ), -1);
> + TEST_COMPARE (errno, EBADF);
> +
> return ret;
> }
>
> diff --git a/sysdeps/unix/sysv/linux/fexecve.c b/sysdeps/unix/sysv/linux/fexecve.c
> index 46c8170092..c2303735de 100644
> --- a/sysdeps/unix/sysv/linux/fexecve.c
> +++ b/sysdeps/unix/sysv/linux/fexecve.c
> @@ -45,9 +45,18 @@ __do_fexecve (int fd, char *const argv[], char *const envp[])
>
> /* We come here only if the 'execve' call fails. Determine whether
> /proc is mounted. If not we return ENOSYS. */
> - if (INTERNAL_SYSCALL_CALL (faccessat, AT_FDCWD, "/proc/self/fd", 0)
> - == -ENOENT)
> - err = ENOSYS;
> + if (err == ENOENT)
> + {
> + if (INTERNAL_SYSCALL_CALL (faccessat, AT_FDCWD, "/proc/self/fd", 0)
> + == -ENOENT)
> + err = ENOSYS;
> + else if (INTERNAL_SYSCALL_CALL (faccessat, AT_FDCWD, path, 0)
> + == -ENOENT)
> + /* The file descriptor does not exist, but /proc is mounted. */
> + err = EBADF;
> + /* Otherwise ENOENT comes from an invalid program
> + interpreter or some other property of the file. */
> + }
> }
> #endif /* __ASSUME_EXECVEAT */
>
> @@ -60,9 +69,10 @@ __do_fexecve (int fd, char *const argv[], char *const envp[])
> int
> fexecve (int fd, char *const argv[], char *const envp[])
> {
> - if (fd < 0 || argv == NULL || envp == NULL)
> + /* Avoid unexpected behavior of execveat with AT_FDCWD. */
> + if (fd < 0)
> {
> - __set_errno (EINVAL);
> + __set_errno (EBADF);
> return -1;
> }
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 05/13] posix: Consolidate execve family of functions
2024-07-28 19:02 ` [PATCH v2 05/13] posix: Consolidate execve family of functions Florian Weimer
@ 2024-10-16 13:40 ` Adhemerval Zanella Netto
0 siblings, 0 replies; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-10-16 13:40 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
On 28/07/24 16:02, Florian Weimer wrote:
> Ports only need to implement __arch_execveat going forward.
> Generic code implements execve, fexecve, execveat on top of it.
> The __arch_execveat/execveat split makes it easy to add
> generic code to the execveat implementation, e.g., for environ
> handling.
>
> The Linux implementation prefers the execve system call if
> available and keeps emulation for fexecve-style execveat,
> for maximum compatibility.
LGTM, thansk.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> ---
> posix/execve.c | 13 +----
> posix/execveat.c | 12 ++---
> posix/fexecve.c | 13 ++---
> .../hurd/execve.c => generic/arch-execveat.h} | 21 +++------
> .../mach/hurd/{execveat.c => arch-execveat.h} | 11 ++---
> sysdeps/mach/hurd/fexecve.c | 47 -------------------
> .../sysv/linux/{fexecve.c => arch-execveat.h} | 38 +++++++--------
> sysdeps/unix/sysv/linux/execveat.c | 32 -------------
> sysdeps/unix/sysv/linux/syscalls.list | 1 -
> 9 files changed, 40 insertions(+), 148 deletions(-)
> rename sysdeps/{mach/hurd/execve.c => generic/arch-execveat.h} (61%)
> rename sysdeps/mach/hurd/{execveat.c => arch-execveat.h} (90%)
> delete mode 100644 sysdeps/mach/hurd/fexecve.c
> rename sysdeps/unix/sysv/linux/{fexecve.c => arch-execveat.h} (73%)
> delete mode 100644 sysdeps/unix/sysv/linux/execveat.c
>
> diff --git a/posix/execve.c b/posix/execve.c
> index c8f08658cb..6d6cefaebe 100644
> --- a/posix/execve.c
> +++ b/posix/execve.c
> @@ -16,24 +16,15 @@
> <https://www.gnu.org/licenses/>. */
>
> #include <errno.h>
> -#include <stddef.h>
> +#include <fcntl.h>
> #include <unistd.h>
>
> -
> /* Replace the current process, executing PATH with arguments ARGV and
> environment ENVP. ARGV and ENVP are terminated by NULL pointers. */
> int
> __execve (const char *path, char *const argv[], char *const envp[])
> {
> - if (path == NULL || argv == NULL || envp == NULL)
> - {
> - __set_errno (EINVAL);
> - return -1;
> - }
> -
> - __set_errno (ENOSYS);
> - return -1;
> + return __execveat (AT_FDCWD, path, argv, envp, 0);
> }
> -stub_warning (execve)
>
> weak_alias (__execve, execve)
> diff --git a/posix/execveat.c b/posix/execveat.c
> index bddc4d5fb3..110177ea54 100644
> --- a/posix/execveat.c
> +++ b/posix/execveat.c
> @@ -20,6 +20,8 @@
> #include <stddef.h>
> #include <unistd.h>
>
> +#include <arch-execveat.h>
> +
> /* Replace the current process, executing PATH relative to difrd with
> arguments argv and environment envp.
> argv and envp are terminated by NULL pointers. */
> @@ -27,15 +29,7 @@ int
> __execveat (int dirfd, const char *path, char *const argv[], char *const envp[],
> int flags)
> {
> - if (path == NULL || argv == NULL || envp == NULL)
> - {
> - __set_errno (EINVAL);
> - return -1;
> - }
> -
> - __set_errno (ENOSYS);
> - return -1;
> + return __arch_execveat (dirfd, path, argv, envp, flags);
> }
> -stub_warning (execveat)
>
> weak_alias (__execveat, execveat)
> diff --git a/posix/fexecve.c b/posix/fexecve.c
> index 04e59f97ad..3f632b3ea3 100644
> --- a/posix/fexecve.c
> +++ b/posix/fexecve.c
> @@ -16,23 +16,20 @@
> <https://www.gnu.org/licenses/>. */
>
> #include <errno.h>
> -#include <stddef.h>
> +#include <fcntl.h>
> #include <unistd.h>
>
> -
> /* Execute the file FD refers to, overlaying the running program image.
> ARGV and ENVP are passed to the new program, as for `execve'. */
> int
> fexecve (int fd, char *const argv[], char *const envp[])
> {
> - if (fd < 0 || argv == NULL || envp == NULL)
> + /* Avoid unexpected behavior of execveat with AT_FDCWD. */
> + if (fd < 0)
> {
> - __set_errno (EINVAL);
> + __set_errno (EBADF);
> return -1;
> }
>
> - __set_errno (ENOSYS);
> - return -1;
> + return __execveat (fd, "", argv, envp, AT_EMPTY_PATH);
> }
> -
> -stub_warning (fexecve)
> diff --git a/sysdeps/mach/hurd/execve.c b/sysdeps/generic/arch-execveat.h
> similarity index 61%
> rename from sysdeps/mach/hurd/execve.c
> rename to sysdeps/generic/arch-execveat.h
> index 11e3f34b39..cd064df28f 100644
> --- a/sysdeps/mach/hurd/execve.c
> +++ b/sysdeps/generic/arch-execveat.h
> @@ -1,4 +1,5 @@
> -/* Copyright (C) 1991-2024 Free Software Foundation, Inc.
> +/* Architecture-specific execveat implementation. Stub version.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> This file is part of the GNU C Library.
>
> The GNU C Library is free software; you can redistribute it and/or
> @@ -15,18 +16,10 @@
> License along with the GNU C Library; if not, see
> <https://www.gnu.org/licenses/>. */
>
> -#include <unistd.h>
> -#include <hurd.h>
> -#include <fcntl.h>
> -#include <stdlib.h>
> -#include <stdio.h>
> -
> -/* Replace the current process, executing FILE_NAME with arguments ARGV and
> - environment ENVP. ARGV and ENVP are terminated by NULL pointers. */
> -int
> -__execve (const char *file_name, char *const argv[], char *const envp[])
> +static int
> +__arch_execveat (int dirfd, const char *path, char *const argv,
> + char *const envp, int flags)
> {
> - return __execveat (AT_FDCWD, file_name, argv, envp, 0);
> + __set_errno (ENOSYS);
> + return -1;
> }
> -
> -weak_alias (__execve, execve)
> diff --git a/sysdeps/mach/hurd/execveat.c b/sysdeps/mach/hurd/arch-execveat.h
> similarity index 90%
> rename from sysdeps/mach/hurd/execveat.c
> rename to sysdeps/mach/hurd/arch-execveat.h
> index f24dd7b62f..999779cbd5 100644
> --- a/sysdeps/mach/hurd/execveat.c
> +++ b/sysdeps/mach/hurd/arch-execveat.h
> @@ -1,4 +1,5 @@
> -/* Copyright (C) 1991-2024 Free Software Foundation, Inc.
> +/* Execute program relative to a directory file descriptor. Hurd version.
> + Copyright (C) 1991-2024 Free Software Foundation, Inc.
> This file is part of the GNU C Library.
>
> The GNU C Library is free software; you can redistribute it and/or
> @@ -25,9 +26,9 @@
>
> /* Replace the current process, executing FILE_NAME with arguments ARGV and
> environment ENVP. ARGV and ENVP are terminated by NULL pointers. */
> -int
> -__execveat (int dirfd, const char *file_name, char *const argv[],
> - char *const envp[], int flags)
> +static int
> +__arch_execveat (int dirfd, const char *file_name, char *const argv[],
> + char *const envp[], int flags)
> {
> error_t err;
> char *concat_name = NULL;
> @@ -88,5 +89,3 @@ __execveat (int dirfd, const char *file_name, char *const argv[],
>
> return __hurd_fail (err);
> }
> -
> -weak_alias (__execveat, execveat)
> diff --git a/sysdeps/mach/hurd/fexecve.c b/sysdeps/mach/hurd/fexecve.c
> deleted file mode 100644
> index 50472d5ca5..0000000000
> --- a/sysdeps/mach/hurd/fexecve.c
> +++ /dev/null
> @@ -1,47 +0,0 @@
> -/* Copyright (C) 1993-2024 Free Software Foundation, Inc.
> - This file is part of the GNU C Library.
> -
> - The GNU C Library is free software; you can redistribute it and/or
> - 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.
> -
> - 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
> - Lesser General Public License for more details.
> -
> - You should have received a copy of the GNU Lesser General Public
> - License along with the GNU C Library; if not, see
> - <https://www.gnu.org/licenses/>. */
> -
> -#include <unistd.h>
> -#include <hurd.h>
> -#include <hurd/fd.h>
> -#include <errno.h>
> -
> -/* Execute the file FD refers to, overlaying the running program image. */
> -
> -int
> -fexecve (int fd, char *const argv[], char *const envp[])
> -{
> - file_t file;
> - error_t err;
> - enum retry_type doretry;
> - char retryname[1024];
> -
> - err = HURD_DPORT_USE (fd,
> - __dir_lookup (port, "", O_EXEC, 0, &doretry, retryname, &file));
> -
> - if (! err && (doretry != FS_RETRY_NORMAL || retryname[0] != '\0'))
> - err = EGRATUITOUS;
> - if (err)
> - return __hurd_fail(err);
> -
> - err = _hurd_exec_paths (__mach_task_self (), file, NULL, NULL, argv, envp);
> - if (! err)
> - err = EGRATUITOUS;
> -
> - __mach_port_deallocate (__mach_task_self (), file);
> - return __hurd_fail (err);
> -}
> diff --git a/sysdeps/unix/sysv/linux/fexecve.c b/sysdeps/unix/sysv/linux/arch-execveat.h
> similarity index 73%
> rename from sysdeps/unix/sysv/linux/fexecve.c
> rename to sysdeps/unix/sysv/linux/arch-execveat.h
> index c2303735de..b6bc286829 100644
> --- a/sysdeps/unix/sysv/linux/fexecve.c
> +++ b/sysdeps/unix/sysv/linux/arch-execveat.h
> @@ -1,4 +1,5 @@
> -/* Copyright (C) 1994-2024 Free Software Foundation, Inc.
> +/* Execute program relative to a directory file descriptor. Linux version.
> + Copyright (C) 1994-2024 Free Software Foundation, Inc.
> This file is part of the GNU C Library.
>
> The GNU C Library is free software; you can redistribute it and/or
> @@ -15,17 +16,11 @@
> License along with the GNU C Library; if not, see
> <https://www.gnu.org/licenses/>. */
>
> -#include <errno.h>
> -#include <stddef.h>
> -#include <stdio.h>
> -#include <unistd.h>
> #include <fcntl.h>
> -#include <sys/stat.h>
> -
> #include <fd_to_filename.h>
> -#include <sysdep.h>
> -#include <sys/syscall.h>
> #include <kernel-features.h>
> +#include <sys/syscall.h>
> +#include <sysdep.h>
>
> static int
> __do_fexecve (int fd, char *const argv[], char *const envp[])
> @@ -64,17 +59,20 @@ __do_fexecve (int fd, char *const argv[], char *const envp[])
> return -1;
> }
>
> -/* Execute the file FD refers to, overlaying the running program image.
> - ARGV and ENVP are passed to the new program, as for `execve'. */
> -int
> -fexecve (int fd, char *const argv[], char *const envp[])
> +static int
> +__arch_execveat (int dirfd, const char *path, char *const *argv,
> + char *const *envp, int flags)
> {
> - /* Avoid unexpected behavior of execveat with AT_FDCWD. */
> - if (fd < 0)
> - {
> - __set_errno (EBADF);
> - return -1;
> - }
> + /* Use execve if possible, to preserve the historic system call
> + profile. */
> + if (dirfd == AT_FDCWD && flags == 0)
> + return INLINE_SYSCALL_CALL (execve, path, argv, envp, flags);
> +
> + /* Provide emulation for an fexecve-style execveat call. */
> + if (dirfd >= 0 && flags == AT_EMPTY_PATH && path[0] == '\0')
> + return __do_fexecve (dirfd, argv, envp);
>
> - return __do_fexecve (fd, argv, envp);
> + /* Otherwise perform the system call without emulation. */
> + return INLINE_SYSCALL_CALL (execveat, dirfd, path, &argv[0], &envp[0],
> + flags);
> }
> diff --git a/sysdeps/unix/sysv/linux/execveat.c b/sysdeps/unix/sysv/linux/execveat.c
> deleted file mode 100644
> index e1d68b9043..0000000000
> --- a/sysdeps/unix/sysv/linux/execveat.c
> +++ /dev/null
> @@ -1,32 +0,0 @@
> -/* Execute program relative to a directory file descriptor.
> - Copyright (C) 2021-2024 Free Software Foundation, Inc.
> - This file is part of the GNU C Library.
> -
> - The GNU C Library is free software; you can redistribute it and/or
> - 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.
> -
> - 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
> - Lesser General Public License for more details.
> -
> - You should have received a copy of the GNU Lesser General Public
> - License along with the GNU C Library; if not, see
> - <https://www.gnu.org/licenses/>. */
> -
> -#include <fd_to_filename.h>
> -#include <sysdep.h>
> -#include <unistd.h>
> -
> -/* Execute the file FD refers to, overlaying the running program image.
> - ARGV and ENVP are passed to the new program, as for 'execve'. */
> -int
> -execveat (int dirfd, const char *path, char *const argv[], char *const envp[],
> - int flags)
> -{
> - /* Avoid implicit array coercion in syscall macros. */
> - return INLINE_SYSCALL_CALL (execveat, dirfd, path, &argv[0], &envp[0],
> - flags);
> -}
> diff --git a/sysdeps/unix/sysv/linux/syscalls.list b/sysdeps/unix/sysv/linux/syscalls.list
> index 9ac42c3436..14bbb5fe6e 100644
> --- a/sysdeps/unix/sysv/linux/syscalls.list
> +++ b/sysdeps/unix/sysv/linux/syscalls.list
> @@ -9,7 +9,6 @@ delete_module EXTRA delete_module 3 delete_module
> epoll_create1 EXTRA epoll_create1 i:i epoll_create1
> epoll_ctl EXTRA epoll_ctl i:iiip epoll_ctl
> eventfd EXTRA eventfd2 i:ii eventfd
> -execve - execve i:spp __execve execve
> flock - flock i:ii __flock flock
> fsconfig EXTRA fsconfig i:iUsNi fsconfig
> fsmount EXTRA fsmount i:iUU fsmount
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 01/13] stdlib: Make getenv thread-safe in more cases
2024-07-28 19:02 ` [PATCH v2 01/13] stdlib: Make getenv thread-safe in more cases Florian Weimer
@ 2024-10-16 17:30 ` Adhemerval Zanella Netto
2024-11-18 9:54 ` Florian Weimer
0 siblings, 1 reply; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-10-16 17:30 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
On 28/07/24 16:02, Florian Weimer wrote:
> Async-signal-safety is preserved, too.
Maybe extend the commit message with some description of the strategy
used, the cases it now handle correctly, and the ones that still
require some more work?
I am still grasping the algorithm and the SeqLock usage within it,
but it does sounds a good move forward. I am still not sure about
async-signal-safeness, I will need to think more about it.
> ---
> stdlib/Makefile | 7 ++
> stdlib/getenv.c | 138 +++++++++++++++++++--
> stdlib/putenv.c | 2 +
> stdlib/setenv.c | 225 +++++++++++++++++++++++++----------
> stdlib/setenv.h | 73 ++++++++++++
> stdlib/tst-environ.c | 13 +-
> stdlib/tst-getenv-signal.c | 94 +++++++++++++++
> stdlib/tst-getenv-thread.c | 61 ++++++++++
> stdlib/tst-getenv-unsetenv.c | 75 ++++++++++++
> 9 files changed, 605 insertions(+), 83 deletions(-)
> create mode 100644 stdlib/setenv.h
> create mode 100644 stdlib/tst-getenv-signal.c
> create mode 100644 stdlib/tst-getenv-thread.c
> create mode 100644 stdlib/tst-getenv-unsetenv.c
>
> diff --git a/stdlib/Makefile b/stdlib/Makefile
> index 8b0ac63ddb..697b547540 100644
> --- a/stdlib/Makefile
> +++ b/stdlib/Makefile
> @@ -275,6 +275,9 @@ tests := \
> tst-canon-bz26341 \
> tst-cxa_atexit \
> tst-environ \
> + tst-getenv-signal \
> + tst-getenv-thread \
> + tst-getenv-unsetenv \
> tst-getrandom \
> tst-labs \
> tst-limits \
> @@ -623,3 +626,7 @@ $(objpfx)tst-setcontext3.out: tst-setcontext3.sh $(objpfx)tst-setcontext3
> $(evaluate-test)
>
> $(objpfx)tst-qsort5: $(libm)
> +
> +$(objpfx)tst-getenv-signal: $(shared-thread-library)
> +$(objpfx)tst-getenv-thread: $(shared-thread-library)
> +$(objpfx)tst-getenv-unsetenv: $(shared-thread-library)
Ok.
> diff --git a/stdlib/getenv.c b/stdlib/getenv.c
> index bea69e0f40..ad7f0d00c8 100644
> --- a/stdlib/getenv.c
> +++ b/stdlib/getenv.c
> @@ -15,24 +15,140 @@
> License along with the GNU C Library; if not, see
> <https://www.gnu.org/licenses/>. */
>
> -#include <stdlib.h>
> +#include <atomic.h>
> +#include <setenv.h>
> #include <string.h>
> #include <unistd.h>
>
> +struct environ_array *__environ_array_list;
> +environ_counter __environ_counter;
> +
> char *
> getenv (const char *name)
> {
> - if (__environ == NULL || name[0] == '\0')
> - return NULL;
> -
> - size_t len = strlen (name);
> - for (char **ep = __environ; *ep != NULL; ++ep)
> + while (true)
> {
> - if (name[0] == (*ep)[0]
> - && strncmp (name, *ep, len) == 0 && (*ep)[len] == '=')
> - return *ep + len + 1;
> - }
> + /* Used to deal with concurrent unsetenv. */
> + environ_counter start_counter = atomic_load_acquire (&__environ_counter);
> +
> + /* We use relaxed MO here because we assume that loads carry a
This comment is confusing, the C11 memory model and Hans Boehm's paper
you reference for the thread fence states that MO adds no happens-before
ordering. So it is not clear that load dependency here really synchronizes
with the release store.
My understanding, reading the paper (and I am far from the memory
synchronization expert) is MO is suffice to avoid data races for the
data access; but in the end is the seqlock with a release MO that keep
consistency.
> + dependency, matching the release MO store to __environ in
> + __add_to_environ. Objects pointed to by pointers stored in the
> + __environ array are never 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. String stores into the environment
> + array use release MO, so we can assume the loads here see the
> + initialized contents (againt except for putenv). */
> + 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)
It is not clear why a load acquire is required here, since on the section 5
of the very paper it uses a MO load after the atomic_thread_fence_acquire.
> + /* 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 remaing after the removals, and
s/remaing/remaining
> + 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], ...
>
> - return NULL;
> + (the N[k] are optional.) Let's assume that
Is 'k' the key env. var we are looking here? It is not clear from the
comment.
> + 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. */
I must be sincere that I haven't grokked all the rationale that
returning NULL is correct here. Is looping just wasteful (as the paper
uses for the reader example) or is it incorrect here?
> + return NULL;
> + /* Otherwise retry. This will never happen in a signal handler
> + that interrupted unsetenv because the suspended unsetenv call
> + cannot change the counter value. */
> + }
> }
> libc_hidden_def (getenv)
> diff --git a/stdlib/putenv.c b/stdlib/putenv.c
> index b234ce0612..c61beb226f 100644
> --- a/stdlib/putenv.c
> +++ b/stdlib/putenv.c
> @@ -23,6 +23,8 @@
> # include <config.h>
> #endif
>
> +#include <setenv.h>
> +
Why is this required for this patch?
> #if _LIBC || HAVE_STDLIB_H
> # include <stdlib.h>
> #endif
> diff --git a/stdlib/setenv.c b/stdlib/setenv.c
> index e2164371ad..6bd0596b79 100644
> --- a/stdlib/setenv.c
> +++ b/stdlib/setenv.c
> @@ -19,6 +19,9 @@
> # include <config.h>
> #endif
>
> +#include <assert.h>
> +#include <setenv.h>
> +
> /* Pacify GCC; see the commentary about VALLEN below. This is needed
> at least through GCC 4.9.2. Pacify GCC for the entire file, as
> there seems to be no way to pacify GCC selectively, only for the
> @@ -100,25 +103,51 @@ static void *known_values;
>
> #endif
>
As a side note, can we clean up all the unrequired definitions from
gnulib now that we have a quite different implementation? The multiple
ifdef is really just clutter for glibc and only makes the implementation
harder to read.
> +/* Allocate a new environment array and put it o the
> + __environ_array_list. Returns NULL on memory allocation
> + failure. */
> +static struct environ_array *
> +__environ_new_array (size_t required_size)
> +{
> + /* No backing array yet, or insufficient room. */
> + size_t new_size;
> + if (__environ_array_list == NULL
> + || __environ_array_list->allocated * 2 < required_size)
> + /* Add some unused space for future growth. */
> + new_size = required_size + 16;
> + else
> + new_size = __environ_array_list->allocated * 2;
> +
> + size_t new_size_in_bytes;
> + if (__builtin_mul_overflow (new_size, sizeof (char *),
> + &new_size_in_bytes)
> + || __builtin_add_overflow (new_size_in_bytes,
> + offsetof (struct environ_array,
> + array),
> + &new_size_in_bytes))
> + {
> + __set_errno (ENOMEM);
> + return NULL;
> + }
>
Ok.
> -/* If this variable is not a null pointer we allocated the current
> - environment. */
> -static char **last_environ;
> -
> + /* Zero-initialize everything, so that getenv can only
> + observe valid or null pointers. */
> + struct environ_array *target_array = calloc (1, new_size_in_bytes);
> + if (target_array == NULL)
> + return NULL;
> + target_array->allocated = new_size;
> + assert (new_size >= target_array->allocated);
> +
> + /* Put it onto the list. */
> + target_array->next = __environ_array_list;
> + __environ_array_list = target_array;
> + return target_array;
> +}
>
> -/* This function is used by `setenv' and `putenv'. The difference between
> - the two functions is that for the former must create a new string which
> - is then placed in the environment, while the argument of `putenv'
> - must be used directly. This is all complicated by the fact that we try
> - to reuse values once generated for a `setenv' call since we can never
> - free the strings. */
> int
> __add_to_environ (const char *name, const char *value, const char *combined,
> int replace)
> {
> - char **ep;
> - size_t size;
> -
> /* Compute lengths before locking, so that the critical section is
> less of a performance bottleneck. VALLEN is needed only if
> COMBINED is null (unfortunately GCC is not smart enough to deduce
> @@ -133,45 +162,80 @@ __add_to_environ (const char *name, const char *value, const char *combined,
> LOCK;
>
> /* We have to get the pointer now that we have the lock and not earlier
> - since another thread might have created a new environment. */
> - ep = __environ;
> + since another thread might have created a new environment. */
> + char **start_environ = atomic_load_relaxed (&__environ);
> + char **ep = start_environ;
>
> - size = 0;
> + /* This gets written to __environ in the end. */
> + char **result_environ = start_environ;
> +
> + /* Size of the environment if *ep == NULL. */
> if (ep != NULL)
> - {
> - for (; *ep != NULL; ++ep)
> - if (!strncmp (*ep, name, namelen) && (*ep)[namelen] == '=')
> - break;
> - else
> - ++size;
> - }
> + for (; *ep != NULL; ++ep)
> + if (strncmp (*ep, name, namelen) == 0 && (*ep)[namelen] == '=')
> + break;
>
> - if (ep == NULL || __builtin_expect (*ep == NULL, 1))
> + if (ep == NULL || __glibc_likely (*ep == NULL))
> {
Ok.
> - char **new_environ;
> -
> - /* We allocated this space; we can extend it. Avoid using the raw
> - reallocated pointer to avoid GCC -Wuse-after-free. */
> - uintptr_t ip_last_environ = (uintptr_t)last_environ;
> - new_environ = (char **) realloc (last_environ,
> - (size + 2) * sizeof (char *));
> - if (new_environ == NULL)
> + /* The scanning loop above reached the end of the environment.
> + Add a new string to it. */
> + replace = true;
> +
> + /* + 2 for the new entry and the terminating NULL. */
> + size_t required_size = (ep - start_environ) + 2;
> + if (__environ_is_from_array_list (start_environ)
> + && required_size <= __environ_array_list->allocated)
> + /* The __environ array is ours, and we have room in it. We
> + can use ep as-is. Add a null terminator in case current
> + usage is less than previous usage. */
> + ep[1] = NULL;
> + else
> {
> - UNLOCK;
> - return -1;
> - }
> -
> - if ((uintptr_t)__environ != ip_last_environ)
> - memcpy ((char *) new_environ, (char *) __environ,
> - size * sizeof (char *));
> -
> - new_environ[size] = NULL;
> - new_environ[size + 1] = NULL;
> - ep = new_environ + size;
> + /* We cannot use __environ as is and need to copy over the
> + __environ contents into an array managed via
> + __environ_array_list. */
> +
> + struct environ_array *target_array;
> + if (__environ_array_list != NULL
> + && required_size <= __environ_array_list->allocated)
> + /* Existing array has enough room. Contents is copied below. */
> + target_array = __environ_array_list;
> + else
> + {
> + /* Allocate a new array. */
> + target_array = __environ_new_array (required_size);
> + if (target_array == NULL)
> + {
> + UNLOCK;
> + return -1;
> + }
> + }
>
> - last_environ = __environ = new_environ;
> + /* Copy over the __environ array contents. This is a
> + backwards copy if __environ points into
> + target_array->array, and it does not clobber values still
> + needing copying. This handles the case start_environ
> + == ep == NULL, too. */
It is not clear why you need to copy backwards here.
> + size_t i;
> + for (i = 0; start_environ + i < ep; ++i)
> + /* Regular store because unless there has been direct
> + manipulation of the environment, target_array is still
> + a private copy. */
> + target_array->array[i] = atomic_load_relaxed (start_environ + i);
> +
> + /* This is the new place where we should add the element. */
> + ep = target_array->array + i;
> +
> + /* Add the null terminator in case there was a pointer there
> + previously. */
> + ep[1] = NULL;
> +
> + /* And __environ should be repointed to our array. */
> + result_environ = &target_array->array[0];
> + }
> }
> - if (*ep == NULL || replace)
> +
> + if (replace || *ep == NULL)
> {
> char *np;
>
> @@ -213,9 +277,15 @@ __add_to_environ (const char *name, const char *value, const char *combined,
> #endif
> }
>
> - *ep = np;
> + /* Use release MO so that loads are sufficient to observe the
> + pointer contents because the CPU carries the dependency for
> + us. This also acts as a thread fence, making getenv
> + async-signal-safe. */
> + atomic_store_release (ep, np);
> + atomic_store_release (&__environ, result_environ);
Right, I take this should slight better than a MO and a atomic_thread_fence_release
(which now maps to a seqcst on all ABI, and this noticiable on archs like powerpc
that requires a sync instead of lwsync).
> }
>
> +
> UNLOCK;
>
> return 0;
> @@ -249,18 +319,40 @@ unsetenv (const char *name)
>
> LOCK;
>
> - ep = __environ;
> + ep = atomic_load_relaxed (&__environ);
> if (ep != NULL)
> - while (*ep != NULL)
> + while (true)
> {
> - if (!strncmp (*ep, name, len) && (*ep)[len] == '=')
> + char *entry = atomic_load_relaxed (ep);
> + if (entry == NULL)
> + break;
> + if (!strncmp (entry, name, len) && entry[len] == '=')
> {
> /* Found it. Remove this pointer by moving later ones back. */
> char **dp = ep;
>
> - do
> - dp[0] = dp[1];
> - while (*dp++);
> + while (true)
> + {
> + char *next_value = atomic_load_relaxed (dp + 1);
> + /* This store overwrites a value that has been
> + removed, or that has already been written to a
> + previous value. Release MO so that this store does
> + not get reordered before the counter update in the
> + previous loop iteration. */
> + atomic_store_release (dp, next_value);
> + /* Release store synchronizes with acquire loads in
> + getenv. Non-atomic update because there is just
> + one writer due to the lock.
> +
> + See discussion of the counter check in getenv for
> + an explanation why this is sufficient synchronization. */
> + atomic_store_release (&__environ_counter,
> + atomic_load_relaxed (&__environ_counter)
> + + 1);
> + if (next_value == NULL)
> + break;
> + ++dp;
> + }
> /* Continue the loop in case NAME appears again. */
> }
> else
> @@ -279,19 +371,20 @@ int
> clearenv (void)
> {
> LOCK;
> -
> - if (__environ == last_environ && __environ != NULL)
> + char **start_environ = atomic_load_relaxed (&__environ);
> + if (__environ_is_from_array_list (start_environ))
> {
> - /* We allocated this environment so we can free it. */
> - free (__environ);
> - last_environ = NULL;
> + /* Store null pointers to avoid strange effects when the array
> + is reused in setenv. */
> + for (char **ep = start_environ; *ep != NULL; ++ep)
> + atomic_store_relaxed(ep, NULL);
Missing space after macro name.
> + /* Update the counter similar to unsetenv, so that the writes in
> + setenv do not need to update the counter. */
> + atomic_store_release (&__environ_counter,
> + atomic_load_relaxed (&__environ_counter) + 1);
Why not atomic_fetch_add_release here (as indicated by the paper)?
> }
> -
> - /* Clear the environment pointer removes the whole environment. */
> - __environ = NULL;
> -
> + atomic_store_relaxed (&__environ, NULL);
> UNLOCK;
> -
Spurious new line removal.
> return 0;
> }
> #ifdef _LIBC
> @@ -301,6 +394,14 @@ __libc_setenv_freemem (void)
> /* Remove all traces. */
> clearenv ();
>
> + /* Clear all backing arrays. */
> + while (__environ_array_list != NULL)
> + {
> + void *ptr = __environ_array_list;
> + __environ_array_list = __environ_array_list->next;
> + free (ptr);
> + }
> +
> /* Now remove the search tree. */
> __tdestroy (known_values, free);
> known_values = NULL;
> diff --git a/stdlib/setenv.h b/stdlib/setenv.h
> new file mode 100644
> index 0000000000..3919943f87
> --- /dev/null
> +++ b/stdlib/setenv.h
> @@ -0,0 +1,73 @@
> +/* Common declarations for the setenv/getenv family of functions.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#ifndef _SETENV_H
> +#define _SETENV_H
> +
> +#include <atomic.h>
> +#include <stdbool.h>
> +
> +/* We use an exponential sizing policy for environment arrays. The
> + arrays are not deallocating during the lifetime of the process.
> + This adds between one and two additional pointers per active
> + environemnt entry, on top of what is used by setenv to keep track
> + of environment values used before. */
> +struct environ_array
> +{
> + struct environ_array *next; /* Previously used environment array. */
> + size_t allocated; /* Number of allocated array elments. */
> + char *array[]; /* The actual environment array. */
> +};
> +
> +/* After initialization, and until the user resets environ (perhaps by
> + calling clearenv), &__environ[0] == &environ_array_list->array[0]. */
> +extern struct environ_array *__environ_array_list attribute_hidden;
> +
> +/* Returns true if EP (which should be an __environ value) is a
> + pointer managed by setenv. */
> +static inline _Bool
This is an internal header, it is ok to use 'bool' since it is already being
included.
> +__environ_is_from_array_list (char **ep)
> +{
> + struct environ_array *eal = atomic_load_relaxed (&__environ_array_list);
> + return eal != NULL && &eal->array[0] == ep;
> +}
> +
> +/* Counter for detecting concurrent modification in unsetenv.
> + Ideally, this should be a 64-bit counter that cannot wrap around,
> + but given that counter wrapround is probably impossible to hit
> + (2**32 operations in unsetenv concurrently with getenv), using
> + <atomic_wide_counter.h> seems unnecessary. */
> +#if __HAVE_64B_ATOMICS
> +typedef uint64_t environ_counter;
> +#else
> +typedef uint32_t environ_counter;
> +#endif
> +
> +/* Updated by unsetenv to detect multiple overwrites in getenv. */
> +extern environ_counter __environ_counter attribute_hidden;
> +
> +/* This function is used by `setenv' and `putenv'. The difference between
> + the two functions is that for the former must create a new string which
> + is then placed in the environment, while the argument of `putenv'
> + must be used directly. This is all complicated by the fact that we try
> + to reuse values once generated for a `setenv' call since we can never
> + free the strings. */
> +int __add_to_environ (const char *name, const char *value,
> + const char *combines, int replace) attribute_hidden;
> +
> +#endif /* _SETENV_H */
> diff --git a/stdlib/tst-environ.c b/stdlib/tst-environ.c
> index bab8873f08..aff191bb4d 100644
> --- a/stdlib/tst-environ.c
> +++ b/stdlib/tst-environ.c
> @@ -20,6 +20,7 @@
> #include <stdlib.h>
> #include <string.h>
> #include <libc-diag.h>
> +#include <support/check.h>
>
> #define VAR "FOOBAR"
>
> @@ -50,11 +51,7 @@ do_test (void)
>
> /* Getting this value should now be possible. */
> valp = getenv (VAR);
> - if (valp == NULL || strcmp (valp, "one") != 0)
> - {
> - puts ("getenv #2 failed");
> - result = 1;
> - }
> + TEST_COMPARE_STRING (valp, "one");
>
> /* Try to replace without the replace flag set. This should fail. */
> if (setenv (VAR, "two", 0) != 0)
> @@ -65,11 +62,7 @@ do_test (void)
>
> /* The value shouldn't have changed. */
> valp = getenv (VAR);
> - if (valp == NULL || strcmp (valp, "one") != 0)
> - {
> - puts ("getenv #3 failed");
> - result = 1;
> - }
> + TEST_COMPARE_STRING (valp, "one");
>
> /* Now replace the value using putenv. */
> if (putenv (putenv_val) != 0)
Ok.
> diff --git a/stdlib/tst-getenv-signal.c b/stdlib/tst-getenv-signal.c
> new file mode 100644
> index 0000000000..bd35f886b5
> --- /dev/null
> +++ b/stdlib/tst-getenv-signal.c
> @@ -0,0 +1,94 @@
> +/* Test getenv from a signal handler interrupting environent updates.
s/environent/environment
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <array_length.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/xthread.h>
> +#include <support/xsignal.h>
> +
> +/* Set to false by the main thread after doing all the setenv
> + calls. */
> +static bool running = true;
> +
> +/* Used to synchronize the start of signal sending. */
> +static pthread_barrier_t barrier;
> +
> +/* Identity of the main thread. */
> +static pthread_t main_thread;
> +
> +/* Send SIGUSR1 signals to main_thread. */
> +static void *
> +signal_thread (void *ignored)
> +{
> + xpthread_barrier_wait (&barrier);
> + while (__atomic_load_n (&running, __ATOMIC_RELAXED))
> + xpthread_kill (main_thread, SIGUSR1);
> + return NULL;
> +}
> +
> +/* Call getenv from a signal handler. */
> +static void
> +signal_handler (int signo)
> +{
> + TEST_COMPARE_STRING (getenv ("unset_variable"), NULL);
> + char *value = getenv ("set_variable");
> + TEST_VERIFY (strncmp (value, "value", strlen ("value")) == 0);
> +}
> +
> +static int
> +do_test (void)
> +{
> + /* Added to the environment using putenv. */
> + char *variables[30];
> + for (int i = 0; i < array_length (variables); ++i)
> + variables[i] = xasprintf ("v%d=%d", i, i);
> +
> + xsignal (SIGUSR1, signal_handler);
> + TEST_COMPARE (setenv ("set_variable", "value", 1), 0);
> + xraise (SIGUSR1);
> + main_thread = pthread_self ();
> + xpthread_barrier_init (&barrier, NULL, 2);
> + pthread_t thr = xpthread_create (NULL, signal_thread, NULL);
> + xpthread_barrier_wait (&barrier);
> +
> + for (int i = 0; i < array_length (variables); ++i)
> + {
> + char buf[30];
> + TEST_COMPARE (setenv ("temporary_variable", "1", 1), 0);
> + snprintf (buf, sizeof (buf), "V%d", i);
> + TEST_COMPARE (setenv (buf, buf + 1, 1), 0);
> + TEST_COMPARE (putenv (variables[i]), 0);
> + snprintf (buf, sizeof (buf), "value%d", i);
> + TEST_COMPARE (setenv ("set_variable", buf, 1), 0);
> + TEST_COMPARE (unsetenv ("temporary_variable"), 0);
> + }
> +
> + __atomic_store_n (&running, false, __ATOMIC_RELAXED);
> + xpthread_join (thr);
> + xpthread_barrier_destroy (&barrier);
> +
> + for (int i = 0; i < array_length (variables); ++i)
> + free (variables[i]);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
Ok.
> diff --git a/stdlib/tst-getenv-thread.c b/stdlib/tst-getenv-thread.c
> new file mode 100644
> index 0000000000..e5a79c5227
> --- /dev/null
> +++ b/stdlib/tst-getenv-thread.c
> @@ -0,0 +1,61 @@
> +/* Test getenv with concurrent setenv.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <support/check.h>
> +#include <support/xthread.h>
> +
> +/* Set to false by the main thread after doing all the setenv
> + calls. */
> +static bool running = true;
> +
> +/* Used to synchronize the start of the getenv thread. */
> +static pthread_barrier_t barrier;
> +
> +/* Invoke getenv for a nonexisting environment variable in a loop.
> + This checks that concurrent setenv does not invalidate the
> + environment array while getenv reads it. */
> +static void *
> +getenv_thread (void *ignored)
> +{
> + xpthread_barrier_wait (&barrier);
> + while (__atomic_load_n (&running, __ATOMIC_RELAXED))
> + TEST_VERIFY (getenv ("unset_variable") == NULL);
> + return NULL;
> +}
> +
> +static int
> +do_test (void)
> +{
> + xpthread_barrier_init (&barrier, NULL, 2);
> + pthread_t thr = xpthread_create (NULL, getenv_thread, NULL);
> + xpthread_barrier_wait (&barrier);
> + for (int i = 0; i < 1000; ++i)
> + {
> + char buf[30];
> + snprintf (buf, sizeof (buf), "V%d", i);
> + TEST_COMPARE (setenv (buf, buf + 1, 1), 0);
> + }
> + __atomic_store_n (&running, false, __ATOMIC_RELAXED);
> + xpthread_join (thr);
> + xpthread_barrier_destroy (&barrier);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
Ok.
> diff --git a/stdlib/tst-getenv-unsetenv.c b/stdlib/tst-getenv-unsetenv.c
> new file mode 100644
> index 0000000000..4d42b5fd69
> --- /dev/null
> +++ b/stdlib/tst-getenv-unsetenv.c
> @@ -0,0 +1,75 @@
> +/* Test getenv with concurrent unsetenv.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <array_length.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/xthread.h>
> +
> +/* Used to synchronize the start of each test iteration. */
> +static pthread_barrier_t barrier;
> +
> +/* Number of iterations. */
> +enum { iterations = 10000 };
> +
> +/* Check that even with concurrent unsetenv, a variable that is known
> + to be there is found. */
> +static void *
> +getenv_thread (void *ignored)
> +{
> + for (int i = 0; i < iterations; ++i)
> + {
> + xpthread_barrier_wait (&barrier);
> + TEST_COMPARE_STRING (getenv ("variable"), "value");
> + xpthread_barrier_wait (&barrier);
> + }
> + return NULL;
> +}
> +
> +static int
> +do_test (void)
> +{
> + xpthread_barrier_init (&barrier, NULL, 2);
> + pthread_t thr = xpthread_create (NULL, getenv_thread, NULL);
> +
> + char *variables[50];
> + for (int i = 0; i < array_length (variables); ++i)
> + variables[i] = xasprintf ("V%d", i);
> +
> + for (int i = 0; i < iterations; ++i)
> + {
> + clearenv ();
> + for (int j = 0; j < array_length (variables); ++j)
> + TEST_COMPARE (setenv (variables[j], variables[j] + 1, 1), 0);
> + TEST_COMPARE (setenv ("variable", "value", 1), 0);
> + xpthread_barrier_wait (&barrier);
> + /* Test runs. */
> + for (int j = 0; j < array_length (variables); ++j)
> + TEST_COMPARE (unsetenv (variables[j]), 0);
> + xpthread_barrier_wait (&barrier);
> + }
> + xpthread_join (thr);
> + xpthread_barrier_destroy (&barrier);
> + for (int i = 0; i < array_length (variables); ++i)
> + free (variables[i]);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
Ok.
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 06/13] posix: Introduce the __arch_spawni function
2024-07-28 19:02 ` [PATCH v2 06/13] posix: Introduce the __arch_spawni function Florian Weimer
@ 2024-10-29 20:29 ` Adhemerval Zanella Netto
0 siblings, 0 replies; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-10-29 20:29 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
On 28/07/24 16:02, Florian Weimer wrote:
> And move the definition of SPAWN_ERROR to spawn_int.h.
>
> The separate __arch_spawni function allows centralized
> implementation of preparatory code in posix/spawni.c, which is no
> longer a stub after this commit.
LGTM, thanks.
> ---
> posix/spawn_int.h | 7 +++++
> posix/spawni.c | 15 ++---------
> sysdeps/generic/arch-spawni.h | 27 +++++++++++++++++++
> sysdeps/mach/hurd/{spawni.c => arch-spawni.h} | 12 ++++-----
> .../sysv/linux/{spawni.c => arch-spawni.h} | 18 ++++---------
> 5 files changed, 47 insertions(+), 32 deletions(-)
> create mode 100644 sysdeps/generic/arch-spawni.h
> rename sysdeps/mach/hurd/{spawni.c => arch-spawni.h} (99%)
> rename sysdeps/unix/sysv/linux/{spawni.c => arch-spawni.h} (96%)
>
> diff --git a/posix/spawn_int.h b/posix/spawn_int.h
> index 2cdb55b4d6..9d0de483fe 100644
> --- a/posix/spawn_int.h
> +++ b/posix/spawn_int.h
> @@ -78,6 +78,13 @@ struct __spawn_action
> #define SPAWN_XFLAGS_TRY_SHELL 0x2
> #define SPAWN_XFLAGS_RET_PIDFD 0x4
>
> +/* The Unix standard contains a long explanation of the way to signal
> + an error after the fork() was successful. Since no new wait status
> + was wanted there is no way to signal an error using one of the
> + available methods. The committee chose to signal an error by a
> + normal program exit with the exit code 127. */
> +#define SPAWN_ERROR 127
> +
> extern int __posix_spawn_file_actions_realloc (posix_spawn_file_actions_t *
> file_actions)
> attribute_hidden;
> diff --git a/posix/spawni.c b/posix/spawni.c
> index 52ff5f1966..ef47dd9ea4 100644
> --- a/posix/spawni.c
> +++ b/posix/spawni.c
> @@ -19,15 +19,7 @@
> #include <errno.h>
> #include <spawn.h>
> #include "spawn_int.h"
> -
> -
> -/* The Unix standard contains a long explanation of the way to signal
> - an error after the fork() was successful. Since no new wait status
> - was wanted there is no way to signal an error using one of the
> - available methods. The committee chose to signal an error by a
> - normal program exit with the exit code 127. */
> -#define SPAWN_ERROR 127
> -
> +#include <arch-spawni.h>
>
> /* Spawn a new process executing PATH with the attributes describes in *ATTRP.
> Before running the process perform the actions described in FILE-ACTIONS. */
> @@ -37,8 +29,5 @@ __spawni (pid_t *pid, const char *file,
> const posix_spawnattr_t *attrp, char *const argv[],
> char *const envp[], int xflags)
> {
> - __set_errno (ENOSYS);
> - return -1;
> + return __arch_spawni (pid, file, file_actions, attrp, argv, envp, xflags);
> }
> -
> -stub_warning (__spawni)
I think it should be ok to remove the stub warning since Hurd also supports it.
> diff --git a/sysdeps/generic/arch-spawni.h b/sysdeps/generic/arch-spawni.h
> new file mode 100644
> index 0000000000..ad225d642d
> --- /dev/null
> +++ b/sysdeps/generic/arch-spawni.h
> @@ -0,0 +1,27 @@
> +/* Architecture-specific __spawni implementation. Stub version.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +static int
> +__arch_spawni (pid_t *pid, const char *file,
> + const posix_spawn_file_actions_t *file_actions,
> + const posix_spawnattr_t *attrp, char *const argv[],
> + char *const envp[], int xflags)
> +{
> + __set_errno (ENOSYS);
> + return -1;
> +}
> diff --git a/sysdeps/mach/hurd/spawni.c b/sysdeps/mach/hurd/arch-spawni.h
> similarity index 99%
> rename from sysdeps/mach/hurd/spawni.c
> rename to sysdeps/mach/hurd/arch-spawni.h
> index a0d2e28c8e..6c1b9e98ad 100644
> --- a/sysdeps/mach/hurd/spawni.c
> +++ b/sysdeps/mach/hurd/arch-spawni.h
> @@ -36,12 +36,12 @@
>
> /* Spawn a new process executing PATH with the attributes describes in *ATTRP.
> Before running the process perform the actions described in FILE-ACTIONS. */
> -int
> -__spawni (pid_t *pid, const char *file,
> - const posix_spawn_file_actions_t *file_actions,
> - const posix_spawnattr_t *attrp,
> - char *const argv[], char *const envp[],
> - int xflags)
> +static int
> +__arch_spawni (pid_t *pid, const char *file,
> + const posix_spawn_file_actions_t *file_actions,
> + const posix_spawnattr_t *attrp,
> + char *const argv[], char *const envp[],
> + int xflags)
> {
> pid_t new_pid;
> char *path, *p, *name;
> diff --git a/sysdeps/unix/sysv/linux/spawni.c b/sysdeps/unix/sysv/linux/arch-spawni.h
> similarity index 96%
> rename from sysdeps/unix/sysv/linux/spawni.c
> rename to sysdeps/unix/sysv/linux/arch-spawni.h
> index f57e92815e..8caf74f7f5 100644
> --- a/sysdeps/unix/sysv/linux/spawni.c
> +++ b/sysdeps/unix/sysv/linux/arch-spawni.h
> @@ -47,14 +47,6 @@
> child has either exec'ed successfully or exited. */
>
>
> -/* The Unix standard contains a long explanation of the way to signal
> - an error after the fork() was successful. Since no new wait status
> - was wanted there is no way to signal an error using one of the
> - available methods. The committee chose to signal an error by a
> - normal program exit with the exit code 127. */
> -#define SPAWN_ERROR 127
> -
> -
> struct posix_spawn_args
> {
> internal_sigset_t oldmask;
> @@ -483,11 +475,11 @@ __spawnix (int *pid, const char *file,
>
> /* Spawn a new process executing PATH with the attributes describes in *ATTRP.
> Before running the process perform the actions described in FILE-ACTIONS. */
> -int
> -__spawni (pid_t * pid, const char *file,
> - const posix_spawn_file_actions_t * acts,
> - const posix_spawnattr_t * attrp, char *const argv[],
> - char *const envp[], int xflags)
> +static int
> +__arch_spawni (pid_t * pid, const char *file,
> + const posix_spawn_file_actions_t * acts,
> + const posix_spawnattr_t * attrp, char *const argv[],
> + char *const envp[], int xflags)
> {
> /* It uses __execvpex to avoid run ENOEXEC in non compatibility mode (it
> will be handled by maybe_script_execute). */
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 07/13] stdlib: Add internal __getenvarray function
2024-07-28 19:02 ` [PATCH v2 07/13] stdlib: Add internal __getenvarray function Florian Weimer
@ 2024-10-30 16:21 ` Adhemerval Zanella Netto
2024-10-30 16:38 ` Florian Weimer
0 siblings, 1 reply; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-10-30 16:21 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
On 28/07/24 16:02, Florian Weimer wrote:
> ---
> stdlib/Makefile | 1 +
> stdlib/getenvarray.c | 64 ++++++++++++++++++++++++++++++++++++++++++++
> stdlib/setenv.h | 7 +++++
> 3 files changed, 72 insertions(+)
> create mode 100644 stdlib/getenvarray.c
>
> diff --git a/stdlib/Makefile b/stdlib/Makefile
> index 697b547540..e1f1848439 100644
> --- a/stdlib/Makefile
> +++ b/stdlib/Makefile
> @@ -79,6 +79,7 @@ routines := \
> getcontext \
> getentropy \
> getenv \
> + getenvarray \
> getrandom \
> getsubopt \
> jrand48 \
> diff --git a/stdlib/getenvarray.c b/stdlib/getenvarray.c
> new file mode 100644
> index 0000000000..cac308fb32
> --- /dev/null
> +++ b/stdlib/getenvarray.c
> @@ -0,0 +1,64 @@
> +/* Obtain a consistent copy of the environ array.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <atomic.h>
> +#include <setenv.h>
> +#include <unistd.h>
> +
> +size_t
> +__getenvarray (char **result, size_t length)
> +{
> + while (true)
> + {
> + /* The snapshotting approach follows getenv. Duplicates are
> + filtered out explicitly. */
> + environ_counter start_counter = atomic_load_acquire (&__environ_counter);
> + char **start_environ = atomic_load_relaxed (&__environ);
> + if (start_environ == NULL)
> + return 0;
> +
> + const char *previous = NULL;
> + size_t count = 0;
> + for (char *const *ep = start_environ; ; ++ep)
> + {
> + char *entry = atomic_load_relaxed (ep);
> + if (entry == NULL)
> + break;
> + if (entry == previous)
> + /* Filter out a duplicate created by concurrent unsetenv. */
> + continue;
> + if (count < length)
> + result[count] = entry;
> + ++count;
> + previous = entry;
> + }
> +
> + atomic_thread_fence_acquire ();
> +
> + if (atomic_load_acquire (&__environ_counter) != start_counter)
> + /* Retry if there has been more than one write to the array
> + from within unsetenv. Writes in setenv/putenv do not
> + matter because they do not change the key, so they cannot
> + introduce duplicates. */
> + continue;
> +
> + if (count < length)
> + result[count] = NULL;
> + return count;
> + }
> +}
> diff --git a/stdlib/setenv.h b/stdlib/setenv.h
> index 3919943f87..69fa9367ed 100644
> --- a/stdlib/setenv.h
> +++ b/stdlib/setenv.h
> @@ -47,6 +47,13 @@ __environ_is_from_array_list (char **ep)
> return eal != NULL && &eal->array[0] == ep;
> }
>
> +/* Make a copy of the environ array. Writes up to LENGTH pointers to
> + *RESULT, including a terminating null pointer if there is room.
> + *Returns the total number of non-null elements in the environ
> + *array. This number can be larger than LENGTH. */
I am not sure returning a partial copy is really a good interface,
specially because it won't be NULL terminated if there is no size.
Maybe it would be better to just return -1 if there is no size,
even if input array is partially written.
> +size_t __getenvarray (char **result, size_t length)
> + __attr_access ((__write_only__, 1, 2)) attribute_hidden;
> +
> /* Counter for detecting concurrent modification in unsetenv.
> Ideally, this should be a 64-bit counter that cannot wrap around,
> but given that counter wrapround is probably impossible to hit
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 07/13] stdlib: Add internal __getenvarray function
2024-10-30 16:21 ` Adhemerval Zanella Netto
@ 2024-10-30 16:38 ` Florian Weimer
2024-10-30 20:11 ` Adhemerval Zanella Netto
0 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-10-30 16:38 UTC (permalink / raw)
To: Adhemerval Zanella Netto; +Cc: libc-alpha
* Adhemerval Zanella Netto:
>> diff --git a/stdlib/setenv.h b/stdlib/setenv.h
>> index 3919943f87..69fa9367ed 100644
>> --- a/stdlib/setenv.h
>> +++ b/stdlib/setenv.h
>> @@ -47,6 +47,13 @@ __environ_is_from_array_list (char **ep)
>> return eal != NULL && &eal->array[0] == ep;
>> }
>>
>> +/* Make a copy of the environ array. Writes up to LENGTH pointers to
>> + *RESULT, including a terminating null pointer if there is room.
>> + *Returns the total number of non-null elements in the environ
>> + *array. This number can be larger than LENGTH. */
(I see that Emacs has botched the comment formatting.)
> I am not sure returning a partial copy is really a good interface,
> specially because it won't be NULL terminated if there is no size.
> Maybe it would be better to just return -1 if there is no size,
> even if input array is partially written.
Then I would have to add a size_t *lengthp argument that receives the
actual size, otherwise there is no good way for the caller to get it.
The getgroups function allows a caller to query to the number of groups
by specifying a zero size, but that does not seem much better.
Thanks,
Florian
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 07/13] stdlib: Add internal __getenvarray function
2024-10-30 16:38 ` Florian Weimer
@ 2024-10-30 20:11 ` Adhemerval Zanella Netto
0 siblings, 0 replies; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-10-30 20:11 UTC (permalink / raw)
To: Florian Weimer; +Cc: libc-alpha
On 30/10/24 13:38, Florian Weimer wrote:
> * Adhemerval Zanella Netto:
>
>>> diff --git a/stdlib/setenv.h b/stdlib/setenv.h
>>> index 3919943f87..69fa9367ed 100644
>>> --- a/stdlib/setenv.h
>>> +++ b/stdlib/setenv.h
>>> @@ -47,6 +47,13 @@ __environ_is_from_array_list (char **ep)
>>> return eal != NULL && &eal->array[0] == ep;
>>> }
>>>
>>> +/* Make a copy of the environ array. Writes up to LENGTH pointers to
>>> + *RESULT, including a terminating null pointer if there is room.
>>> + *Returns the total number of non-null elements in the environ
>>> + *array. This number can be larger than LENGTH. */
>
> (I see that Emacs has botched the comment formatting.)
>
>> I am not sure returning a partial copy is really a good interface,
>> specially because it won't be NULL terminated if there is no size.
>> Maybe it would be better to just return -1 if there is no size,
>> even if input array is partially written.
>
> Then I would have to add a size_t *lengthp argument that receives the
> actual size, otherwise there is no good way for the caller to get it.
> The getgroups function allows a caller to query to the number of groups
> by specifying a zero size, but that does not seem much better.
It is not clear to me why you need to get the length result, are we aiming
to support partial __getenvarray calls (sorry if I haven't read the patch
in full, it might be something later in the patch)?
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 01/13] stdlib: Make getenv thread-safe in more cases
2024-10-16 17:30 ` Adhemerval Zanella Netto
@ 2024-11-18 9:54 ` Florian Weimer
2024-11-20 13:25 ` Adhemerval Zanella Netto
0 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-11-18 9:54 UTC (permalink / raw)
To: Adhemerval Zanella Netto; +Cc: libc-alpha
* Adhemerval Zanella Netto:
>> +struct environ_array *__environ_array_list;
>> +environ_counter __environ_counter;
>> +
>> char *
>> getenv (const char *name)
>> {
>> - if (__environ == NULL || name[0] == '\0')
>> - return NULL;
>> -
>> - size_t len = strlen (name);
>> - for (char **ep = __environ; *ep != NULL; ++ep)
>> + while (true)
>> {
>> - if (name[0] == (*ep)[0]
>> - && strncmp (name, *ep, len) == 0 && (*ep)[len] == '=')
>> - return *ep + len + 1;
>> - }
>> + /* Used to deal with concurrent unsetenv. */
>> + environ_counter start_counter = atomic_load_acquire (&__environ_counter);
>> +
>> + /* We use relaxed MO here because we assume that loads carry a
>
> This comment is confusing, the C11 memory model and Hans Boehm's paper
> you reference for the thread fence states that MO adds no happens-before
> ordering. So it is not clear that load dependency here really synchronizes
> with the release store.
>
> My understanding, reading the paper (and I am far from the memory
> synchronization expert) is MO is suffice to avoid data races for the
> data access; but in the end is the seqlock with a release MO that keep
> consistency.
Would this be clearer?
/* We use relaxed MO for loading the string pointers because we
assume the strings themselves are immutable and that loads
throigh 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. */
As far as I understand it, the standard pattern for publishing immutable
objects is release MO store for the pointer combined with relaxed MO
loads. The dependency ensures that loads through the object pointer to
do not observe incomplete initialization.
>> + /* 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)
>
> It is not clear why a load acquire is required here, since on the section 5
> of the very paper it uses a MO load after the atomic_thread_fence_acquire.
I'm just following what we are doing in _dlfo_read_success, which is the
closest code we have?
>> + /* 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 remaing after the removals, and
>
> s/remaing/remaining
Fixed.
>
>> + 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], ...
>>
>> - return NULL;
>> + (the N[k] are optional.) Let's assume that
>
> Is 'k' the key env. var we are looking here? It is not clear from the
> comment.
No, I changed it to:
(the N[0], N[1], .... are optional.) Let's assume that
>> + 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. */
>
> I must be sincere that I haven't grokked all the rationale that
> returning NULL is correct here. Is looping just wasteful (as the paper
> uses for the reader example) or is it incorrect here?
We still loop if there is more than one write. The difference to the
paper is that we tolerate one write. Otherwise, we'd need two copies of
the array for async-signal-safety because a more traditional seqlock is
not async-signal-safe, and a unmodified copy would have to be used if an
update is in progress.
>> diff --git a/stdlib/putenv.c b/stdlib/putenv.c
>> index b234ce0612..c61beb226f 100644
>> --- a/stdlib/putenv.c
>> +++ b/stdlib/putenv.c
>> @@ -23,6 +23,8 @@
>> # include <config.h>
>> #endif
>>
>> +#include <setenv.h>
>> +
>
> Why is this required for this patch?
Leftover, now fixed.
>
>> #if _LIBC || HAVE_STDLIB_H
>> # include <stdlib.h>
>> #endif
>> diff --git a/stdlib/setenv.c b/stdlib/setenv.c
>> index e2164371ad..6bd0596b79 100644
>> --- a/stdlib/setenv.c
>> +++ b/stdlib/setenv.c
>> @@ -19,6 +19,9 @@
>> # include <config.h>
>> #endif
>>
>> +#include <assert.h>
>> +#include <setenv.h>
>> +
>> /* Pacify GCC; see the commentary about VALLEN below. This is needed
>> at least through GCC 4.9.2. Pacify GCC for the entire file, as
>> there seems to be no way to pacify GCC selectively, only for the
>> @@ -100,25 +103,51 @@ static void *known_values;
>>
>> #endif
>>
>
> As a side note, can we clean up all the unrequired definitions from
> gnulib now that we have a quite different implementation? The multiple
> ifdef is really just clutter for glibc and only makes the implementation
> harder to read.
Agreed. We can do that afterwards, I think.
>> + /* Copy over the __environ array contents. This is a
>> + backwards copy if __environ points into
>> + target_array->array, and it does not clobber values still
>> + needing copying. This handles the case start_environ
>> + == ep == NULL, too. */
>
> It is not clear why you need to copy backwards here.
A backwards copy is required because of the potential overlap. An
application might do this:
environ = &environ[1];
I'm not happy that we need to support this.
>> clearenv (void)
>> {
>> LOCK;
>> + char **start_environ = atomic_load_relaxed (&__environ);
>> + if (__environ_is_from_array_list (start_environ))
>> {
>> + /* Store null pointers to avoid strange effects when the array
>> + is reused in setenv. */
>> + for (char **ep = start_environ; *ep != NULL; ++ep)
>> + atomic_store_relaxed(ep, NULL);
>
> Missing space after macro name.
Fixed.
>> + /* Update the counter similar to unsetenv, so that the writes in
>> + setenv do not need to update the counter. */
>> + atomic_store_release (&__environ_counter,
>> + atomic_load_relaxed (&__environ_counter) + 1);
>
> Why not atomic_fetch_add_release here (as indicated by the paper)?
We don't need a CAS because we have the environment lock. So no
concurrent change to __environ_counter is possible.
>> }
>> -
>> - /* Clear the environment pointer removes the whole environment. */
>> - __environ = NULL;
>> -
>> + atomic_store_relaxed (&__environ, NULL);
>> UNLOCK;
>> -
>
> Spurious new line removal.
Fixed.
>> diff --git a/stdlib/setenv.h b/stdlib/setenv.h
>> new file mode 100644
>> index 0000000000..3919943f87
>> --- /dev/null
>> +++ b/stdlib/setenv.h
>> +/* Returns true if EP (which should be an __environ value) is a
>> + pointer managed by setenv. */
>> +static inline _Bool
>
> This is an internal header, it is ok to use 'bool' since it is already being
> included.
Fixed.
>> diff --git a/stdlib/tst-getenv-signal.c b/stdlib/tst-getenv-signal.c
>> new file mode 100644
>> index 0000000000..bd35f886b5
>> --- /dev/null
>> +++ b/stdlib/tst-getenv-signal.c
>> @@ -0,0 +1,94 @@
>> +/* Test getenv from a signal handler interrupting environent updates.
>
> s/environent/environment
Fixed.
Thanks,
Florian
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 01/13] stdlib: Make getenv thread-safe in more cases
2024-11-18 9:54 ` Florian Weimer
@ 2024-11-20 13:25 ` Adhemerval Zanella Netto
2024-11-20 18:36 ` Florian Weimer
0 siblings, 1 reply; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-11-20 13:25 UTC (permalink / raw)
To: Florian Weimer; +Cc: libc-alpha
On 18/11/24 06:54, Florian Weimer wrote:
> * Adhemerval Zanella Netto:
>
>>> +struct environ_array *__environ_array_list;
>>> +environ_counter __environ_counter;
>>> +
>>> char *
>>> getenv (const char *name)
>>> {
>>> - if (__environ == NULL || name[0] == '\0')
>>> - return NULL;
>>> -
>>> - size_t len = strlen (name);
>>> - for (char **ep = __environ; *ep != NULL; ++ep)
>>> + while (true)
>>> {
>>> - if (name[0] == (*ep)[0]
>>> - && strncmp (name, *ep, len) == 0 && (*ep)[len] == '=')
>>> - return *ep + len + 1;
>>> - }
>>> + /* Used to deal with concurrent unsetenv. */
>>> + environ_counter start_counter = atomic_load_acquire (&__environ_counter);
>>> +
>>> + /* We use relaxed MO here because we assume that loads carry a
>>
>> This comment is confusing, the C11 memory model and Hans Boehm's paper
>> you reference for the thread fence states that MO adds no happens-before
>> ordering. So it is not clear that load dependency here really synchronizes
>> with the release store.
>>
>> My understanding, reading the paper (and I am far from the memory
>> synchronization expert) is MO is suffice to avoid data races for the
>> data access; but in the end is the seqlock with a release MO that keep
>> consistency.
>
> Would this be clearer?
>
> /* We use relaxed MO for loading the string pointers because we
> assume the strings themselves are immutable and that loads
> throigh the string pointers carry a dependency. (This
s/throigh/through
> depends on the the release MO store to __environ in
> __add_to_environ.) Objects pointed to by pointers stored in
Maybe put the full stop after the closing parenthesis.
> 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. */
>
> As far as I understand it, the standard pattern for publishing immutable
> objects is release MO store for the pointer combined with relaxed MO
> loads. The dependency ensures that loads through the object pointer to
> do not observe incomplete initialization.
Right, it is more clear now.
>
>>> + /* 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)
>>
>> It is not clear why a load acquire is required here, since on the section 5
>> of the very paper it uses a MO load after the atomic_thread_fence_acquire.
>
> I'm just following what we are doing in _dlfo_read_success, which is the
> closest code we have?
Fair enough, I think acquire here shouldn't be a problem.
>
>>> + /* 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 remaing after the removals, and
>>
>> s/remaing/remaining
>
> Fixed.
>
>>
>>> + 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], ...
>>>
>>> - return NULL;
>>> + (the N[k] are optional.) Let's assume that
>>
>> Is 'k' the key env. var we are looking here? It is not clear from the
>> comment.
>
> No, I changed it to:
>
> (the N[0], N[1], .... are optional.) Let's assume that
Ok.
>
>>> + 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. */
>>
>> I must be sincere that I haven't grokked all the rationale that
>> returning NULL is correct here. Is looping just wasteful (as the paper
>> uses for the reader example) or is it incorrect here?
>
> We still loop if there is more than one write. The difference to the
> paper is that we tolerate one write. Otherwise, we'd need two copies of
> the array for async-signal-safety because a more traditional seqlock is
> not async-signal-safe, and a unmodified copy would have to be used if an
> update is in progress.
It is more clear now, maybe add this very explanation at the end of the comment.
>
>>> diff --git a/stdlib/putenv.c b/stdlib/putenv.c
>>> index b234ce0612..c61beb226f 100644
>>> --- a/stdlib/putenv.c
>>> +++ b/stdlib/putenv.c
>>> @@ -23,6 +23,8 @@
>>> # include <config.h>
>>> #endif
>>>
>>> +#include <setenv.h>
>>> +
>>
>> Why is this required for this patch?
>
> Leftover, now fixed.
>
>>
>>> #if _LIBC || HAVE_STDLIB_H
>>> # include <stdlib.h>
>>> #endif
>>> diff --git a/stdlib/setenv.c b/stdlib/setenv.c
>>> index e2164371ad..6bd0596b79 100644
>>> --- a/stdlib/setenv.c
>>> +++ b/stdlib/setenv.c
>>> @@ -19,6 +19,9 @@
>>> # include <config.h>
>>> #endif
>>>
>>> +#include <assert.h>
>>> +#include <setenv.h>
>>> +
>>> /* Pacify GCC; see the commentary about VALLEN below. This is needed
>>> at least through GCC 4.9.2. Pacify GCC for the entire file, as
>>> there seems to be no way to pacify GCC selectively, only for the
>>> @@ -100,25 +103,51 @@ static void *known_values;
>>>
>>> #endif
>>>
>>
>> As a side note, can we clean up all the unrequired definitions from
>> gnulib now that we have a quite different implementation? The multiple
>> ifdef is really just clutter for glibc and only makes the implementation
>> harder to read.
>
> Agreed. We can do that afterwards, I think.
>
>>> + /* Copy over the __environ array contents. This is a
>>> + backwards copy if __environ points into
>>> + target_array->array, and it does not clobber values still
>>> + needing copying. This handles the case start_environ
>>> + == ep == NULL, too. */
>>
>> It is not clear why you need to copy backwards here.
>
> A backwards copy is required because of the potential overlap. An
> application might do this:
>
> environ = &environ[1];
>
> I'm not happy that we need to support this.
Right, maybe also add a note why backwards copy is used here.
>
>>> clearenv (void)
>>> {
>>> LOCK;
>>> + char **start_environ = atomic_load_relaxed (&__environ);
>>> + if (__environ_is_from_array_list (start_environ))
>>> {
>>> + /* Store null pointers to avoid strange effects when the array
>>> + is reused in setenv. */
>>> + for (char **ep = start_environ; *ep != NULL; ++ep)
>>> + atomic_store_relaxed(ep, NULL);
>>
>> Missing space after macro name.
>
> Fixed.
>
>>> + /* Update the counter similar to unsetenv, so that the writes in
>>> + setenv do not need to update the counter. */
>>> + atomic_store_release (&__environ_counter,
>>> + atomic_load_relaxed (&__environ_counter) + 1);
>>
>> Why not atomic_fetch_add_release here (as indicated by the paper)?
>
> We don't need a CAS because we have the environment lock. So no
> concurrent change to __environ_counter is possible.
Ack.
>
>>> }
>>> -
>>> - /* Clear the environment pointer removes the whole environment. */
>>> - __environ = NULL;
>>> -
>>> + atomic_store_relaxed (&__environ, NULL);
>>> UNLOCK;
>>> -
>>
>> Spurious new line removal.
>
> Fixed.
>
>>> diff --git a/stdlib/setenv.h b/stdlib/setenv.h
>>> new file mode 100644
>>> index 0000000000..3919943f87
>>> --- /dev/null
>>> +++ b/stdlib/setenv.h
>
>>> +/* Returns true if EP (which should be an __environ value) is a
>>> + pointer managed by setenv. */
>>> +static inline _Bool
>>
>> This is an internal header, it is ok to use 'bool' since it is already being
>> included.
>
> Fixed.
>
>>> diff --git a/stdlib/tst-getenv-signal.c b/stdlib/tst-getenv-signal.c
>>> new file mode 100644
>>> index 0000000000..bd35f886b5
>>> --- /dev/null
>>> +++ b/stdlib/tst-getenv-signal.c
>>> @@ -0,0 +1,94 @@
>>> +/* Test getenv from a signal handler interrupting environent updates.
>>
>> s/environent/environment
>
> Fixed.
>
> Thanks,
> Florian
>
I think it should be ok to install this with the nits below fixed. I will
try to check on the rest of the patchset.
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 01/13] stdlib: Make getenv thread-safe in more cases
2024-11-20 13:25 ` Adhemerval Zanella Netto
@ 2024-11-20 18:36 ` Florian Weimer
0 siblings, 0 replies; 39+ messages in thread
From: Florian Weimer @ 2024-11-20 18:36 UTC (permalink / raw)
To: Adhemerval Zanella Netto; +Cc: libc-alpha
* Adhemerval Zanella Netto:
>> Would this be clearer?
>>
>> /* We use relaxed MO for loading the string pointers because we
>> assume the strings themselves are immutable and that loads
>> throigh the string pointers carry a dependency. (This
>
> s/throigh/through
Fixed.
>> depends on the the release MO store to __environ in
>> __add_to_environ.) Objects pointed to by pointers stored in
>
> Maybe put the full stop after the closing parenthesis.
That would be unusual because the full setence is parenthesized.
>>>> + In summary, if there was at most one write, a negative
>>>> + result is a true negative, and we can return NULL. */
>>>
>>> I must be sincere that I haven't grokked all the rationale that
>>> returning NULL is correct here. Is looping just wasteful (as the paper
>>> uses for the reader example) or is it incorrect here?
>>
>> We still loop if there is more than one write. The difference to the
>> paper is that we tolerate one write. Otherwise, we'd need two copies of
>> the array for async-signal-safety because a more traditional seqlock is
>> not async-signal-safe, and a unmodified copy would have to be used if an
>> update is in progress.
>
> It is more clear now, maybe add this very explanation at the end of
> the comment.
I'll add it.
>>>> + /* Copy over the __environ array contents. This is a
>>>> + backwards copy if __environ points into
>>>> + target_array->array, and it does not clobber values still
>>>> + needing copying. This handles the case start_environ
>>>> + == ep == NULL, too. */
>>>
>>> It is not clear why you need to copy backwards here.
>>
>> A backwards copy is required because of the potential overlap. An
>> application might do this:
>>
>> environ = &environ[1];
>>
>> I'm not happy that we need to support this.
>
> Right, maybe also add a note why backwards copy is used here.
I confused forwards and backwards. This is a forward copy, but it
slides the array backwards. I think it is still correct. I updated
comment to:
/* Copy over the __environ array contents. This forward
copy slides backwards part of the array if __environ
points into target_array->array. This happens if an
application makes an assignment like:
environ = &environ[1];
The forward copy avoids clobbering values that still
needing copying. This code handles the case
start_environ == ep == NULL, too. */
> I think it should be ok to install this with the nits below fixed. I
> will try to check on the rest of the patchset.
Thanks, I'll repost one final time (hopefully).
Florian
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 08/13] support: Add support_count_maps
2024-07-28 19:03 ` [PATCH v2 08/13] support: Add support_count_maps Florian Weimer
@ 2024-11-27 13:07 ` Adhemerval Zanella Netto
2024-11-29 13:35 ` Florian Weimer
0 siblings, 1 reply; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-11-27 13:07 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
LGTM, some suggestion below.
On 28/07/24 16:03, Florian Weimer wrote:
> ---
> support/Makefile | 2 ++
> support/support.h | 5 +++
> support/support_count_maps.c | 51 +++++++++++++++++++++++++++++++
> support/tst-support_count_maps.c | 52 ++++++++++++++++++++++++++++++++
> 4 files changed, 110 insertions(+)
> create mode 100644 support/support_count_maps.c
> create mode 100644 support/tst-support_count_maps.c
>
> diff --git a/support/Makefile b/support/Makefile
> index aa57207bdc..bc35faa325 100644
> --- a/support/Makefile
> +++ b/support/Makefile
> @@ -53,6 +53,7 @@ libsupport-routines = \
> support_chroot \
> support_copy_file \
> support_copy_file_range \
> + support_count_maps \
> support_create_timer \
> support_descriptor_supports_holes \
> support_descriptors \
> @@ -319,6 +320,7 @@ tests = \
> tst-support-process_state \
> tst-support_blob_repeat \
> tst-support_capture_subprocess \
> + tst-support_count_maps \
> tst-support_descriptors \
> tst-support_format_dns_packet \
> tst-support_quote_blob \
> diff --git a/support/support.h b/support/support.h
> index ba21ec9b5a..5769e8c5f3 100644
> --- a/support/support.h
> +++ b/support/support.h
> @@ -96,6 +96,11 @@ int support_descriptor_supports_holes (int fd);
> WHY_MSG as part of the diagnostic. */
> void support_need_proc (const char *why_msg);
>
> +/* Counts the number of mappings in the process address space.
> + Currently implemented using /proc/self/maps on Linux, so it needs
> + that to exist. */
> +size_t support_count_maps (void);
> +
> /* Error-checking wrapper functions which terminate the process on
> error. */
>
> diff --git a/support/support_count_maps.c b/support/support_count_maps.c
> new file mode 100644
> index 0000000000..2a6dd9fee3
> --- /dev/null
> +++ b/support/support_count_maps.c
> @@ -0,0 +1,51 @@
> +/* Count the number of mappings.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <support/support.h>
> +#include <support/check.h>
> +#include <support/xstdio.h>
> +#include <errno.h>
> +
> +size_t
> +support_count_maps (void)
> +{
Maybe add a support_need_proc().
> + FILE *fp = fopen ("/proc/self/maps", "r");
> + if (fp == NULL)
> + {
> + if (errno == ENOENT)
> + FAIL_UNSUPPORTED ("support_count_maps: test needs /proc/self/maps");
> + else
> + FAIL_EXIT1 ("cannot open /proc/self/maps: %m");
> + }
And then you can just use xfopen here.
> + size_t count = 0;
> + while (true)
> + {
> + switch (getc_unlocked (fp))
> + {
> + case EOF:
> + /* Error will be reported by xfclose below. */
> + goto out;
> + case '\n':
> + ++count;
> + break;
> + }
> + }
> + out:
> + xfclose (fp);
> + return count;
> +}
> diff --git a/support/tst-support_count_maps.c b/support/tst-support_count_maps.c
> new file mode 100644
> index 0000000000..e904a00cd4
> --- /dev/null
> +++ b/support/tst-support_count_maps.c
> @@ -0,0 +1,52 @@
> +/* Test counting the number of mappings.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <stdio.h>
> +#include <support/support.h>
> +#include <support/check.h>
> +#include <support/xstdio.h>
> +#include <sys/mman.h>
> +#include <support/xunistd.h>
> +
> +static int
> +do_test (void)
> +{
> + long int page_size = xsysconf (_SC_PAGESIZE);
> +
> + size_t initial = support_count_maps ();
> + printf ("info: initial number of maps: %zu\n", initial);
> +
> + void *ptr = xmmap (NULL, 2 * page_size, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1);
> + size_t one_mapping = support_count_maps ();
> + printf ("info: new number of maps: %zu\n", one_mapping);
> + TEST_COMPARE (initial + 1, one_mapping);
> +
> + /* The mapping gets split due to the protection flags change. */
> + xmprotect (ptr, page_size, PROT_READ);
> + size_t two_mappings = support_count_maps ();
> + TEST_COMPARE (initial + 2, two_mappings);
> +
> + xmunmap (ptr, 2 * page_size);
> + size_t final = support_count_maps ();
> + TEST_COMPARE (initial, final);
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 09/13] posix: New tst-vfork-mappings test case
2024-07-28 19:03 ` [PATCH v2 09/13] posix: New tst-vfork-mappings test case Florian Weimer
@ 2024-11-27 14:07 ` Adhemerval Zanella Netto
0 siblings, 0 replies; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-11-27 14:07 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
Look good, some suggestions below.
On 28/07/24 16:03, Florian Weimer wrote:
> It checks if vfork followed by execve leaves additional memory mappings
> behind.
> ---
> posix/Makefile | 2 +
> posix/tst-vfork-mappings.c | 195 +++++++++++++++++++++++++++++++++++++
> 2 files changed, 197 insertions(+)
> create mode 100644 posix/tst-vfork-mappings.c
>
> diff --git a/posix/Makefile b/posix/Makefile
> index 2c598cd20a..ed3c9d0659 100644
> --- a/posix/Makefile
> +++ b/posix/Makefile
> @@ -319,6 +319,7 @@ tests := \
> tst-sysconf-empty-chroot \
> tst-truncate \
> tst-truncate64 \
> + tst-vfork-mappings \
> tst-vfork1 \
> tst-vfork2 \
> tst-wait3 \
> @@ -635,6 +636,7 @@ $(objpfx)runptests.o: $(objpfx)ptestcases.h
>
> $(objpfx)tst-getopt-cancel: $(shared-thread-library)
> $(objpfx)tst-_Fork: $(shared-thread-library)
> +$(objpfx)tst-vfork-mappings: $(shared-thread-library)
>
> test-xfail-annexc = yes
> $(objpfx)annexc.out: $(objpfx)annexc
> diff --git a/posix/tst-vfork-mappings.c b/posix/tst-vfork-mappings.c
> new file mode 100644
> index 0000000000..fcab423383
> --- /dev/null
> +++ b/posix/tst-vfork-mappings.c
> @@ -0,0 +1,195 @@
> +/* Test that vfork, *exec* does not accumulate mappings.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <array_length.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/xthread.h>
> +#include <support/xunistd.h>
> +#include <sys/mman.h>
> +#include <spawn.h>
> +#include <unistd.h>
> +
> +/* 0 for direct calls, 1 for using an empheral thread. */
Maybe ephemeral here?
> +static int use_thread;
> +
> +/* Wrapper used by test_one to invoke the callback on a new thread. */
> +static void *
> +thread_wrapper (void *action)
> +{
> + ((void (*) (void)) action) ();
> + return NULL;
> +}
> +
> +static void
> +test_one (const char *label, void (*action) (void))
> +{
> + printf ("info: testing %s%s\n",
> + label, use_thread ? " (threading mode)" : "");
> + size_t before = support_count_maps ();
> +
> + /* Create mappings which should prevent merging of any persistant
s/persistant/persistent
> + new mappings, so that the count actually goes up in case of a
> + leak. */
> + void *gap_maps[50];
> +
> + for (int i = 0; i < array_length (gap_maps); )
> + {
> + gap_maps[i++] = xmmap (NULL, 1, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1);
> + gap_maps[i++] = xmmap (NULL, 1, PROT_NONE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1);
> + if (use_thread)
> + xpthread_join (xpthread_create (NULL, thread_wrapper, action));
> + else
> + action ();
> + }
> +
> + for (int i = 0; i < array_length (gap_maps); ++i)
> + xmunmap (gap_maps[i], 1);
> +
> + size_t afterwards = support_count_maps ();
> + if (afterwards > before + 1)
> + {
> + printf ("error: %s: additional maps created: %zu (%zu -> %zu)\n",
> + label, afterwards - before, before, afterwards);
> + support_record_failure ();
> + }
> +}
> +
> +static void *
> +dummy_thread (void *ignored)
> +{
> + return NULL;
> +}
> +
> +static void
> +do_system (void)
> +{
> + TEST_COMPARE (system ("exit"), 0);
> +}
> +
> +static void
> +do_vfork_execl (void)
> +{
> + pid_t ret = vfork ();
> + if (ret == 0)
> + {
> + execl ("/bin/true", "true", NULL);
> + _exit (1);
> + }
> + if (ret < 0)
> + FAIL_EXIT1 ("vfork");
> + int status;
> + xwaitpid (ret, &status, 0);
> + TEST_COMPARE (status, 0);
> +}
> +
> +static void
> +do_vfork_execlp (void)
> +{
> + pid_t ret = vfork ();
> + if (ret == 0)
> + {
> + execlp ("true", "true", NULL);
> + _exit (1);
> + }
> + if (ret < 0)
> + FAIL_EXIT1 ("vfork");
> + int status;
> + xwaitpid (ret, &status, 0);
> + TEST_COMPARE (status, 0);
> +}
> +
> +static void
> +do_posix_spawn (void)
> +{
> + pid_t pid;
> + char *const argv[] = { NULL };
> + TEST_COMPARE (posix_spawn (&pid, "/bin/true", NULL, NULL, argv, environ), 0);
> + int status;
> + xwaitpid (pid, &status, 0);
> + TEST_COMPARE (status, 0);
> +}
> +
> +static void
> +do_posix_spawnp (void)
> +{
> + pid_t pid;
> + char *const argv[] = { NULL };
> + TEST_COMPARE (posix_spawnp (&pid, "true", NULL, NULL, argv, environ), 0);
> + int status;
> + xwaitpid (pid, &status, 0);
> + TEST_COMPARE (status, 0);
> +}
> +
> +static int
> +do_test (void)
> +{
> + xpthread_join (xpthread_create (NULL, dummy_thread, NULL));
> +
> + for (int do_pass = 0; do_pass < 4; ++do_pass)
> + {
> + printf ("info: pass %d\n", do_pass);
> + switch (do_pass)
> + {
> + case 1:
> + /* Switch on multi-threaded mode. */
> + xpthread_join (xpthread_create (NULL, dummy_thread, NULL));
> + break;
> + case 2:
> + /* Stuff the environment with some values. Aim for
> + consuming at least one page of pointers. */
> + for (int i = 0; i <= 4096 / sizeof (char *) ; ++i)
Maybe use the system page instead of hardcoded 4096 here, since it might not
consume a extra page size on system with large pagesizes as 64k.
> + {
> + char buf[20];
Maybe use '1 + INT_BUFSIZE_BOUND (i)' instead of 20?
> + snprintf (buf, sizeof (buf), "V%d", i);
> + TEST_COMPARE (setenv (buf, buf + 1, 1), 0);
> + }
> + break;
> + case 3:
> + /* Stuff the environment with even more values. Aim for three
> + pages (for small page sizes). */
> + for (int i = 0; i <= 2 * 4096 / sizeof (char *); ++i)
> + {
> + char buf[20];
> + snprintf (buf, sizeof (buf), "U%d", i);
> + TEST_COMPARE (setenv (buf, buf + 1, 1), 0);
> + }
> + break;
> + }
> +
> + for (use_thread = 0; use_thread < 2; ++use_thread)
> + {
> + if (do_pass >= 3)
> + /* With many environment variables, bash becomes rather
> + slow, so skip testing it. */
> + test_one ("system", do_system);
> + test_one ("vfork/execl", do_vfork_execl);
> + test_one ("vfork/execlp", do_vfork_execlp);
> + test_one ("posix_spawn", do_posix_spawn);
> + test_one ("posix_spawnp", do_posix_spawnp);
> + }
> + }
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat
2024-07-28 19:03 ` [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat Florian Weimer
@ 2024-11-27 20:01 ` Adhemerval Zanella Netto
2024-11-28 8:22 ` Florian Weimer
0 siblings, 1 reply; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-11-27 20:01 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
On 28/07/24 16:03, Florian Weimer wrote:
> The environment snapshot is stored on the stack, for easy
> memory management if vfork is involved.
> ---
> posix/execveat.c | 27 ++-
> stdlib/Makefile | 18 +-
> stdlib/environ_single_threaded_no_snapshot.c | 40 ++++
> stdlib/setenv.h | 24 +-
> stdlib/tst-environ-execl.c | 35 +++
> stdlib/tst-environ-snapshot-skeleton.c | 218 +++++++++++++++++++
> 6 files changed, 354 insertions(+), 8 deletions(-)
> create mode 100644 stdlib/environ_single_threaded_no_snapshot.c
> create mode 100644 stdlib/tst-environ-execl.c
> create mode 100644 stdlib/tst-environ-snapshot-skeleton.c
>
> diff --git a/posix/execveat.c b/posix/execveat.c
> index 110177ea54..2cd275d02a 100644
> --- a/posix/execveat.c
> +++ b/posix/execveat.c
> @@ -21,15 +21,38 @@
> #include <unistd.h>
>
> #include <arch-execveat.h>
> +#include <stdlib/setenv.h>
>
> /* Replace the current process, executing PATH relative to difrd with
> arguments argv and environment envp.
> argv and envp are terminated by NULL pointers. */
> int
> -__execveat (int dirfd, const char *path, char *const argv[], char *const envp[],
> +__execveat (int dirfd, const char *path, char *const *argv, char *const *envp,
> int flags)
> {
> - return __arch_execveat (dirfd, path, argv, envp, flags);
> + if (envp == NULL || envp != __environ
Wouldn't 'envp != __environ' already handle the 'envp == NULL', assuming
that __environ is always non-NULL?
> + || !__environ_is_from_array_list ((char **) envp)
> + || __environ_single_threaded_no_snapshot (envp))
> + return __arch_execveat (dirfd, path, argv, envp, flags);
> +
> + size_t env_size = ENVIRON_STACK_SNAPSHOT_SIZE;
The size here is only the initial size, so maybe name it hinting it?
> +
> + while (true)
> + {
> + /* Using an on-stack buffer is not ideal, but it is the most
> + practical way to avoid memory leaks with vfork: New mappings
> + created before the execveat call will be preserved in the
> + original process before the vfork call even if the execve at
> + call succeeds and replaces the current process. */
Can't we use MADV_DONTFORK here? There still the small windows between
mmap return and madvise call, but I am not very confident that adding a possible
unbounded dynamic stack allocation is best way forward here.
> + ++env_size; /* Make room for the null terminator. */
> + char *env_copy[env_size];
> + size_t env_count = __getenvarray (env_copy, env_size);
> + if (env_count < env_size)
> + return __arch_execveat (dirfd, path, argv, env_copy, flags);
> +
> + /* Retry with larger buffer. */
> + env_size = env_count;
> + }
> }
>
> weak_alias (__execveat, execveat)
> diff --git a/stdlib/Makefile b/stdlib/Makefile
> index e1f1848439..498ec15e91 100644
> --- a/stdlib/Makefile
> +++ b/stdlib/Makefile
> @@ -72,6 +72,7 @@ routines := \
> drand48 \
> drand48-iter \
> drand48_r \
> + environ_single_threaded_no_snapshot \
> erand48 \
> erand48_r \
> exit \
> @@ -374,11 +375,20 @@ tests-container := \
> tst-system \
> #tests-container
>
> +# These tests re-exec themselves, so they need to be linked
> +# statically, or use harcoded paths.
> +tests-with-reexec := \
> + tst-environ-execl \
> + # tests-with-reexec
> +tests += $(tests-with-reexec)
> +
> ifeq ($(build-hardcoded-path-in-tests),yes)
> tests += \
> tst-empty-env \
> # tests
> -endif
> +else # !$(build-hardcoded-path-in-tests)
> +tests-static += $(tests-with-reexec)
> +endif # !$(build-hardcoded-path-in-tests)
>
> LDLIBS-test-atexit-race = $(shared-thread-library)
> LDLIBS-test-at_quick_exit-race = $(shared-thread-library)
> @@ -631,3 +641,9 @@ $(objpfx)tst-qsort5: $(libm)
> $(objpfx)tst-getenv-signal: $(shared-thread-library)
> $(objpfx)tst-getenv-thread: $(shared-thread-library)
> $(objpfx)tst-getenv-unsetenv: $(shared-thread-library)
> +# See above for $(tests-with-reexec).
> +ifeq ($(build-hardcoded-path-in-tests),yes)
> +$(objpfx)tst-environ-execl: $(shared-thread-library)
> +else
> +$(objpfx)tst-environ-execl: $(static-thread-library)
> +endif
> diff --git a/stdlib/environ_single_threaded_no_snapshot.c b/stdlib/environ_single_threaded_no_snapshot.c
> new file mode 100644
> index 0000000000..6dc1d22ea2
> --- /dev/null
> +++ b/stdlib/environ_single_threaded_no_snapshot.c
> @@ -0,0 +1,40 @@
> +/* Check for environ snapshots in single-threaded case.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <setenv.h>
> +#include <single-thread.h>
> +#include <tls.h>
> +
> +bool
> +__environ_single_threaded_no_snapshot (char *const *envp)
> +{
> + if (!SINGLE_THREAD_P)
> + return false;
> +
> + const char *previous = NULL;
> + for (char *const *ep = envp; *ep != NULL; ++ep)
> + {
> + if (*ep == previous)
> + /* Must perform a snapshot to remove the duplicate. */
> + return false;
> + previous = *ep;
> + }
> + /* No duplicates, no snapshot needed because no interference
> + from other threads is possible. */
> + return true;
> +}
> diff --git a/stdlib/setenv.h b/stdlib/setenv.h
> index 69fa9367ed..3e530f0bc0 100644
> --- a/stdlib/setenv.h
> +++ b/stdlib/setenv.h
> @@ -19,8 +19,10 @@
> #ifndef _SETENV_H
> #define _SETENV_H
>
> -#include <atomic.h>
> -#include <stdbool.h>
> +/* Not actually usable in rtld, but it is required for symbol discovery. */
> +#if IS_IN (libc) || IS_IN (rtld)
> +# include <atomic.h>
> +# include <stdbool.h>
>
> /* We use an exponential sizing policy for environment arrays. The
> arrays are not deallocating during the lifetime of the process.
> @@ -59,11 +61,11 @@ size_t __getenvarray (char **result, size_t length)
> but given that counter wrapround is probably impossible to hit
> (2**32 operations in unsetenv concurrently with getenv), using
> <atomic_wide_counter.h> seems unnecessary. */
> -#if __HAVE_64B_ATOMICS
> +# if __HAVE_64B_ATOMICS
> typedef uint64_t environ_counter;
> -#else
> +# else
> typedef uint32_t environ_counter;
> -#endif
> +# endif
>
> /* Updated by unsetenv to detect multiple overwrites in getenv. */
> extern environ_counter __environ_counter attribute_hidden;
> @@ -77,4 +79,16 @@ extern environ_counter __environ_counter attribute_hidden;
> int __add_to_environ (const char *name, const char *value,
> const char *combines, int replace) attribute_hidden;
>
> +/* Returns true if the environ pointer ENVP (which should be equal to
> + environ) does not need snapshotting because execution is
> + single-threaded and there are no duplicates in the array. */
> +bool __environ_single_threaded_no_snapshot (char *const *envp)
> + attribute_hidden;
> +
> +#endif /* IS_IN (libc) */
> +
> +/* This seems to cover the majority of situations on the first
> + pass and requires only a moderate amount of stack size. */
> +enum { ENVIRON_STACK_SNAPSHOT_SIZE = 120 };
> +
> #endif /* _SETENV_H */
> diff --git a/stdlib/tst-environ-execl.c b/stdlib/tst-environ-execl.c
> new file mode 100644
> index 0000000000..b028a94c7d
> --- /dev/null
> +++ b/stdlib/tst-environ-execl.c
> @@ -0,0 +1,35 @@
> +/* Test environment snapshots with execl.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#define PROGRAM "tst-environ-execl"
> +#include "tst-environ-snapshot-skeleton.c"
> +
> +static pid_t
> +create_process (void)
> +{
> + pid_t pid = vfork ();
> + if (pid < 0)
> + FAIL_EXIT1 ("vfork: %m");
> + if (pid == 0)
> + {
> + execl (self_path, self_path, "verify", NULL);
> + printf ("error: execl failed: %m\n");
> + _exit (17);
> + }
> + return pid;
> +}
> diff --git a/stdlib/tst-environ-snapshot-skeleton.c b/stdlib/tst-environ-snapshot-skeleton.c
> new file mode 100644
> index 0000000000..8548e0a9e0
> --- /dev/null
> +++ b/stdlib/tst-environ-snapshot-skeleton.c
> @@ -0,0 +1,218 @@
> +/* Test skeleton for environment snapshots.
> + Copyright (C) 2024 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + 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.
> +
> + 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
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +/* The actual test including this file needs to define the macro
> + PROGRAM as a string literal, the name of the test program, before
> + including this file.
> +
> + It laso needs to define a function
s/laso/also
> +
> + static pid_t create_process (void);
> +
> + after including this file, describing how the subprocess with the
> + environment snapshot is created. */
> +
> +#include <array_length.h>
> +#include <setenv.h>
> +#include <stddef.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/xthread.h>
> +#include <support/xunistd.h>
> +#include <unistd.h>
> +
> +static pid_t create_process (void);
> +
> +/* Absolute path to the executable. */
> +static char *self_path;
> +
> +/* Used to synchronize the start of each test iteration. */
> +static pthread_barrier_t barrier;
> +
> +/* These are the variables set by the main thread. This needs to
> + exceed the stack reservation for the snapshot, to exercise that
> + code path. Variables before start_variable are not removed in the
> + unsetenv loop. */
> +enum
> + {
> + on_stack_count = ENVIRON_STACK_SNAPSHOT_SIZE,
> + start_variable = 50,
> + };
> +static char *variables[start_variable + on_stack_count];
> +
> +/* Number of iterations. */
> +enum { iterations = 5000 };
> +
> +/* Check that even with concurrent unsetenv, a variable that is known
> + to be there is found. */
> +static void *
> +test_thread (void *ignored)
> +{
> + for (int i = 0; i < iterations; ++i)
> + {
> + xpthread_barrier_wait (&barrier);
> + pid_t pid = create_process ();
> + int status;
> + xwaitpid (pid, &status, 0);
> + TEST_COMPARE (status, 0);
> + xpthread_barrier_wait (&barrier);
> + }
> + return NULL;
> +}
> +
> +/* Called to verify the contents of the environment. */
> +static void
> +verify_environ (void)
> +{
> + TEST_COMPARE_STRING (getenv ("variable"), "value");
> + char *changing = getenv ("changing");
> + if (strcmp (changing, "value-1") != 0)
> + TEST_COMPARE_STRING (changing, "value-2");
> +
> + bool seen[array_length (variables)] = { 0, };
> + bool seen_changing = false;
> + bool seen_variable = false;
> + char **ep = environ;
> + for (int i = 0; i < start_variable; ++i)
> + {
> + char expected[20];
> + snprintf (expected, sizeof (expected), "V%d=%d", i, i);
> + TEST_COMPARE_STRING (ep[i], expected);
> + if (ep[i] == NULL)
> + break;
> + }
> +
> + if (*ep == NULL)
> + return;
> +
> + for (ep += start_variable; *ep != NULL; ++ep)
> + {
> + if (**ep == 'V')
> + {
> + char *eq = strchr (*ep, '=');
> + TEST_VERIFY (eq != NULL);
> + if (eq != NULL)
> + {
> + int idx = atoi (eq + 1);
> + TEST_VERIFY (idx >= 0);
> + TEST_VERIFY (idx < array_length (variables));
> + if (idx >= 0 && idx < array_length (variables))
> + {
> + char expected[20];
> + snprintf (expected, sizeof (expected), "V%d=%d", idx, idx);
> + TEST_COMPARE_STRING (*ep, expected);
> + if (seen[idx])
> + {
> + printf ("error: duplicate: %s\n", *ep);
> + support_record_failure ();
> + }
> + seen[idx] = true;
> + }
> + }
> + }
> + else if (strcmp (*ep, "changing=value-1") == 0
> + || strcmp (*ep, "changing=value-2") == 0)
> + {
> + if (seen_changing)
> + {
> + printf ("error: duplicate: %s\n", *ep);
> + support_record_failure ();
> + }
> + seen_changing = true;
> + }
> + else
> + {
> + TEST_COMPARE_STRING (*ep, "variable=value");
> + if (seen_variable)
> + {
> + printf ("error: duplicate: %s\n", *ep);
> + support_record_failure ();
> + }
> + seen_variable = true;
> + }
> + }
> +}
> +
> +/* In the prepare function, verify the environment contents after a
> + self-exec. */
> +#define PREPARE do_prepare
> +static void
> +do_prepare (int argc, char *argv[])
> +{
> + if (argc == 2 && strcmp (argv[1], "verify") == 0)
> + {
> + verify_environ ();
> +
> + /* Pass errors through to the other process. */
> + _exit (support_record_failure_is_failed () ? 1 : 0);
> + }
> +}
> +
> +static int
> +do_test (void)
> +{
> + self_path = xasprintf ("%s/stdlib/%s", support_objdir_root, PROGRAM);
> + xpthread_barrier_init (&barrier, NULL, 2);
> + pthread_t thr = xpthread_create (NULL, test_thread, NULL);
> +
> + for (int i = 0; i < array_length (variables); ++i)
> + variables[i] = xasprintf ("V%d", i);
> +
> + for (int i = 0; i < iterations; ++i)
> + {
> + int variable_count = array_length (variables);
> + if ((iterations % 2) == 0)
> + /* Use the on-stack code path in __environ_snapshot_get. */
> + variable_count = on_stack_count - 1;
> +
> + clearenv ();
> + for (int j = 0; j < variable_count; ++j)
> + {
> + /* Add the changing variable somewhere in the middle. */
> + if (j == start_variable + 5)
> + TEST_COMPARE (setenv ("changing", "value-2", 1), 0);
> + TEST_COMPARE (setenv (variables[j], variables[j] + 1, 1), 0);
> + }
> + TEST_COMPARE (setenv ("variable", "value", 1), 0);
> + xpthread_barrier_wait (&barrier);
> +
> + /* Leave a certain amount of variables in place. */
> + for (int j = start_variable; j < variable_count; ++j)
> + {
> + /* Every few iterations, change the variable. */
> + if ((j % 4) == 0)
> + TEST_COMPARE (setenv ("changing",
> + ((j / 4) % 2) == 0 ? "value-1" : "value-2",
> + 1), 0);
> + TEST_COMPARE (unsetenv (variables[j]), 0);
> + }
> +
> + xpthread_barrier_wait (&barrier);
> + }
> + xpthread_join (thr);
> + xpthread_barrier_destroy (&barrier);
> + for (int i = 0; i < array_length (variables); ++i)
> + free (variables[i]);
> + free (self_path);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat
2024-11-27 20:01 ` Adhemerval Zanella Netto
@ 2024-11-28 8:22 ` Florian Weimer
2024-11-28 17:41 ` Adhemerval Zanella Netto
0 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-11-28 8:22 UTC (permalink / raw)
To: Adhemerval Zanella Netto; +Cc: libc-alpha
* Adhemerval Zanella Netto:
>> - return __arch_execveat (dirfd, path, argv, envp, flags);
>> + if (envp == NULL || envp != __environ
>
> Wouldn't 'envp != __environ' already handle the 'envp == NULL', assuming
> that __environ is always non-NULL?
Is that guaranteed? Applications could assign NULL to environ:
<https://codesearch.debian.net/search?q=%5Csenviron%5Cs*%3D%5Cs*%28NULL%7C0%29&literal=0>
>> + || !__environ_is_from_array_list ((char **) envp)
>> + || __environ_single_threaded_no_snapshot (envp))
>> + return __arch_execveat (dirfd, path, argv, envp, flags);
>> +
>> + size_t env_size = ENVIRON_STACK_SNAPSHOT_SIZE;
>
> The size here is only the initial size, so maybe name it hinting it?
You mean for ENVIRON_STACK_SNAPSHOT_SIZE? That's why I put STACK there.
>> + while (true)
>> + {
>> + /* Using an on-stack buffer is not ideal, but it is the most
>> + practical way to avoid memory leaks with vfork: New mappings
>> + created before the execveat call will be preserved in the
>> + original process before the vfork call even if the execve at
>> + call succeeds and replaces the current process. */
>
> Can't we use MADV_DONTFORK here? There still the small windows between
> mmap return and madvise call, but I am not very confident that adding
> a possible unbounded dynamic stack allocation is best way forward
> here.
No, the vfork has already happened at this point. We would need
something to unmap the mapping on a successful execve call.
>> diff --git a/stdlib/tst-environ-snapshot-skeleton.c b/stdlib/tst-environ-snapshot-skeleton.c
>> new file mode 100644
>> index 0000000000..8548e0a9e0
>> + It laso needs to define a function
>
> s/laso/also
Fixed.
Thanks,
Florian
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat
2024-11-28 8:22 ` Florian Weimer
@ 2024-11-28 17:41 ` Adhemerval Zanella Netto
2024-11-28 18:41 ` Cristian Rodríguez
0 siblings, 1 reply; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-11-28 17:41 UTC (permalink / raw)
To: Florian Weimer; +Cc: libc-alpha
On 28/11/24 05:22, Florian Weimer wrote:
> * Adhemerval Zanella Netto:
>
>>> - return __arch_execveat (dirfd, path, argv, envp, flags);
>>> + if (envp == NULL || envp != __environ
>>
>> Wouldn't 'envp != __environ' already handle the 'envp == NULL', assuming
>> that __environ is always non-NULL?
>
> Is that guaranteed? Applications could assign NULL to environ:
>
> <https://codesearch.debian.net/search?q=%5Csenviron%5Cs*%3D%5Cs*%28NULL%7C0%29&literal=0>
Indeed, it is not guarantee for direct manipulation of environ.
>
>>> + || !__environ_is_from_array_list ((char **) envp)
>>> + || __environ_single_threaded_no_snapshot (envp))
>>> + return __arch_execveat (dirfd, path, argv, envp, flags);
>>> +
>>> + size_t env_size = ENVIRON_STACK_SNAPSHOT_SIZE;
>>
>> The size here is only the initial size, so maybe name it hinting it?
>
> You mean for ENVIRON_STACK_SNAPSHOT_SIZE? That's why I put STACK there.
Yes, I am wondering if ENVIRON_STACK_SNAPSHOT_INITIAL_SIZE would make
more sense.
>
>>> + while (true)
>>> + {
>>> + /* Using an on-stack buffer is not ideal, but it is the most
>>> + practical way to avoid memory leaks with vfork: New mappings
>>> + created before the execveat call will be preserved in the
>>> + original process before the vfork call even if the execve at
>>> + call succeeds and replaces the current process. */
>>
>> Can't we use MADV_DONTFORK here? There still the small windows between
>> mmap return and madvise call, but I am not very confident that adding
>> a possible unbounded dynamic stack allocation is best way forward
>> here.
>
> No, the vfork has already happened at this point. We would need
> something to unmap the mapping on a successful execve call.
Right, and it is not clear that vfork would always execv*. I still
don't like this stack solution, but I also don't have a better solution.
>
>>> diff --git a/stdlib/tst-environ-snapshot-skeleton.c b/stdlib/tst-environ-snapshot-skeleton.c
>>> new file mode 100644
>>> index 0000000000..8548e0a9e0
>
>>> + It laso needs to define a function
>>
>> s/laso/also
>
> Fixed.
>
> Thanks,
> Florian
>
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat
2024-11-28 17:41 ` Adhemerval Zanella Netto
@ 2024-11-28 18:41 ` Cristian Rodríguez
2024-11-28 19:01 ` Adhemerval Zanella Netto
2024-11-28 19:02 ` Florian Weimer
0 siblings, 2 replies; 39+ messages in thread
From: Cristian Rodríguez @ 2024-11-28 18:41 UTC (permalink / raw)
To: Adhemerval Zanella Netto; +Cc: Florian Weimer, libc-alpha
[-- Attachment #1: Type: text/plain, Size: 850 bytes --]
On Thu, Nov 28, 2024 at 2:46 PM Adhemerval Zanella Netto <
adhemerval.zanella@linaro.org> wrote:
>
>
> On 28/11/24 05:22, Florian Weimer wrote:
>
> >> Can't we use MADV_DONTFORK here? There still the small windows between
> >> mmap return and madvise call, but I am not very confident that adding
> >> a possible unbounded dynamic stack allocation is best way forward
> >> here.
> >
> > No, the vfork has already happened at this point. We would need
> > something to unmap the mapping on a successful execve call.
>
> Right, and it is not clear that vfork would always execv*. I still
> don't like this stack solution, but I also don't have a better solution.
>
It is my understanding there isn' t better solution without kernel
support.. __cleanup__ attribute will not work on this case..or will it ? I
haven't tried.
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat
2024-11-28 18:41 ` Cristian Rodríguez
@ 2024-11-28 19:01 ` Adhemerval Zanella Netto
2024-11-28 19:02 ` Florian Weimer
1 sibling, 0 replies; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-11-28 19:01 UTC (permalink / raw)
To: Cristian Rodríguez; +Cc: Florian Weimer, libc-alpha
On 28/11/24 15:41, Cristian Rodríguez wrote:
>
>
> On Thu, Nov 28, 2024 at 2:46 PM Adhemerval Zanella Netto <adhemerval.zanella@linaro.org <mailto:adhemerval.zanella@linaro.org>> wrote:
>
>
>
> On 28/11/24 05:22, Florian Weimer wrote:
>
> >> Can't we use MADV_DONTFORK here? There still the small windows between
> >> mmap return and madvise call, but I am not very confident that adding
> >> a possible unbounded dynamic stack allocation is best way forward
> >> here.
> >
> > No, the vfork has already happened at this point. We would need
> > something to unmap the mapping on a successful execve call.
>
> Right, and it is not clear that vfork would always execv*. I still
> don't like this stack solution, but I also don't have a better solution.
>
>
> It is my understanding there isn' t better solution without kernel support.. __cleanup__ attribute will not work on this case..or will it ? I haven't tried.
>
The issue is vfork operates on the VMA as the parent, so any memory allocation
done by execveat should be cleaned by the parent after vfork returns. And even
trying to add per-thread pointer to allocate the __execveat envp snapshort, can
not be fully async-signal-safe without adding a lot of complexity.
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat
2024-11-28 18:41 ` Cristian Rodríguez
2024-11-28 19:01 ` Adhemerval Zanella Netto
@ 2024-11-28 19:02 ` Florian Weimer
2024-11-29 13:41 ` Adhemerval Zanella Netto
1 sibling, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-11-28 19:02 UTC (permalink / raw)
To: Cristian Rodríguez; +Cc: Adhemerval Zanella Netto, libc-alpha
* Cristian Rodríguez:
> On Thu, Nov 28, 2024 at 2:46 PM Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> wrote:
>
>> On 28/11/24 05:22, Florian Weimer wrote:
>>
>> >> Can't we use MADV_DONTFORK here? There still the small windows between
>> >> mmap return and madvise call, but I am not very confident that adding
>> >> a possible unbounded dynamic stack allocation is best way forward
>> >> here.
>> >
>> > No, the vfork has already happened at this point. We would need
>> > something to unmap the mapping on a successful execve call.
>>
>> Right, and it is not clear that vfork would always execv*. I still
>> don't like this stack solution, but I also don't have a better solution.
>
> It is my understanding there isn' t better solution without kernel
> support.. __cleanup__ attribute will not work on this case..or will
> it ? I haven't tried.
Attribute __cleanup__ won't work because on successful execve, the
thread goes away (but not it's mm).
There is an alternative approach: allocate larger buffers with mmap, and
store a pointer to that into the TCB. Then the vfork-ing thread can
eventually reuse the mapping, or it can be deallocated once that thread
exits.
Reusing the mapping in execve leads to a race between the __getenvarray
snapshotting and the following execve systemcall: another execve
function call can happen from a signal handler, with its own
__getenvarray call. If the execve system call after that succeeds,
everything is fine, but if it fails and the signal handler returns, we
have overwritten the environment snapshot from the interrupted execve
function call. But I think the way we document the snapshotting rules,
this is actually fine: any variables that go missing from the
environment due to the overwrite must have been unset.
It's unclear if this is better because it increases complexity
considerably.
Thanks,
Florian
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 08/13] support: Add support_count_maps
2024-11-27 13:07 ` Adhemerval Zanella Netto
@ 2024-11-29 13:35 ` Florian Weimer
2024-11-29 13:38 ` Adhemerval Zanella Netto
0 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-11-29 13:35 UTC (permalink / raw)
To: Adhemerval Zanella Netto; +Cc: libc-alpha
* Adhemerval Zanella Netto:
>> +#include <support/support.h>
>> +#include <support/check.h>
>> +#include <support/xstdio.h>
>> +#include <errno.h>
>> +
>> +size_t
>> +support_count_maps (void)
>> +{
>
> Maybe add a support_need_proc().
>
>> + FILE *fp = fopen ("/proc/self/maps", "r");
>> + if (fp == NULL)
>> + {
>> + if (errno == ENOENT)
>> + FAIL_UNSUPPORTED ("support_count_maps: test needs /proc/self/maps");
>> + else
>> + FAIL_EXIT1 ("cannot open /proc/self/maps: %m");
>> + }
>
> And then you can just use xfopen here.
I don't know if Hurd has /proc/self/maps (but it has other parts of
/proc).
Thanks,
Florian
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 08/13] support: Add support_count_maps
2024-11-29 13:35 ` Florian Weimer
@ 2024-11-29 13:38 ` Adhemerval Zanella Netto
0 siblings, 0 replies; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-11-29 13:38 UTC (permalink / raw)
To: Florian Weimer; +Cc: libc-alpha
On 29/11/24 10:35, Florian Weimer wrote:
> * Adhemerval Zanella Netto:
>
>>> +#include <support/support.h>
>>> +#include <support/check.h>
>>> +#include <support/xstdio.h>
>>> +#include <errno.h>
>>> +
>>> +size_t
>>> +support_count_maps (void)
>>> +{
>>
>> Maybe add a support_need_proc().
>>
>>> + FILE *fp = fopen ("/proc/self/maps", "r");
>>> + if (fp == NULL)
>>> + {
>>> + if (errno == ENOENT)
>>> + FAIL_UNSUPPORTED ("support_count_maps: test needs /proc/self/maps");
>>> + else
>>> + FAIL_EXIT1 ("cannot open /proc/self/maps: %m");
>>> + }
>>
>> And then you can just use xfopen here.
>
> I don't know if Hurd has /proc/self/maps (but it has other parts of
> /proc).
Current support_need_proc() only checks for "/proc/self/maps" and it is
not enabled on Hurd.
Maybe we should rename it.
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat
2024-11-28 19:02 ` Florian Weimer
@ 2024-11-29 13:41 ` Adhemerval Zanella Netto
2024-11-29 14:09 ` Florian Weimer
0 siblings, 1 reply; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-11-29 13:41 UTC (permalink / raw)
To: Florian Weimer, Cristian Rodríguez; +Cc: libc-alpha
On 28/11/24 16:02, Florian Weimer wrote:
> * Cristian Rodríguez:
>
>> On Thu, Nov 28, 2024 at 2:46 PM Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> wrote:
>>
>>> On 28/11/24 05:22, Florian Weimer wrote:
>>>
>>>>> Can't we use MADV_DONTFORK here? There still the small windows between
>>>>> mmap return and madvise call, but I am not very confident that adding
>>>>> a possible unbounded dynamic stack allocation is best way forward
>>>>> here.
>>>>
>>>> No, the vfork has already happened at this point. We would need
>>>> something to unmap the mapping on a successful execve call.
>>>
>>> Right, and it is not clear that vfork would always execv*. I still
>>> don't like this stack solution, but I also don't have a better solution.
>>
>> It is my understanding there isn' t better solution without kernel
>> support.. __cleanup__ attribute will not work on this case..or will
>> it ? I haven't tried.
>
> Attribute __cleanup__ won't work because on successful execve, the
> thread goes away (but not it's mm).
>
> There is an alternative approach: allocate larger buffers with mmap, and
> store a pointer to that into the TCB. Then the vfork-ing thread can
> eventually reuse the mapping, or it can be deallocated once that thread
> exits.
>
> Reusing the mapping in execve leads to a race between the __getenvarray
> snapshotting and the following execve systemcall: another execve
> function call can happen from a signal handler, with its own
> __getenvarray call. If the execve system call after that succeeds,
> everything is fine, but if it fails and the signal handler returns, we
> have overwritten the environment snapshot from the interrupted execve
> function call. But I think the way we document the snapshotting rules,
> this is actually fine: any variables that go missing from the
> environment due to the overwrite must have been unset.
>
> It's unclear if this is better because it increases complexity
> considerably.
It is not clear to me how you handle reentracy in this strategy, where
execveat is called while another execveat is taking the snapshot. In
this case, the per-thread allocated buffer may bepartially written; so
you will need a way to signal (after the execveat called in the signal
handler returns) that the snapshotting should be restarted.
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat
2024-11-29 13:41 ` Adhemerval Zanella Netto
@ 2024-11-29 14:09 ` Florian Weimer
2024-12-03 19:00 ` Adhemerval Zanella Netto
0 siblings, 1 reply; 39+ messages in thread
From: Florian Weimer @ 2024-11-29 14:09 UTC (permalink / raw)
To: Adhemerval Zanella Netto; +Cc: Cristian Rodríguez, libc-alpha
* Adhemerval Zanella Netto:
>> There is an alternative approach: allocate larger buffers with mmap, and
>> store a pointer to that into the TCB. Then the vfork-ing thread can
>> eventually reuse the mapping, or it can be deallocated once that thread
>> exits.
>>
>> Reusing the mapping in execve leads to a race between the __getenvarray
>> snapshotting and the following execve systemcall: another execve
>> function call can happen from a signal handler, with its own
>> __getenvarray call. If the execve system call after that succeeds,
>> everything is fine, but if it fails and the signal handler returns, we
>> have overwritten the environment snapshot from the interrupted execve
>> function call. But I think the way we document the snapshotting rules,
>> this is actually fine: any variables that go missing from the
>> environment due to the overwrite must have been unset.
>>
>> It's unclear if this is better because it increases complexity
>> considerably.
>
> It is not clear to me how you handle reentracy in this strategy, where
> execveat is called while another execveat is taking the snapshot. In
> this case, the per-thread allocated buffer may bepartially written; so
> you will need a way to signal (after the execveat called in the signal
> handler returns) that the snapshotting should be restarted.
Oh, I failed to mention that part. I think we can block signals around
the __getenvarray call. Obviously, we must unblock signals before
invoking the execveat system call.
Thanks,
Florian
^ permalink raw reply [flat|nested] 39+ messages in thread
* Re: [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat
2024-11-29 14:09 ` Florian Weimer
@ 2024-12-03 19:00 ` Adhemerval Zanella Netto
0 siblings, 0 replies; 39+ messages in thread
From: Adhemerval Zanella Netto @ 2024-12-03 19:00 UTC (permalink / raw)
To: Florian Weimer; +Cc: Cristian Rodríguez, libc-alpha
On 29/11/24 11:09, Florian Weimer wrote:
> * Adhemerval Zanella Netto:
>
>>> There is an alternative approach: allocate larger buffers with mmap, and
>>> store a pointer to that into the TCB. Then the vfork-ing thread can
>>> eventually reuse the mapping, or it can be deallocated once that thread
>>> exits.
>>>
>>> Reusing the mapping in execve leads to a race between the __getenvarray
>>> snapshotting and the following execve systemcall: another execve
>>> function call can happen from a signal handler, with its own
>>> __getenvarray call. If the execve system call after that succeeds,
>>> everything is fine, but if it fails and the signal handler returns, we
>>> have overwritten the environment snapshot from the interrupted execve
>>> function call. But I think the way we document the snapshotting rules,
>>> this is actually fine: any variables that go missing from the
>>> environment due to the overwrite must have been unset.
>>>
>>> It's unclear if this is better because it increases complexity
>>> considerably.
>>
>> It is not clear to me how you handle reentracy in this strategy, where
>> execveat is called while another execveat is taking the snapshot. In
>> this case, the per-thread allocated buffer may bepartially written; so
>> you will need a way to signal (after the execveat called in the signal
>> handler returns) that the snapshotting should be restarted.
>
> Oh, I failed to mention that part. I think we can block signals around
> the __getenvarray call. Obviously, we must unblock signals before
> invoking the execveat system call.
Right, this makes sense. I think we can use the stack allocated buffer for
now, vfork() is really a deprecated interface (although I do see it sometime
being use as 'optimization' over fork.
^ permalink raw reply [flat|nested] 39+ messages in thread
end of thread, other threads:[~2024-12-03 19:00 UTC | newest]
Thread overview: 39+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-07-28 19:02 [PATCH v2 00/13] getenv/environ thread safety Florian Weimer
2024-07-28 19:02 ` [PATCH v2 01/13] stdlib: Make getenv thread-safe in more cases Florian Weimer
2024-10-16 17:30 ` Adhemerval Zanella Netto
2024-11-18 9:54 ` Florian Weimer
2024-11-20 13:25 ` Adhemerval Zanella Netto
2024-11-20 18:36 ` Florian Weimer
2024-07-28 19:02 ` [PATCH v2 02/13] Linux: Assume __NR_execveat is defined in fexecve Florian Weimer
2024-10-15 19:15 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 03/13] Linux: Introduce __do_fexecve " Florian Weimer
2024-10-15 19:51 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 04/13] Linux: Error handling fixes for fexecve Florian Weimer
2024-10-15 19:57 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 05/13] posix: Consolidate execve family of functions Florian Weimer
2024-10-16 13:40 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 06/13] posix: Introduce the __arch_spawni function Florian Weimer
2024-10-29 20:29 ` Adhemerval Zanella Netto
2024-07-28 19:02 ` [PATCH v2 07/13] stdlib: Add internal __getenvarray function Florian Weimer
2024-10-30 16:21 ` Adhemerval Zanella Netto
2024-10-30 16:38 ` Florian Weimer
2024-10-30 20:11 ` Adhemerval Zanella Netto
2024-07-28 19:03 ` [PATCH v2 08/13] support: Add support_count_maps Florian Weimer
2024-11-27 13:07 ` Adhemerval Zanella Netto
2024-11-29 13:35 ` Florian Weimer
2024-11-29 13:38 ` Adhemerval Zanella Netto
2024-07-28 19:03 ` [PATCH v2 09/13] posix: New tst-vfork-mappings test case Florian Weimer
2024-11-27 14:07 ` Adhemerval Zanella Netto
2024-07-28 19:03 ` [PATCH v2 10/13] posix: Improve thread safety of functions using environ with execveat Florian Weimer
2024-11-27 20:01 ` Adhemerval Zanella Netto
2024-11-28 8:22 ` Florian Weimer
2024-11-28 17:41 ` Adhemerval Zanella Netto
2024-11-28 18:41 ` Cristian Rodríguez
2024-11-28 19:01 ` Adhemerval Zanella Netto
2024-11-28 19:02 ` Florian Weimer
2024-11-29 13:41 ` Adhemerval Zanella Netto
2024-11-29 14:09 ` Florian Weimer
2024-12-03 19:00 ` Adhemerval Zanella Netto
2024-07-28 19:03 ` [PATCH v2 11/13] posix: Implement environ snapshotting in __spawni Florian Weimer
2024-07-28 19:03 ` [PATCH v2 12/13] posix: Acquire the environment lock during fork Florian Weimer
2024-07-28 19:03 ` [PATCH v2 13/13] manual: Updates for thread-safe getenv Florian Weimer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).