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