1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> |
4 | |
5 | This software is provided 'as-is', without any express or implied |
6 | warranty. In no event will the authors be held liable for any damages |
7 | arising from the use of this software. |
8 | |
9 | Permission is granted to anyone to use this software for any purpose, |
10 | including commercial applications, and to alter it and redistribute it |
11 | freely, subject to the following restrictions: |
12 | |
13 | 1. The origin of this software must not be misrepresented; you must not |
14 | claim that you wrote the original software. If you use this software |
15 | in a product, an acknowledgment in the product documentation would be |
16 | appreciated but is not required. |
17 | 2. Altered source versions must be plainly marked as such, and must not be |
18 | misrepresented as being the original software. |
19 | 3. This notice may not be removed or altered from any source distribution. |
20 | */ |
21 | #include "SDL_internal.h" |
22 | |
23 | #include "SDL_getenv_c.h" |
24 | |
25 | #if defined(SDL_PLATFORM_WINDOWS) |
26 | #include "../core/windows/SDL_windows.h" |
27 | #endif |
28 | |
29 | #ifdef SDL_PLATFORM_ANDROID |
30 | #include "../core/android/SDL_android.h" |
31 | #endif |
32 | |
33 | #if defined(SDL_PLATFORM_WINDOWS) |
34 | #define HAVE_WIN32_ENVIRONMENT |
35 | #elif defined(HAVE_GETENV) && \ |
36 | (defined(HAVE_SETENV) || defined(HAVE_PUTENV)) && \ |
37 | (defined(HAVE_UNSETENV) || defined(HAVE_PUTENV)) |
38 | #define HAVE_LIBC_ENVIRONMENT |
39 | #if defined(SDL_PLATFORM_MACOS) |
40 | #include <crt_externs.h> |
41 | #define environ (*_NSGetEnviron()) |
42 | #elif defined(SDL_PLATFORM_FREEBSD) |
43 | #include <dlfcn.h> |
44 | #define environ ((char **)dlsym(RTLD_DEFAULT, "environ")) |
45 | #else |
46 | extern char **environ; |
47 | #endif |
48 | #else |
49 | #define HAVE_LOCAL_ENVIRONMENT |
50 | static char **environ; |
51 | #endif |
52 | |
53 | |
54 | struct SDL_Environment |
55 | { |
56 | SDL_Mutex *lock; // !!! FIXME: reuse SDL_HashTable's lock. |
57 | SDL_HashTable *strings; |
58 | }; |
59 | static SDL_Environment *SDL_environment; |
60 | |
61 | SDL_Environment *SDL_GetEnvironment(void) |
62 | { |
63 | if (!SDL_environment) { |
64 | SDL_environment = SDL_CreateEnvironment(true); |
65 | } |
66 | return SDL_environment; |
67 | } |
68 | |
69 | bool SDL_InitEnvironment(void) |
70 | { |
71 | return (SDL_GetEnvironment() != NULL); |
72 | } |
73 | |
74 | void SDL_QuitEnvironment(void) |
75 | { |
76 | SDL_Environment *env = SDL_environment; |
77 | |
78 | if (env) { |
79 | SDL_environment = NULL; |
80 | SDL_DestroyEnvironment(env); |
81 | } |
82 | } |
83 | |
84 | SDL_Environment *SDL_CreateEnvironment(bool populated) |
85 | { |
86 | SDL_Environment *env = SDL_calloc(1, sizeof(*env)); |
87 | if (!env) { |
88 | return NULL; |
89 | } |
90 | |
91 | env->strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashKey, NULL); |
92 | if (!env->strings) { |
93 | SDL_free(env); |
94 | return NULL; |
95 | } |
96 | |
97 | // Don't fail if we can't create a mutex (e.g. on a single-thread environment) // !!! FIXME: single-threaded environments should still return a non-NULL, do-nothing object here. Check for failure! |
98 | env->lock = SDL_CreateMutex(); |
99 | |
100 | if (populated) { |
101 | #ifdef SDL_PLATFORM_WINDOWS |
102 | LPWCH strings = GetEnvironmentStringsW(); |
103 | if (strings) { |
104 | for (LPWCH string = strings; *string; string += SDL_wcslen(string) + 1) { |
105 | char *variable = WIN_StringToUTF8W(string); |
106 | if (!variable) { |
107 | continue; |
108 | } |
109 | |
110 | char *value = SDL_strchr(variable, '='); |
111 | if (!value || value == variable) { |
112 | SDL_free(variable); |
113 | continue; |
114 | } |
115 | *value++ = '\0'; |
116 | |
117 | SDL_InsertIntoHashTable(env->strings, variable, value, true); |
118 | } |
119 | FreeEnvironmentStringsW(strings); |
120 | } |
121 | #else |
122 | #ifdef SDL_PLATFORM_ANDROID |
123 | // Make sure variables from the application manifest are available |
124 | Android_JNI_GetManifestEnvironmentVariables(); |
125 | #endif |
126 | char **strings = environ; |
127 | if (strings) { |
128 | for (int i = 0; strings[i]; ++i) { |
129 | char *variable = SDL_strdup(strings[i]); |
130 | if (!variable) { |
131 | continue; |
132 | } |
133 | |
134 | char *value = SDL_strchr(variable, '='); |
135 | if (!value || value == variable) { |
136 | SDL_free(variable); |
137 | continue; |
138 | } |
139 | *value++ = '\0'; |
140 | |
141 | SDL_InsertIntoHashTable(env->strings, variable, value, true); |
142 | } |
143 | } |
144 | #endif // SDL_PLATFORM_WINDOWS |
145 | } |
146 | |
147 | return env; |
148 | } |
149 | |
150 | const char *SDL_GetEnvironmentVariable(SDL_Environment *env, const char *name) |
151 | { |
152 | const char *result = NULL; |
153 | |
154 | if (!env) { |
155 | return NULL; |
156 | } else if (!name || *name == '\0') { |
157 | return NULL; |
158 | } |
159 | |
160 | SDL_LockMutex(env->lock); |
161 | { |
162 | const char *value; |
163 | |
164 | if (SDL_FindInHashTable(env->strings, name, (const void **)&value)) { |
165 | result = SDL_GetPersistentString(value); |
166 | } |
167 | } |
168 | SDL_UnlockMutex(env->lock); |
169 | |
170 | return result; |
171 | } |
172 | |
173 | typedef struct CountEnvStringsData |
174 | { |
175 | size_t count; |
176 | size_t length; |
177 | } CountEnvStringsData; |
178 | |
179 | static bool SDLCALL CountEnvStrings(void *userdata, const SDL_HashTable *table, const void *key, const void *value) |
180 | { |
181 | CountEnvStringsData *data = (CountEnvStringsData *) userdata; |
182 | data->length += SDL_strlen((const char *) key) + 1 + SDL_strlen((const char *) value) + 1; |
183 | data->count++; |
184 | return true; // keep iterating. |
185 | } |
186 | |
187 | typedef struct CopyEnvStringsData |
188 | { |
189 | char **result; |
190 | char *string; |
191 | size_t count; |
192 | } CopyEnvStringsData; |
193 | |
194 | static bool SDLCALL CopyEnvStrings(void *userdata, const SDL_HashTable *table, const void *vkey, const void *vvalue) |
195 | { |
196 | CopyEnvStringsData *data = (CopyEnvStringsData *) userdata; |
197 | const char *key = (const char *) vkey; |
198 | const char *value = (const char *) vvalue; |
199 | size_t len; |
200 | |
201 | len = SDL_strlen(key); |
202 | data->result[data->count] = data->string; |
203 | SDL_memcpy(data->string, key, len); |
204 | data->string += len; |
205 | *(data->string++) = '='; |
206 | |
207 | len = SDL_strlen(value); |
208 | SDL_memcpy(data->string, value, len); |
209 | data->string += len; |
210 | *(data->string++) = '\0'; |
211 | data->count++; |
212 | |
213 | return true; // keep iterating. |
214 | } |
215 | |
216 | char **SDL_GetEnvironmentVariables(SDL_Environment *env) |
217 | { |
218 | char **result = NULL; |
219 | |
220 | if (!env) { |
221 | SDL_InvalidParamError("env" ); |
222 | return NULL; |
223 | } |
224 | |
225 | SDL_LockMutex(env->lock); |
226 | { |
227 | // First pass, get the size we need for all the strings |
228 | CountEnvStringsData countdata = { 0, 0 }; |
229 | SDL_IterateHashTable(env->strings, CountEnvStrings, &countdata); |
230 | |
231 | // Allocate memory for the strings |
232 | result = (char **)SDL_malloc((countdata.count + 1) * sizeof(*result) + countdata.length); |
233 | if (result) { |
234 | // Second pass, copy the strings |
235 | char *string = (char *)(result + countdata.count + 1); |
236 | CopyEnvStringsData cpydata = { result, string, 0 }; |
237 | SDL_IterateHashTable(env->strings, CopyEnvStrings, &cpydata); |
238 | SDL_assert(countdata.count == cpydata.count); |
239 | result[cpydata.count] = NULL; |
240 | } |
241 | } |
242 | SDL_UnlockMutex(env->lock); |
243 | |
244 | return result; |
245 | } |
246 | |
247 | bool SDL_SetEnvironmentVariable(SDL_Environment *env, const char *name, const char *value, bool overwrite) |
248 | { |
249 | bool result = false; |
250 | |
251 | if (!env) { |
252 | return SDL_InvalidParamError("env" ); |
253 | } else if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) { |
254 | return SDL_InvalidParamError("name" ); |
255 | } else if (!value) { |
256 | return SDL_InvalidParamError("value" ); |
257 | } |
258 | |
259 | SDL_LockMutex(env->lock); |
260 | { |
261 | char *string = NULL; |
262 | if (SDL_asprintf(&string, "%s=%s" , name, value) > 0) { |
263 | const size_t len = SDL_strlen(name); |
264 | string[len] = '\0'; |
265 | const char *origname = name; |
266 | name = string; |
267 | value = string + len + 1; |
268 | result = SDL_InsertIntoHashTable(env->strings, name, value, overwrite); |
269 | if (!result) { |
270 | SDL_free(string); |
271 | if (!overwrite) { |
272 | const void *existing_value = NULL; |
273 | // !!! FIXME: InsertIntoHashTable does this lookup too, maybe we should have a means to report that, to avoid duplicate work? |
274 | if (SDL_FindInHashTable(env->strings, origname, &existing_value)) { |
275 | result = true; // it already existed, and we refused to overwrite it. Call it success. |
276 | } |
277 | } |
278 | } |
279 | } |
280 | } |
281 | SDL_UnlockMutex(env->lock); |
282 | |
283 | return result; |
284 | } |
285 | |
286 | bool SDL_UnsetEnvironmentVariable(SDL_Environment *env, const char *name) |
287 | { |
288 | bool result = false; |
289 | |
290 | if (!env) { |
291 | return SDL_InvalidParamError("env" ); |
292 | } else if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) { |
293 | return SDL_InvalidParamError("name" ); |
294 | } |
295 | |
296 | SDL_LockMutex(env->lock); |
297 | { |
298 | const void *value; |
299 | if (SDL_FindInHashTable(env->strings, name, &value)) { |
300 | result = SDL_RemoveFromHashTable(env->strings, name); |
301 | } else { |
302 | result = true; |
303 | } |
304 | } |
305 | SDL_UnlockMutex(env->lock); |
306 | |
307 | return result; |
308 | } |
309 | |
310 | void SDL_DestroyEnvironment(SDL_Environment *env) |
311 | { |
312 | if (!env || env == SDL_environment) { |
313 | return; |
314 | } |
315 | |
316 | SDL_DestroyMutex(env->lock); |
317 | SDL_DestroyHashTable(env->strings); |
318 | SDL_free(env); |
319 | } |
320 | |
321 | // Put a variable into the environment |
322 | // Note: Name may not contain a '=' character. (Reference: http://www.unix.com/man-page/Linux/3/setenv/) |
323 | #ifdef HAVE_LIBC_ENVIRONMENT |
324 | #if defined(HAVE_SETENV) |
325 | int SDL_setenv_unsafe(const char *name, const char *value, int overwrite) |
326 | { |
327 | // Input validation |
328 | if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) { |
329 | return -1; |
330 | } |
331 | |
332 | SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0)); |
333 | |
334 | return setenv(name, value, overwrite); |
335 | } |
336 | // We have a real environment table, but no real setenv? Fake it w/ putenv. |
337 | #else |
338 | int SDL_setenv_unsafe(const char *name, const char *value, int overwrite) |
339 | { |
340 | char *new_variable; |
341 | |
342 | // Input validation |
343 | if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) { |
344 | return -1; |
345 | } |
346 | |
347 | SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0)); |
348 | |
349 | if (getenv(name) != NULL) { |
350 | if (!overwrite) { |
351 | return 0; // leave the existing one there. |
352 | } |
353 | } |
354 | |
355 | // This leaks. Sorry. Get a better OS so we don't have to do this. |
356 | SDL_asprintf(&new_variable, "%s=%s" , name, value); |
357 | if (!new_variable) { |
358 | return -1; |
359 | } |
360 | return putenv(new_variable); |
361 | } |
362 | #endif |
363 | #elif defined(HAVE_WIN32_ENVIRONMENT) |
364 | int SDL_setenv_unsafe(const char *name, const char *value, int overwrite) |
365 | { |
366 | // Input validation |
367 | if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) { |
368 | return -1; |
369 | } |
370 | |
371 | SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0)); |
372 | |
373 | if (!overwrite) { |
374 | if (GetEnvironmentVariableA(name, NULL, 0) > 0) { |
375 | return 0; // asked not to overwrite existing value. |
376 | } |
377 | } |
378 | if (!SetEnvironmentVariableA(name, value)) { |
379 | return -1; |
380 | } |
381 | return 0; |
382 | } |
383 | #else // roll our own |
384 | |
385 | int SDL_setenv_unsafe(const char *name, const char *value, int overwrite) |
386 | { |
387 | int added; |
388 | size_t len, i; |
389 | char **new_env; |
390 | char *new_variable; |
391 | |
392 | // Input validation |
393 | if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) { |
394 | return -1; |
395 | } |
396 | |
397 | // See if it already exists |
398 | if (!overwrite && SDL_getenv_unsafe(name)) { |
399 | return 0; |
400 | } |
401 | |
402 | SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0)); |
403 | |
404 | // Allocate memory for the variable |
405 | len = SDL_strlen(name) + SDL_strlen(value) + 2; |
406 | new_variable = (char *)SDL_malloc(len); |
407 | if (!new_variable) { |
408 | return -1; |
409 | } |
410 | |
411 | SDL_snprintf(new_variable, len, "%s=%s" , name, value); |
412 | value = new_variable + SDL_strlen(name) + 1; |
413 | name = new_variable; |
414 | |
415 | // Actually put it into the environment |
416 | added = 0; |
417 | i = 0; |
418 | if (environ) { |
419 | // Check to see if it's already there... |
420 | len = (value - name); |
421 | for (; environ[i]; ++i) { |
422 | if (SDL_strncmp(environ[i], name, len) == 0) { |
423 | // If we found it, just replace the entry |
424 | SDL_free(environ[i]); |
425 | environ[i] = new_variable; |
426 | added = 1; |
427 | break; |
428 | } |
429 | } |
430 | } |
431 | |
432 | // Didn't find it in the environment, expand and add |
433 | if (!added) { |
434 | new_env = SDL_realloc(environ, (i + 2) * sizeof(char *)); |
435 | if (new_env) { |
436 | environ = new_env; |
437 | environ[i++] = new_variable; |
438 | environ[i++] = (char *)0; |
439 | added = 1; |
440 | } else { |
441 | SDL_free(new_variable); |
442 | } |
443 | } |
444 | return added ? 0 : -1; |
445 | } |
446 | #endif // HAVE_LIBC_ENVIRONMENT |
447 | |
448 | #ifdef HAVE_LIBC_ENVIRONMENT |
449 | #if defined(HAVE_UNSETENV) |
450 | int SDL_unsetenv_unsafe(const char *name) |
451 | { |
452 | // Input validation |
453 | if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) { |
454 | return -1; |
455 | } |
456 | |
457 | SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name); |
458 | |
459 | return unsetenv(name); |
460 | } |
461 | // We have a real environment table, but no unsetenv? Fake it w/ putenv. |
462 | #else |
463 | int SDL_unsetenv_unsafe(const char *name) |
464 | { |
465 | // Input validation |
466 | if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) { |
467 | return -1; |
468 | } |
469 | |
470 | SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name); |
471 | |
472 | // Hope this environment uses the non-standard extension of removing the environment variable if it has no '=' |
473 | return putenv(name); |
474 | } |
475 | #endif |
476 | #elif defined(HAVE_WIN32_ENVIRONMENT) |
477 | int SDL_unsetenv_unsafe(const char *name) |
478 | { |
479 | // Input validation |
480 | if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) { |
481 | return -1; |
482 | } |
483 | |
484 | SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name); |
485 | |
486 | if (!SetEnvironmentVariableA(name, NULL)) { |
487 | return -1; |
488 | } |
489 | return 0; |
490 | } |
491 | #else |
492 | int SDL_unsetenv_unsafe(const char *name) |
493 | { |
494 | size_t len, i; |
495 | |
496 | // Input validation |
497 | if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) { |
498 | return -1; |
499 | } |
500 | |
501 | SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name); |
502 | |
503 | if (environ) { |
504 | len = SDL_strlen(name); |
505 | for (i = 0; environ[i]; ++i) { |
506 | if ((SDL_strncmp(environ[i], name, len) == 0) && |
507 | (environ[i][len] == '=')) { |
508 | // Just clear out this entry for now |
509 | *environ[i] = '\0'; |
510 | break; |
511 | } |
512 | } |
513 | } |
514 | return 0; |
515 | } |
516 | #endif // HAVE_LIBC_ENVIRONMENT |
517 | |
518 | // Retrieve a variable named "name" from the environment |
519 | #ifdef HAVE_LIBC_ENVIRONMENT |
520 | const char *SDL_getenv_unsafe(const char *name) |
521 | { |
522 | #ifdef SDL_PLATFORM_ANDROID |
523 | // Make sure variables from the application manifest are available |
524 | Android_JNI_GetManifestEnvironmentVariables(); |
525 | #endif |
526 | |
527 | // Input validation |
528 | if (!name || *name == '\0') { |
529 | return NULL; |
530 | } |
531 | |
532 | return getenv(name); |
533 | } |
534 | #elif defined(HAVE_WIN32_ENVIRONMENT) |
535 | const char *SDL_getenv_unsafe(const char *name) |
536 | { |
537 | DWORD length, maxlen = 0; |
538 | char *string = NULL; |
539 | const char *result = NULL; |
540 | |
541 | // Input validation |
542 | if (!name || *name == '\0') { |
543 | return NULL; |
544 | } |
545 | |
546 | for ( ; ; ) { |
547 | SetLastError(ERROR_SUCCESS); |
548 | length = GetEnvironmentVariableA(name, string, maxlen); |
549 | |
550 | if (length > maxlen) { |
551 | char *temp = (char *)SDL_realloc(string, length); |
552 | if (!temp) { |
553 | return NULL; |
554 | } |
555 | string = temp; |
556 | maxlen = length; |
557 | } else { |
558 | if (GetLastError() != ERROR_SUCCESS) { |
559 | if (string) { |
560 | SDL_free(string); |
561 | } |
562 | return NULL; |
563 | } |
564 | break; |
565 | } |
566 | } |
567 | if (string) { |
568 | result = SDL_GetPersistentString(string); |
569 | SDL_free(string); |
570 | } |
571 | return result; |
572 | } |
573 | #else |
574 | const char *SDL_getenv_unsafe(const char *name) |
575 | { |
576 | size_t len, i; |
577 | const char *value = NULL; |
578 | |
579 | // Input validation |
580 | if (!name || *name == '\0') { |
581 | return NULL; |
582 | } |
583 | |
584 | if (environ) { |
585 | len = SDL_strlen(name); |
586 | for (i = 0; environ[i]; ++i) { |
587 | if ((SDL_strncmp(environ[i], name, len) == 0) && |
588 | (environ[i][len] == '=')) { |
589 | value = &environ[i][len + 1]; |
590 | break; |
591 | } |
592 | } |
593 | } |
594 | return value; |
595 | } |
596 | #endif // HAVE_LIBC_ENVIRONMENT |
597 | |
598 | const char *SDL_getenv(const char *name) |
599 | { |
600 | return SDL_GetEnvironmentVariable(SDL_GetEnvironment(), name); |
601 | } |
602 | |