1 /*
2  * Copyright (c) 2023, Meta
3  * Copyright (c) 2025 Tenstorrent AI ULC
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <errno.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 
13 #include <zephyr/logging/log.h>
14 #include <zephyr/sys/sem.h>
15 
16 #define TRACK_ALLOC (IS_ENABLED(CONFIG_POSIX_ENV_LOG_LEVEL_DBG) || IS_ENABLED(CONFIG_ZTEST))
17 
18 LOG_MODULE_REGISTER(posix_env, CONFIG_POSIX_ENV_LOG_LEVEL);
19 
20 static SYS_SEM_DEFINE(environ_lock, 1, 1);
21 static size_t allocated;
22 char **environ;
23 
24 #ifdef CONFIG_ZTEST
posix_env_get_allocated_space(void)25 size_t posix_env_get_allocated_space(void)
26 {
27 	return allocated;
28 }
29 #endif
30 
environ_size(void)31 static size_t environ_size(void)
32 {
33 	size_t ret;
34 
35 	if (environ == NULL) {
36 		return 0;
37 	}
38 
39 	for (ret = 0; environ[ret] != NULL; ++ret) {
40 	}
41 
42 	return ret;
43 }
44 
findenv(const char * name,size_t namelen)45 static int findenv(const char *name, size_t namelen)
46 {
47 	const char *env;
48 
49 	if (name == NULL || namelen == 0 || strchr(name, '=') != NULL) {
50 		/* Note: '=' is not a valid name character */
51 		return -EINVAL;
52 	}
53 
54 	if (environ == NULL) {
55 		return -ENOENT;
56 	}
57 
58 	for (char **envp = &environ[0]; *envp != NULL; ++envp) {
59 		env = *envp;
60 		if (strncmp(env, name, namelen) == 0 && env[namelen] == '=') {
61 			return envp - environ;
62 		}
63 	}
64 
65 	return -ENOENT;
66 }
67 
z_getenv(const char * name)68 char *z_getenv(const char *name)
69 {
70 	int ret;
71 	size_t nsize;
72 	char *val = NULL;
73 
74 	nsize = (name == NULL) ? 0 : strlen(name);
75 	SYS_SEM_LOCK(&environ_lock) {
76 		ret = findenv(name, nsize);
77 		if (ret < 0) {
78 			SYS_SEM_LOCK_BREAK;
79 		}
80 
81 		val = environ[ret] + nsize + 1;
82 	}
83 
84 	return val;
85 }
86 
z_getenv_r(const char * name,char * buf,size_t len)87 int z_getenv_r(const char *name, char *buf, size_t len)
88 {
89 	int ret = 0;
90 	size_t vsize;
91 	size_t nsize;
92 	char *val = NULL;
93 
94 	nsize = (name == NULL) ? 0 : strlen(name);
95 	SYS_SEM_LOCK(&environ_lock) {
96 		ret = findenv(name, nsize);
97 		if (ret < 0) {
98 			LOG_DBG("No entry for name '%s'", name);
99 			SYS_SEM_LOCK_BREAK;
100 		}
101 
102 		val = environ[ret] + nsize + 1;
103 		vsize = strlen(val) + 1;
104 		if (vsize > len) {
105 			ret = -ERANGE;
106 			SYS_SEM_LOCK_BREAK;
107 		}
108 		strcpy(buf, val);
109 		LOG_DBG("Found entry %s", environ[ret]);
110 	}
111 
112 	if (ret < 0) {
113 		errno = -ret;
114 		ret = -1;
115 	}
116 
117 	return ret;
118 }
119 
z_setenv(const char * name,const char * val,int overwrite)120 int z_setenv(const char *name, const char *val, int overwrite)
121 {
122 	int ret = 0;
123 	char *env;
124 	char **envp;
125 	size_t esize;
126 	const size_t vsize = (val == NULL) ? 0 : strlen(val);
127 	const size_t nsize = (name == NULL) ? 0 : strlen(name);
128 	/* total size of name + '=' + val + '\0' */
129 	const size_t tsize = nsize + 1 /* '=' */ + vsize + 1 /* '\0' */;
130 
131 	if (name == NULL || val == NULL) {
132 		LOG_DBG("Invalid name '%s' or value '%s'", name, val);
133 		errno = EINVAL;
134 		return -1;
135 	}
136 
137 	SYS_SEM_LOCK(&environ_lock) {
138 		ret = findenv(name, nsize);
139 		if (ret == -EINVAL) {
140 			LOG_DBG("Invalid name '%s'", name);
141 			SYS_SEM_LOCK_BREAK;
142 		}
143 		if (ret >= 0) {
144 			/* name was found in environ */
145 			esize = strlen(environ[ret]) + 1;
146 			if (overwrite == 0) {
147 				LOG_DBG("Found entry %s", environ[ret]);
148 				ret = 0;
149 				SYS_SEM_LOCK_BREAK;
150 			}
151 		} else {
152 			/* name was not found in environ -> add new entry */
153 			esize = environ_size();
154 			envp = realloc(environ,
155 				       sizeof(void *) * (esize + 1 /* new entry */ + 1 /* NULL */));
156 			if (envp == NULL) {
157 				ret = -ENOMEM;
158 				SYS_SEM_LOCK_BREAK;
159 			}
160 
161 			if (TRACK_ALLOC) {
162 				allocated += sizeof(void *) * (esize + 2);
163 				LOG_DBG("realloc %zu bytes (allocated: %zu)",
164 					sizeof(void *) * (esize + 2), allocated);
165 			}
166 
167 			environ = envp;
168 			ret = esize;
169 			environ[ret] = NULL;
170 			environ[ret + 1] = NULL;
171 			esize = 0;
172 		}
173 
174 		if (esize < tsize) {
175 			/* need to malloc or realloc space for new environ entry */
176 			env = realloc(environ[ret], tsize);
177 			if (env == NULL) {
178 				ret = -ENOMEM;
179 				SYS_SEM_LOCK_BREAK;
180 			}
181 			if (TRACK_ALLOC) {
182 				allocated += tsize - esize;
183 				LOG_DBG("realloc %zu bytes (allocated: %zu)", tsize - esize,
184 					allocated);
185 			}
186 			environ[ret] = env;
187 		}
188 
189 		strcpy(environ[ret], name);
190 		environ[ret][nsize] = '=';
191 		strncpy(environ[ret] + nsize + 1, val, vsize + 1);
192 		LOG_DBG("Added entry %s", environ[ret]);
193 
194 		ret = 0;
195 	}
196 
197 	if (ret < 0) {
198 		errno = -ret;
199 		ret = -1;
200 	}
201 
202 	return ret;
203 }
204 
z_unsetenv(const char * name)205 int z_unsetenv(const char *name)
206 {
207 	int ret = 0;
208 	char **envp;
209 	size_t esize;
210 	size_t nsize;
211 
212 	nsize = (name == NULL) ? 0 : strlen(name);
213 	SYS_SEM_LOCK(&environ_lock) {
214 		ret = findenv(name, nsize);
215 		if (ret < 0) {
216 			ret = (ret == -EINVAL) ? -EINVAL : 0;
217 			SYS_SEM_LOCK_BREAK;
218 		}
219 
220 		esize = environ_size();
221 		if (TRACK_ALLOC) {
222 			allocated -= strlen(environ[ret]) + 1;
223 			LOG_DBG("free %zu bytes (allocated: %zu)", strlen(environ[ret]) + 1,
224 				allocated);
225 		}
226 		free(environ[ret]);
227 
228 		/* shuffle remaining environment variable pointers forward */
229 		for (; ret < esize; ++ret) {
230 			environ[ret] = environ[ret + 1];
231 		}
232 		/* environ must be terminated with a NULL pointer */
233 		environ[ret] = NULL;
234 
235 		/* reduce environ size and update allocation */
236 		--esize;
237 		if (esize == 0) {
238 			free(environ);
239 			environ = NULL;
240 		} else {
241 			envp = realloc(environ, (esize + 1 /* NULL */) * sizeof(void *));
242 			if (envp != NULL) {
243 				environ = envp;
244 			}
245 		}
246 		__ASSERT_NO_MSG((esize >= 1 && environ != NULL) || environ == NULL);
247 
248 		if (TRACK_ALLOC) {
249 			/* recycle nsize here */
250 			nsize = ((esize == 0) ? 2 : 1) * sizeof(void *);
251 			allocated -= nsize;
252 			LOG_DBG("free %zu bytes (allocated: %zu)", nsize, allocated);
253 		}
254 
255 		ret = 0;
256 	}
257 
258 	if (ret < 0) {
259 		errno = -ret;
260 		ret = -1;
261 	}
262 
263 	return ret;
264 }
265