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