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