1/*	$NetBSD: _env.c,v 1.15 2024/01/02 19:27:26 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.15 2024/01/02 19:27:26 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#include "csu-common.h"
48
49#include "env.h"
50#include "local.h"
51
52/*
53 * Red-Black tree node for tracking memory used by environment variables.
54 * The tree is sorted by the address of the nodes themselves.
55 */
56typedef struct {
57	rb_node_t	rb_node;
58	size_t		length;
59	uint8_t		marker;
60	char		data[];
61} env_node_t;
62
63/* Compare functions for above tree. */
64static signed int env_tree_compare_nodes(void *, const void *, const void *);
65static signed int env_tree_compare_key(void *, const void *, const void *);
66
67/* Operations for above tree. */
68static const rb_tree_ops_t env_tree_ops = {
69	.rbto_compare_nodes = env_tree_compare_nodes,
70	.rbto_compare_key = env_tree_compare_key,
71	.rbto_node_offset = offsetof(env_node_t, rb_node),
72	.rbto_context = NULL
73};
74
75/* The single instance of above tree. */
76static rb_tree_t	env_tree =
77    RB_TREE_INITIALIZER(env_tree, &env_tree_ops);
78
79/* The allocated environment. */
80static char	**allocated_environ;
81static size_t	allocated_environ_size;
82
83#define	ENV_ARRAY_SIZE_MIN	16
84
85/* The lock protecting access to the environment. */
86#ifdef _REENTRANT
87static rwlock_t env_lock = RWLOCK_INITIALIZER;
88#endif
89
90/* Compatibility function. */
91extern char *__findenv(const char *name, int *offsetp);
92
93__warn_references(__findenv,
94    "warning: __findenv is an internal obsolete function.")
95
96/* Our initialization function. */
97void __libc_env_init(void);
98
99/*ARGSUSED*/
100static signed int
101env_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
117static signed int
118env_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 */
128size_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 */
155void
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 */
172char *
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 */
193bool
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. */
205static 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 */
252ssize_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	if (environ != NULL) {
261		while (environ[num_entries] != NULL) {
262			if (strncmp(environ[num_entries], name, l_name) == 0 &&
263			    environ[num_entries][l_name] == '=') {
264				/* We found a match. */
265				return num_entries;
266			}
267			num_entries ++;
268		}
269	}
270
271	/* No match found, return if we don't want to allocate a new slot. */
272	if (!allocate)
273		return -1;
274
275	/* Does the environ need scrubbing? */
276	if (environ != allocated_environ && allocated_environ != NULL)
277		__scrubenv();
278
279	/* Create a new slot in the environment. */
280	required_size = num_entries + 1;
281	if (environ == allocated_environ &&
282	    required_size < allocated_environ_size) {
283		/* Does the environment need scrubbing? */
284		if (required_size < allocated_environ_size &&
285		    allocated_environ[required_size] != NULL) {
286			__scrubenv();
287		}
288
289		/* Return a free slot. */
290		return num_entries;
291	}
292
293	/* Determine size of a new environment array. */
294	new_size = ENV_ARRAY_SIZE_MIN;
295	while (new_size <= required_size)
296		new_size <<= 1;
297
298	/* Allocate a new environment array. */
299	if (environ == allocated_environ) {
300		new_environ = environ;
301		errno = reallocarr(&new_environ,
302		    new_size, sizeof(*new_environ));
303		if (errno)
304			return -1;
305	} else {
306		free(allocated_environ);
307		allocated_environ = NULL;
308		allocated_environ_size = 0;
309
310		new_environ = NULL;
311		errno = reallocarr(&new_environ,
312		    new_size, sizeof(*new_environ));
313		if (errno)
314			return -1;
315		(void)memcpy(new_environ, environ,
316		    num_entries * sizeof(*new_environ));
317	}
318
319	/* Clear remaining entries. */
320	(void)memset(&new_environ[num_entries], 0,
321	    (new_size - num_entries) * sizeof(*new_environ));
322
323	/* Use the new environment array. */
324	environ = allocated_environ = new_environ;
325	allocated_environ_size = new_size;
326
327	/* Return a free slot. */
328	return num_entries;
329}
330
331/* Find a string in the environment. */
332char *
333__findenvvar(const char *name, size_t l_name)
334{
335	ssize_t offset;
336
337	offset = __getenvslot(name, l_name, false);
338	return (offset != -1) ? environ[offset] + l_name + 1 : NULL;
339}
340
341/* Compatibility interface, do *not* call this function. */
342char *
343__findenv(const char *name, int *offsetp)
344{
345	size_t l_name;
346	ssize_t offset;
347
348	l_name = __envvarnamelen(name, false);
349	if (l_name == 0)
350		return NULL;
351
352	offset = __getenvslot(name, l_name, false);
353	if (offset < 0 || offset > INT_MAX)
354		return NULL;
355
356	*offsetp = (int)offset;
357	return environ[offset] + l_name + 1;
358}
359
360#ifdef _REENTRANT
361
362/* Lock the environment for read. */
363bool
364__readlockenv(void)
365{
366	int error;
367
368	error = rwlock_rdlock(&env_lock);
369	if (error == 0)
370		return true;
371
372	errno = error;
373	return false;
374}
375
376/* Lock the environment for write. */
377bool
378__writelockenv(void)
379{
380	int error;
381
382	error = rwlock_wrlock(&env_lock);
383	if (error == 0)
384		return true;
385
386	errno = error;
387	return false;
388}
389
390/* Unlock the environment for write. */
391bool
392__unlockenv(void)
393{
394	int error;
395
396	error = rwlock_unlock(&env_lock);
397	if (error == 0)
398		return true;
399
400	errno = error;
401	return false;
402}
403
404#endif
405