1 /* $NetBSD: _env.c,v 1.9 2015/01/20 18:31:25 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 2010 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Matthias Scheler. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #if defined(LIBC_SCCS) && !defined(lint) 34 __RCSID("$NetBSD: _env.c,v 1.9 2015/01/20 18:31:25 christos Exp $"); 35 #endif /* LIBC_SCCS and not lint */ 36 37 #include "namespace.h" 38 39 #include <sys/rbtree.h> 40 41 #include <assert.h> 42 #include <errno.h> 43 #include <limits.h> 44 #include <stdlib.h> 45 #include <stddef.h> 46 #include <string.h> 47 48 #include "env.h" 49 #include "local.h" 50 51 /* 52 * Red-Black tree node for tracking memory used by environment variables. 53 * The tree is sorted by the address of the nodes themselves. 54 */ 55 typedef struct { 56 rb_node_t rb_node; 57 size_t length; 58 uint8_t marker; 59 char data[]; 60 } env_node_t; 61 62 /* Compare functions for above tree. */ 63 static signed int env_tree_compare_nodes(void *, const void *, const void *); 64 static signed int env_tree_compare_key(void *, const void *, const void *); 65 66 /* Operations for above tree. */ 67 static const rb_tree_ops_t env_tree_ops = { 68 .rbto_compare_nodes = env_tree_compare_nodes, 69 .rbto_compare_key = env_tree_compare_key, 70 .rbto_node_offset = offsetof(env_node_t, rb_node), 71 .rbto_context = NULL 72 }; 73 74 /* The single instance of above tree. */ 75 static rb_tree_t env_tree; 76 77 /* The allocated environment. */ 78 static char **allocated_environ; 79 static size_t allocated_environ_size; 80 81 #define ENV_ARRAY_SIZE_MIN 16 82 83 /* The lock protecting access to the environment. */ 84 #ifdef _REENTRANT 85 static rwlock_t env_lock = RWLOCK_INITIALIZER; 86 #endif 87 88 /* Compatibility function. */ 89 char *__findenv(const char *name, int *offsetp); 90 91 __warn_references(__findenv, 92 "warning: __findenv is an internal obsolete function.") 93 94 /* Our initialization function. */ 95 void __libc_env_init(void); 96 97 char **environ; 98 99 /*ARGSUSED*/ 100 static signed int 101 env_tree_compare_nodes(void *ctx, const void *node_a, const void *node_b) 102 { 103 uintptr_t addr_a, addr_b; 104 105 addr_a = (uintptr_t)node_a; 106 addr_b = (uintptr_t)node_b; 107 108 if (addr_a < addr_b) 109 return -1; 110 111 if (addr_a > addr_b) 112 return 1; 113 114 return 0; 115 } 116 117 static signed int 118 env_tree_compare_key(void *ctx, const void *node, const void *key) 119 { 120 return env_tree_compare_nodes(ctx, node, 121 (const uint8_t *)key - offsetof(env_node_t, data)); 122 } 123 124 /* 125 * Determine the of the name in an environment string. Return 0 if the 126 * name is not valid. 127 */ 128 size_t 129 __envvarnamelen(const char *str, bool withequal) 130 { 131 size_t l_name; 132 133 if (str == NULL) 134 return 0; 135 136 l_name = strcspn(str, "="); 137 if (l_name == 0) 138 return 0; 139 140 if (withequal) { 141 if (str[l_name] != '=') 142 return 0; 143 } else { 144 if (str[l_name] == '=') 145 return 0; 146 } 147 148 return l_name; 149 } 150 151 /* 152 * Free memory occupied by environment variable if possible. This function 153 * must be called with the environment write locked. 154 */ 155 void 156 __freeenvvar(char *envvar) 157 { 158 env_node_t *node; 159 160 _DIAGASSERT(envvar != NULL); 161 node = rb_tree_find_node(&env_tree, envvar); 162 if (node != NULL) { 163 rb_tree_remove_node(&env_tree, node); 164 free(node); 165 } 166 } 167 168 /* 169 * Allocate memory for an environment variable. This function must be called 170 * with the environment write locked. 171 */ 172 char * 173 __allocenvvar(size_t length) 174 { 175 env_node_t *node; 176 177 node = malloc(sizeof(*node) + length); 178 if (node != NULL) { 179 node->length = length; 180 node->marker = 0; 181 rb_tree_insert_node(&env_tree, node); 182 return node->data; 183 } else { 184 return NULL; 185 } 186 } 187 188 /* 189 * Check whether an environment variable is writable. This function must be 190 * called with the environment write locked as the caller will probably 191 * overwrite the environment variable afterwards. 192 */ 193 bool 194 __canoverwriteenvvar(char *envvar, size_t length) 195 { 196 env_node_t *node; 197 198 _DIAGASSERT(envvar != NULL); 199 200 node = rb_tree_find_node(&env_tree, envvar); 201 return (node != NULL && length <= node->length); 202 } 203 204 /* Free all allocated environment variables that are no longer used. */ 205 static void 206 __scrubenv(void) 207 { 208 static uint8_t marker = 0; 209 size_t num_entries; 210 env_node_t *node, *next; 211 212 while (++marker == 0); 213 214 /* Mark all nodes which are currently used. */ 215 for (num_entries = 0; environ[num_entries] != NULL; num_entries++) { 216 node = rb_tree_find_node(&env_tree, environ[num_entries]); 217 if (node != NULL) 218 node->marker = marker; 219 } 220 221 /* Free all nodes which are currently not used. */ 222 for (node = RB_TREE_MIN(&env_tree); node != NULL; node = next) { 223 next = rb_tree_iterate(&env_tree, node, RB_DIR_RIGHT); 224 225 if (node->marker != marker) { 226 rb_tree_remove_node(&env_tree, node); 227 free(node); 228 } 229 } 230 231 /* Deal with the environment array itself. */ 232 if (environ == allocated_environ) { 233 /* Clear out spurious entries in the environment. */ 234 (void)memset(&environ[num_entries + 1], 0, 235 (allocated_environ_size - num_entries - 1) * 236 sizeof(*environ)); 237 } else { 238 /* 239 * The environment array was not allocated by "libc". 240 * Free our array if we allocated one. 241 */ 242 free(allocated_environ); 243 allocated_environ = NULL; 244 allocated_environ_size = 0; 245 } 246 } 247 248 /* 249 * Get a (new) slot in the environment. This function must be called with 250 * the environment write locked. 251 */ 252 ssize_t 253 __getenvslot(const char *name, size_t l_name, bool allocate) 254 { 255 size_t new_size, num_entries, required_size; 256 char **new_environ; 257 258 /* Search for an existing environment variable of the given name. */ 259 num_entries = 0; 260 while (environ[num_entries] != NULL) { 261 if (strncmp(environ[num_entries], name, l_name) == 0 && 262 environ[num_entries][l_name] == '=') { 263 /* We found a match. */ 264 return num_entries; 265 } 266 num_entries ++; 267 } 268 269 /* No match found, return if we don't want to allocate a new slot. */ 270 if (!allocate) 271 return -1; 272 273 /* Does the environ need scrubbing? */ 274 if (environ != allocated_environ && allocated_environ != NULL) 275 __scrubenv(); 276 277 /* Create a new slot in the environment. */ 278 required_size = num_entries + 1; 279 if (environ == allocated_environ && 280 required_size < allocated_environ_size) { 281 /* Does the environment need scrubbing? */ 282 if (required_size < allocated_environ_size && 283 allocated_environ[required_size] != NULL) { 284 __scrubenv(); 285 } 286 287 /* Return a free slot. */ 288 return num_entries; 289 } 290 291 /* Determine size of a new environment array. */ 292 new_size = ENV_ARRAY_SIZE_MIN; 293 while (new_size <= required_size) 294 new_size <<= 1; 295 296 /* Allocate a new environment array. */ 297 if (environ == allocated_environ) { 298 new_environ = realloc(environ, 299 new_size * sizeof(*new_environ)); 300 if (new_environ == NULL) 301 return -1; 302 } else { 303 free(allocated_environ); 304 allocated_environ = NULL; 305 allocated_environ_size = 0; 306 307 new_environ = malloc(new_size * sizeof(*new_environ)); 308 if (new_environ == NULL) 309 return -1; 310 (void)memcpy(new_environ, environ, 311 num_entries * sizeof(*new_environ)); 312 } 313 314 /* Clear remaining entries. */ 315 (void)memset(&new_environ[num_entries], 0, 316 (new_size - num_entries) * sizeof(*new_environ)); 317 318 /* Use the new environment array. */ 319 environ = allocated_environ = new_environ; 320 allocated_environ_size = new_size; 321 322 /* Return a free slot. */ 323 return num_entries; 324 } 325 326 /* Find a string in the environment. */ 327 char * 328 __findenvvar(const char *name, size_t l_name) 329 { 330 ssize_t offset; 331 332 offset = __getenvslot(name, l_name, false); 333 return (offset != -1) ? environ[offset] + l_name + 1 : NULL; 334 } 335 336 /* Compatibility interface, do *not* call this function. */ 337 char * 338 __findenv(const char *name, int *offsetp) 339 { 340 size_t l_name; 341 ssize_t offset; 342 343 l_name = __envvarnamelen(name, false); 344 if (l_name == 0) 345 return NULL; 346 347 offset = __getenvslot(name, l_name, false); 348 if (offset < 0 || offset > INT_MAX) 349 return NULL; 350 351 *offsetp = (int)offset; 352 return environ[offset] + l_name + 1; 353 } 354 355 #ifdef _REENTRANT 356 357 /* Lock the environment for read. */ 358 bool 359 __readlockenv(void) 360 { 361 int error; 362 363 error = rwlock_rdlock(&env_lock); 364 if (error == 0) 365 return true; 366 367 errno = error; 368 return false; 369 } 370 371 /* Lock the environment for write. */ 372 bool 373 __writelockenv(void) 374 { 375 int error; 376 377 error = rwlock_wrlock(&env_lock); 378 if (error == 0) 379 return true; 380 381 errno = error; 382 return false; 383 } 384 385 /* Unlock the environment for write. */ 386 bool 387 __unlockenv(void) 388 { 389 int error; 390 391 error = rwlock_unlock(&env_lock); 392 if (error == 0) 393 return true; 394 395 errno = error; 396 return false; 397 } 398 399 #endif 400 401 /* Initialize environment memory RB tree. */ 402 void __section(".text.startup") 403 __libc_env_init(void) 404 { 405 rb_tree_init(&env_tree, &env_tree_ops); 406 } 407