aboutsummaryrefslogtreecommitdiff
path: root/stdlib/getenv.c
blob: 1a7b0bfc063e5fa63cd8d4bf4b661c09c621a0fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/* Copyright (C) 1991-2025 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 <string.h>
#include <unistd.h>

char *
getenv (const char *name)
{
  while (true)
    {
      /* Used to deal with concurrent unsetenv.  */
      environ_counter start_counter = atomic_load_acquire (&__environ_counter);

      /* We use relaxed MO for loading the string pointers because we
	 assume the strings themselves are immutable and that loads
	 through the string pointers carry a dependency.  (This
	 depends on the the release MO store to __environ in
	 __add_to_environ.)  Objects pointed to by pointers stored in
	 the __environ array are never modified or deallocated (except
	 perhaps if putenv is used, but then synchronization is the
	 responsibility of the applications).  The backing store for
	 __environ is allocated zeroed.  In summary, we can assume
	 that the pointers we observe are either valid or null, and
	 that only initialized string contents is visible.  */
      char **start_environ = atomic_load_relaxed (&__environ);
      if (start_environ == NULL || name[0] == '\0')
	return NULL;

      size_t len = strlen (name);
      for (char **ep = start_environ; ; ++ep)
	{
	  char *entry = atomic_load_relaxed (ep);
	  if (entry == NULL)
	    break;

	  /* If there is a match, return that value.  It was valid at
	     one point, so we can return it.  */
	  if (name[0] == entry[0]
	      && strncmp (name, entry, len) == 0 && entry[len] == '=')
	    return entry + len + 1;
	}

      /* The variable was not found.  This might be a false negative
	 because unsetenv has shuffled around entries.  Check if it is
	 necessary to retry.  */

      /* See Hans Boehm, Can Seqlocks Get Along with Programming Language
	 Memory Models?, Section 4.  This is necessary so that loads in
	 the loop above are not ordered past the counter check below.  */
      atomic_thread_fence_acquire ();

      if (atomic_load_acquire (&__environ_counter) == start_counter)
	  /* If we reach this point and there was a concurrent
	     unsetenv call which removed the key we tried to find, the
	     NULL return value is valid.  We can also try again, not
	     find the value, and then return NULL (assuming there are
	     no further concurrent unsetenv calls).

	     However, if getenv is called to find a value that is
	     present originally and not removed by any of the
	     concurrent unsetenv calls, we must not return NULL here.

	     If the counter did not change, there was at most one
	     write to the array in unsetenv while the scanning loop
	     above was running.  This means that there are at most two
	     different versions of the array to consider.  For the
	     sake of argument, we assume that each load can make an
	     independent choice which version to use.  An arbitrary
	     number of unsetenv and setenv calls may have happened
	     since start of getenv.  Lets write E[0], E[1], ... for
	     the original environment elements, a(0) < (1) < ... for a
	     sequence of increasing integers that are the indices of
	     the environment variables remaining after the removals, and
	     N[0], N[1], ... for the new variables added by setenv or
	     putenv.  Then at the start of the last unsetenv call, the
	     environment contains

	       E[a(0)], E[a(1)], ..., N[0], N[1], ...

	     (the N[0], N[1], .... are optional.)  Let's assume that
	     we are looking for the value E[j].  Then one of the
	     a(i) == j (otherwise we may return NULL here because
	     of a unsetenv for the value we are looking for).  In the
	     discussion below it will become clear that the N[k] do
	     not actually matter.

	     The two versions of array we can choose from differ only
	     in one element, say E[a(i)].  There are two cases:

	     Case (A): E[a(i)] is an element being removed by unsetenv
	     (the target of the first write).  We can see the original
	     version:

	     ..., E[a(i-1)], E[a(i)],   E[a(i+1)], ..., N[0], ...
                             -------
	     And the overwritten version:

	     ..., E[a(i-1)], E[a(i+1)], E[a(i+1)], ..., N[0], ...
                             ---------

             (The valueE[a(i+1)] can be the terminating NULL.)
	     As discussed, we are not considering the removal of the
	     variable being searched for, so a(i) != j, and the
	     variable getenv is looking for is available in either
	     version, and we would have found it above.

	     Case (B): E[a(i)] is an element that has already been
	     moved forward and is now itself being overwritten with
	     its sucessor value E[a(i+1)].  The two versions of the
	     array look like this:

	     ..., E[a(i-1)], E[a(i)], E[a(i)],   E[a(i+1)], ..., N[0], ...
                                      -------
	     And with the overwrite in place:

	     ..., E[a(i-1)], E[a(i)], E[a(i+1)], E[a(i+1)], ..., N[0], ...
                                      ---------

             The key observation here is that even in the second
             version with the overwrite present, the scanning loop
             will still encounter the overwritten value E[a(i)] in the
             previous array element.  This means that as long as the
             E[j] is still present among the initial E[a(...)] (as we
             assumed because there is no concurrent unsetenv for
             E[j]), we encounter it while scanning here in getenv.

	     In summary, if there was at most one write, a negative
	     result is a true negative, and we can return NULL.  This
	     is different from the seqlock paper, which retries if
	     there was any write at all.  It avoids the need for a
	     second, unwritten copy for async-signal-safety.  */
	return NULL;
      /* If there was one more write, retry.  This will never happen
	 in a signal handler that interrupted unsetenv because the
	 suspended unsetenv call cannot change the counter value.  */
    }
}
libc_hidden_def (getenv)