0
|
1 |
/*
|
|
2 |
* CDDL HEADER START
|
|
3 |
*
|
|
4 |
* The contents of this file are subject to the terms of the
|
|
5 |
* Common Development and Distribution License, Version 1.0 only
|
|
6 |
* (the "License"). You may not use this file except in compliance
|
|
7 |
* with the License.
|
|
8 |
*
|
|
9 |
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
|
10 |
* or http://www.opensolaris.org/os/licensing.
|
|
11 |
* See the License for the specific language governing permissions
|
|
12 |
* and limitations under the License.
|
|
13 |
*
|
|
14 |
* When distributing Covered Code, include this CDDL HEADER in each
|
|
15 |
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
|
16 |
* If applicable, add the following below this CDDL HEADER, with the
|
|
17 |
* fields enclosed by brackets "[]" replaced with your own identifying
|
|
18 |
* information: Portions Copyright [yyyy] [name of copyright owner]
|
|
19 |
*
|
|
20 |
* CDDL HEADER END
|
|
21 |
*/
|
|
22 |
/*
|
|
23 |
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
|
|
24 |
* Use is subject to license terms.
|
|
25 |
*/
|
|
26 |
|
|
27 |
#pragma ident "%Z%%M% %I% %E% SMI"
|
|
28 |
|
|
29 |
/* Copyright (c) 1988 AT&T */
|
|
30 |
/* All Rights Reserved */
|
|
31 |
|
|
32 |
|
|
33 |
#pragma weak putenv = _putenv
|
|
34 |
#pragma weak setenv = _setenv
|
|
35 |
#pragma weak unsetenv = _unsetenv
|
|
36 |
|
|
37 |
#include "synonyms.h"
|
|
38 |
#include <mtlib.h>
|
|
39 |
|
|
40 |
#include <sys/types.h>
|
|
41 |
#include <thread.h>
|
|
42 |
#include <synch.h>
|
|
43 |
#include <stdlib.h>
|
|
44 |
#include <errno.h>
|
|
45 |
#include <string.h>
|
|
46 |
#include <atomic.h>
|
|
47 |
|
|
48 |
#define MIN_ENV_SIZE 128
|
|
49 |
|
|
50 |
extern const char **environ;
|
|
51 |
extern void clean_env();
|
|
52 |
|
|
53 |
/*
|
|
54 |
* For performance and consistency reasons we expand the environ list using
|
|
55 |
* the trusted "power of two, drop it on the floor" method. This allows for
|
|
56 |
* a lockless, single pass implementation of getenv(), yet the memory leak
|
|
57 |
* is bounded - in normal circumstances total wastage is never greater than
|
|
58 |
* 3x the space needed to hold any environ list.
|
|
59 |
*
|
|
60 |
* The only abnormal circumstance is if an application modifies the environ
|
|
61 |
* list pointer directly. Such an application does not conform to POSIX.1
|
|
62 |
* 2001. However, we also care about standards which did not foresee this
|
|
63 |
* issue. For this reason we keep a working copy of our notion of environ in
|
|
64 |
* my_environ. If, when we are called upon to modify environ, we ever detect
|
|
65 |
* a mismatch between environ and my_environ we discard all our assumptions
|
|
66 |
* concerning the location and size of the environ list. As an additional
|
|
67 |
* precaution we only ever update environ once we have finished manipulating
|
|
68 |
* our working copy.
|
|
69 |
*
|
|
70 |
* The setenv() API is inherently leaky but we are completely at the mercy
|
|
71 |
* of the application.
|
|
72 |
*
|
|
73 |
* To pacify leak detectors we chain all allocations which are at risk of
|
|
74 |
* being leaked in either of the above two scenarios. chunk_list must only
|
|
75 |
* be updated under the protection of update_lock.
|
|
76 |
*
|
|
77 |
* Although we don't allocate the original environ list it is likely that
|
|
78 |
* we will leak this too. Accordingly, we create a reference in initenv().
|
|
79 |
* However, we can't be held responsible for such leaks in abnormal (see
|
|
80 |
* above) circumstances.
|
|
81 |
*/
|
|
82 |
|
|
83 |
typedef struct chunk {
|
|
84 |
struct chunk *next;
|
|
85 |
} chunk_t;
|
|
86 |
|
|
87 |
static mutex_t update_lock = DEFAULTMUTEX;
|
|
88 |
static const char **orig_environ = NULL;
|
|
89 |
static const char **my_environ = NULL;
|
|
90 |
static const char **environ_base = NULL;
|
|
91 |
static int environ_size = 0;
|
|
92 |
static int environ_gen = 0;
|
|
93 |
static int initenv_done = 0;
|
|
94 |
static chunk_t *chunk_list = NULL;
|
|
95 |
|
|
96 |
/*
|
|
97 |
* Compute the size an environ list including the terminating NULL entry.
|
|
98 |
* This is the only way we have to determine the size of an environ list
|
|
99 |
* we didn't allocate.
|
|
100 |
*/
|
|
101 |
static int
|
|
102 |
envsize(const char **e)
|
|
103 |
{
|
|
104 |
int size;
|
|
105 |
|
|
106 |
if (e == NULL)
|
|
107 |
return (0);
|
|
108 |
|
|
109 |
for (size = 1; *e != NULL; e++)
|
|
110 |
size++;
|
|
111 |
|
|
112 |
return (size);
|
|
113 |
}
|
|
114 |
|
|
115 |
/*
|
|
116 |
* Initialization for the following scenarios:
|
|
117 |
* 1. The very first time we reference the environ list we must call in the
|
|
118 |
* NLSPATH janitor, make a reference to the original environ list to keep
|
|
119 |
* leak detectors happy, initialize my_environ and environ_base, and then
|
|
120 |
* compute environ_size.
|
|
121 |
* 2. Whenever we detect that someone else has hijacked environ (something
|
|
122 |
* very abnormal) we need to reinitialize my_environ and environ_base,
|
|
123 |
* and then recompute environ_size.
|
|
124 |
*
|
|
125 |
* The local globals my_environ, environ_base and environ_size may be used
|
|
126 |
* by others only if initenv_done is true and only under the protection of
|
|
127 |
* update_lock. However, our callers, who must NOT be holding update_lock,
|
|
128 |
* may safely test initenv_done or my_environ against environ just prior to
|
|
129 |
* calling us because we test these again whilst holding update_lock.
|
|
130 |
*/
|
|
131 |
static void
|
|
132 |
initenv()
|
|
133 |
{
|
|
134 |
lmutex_lock(&update_lock);
|
|
135 |
if ((my_environ != environ) || !initenv_done) {
|
|
136 |
if (!initenv_done) {
|
|
137 |
/* Call the NLSPATH janitor in. */
|
|
138 |
clean_env();
|
|
139 |
|
|
140 |
/* Pacify leak detectors in normal operation. */
|
|
141 |
orig_environ = environ;
|
|
142 |
#ifdef __lint
|
|
143 |
my_environ = orig_environ;
|
|
144 |
#endif
|
|
145 |
|
|
146 |
initenv_done = 1;
|
|
147 |
}
|
|
148 |
|
|
149 |
my_environ = environ;
|
|
150 |
environ_base = my_environ;
|
|
151 |
environ_size = envsize(environ_base);
|
|
152 |
}
|
|
153 |
lmutex_unlock(&update_lock);
|
|
154 |
}
|
|
155 |
|
|
156 |
/*
|
|
157 |
* Search an environ list for a particular entry. If name_only is set, then
|
|
158 |
* string must be the entry name only, and we return the value of the first
|
|
159 |
* match. Otherwise, string must be of the form "name=value", and we return
|
|
160 |
* the address of the first matching entry.
|
|
161 |
*/
|
|
162 |
static const char **
|
|
163 |
findenv(const char **e, const char *string, int name_only, char **value)
|
|
164 |
{
|
|
165 |
char target;
|
|
166 |
const char *s1;
|
|
167 |
const char *s2;
|
|
168 |
|
|
169 |
*value = NULL;
|
|
170 |
|
|
171 |
if (e == NULL)
|
|
172 |
return (NULL);
|
|
173 |
|
|
174 |
target = name_only ? '\0' : '=';
|
|
175 |
|
|
176 |
for (; (s2 = *e) != NULL; e++) {
|
|
177 |
s1 = string;
|
|
178 |
|
|
179 |
/* Fast comparison for first char. */
|
|
180 |
if (*s1 != *s2)
|
|
181 |
continue;
|
|
182 |
|
|
183 |
/* Slow comparison for rest of string. */
|
|
184 |
while (*s1 == *s2 && *s2 != '=') {
|
|
185 |
s1++;
|
|
186 |
s2++;
|
|
187 |
}
|
|
188 |
|
|
189 |
if (*s1 == target && *s2 == '=') {
|
|
190 |
*value = (char *)s2 + 1;
|
|
191 |
return (e);
|
|
192 |
}
|
|
193 |
}
|
|
194 |
return (NULL);
|
|
195 |
}
|
|
196 |
|
|
197 |
/*
|
|
198 |
* Common code for putenv() and setenv(). We support the lockless getenv()
|
|
199 |
* by inserting new entries at the bottom of the list, and by growing the
|
|
200 |
* list using the trusted "power of two, drop it on the floor" method. We
|
|
201 |
* use a lock (update_lock) to protect all updates to the environ list, but
|
|
202 |
* we are obliged to release this lock whenever we call malloc() or free().
|
|
203 |
* A generation number (environ_gen) is bumped whenever names are added to,
|
|
204 |
* or removed from, the environ list so that we can detect collisions with
|
|
205 |
* other updaters.
|
|
206 |
*
|
|
207 |
* Return values
|
|
208 |
* 0 : success
|
|
209 |
* -1 : with errno set
|
|
210 |
* -2 : an entry already existed and overwrite was zero
|
|
211 |
*/
|
|
212 |
static int
|
|
213 |
addtoenv(char *string, int overwrite)
|
|
214 |
{
|
|
215 |
char *value;
|
|
216 |
const char **p;
|
|
217 |
chunk_t *new_chunk;
|
|
218 |
const char **new_environ;
|
|
219 |
const char **new_base;
|
|
220 |
int new_size;
|
|
221 |
int old_gen;
|
|
222 |
|
|
223 |
if ((my_environ != environ) || !initenv_done)
|
|
224 |
initenv();
|
|
225 |
|
|
226 |
lmutex_lock(&update_lock);
|
|
227 |
|
|
228 |
for (;;) {
|
|
229 |
/*
|
|
230 |
* If the name already exists just overwrite the existing
|
|
231 |
* entry -- except when we were called by setenv() without
|
|
232 |
* the overwrite flag.
|
|
233 |
*/
|
|
234 |
if ((p = findenv(my_environ, string, 0, &value)) != NULL) {
|
|
235 |
if (overwrite) {
|
|
236 |
/*
|
|
237 |
* Replace the value in situ. No name was
|
|
238 |
* added, so there is no need to bump the
|
|
239 |
* generation number.
|
|
240 |
*/
|
|
241 |
*p = string;
|
|
242 |
lmutex_unlock(&update_lock);
|
|
243 |
return (0);
|
|
244 |
} else {
|
|
245 |
/* No change. */
|
|
246 |
lmutex_unlock(&update_lock);
|
|
247 |
return (-2);
|
|
248 |
}
|
|
249 |
}
|
|
250 |
|
|
251 |
/* Try to insert the new entry at the bottom of the list. */
|
|
252 |
if (environ_base < my_environ) {
|
|
253 |
/*
|
|
254 |
* The new value must be visible before we decrement
|
|
255 |
* the environ list pointer.
|
|
256 |
*/
|
|
257 |
my_environ[-1] = string;
|
|
258 |
membar_producer();
|
|
259 |
my_environ--;
|
|
260 |
environ = my_environ;
|
|
261 |
|
|
262 |
/*
|
|
263 |
* We've added a name, so bump the generation number.
|
|
264 |
*/
|
|
265 |
environ_gen++;
|
|
266 |
|
|
267 |
lmutex_unlock(&update_lock);
|
|
268 |
return (0);
|
|
269 |
}
|
|
270 |
|
|
271 |
/*
|
|
272 |
* There is no room. Attempt to allocate a new environ list
|
|
273 |
* which is at least double the size of the current one. See
|
|
274 |
* comment above concerning locking and malloc() etc.
|
|
275 |
*/
|
|
276 |
new_size = environ_size * 2;
|
|
277 |
if (new_size < MIN_ENV_SIZE)
|
|
278 |
new_size = MIN_ENV_SIZE;
|
|
279 |
|
|
280 |
old_gen = environ_gen;
|
|
281 |
lmutex_unlock(&update_lock);
|
|
282 |
|
|
283 |
new_chunk = malloc(sizeof (chunk_t) +
|
|
284 |
new_size * sizeof (char *));
|
|
285 |
if (new_chunk == NULL) {
|
|
286 |
errno = ENOMEM;
|
|
287 |
return (-1);
|
|
288 |
}
|
|
289 |
|
|
290 |
lmutex_lock(&update_lock);
|
|
291 |
|
|
292 |
/*
|
|
293 |
* If no other thread added or removed names while the lock
|
|
294 |
* was dropped, it is time to break out of this loop.
|
|
295 |
*/
|
|
296 |
if (environ_gen == old_gen)
|
|
297 |
break;
|
|
298 |
|
|
299 |
/*
|
|
300 |
* At least one name has been added or removed, so we need to
|
|
301 |
* try again. It is very likely that we will find sufficient
|
|
302 |
* space the next time around.
|
|
303 |
*/
|
|
304 |
lmutex_unlock(&update_lock);
|
|
305 |
free(new_chunk);
|
|
306 |
lmutex_lock(&update_lock);
|
|
307 |
}
|
|
308 |
|
|
309 |
/* Add the new chunk to chunk_list to hide potential future leak. */
|
|
310 |
new_chunk->next = chunk_list;
|
|
311 |
chunk_list = new_chunk;
|
|
312 |
|
|
313 |
/* Copy the old environ list into the top of the new environ list. */
|
|
314 |
new_base = (const char **)(new_chunk + 1);
|
|
315 |
new_environ = &new_base[(new_size - 1) - environ_size];
|
|
316 |
(void) memcpy(new_environ, my_environ, environ_size * sizeof (char *));
|
|
317 |
|
|
318 |
/* Insert the new entry at the bottom of the new environ list. */
|
|
319 |
new_environ[-1] = string;
|
|
320 |
new_environ--;
|
|
321 |
|
|
322 |
/* Ensure that the new environ list is visible to all. */
|
|
323 |
membar_producer();
|
|
324 |
|
|
325 |
/* Make the switch (dropping the old environ list on the floor). */
|
|
326 |
environ_base = new_base;
|
|
327 |
my_environ = new_environ;
|
|
328 |
environ = my_environ;
|
|
329 |
environ_size = new_size;
|
|
330 |
|
|
331 |
/* We've added a name, so bump the generation number. */
|
|
332 |
environ_gen++;
|
|
333 |
|
|
334 |
lmutex_unlock(&update_lock);
|
|
335 |
return (0);
|
|
336 |
}
|
|
337 |
|
|
338 |
/*
|
|
339 |
* All the work for putenv() is done in addtoenv().
|
|
340 |
*/
|
|
341 |
int
|
|
342 |
putenv(char *string)
|
|
343 |
{
|
|
344 |
return (addtoenv(string, 1));
|
|
345 |
}
|
|
346 |
|
|
347 |
/*
|
|
348 |
* setenv() is a little more complex than putenv() because we have to allocate
|
|
349 |
* and construct an environ entry on behalf of the caller. The bulk of the
|
|
350 |
* work is still done in addtoenv().
|
|
351 |
*/
|
|
352 |
|
|
353 |
int
|
|
354 |
setenv(const char *envname, const char *envval, int overwrite)
|
|
355 |
{
|
|
356 |
chunk_t *new_chunk;
|
|
357 |
char *new_string;
|
|
358 |
size_t name_len;
|
|
359 |
size_t val_len;
|
|
360 |
int res;
|
|
361 |
|
|
362 |
if (envname == NULL || *envname == 0 || strchr(envname, '=') != NULL) {
|
|
363 |
errno = EINVAL;
|
|
364 |
return (-1);
|
|
365 |
}
|
|
366 |
|
|
367 |
name_len = strlen(envname);
|
|
368 |
val_len = strlen(envval);
|
|
369 |
|
|
370 |
new_chunk = malloc(sizeof (chunk_t) + name_len + val_len + 2);
|
|
371 |
if (new_chunk == NULL) {
|
|
372 |
errno = ENOMEM;
|
|
373 |
return (-1);
|
|
374 |
}
|
|
375 |
new_string = (char *)(new_chunk + 1);
|
|
376 |
|
|
377 |
(void) memcpy(new_string, envname, name_len);
|
|
378 |
new_string[name_len] = '=';
|
|
379 |
(void) memcpy(new_string + name_len + 1, envval, val_len);
|
|
380 |
new_string[name_len + 1 + val_len] = 0;
|
|
381 |
|
|
382 |
if ((res = addtoenv(new_string, overwrite)) < 0) {
|
|
383 |
free(new_chunk);
|
|
384 |
if (res == -2) {
|
|
385 |
/* The name already existed, but not an error. */
|
|
386 |
return (0);
|
|
387 |
} else {
|
|
388 |
/* i.e. res == -1 which means only one thing. */
|
|
389 |
errno = ENOMEM;
|
|
390 |
return (-1);
|
|
391 |
}
|
|
392 |
}
|
|
393 |
|
|
394 |
/* Hide potential leak of new_string. */
|
|
395 |
lmutex_lock(&update_lock);
|
|
396 |
new_chunk->next = chunk_list;
|
|
397 |
chunk_list = new_chunk;
|
|
398 |
lmutex_unlock(&update_lock);
|
|
399 |
|
|
400 |
return (0);
|
|
401 |
}
|
|
402 |
|
|
403 |
/*
|
|
404 |
* unsetenv() is tricky because we need to compress the environ list in a way
|
|
405 |
* which supports a lockless getenv(). The approach here is to move the first
|
|
406 |
* entry from the enrivon list into the space occupied by the entry to be
|
|
407 |
* deleted, and then to increment environ. This has the added advantage of
|
|
408 |
* making _any_ incremental linear search of the environ list consistent (i.e.
|
|
409 |
* we will not break any naughty apps which read the list without our help).
|
|
410 |
*/
|
|
411 |
int
|
|
412 |
unsetenv(const char *name)
|
|
413 |
{
|
|
414 |
const char **p;
|
|
415 |
char *value;
|
|
416 |
|
|
417 |
if (name == NULL || *name == 0 || strchr(name, '=') != NULL) {
|
|
418 |
errno = EINVAL;
|
|
419 |
return (-1);
|
|
420 |
}
|
|
421 |
|
|
422 |
if ((my_environ != environ) || !initenv_done)
|
|
423 |
initenv();
|
|
424 |
|
|
425 |
lmutex_lock(&update_lock);
|
|
426 |
|
|
427 |
/*
|
|
428 |
* Find the target, overwrite it with the first entry, increment the
|
|
429 |
* environ pointer.
|
|
430 |
*/
|
|
431 |
if ((p = findenv(my_environ, name, 1, &value)) != NULL) {
|
|
432 |
/* Overwrite target with the first entry. */
|
|
433 |
*p = my_environ[0];
|
|
434 |
|
|
435 |
/* Ensure that the moved entry is visible to all. */
|
|
436 |
membar_producer();
|
|
437 |
|
|
438 |
/* Shrink the environ list. */
|
|
439 |
my_environ++;
|
|
440 |
environ = my_environ;
|
|
441 |
|
|
442 |
/* Make sure addtoenv() knows that we've removed a name. */
|
|
443 |
environ_gen++;
|
|
444 |
}
|
|
445 |
|
|
446 |
lmutex_unlock(&update_lock);
|
|
447 |
return (0);
|
|
448 |
}
|
|
449 |
|
|
450 |
/*
|
|
451 |
* At last, a lockless implementation of getenv()!
|
|
452 |
*/
|
|
453 |
char *
|
|
454 |
getenv(const char *name)
|
|
455 |
{
|
|
456 |
char *value;
|
|
457 |
|
|
458 |
if (!initenv_done)
|
|
459 |
initenv();
|
|
460 |
|
|
461 |
if (findenv(environ, name, 1, &value) != NULL)
|
|
462 |
return (value);
|
|
463 |
|
|
464 |
return (NULL);
|
|
465 |
}
|