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