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
46extern char **environ;
47#endif
48#else
49#define HAVE_LOCAL_ENVIRONMENT
50static char **environ;
51#endif
52
53
54struct SDL_Environment
55{
56 SDL_Mutex *lock; // !!! FIXME: reuse SDL_HashTable's lock.
57 SDL_HashTable *strings;
58};
59static SDL_Environment *SDL_environment;
60
61SDL_Environment *SDL_GetEnvironment(void)
62{
63 if (!SDL_environment) {
64 SDL_environment = SDL_CreateEnvironment(true);
65 }
66 return SDL_environment;
67}
68
69bool SDL_InitEnvironment(void)
70{
71 return (SDL_GetEnvironment() != NULL);
72}
73
74void 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
84SDL_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
150const 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
173typedef struct CountEnvStringsData
174{
175 size_t count;
176 size_t length;
177} CountEnvStringsData;
178
179static 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
187typedef struct CopyEnvStringsData
188{
189 char **result;
190 char *string;
191 size_t count;
192} CopyEnvStringsData;
193
194static 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
216char **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
247bool 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
286bool 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
310void 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)
325int 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
338int 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)
364int 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
385int 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)
450int 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
463int 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)
477int 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
492int 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
520const 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)
535const 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
574const 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
598const char *SDL_getenv(const char *name)
599{
600 return SDL_GetEnvironmentVariable(SDL_GetEnvironment(), name);
601}
602