]> git.ipfire.org Git - thirdparty/glibc.git/blob - stdlib/setenv.c
0ef5dde37348a98f58c8f5f148fd913e4842919c
[thirdparty/glibc.git] / stdlib / setenv.c
1 /* Copyright (C) 1992-2025 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <https://www.gnu.org/licenses/>. */
17
18 #if HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #include <assert.h>
23 #include <setenv.h>
24
25 /* Pacify GCC; see the commentary about VALLEN below. This is needed
26 at least through GCC 4.9.2. Pacify GCC for the entire file, as
27 there seems to be no way to pacify GCC selectively, only for the
28 place where it's needed. Do not use DIAG_IGNORE_NEEDS_COMMENT
29 here, as it's not defined yet. */
30 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
31
32 #include <errno.h>
33 #if !_LIBC
34 # if !defined errno && !defined HAVE_ERRNO_DECL
35 extern int errno;
36 # endif
37 # define __set_errno(ev) ((errno) = (ev))
38 #endif
39
40 #if _LIBC || HAVE_STDLIB_H
41 # include <stdlib.h>
42 #endif
43 #if _LIBC || HAVE_STRING_H
44 # include <string.h>
45 #endif
46 #if _LIBC || HAVE_UNISTD_H
47 # include <unistd.h>
48 #endif
49
50 #if !_LIBC
51 # define __environ environ
52 # ifndef HAVE_ENVIRON_DECL
53 extern char **environ;
54 # endif
55 #endif
56
57 #if _LIBC
58 /* This lock protects against simultaneous modifications of `environ'. */
59 # include <libc-lock.h>
60 __libc_lock_define_initialized (static, envlock)
61 # define LOCK __libc_lock_lock (envlock)
62 # define UNLOCK __libc_lock_unlock (envlock)
63 #else
64 # define LOCK
65 # define UNLOCK
66 #endif
67
68 /* In the GNU C library we must keep the namespace clean. */
69 #ifdef _LIBC
70 # define setenv __setenv
71 # define unsetenv __unsetenv
72 # define clearenv __clearenv
73 # define tfind __tfind
74 # define tsearch __tsearch
75 #endif
76
77 /* In the GNU C library implementation we try to be more clever and
78 allow arbitrarily many changes of the environment given that the used
79 values are from a small set. Outside glibc this will eat up all
80 memory after a while. */
81 #if defined _LIBC || (defined HAVE_SEARCH_H && defined HAVE_TSEARCH \
82 && defined __GNUC__)
83 # define USE_TSEARCH 1
84 # include <search.h>
85
86 /* This is a pointer to the root of the search tree with the known
87 values. */
88 static void *known_values;
89
90 # define KNOWN_VALUE(Str) \
91 ({ \
92 void *value = tfind (Str, &known_values, (__compar_fn_t) strcmp); \
93 value != NULL ? *(char **) value : NULL; \
94 })
95 # define STORE_VALUE(Str) \
96 tsearch (Str, &known_values, (__compar_fn_t) strcmp)
97
98 #else
99 # undef USE_TSEARCH
100
101 # define KNOWN_VALUE(Str) NULL
102 # define STORE_VALUE(Str) do { } while (0)
103
104 #endif
105
106 /* Allocate a new environment array and put it o the
107 __environ_array_list. Returns NULL on memory allocation
108 failure. */
109 static struct environ_array *
110 __environ_new_array (size_t required_size)
111 {
112 /* No backing array yet, or insufficient room. */
113 size_t new_size;
114 if (__environ_array_list == NULL
115 || __environ_array_list->allocated * 2 < required_size)
116 /* Add some unused space for future growth. */
117 new_size = required_size + 16;
118 else
119 new_size = __environ_array_list->allocated * 2;
120
121 /* Zero-initialize everything, so that getenv can only
122 observe valid or null pointers. */
123 char **new_array = calloc (new_size, sizeof (*new_array));
124 if (new_array == NULL)
125 return NULL;
126
127 struct environ_array *target_array = malloc (sizeof (*target_array));
128 if (target_array == NULL)
129 {
130 free (new_array);
131 return NULL;
132 }
133
134 target_array->allocated = new_size;
135 target_array->array = new_array;
136 assert (new_size >= target_array->allocated);
137
138 /* Put it onto the list. */
139 target_array->next = __environ_array_list;
140 __environ_array_list = target_array;
141 return target_array;
142 }
143
144 int
145 __add_to_environ (const char *name, const char *value, const char *combined,
146 int replace)
147 {
148 /* Compute lengths before locking, so that the critical section is
149 less of a performance bottleneck. VALLEN is needed only if
150 COMBINED is null (unfortunately GCC is not smart enough to deduce
151 this; see the #pragma at the start of this file). Testing
152 COMBINED instead of VALUE causes setenv (..., NULL, ...) to dump
153 core now instead of corrupting memory later. */
154 const size_t namelen = strlen (name);
155 size_t vallen;
156 if (combined == NULL)
157 vallen = strlen (value) + 1;
158
159 LOCK;
160
161 /* We have to get the pointer now that we have the lock and not earlier
162 since another thread might have created a new environment. */
163 char **start_environ = atomic_load_relaxed (&__environ);
164 char **ep = start_environ;
165
166 /* This gets written to __environ in the end. */
167 char **result_environ = start_environ;
168
169 /* Size of the environment if *ep == NULL. */
170 if (ep != NULL)
171 for (; *ep != NULL; ++ep)
172 if (strncmp (*ep, name, namelen) == 0 && (*ep)[namelen] == '=')
173 break;
174
175 if (ep == NULL || __glibc_likely (*ep == NULL))
176 {
177 /* The scanning loop above reached the end of the environment.
178 Add a new string to it. */
179 replace = true;
180
181 /* + 2 for the new entry and the terminating NULL. */
182 size_t required_size = (ep - start_environ) + 2;
183 if (__environ_is_from_array_list (start_environ)
184 && required_size <= __environ_array_list->allocated)
185 /* The __environ array is ours, and we have room in it. We
186 can use ep as-is. Add a null terminator in case current
187 usage is less than previous usage. */
188 ep[1] = NULL;
189 else
190 {
191 /* We cannot use __environ as is and need to copy over the
192 __environ contents into an array managed via
193 __environ_array_list. */
194
195 struct environ_array *target_array;
196 if (__environ_array_list != NULL
197 && required_size <= __environ_array_list->allocated)
198 /* Existing array has enough room. Contents is copied below. */
199 target_array = __environ_array_list;
200 else
201 {
202 /* Allocate a new array. */
203 target_array = __environ_new_array (required_size);
204 if (target_array == NULL)
205 {
206 UNLOCK;
207 return -1;
208 }
209 }
210
211 /* Copy over the __environ array contents. This forward
212 copy slides backwards part of the array if __environ
213 points into target_array->array. This happens if an
214 application makes an assignment like:
215
216 environ = &environ[1];
217
218 The forward copy avoids clobbering values that still
219 needing copying. This code handles the case
220 start_environ == ep == NULL, too. */
221 size_t i;
222 for (i = 0; start_environ + i < ep; ++i)
223 /* Regular store because unless there has been direct
224 manipulation of the environment, target_array is still
225 a private copy. */
226 target_array->array[i] = atomic_load_relaxed (start_environ + i);
227
228 /* This is the new place where we should add the element. */
229 ep = target_array->array + i;
230
231 /* Add the null terminator in case there was a pointer there
232 previously. */
233 ep[1] = NULL;
234
235 /* And __environ should be repointed to our array. */
236 result_environ = target_array->array;
237 }
238 }
239
240 if (replace || *ep == NULL)
241 {
242 char *np;
243
244 /* Use the user string if given. */
245 if (combined != NULL)
246 np = (char *) combined;
247 else
248 {
249 const size_t varlen = namelen + 1 + vallen;
250 #ifdef USE_TSEARCH
251 char *new_value = malloc (varlen);
252 if (new_value == NULL)
253 {
254 UNLOCK;
255 return -1;
256 }
257 # ifdef _LIBC
258 __mempcpy (__mempcpy (__mempcpy (new_value, name, namelen), "=", 1),
259 value, vallen);
260 # else
261 memcpy (new_value, name, namelen);
262 new_value[namelen] = '=';
263 memcpy (&new_value[namelen + 1], value, vallen);
264 # endif
265
266 np = KNOWN_VALUE (new_value);
267 if (__glibc_likely (np == NULL))
268 #endif
269 {
270 #ifdef USE_TSEARCH
271 np = new_value;
272 #endif
273 /* And remember the value. */
274 STORE_VALUE (np);
275 }
276 #ifdef USE_TSEARCH
277 else
278 free (new_value);
279 #endif
280 }
281
282 /* Use release MO so that loads are sufficient to observe the
283 pointer contents because the CPU carries the dependency for
284 us. This also acts as a thread fence, making getenv
285 async-signal-safe. */
286 atomic_store_release (ep, np);
287 atomic_store_release (&__environ, result_environ);
288 }
289
290 UNLOCK;
291
292 return 0;
293 }
294
295 int
296 setenv (const char *name, const char *value, int replace)
297 {
298 if (name == NULL || *name == '\0' || strchr (name, '=') != NULL)
299 {
300 __set_errno (EINVAL);
301 return -1;
302 }
303
304 return __add_to_environ (name, value, NULL, replace);
305 }
306
307 int
308 unsetenv (const char *name)
309 {
310 size_t len;
311 char **ep;
312
313 if (name == NULL || *name == '\0' || strchr (name, '=') != NULL)
314 {
315 __set_errno (EINVAL);
316 return -1;
317 }
318
319 len = strlen (name);
320
321 LOCK;
322
323 ep = atomic_load_relaxed (&__environ);
324 if (ep != NULL)
325 while (true)
326 {
327 char *entry = atomic_load_relaxed (ep);
328 if (entry == NULL)
329 break;
330 if (strncmp (entry, name, len) == 0 && entry[len] == '=')
331 {
332 /* Found it. Remove this pointer by moving later ones back. */
333 char **dp = ep;
334
335 while (true)
336 {
337 char *next_value = atomic_load_relaxed (dp + 1);
338 /* This store overwrites a value that has been
339 removed, or that has already been written to a
340 previous value. Release MO so that this store does
341 not get reordered before the counter update in the
342 previous loop iteration. */
343 atomic_store_release (dp, next_value);
344 /* Release store synchronizes with acquire loads in
345 getenv. Non-atomic update because there is just
346 one writer due to the lock.
347
348 See discussion of the counter check in getenv for
349 an explanation why this is sufficient synchronization. */
350 atomic_store_release (&__environ_counter,
351 atomic_load_relaxed (&__environ_counter)
352 + 1);
353 if (next_value == NULL)
354 break;
355 ++dp;
356 }
357 /* Continue the loop in case NAME appears again. */
358 }
359 else
360 ++ep;
361 }
362
363 UNLOCK;
364
365 return 0;
366 }
367
368 /* The `clearenv' was planned to be added to POSIX.1 but probably
369 never made it. Nevertheless the POSIX.9 standard (POSIX bindings
370 for Fortran 77) requires this function. */
371 int
372 clearenv (void)
373 {
374 LOCK;
375 char **start_environ = atomic_load_relaxed (&__environ);
376 if (__environ_is_from_array_list (start_environ))
377 {
378 /* Store null pointers to avoid strange effects when the array
379 is reused in setenv. */
380 for (char **ep = start_environ; *ep != NULL; ++ep)
381 atomic_store_relaxed (ep, NULL);
382 /* Update the counter similar to unsetenv, so that the writes in
383 setenv do not need to update the counter. */
384 atomic_store_release (&__environ_counter,
385 atomic_load_relaxed (&__environ_counter) + 1);
386 }
387
388 atomic_store_relaxed (&__environ, NULL);
389 UNLOCK;
390
391 return 0;
392 }
393 #ifdef _LIBC
394 void
395 __libc_setenv_freemem (void)
396 {
397 /* Remove all traces. */
398 clearenv ();
399
400 /* Clear all backing arrays. */
401 while (__environ_array_list != NULL)
402 {
403 free (__environ_array_list->array);
404 void *ptr = __environ_array_list;
405 __environ_array_list = __environ_array_list->next;
406 free (ptr);
407 }
408
409 /* Now remove the search tree. */
410 __tdestroy (known_values, free);
411 known_values = NULL;
412 }
413
414 # undef setenv
415 # undef unsetenv
416 # undef clearenv
417 weak_alias (__setenv, setenv)
418 weak_alias (__unsetenv, unsetenv)
419 weak_alias (__clearenv, clearenv)
420 #endif