1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2024 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;
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(NULL, 16, SDL_HashString, SDL_KeyMatchString, SDL_NukeFreeKey, false, false);
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)
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);
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);
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
173char **SDL_GetEnvironmentVariables(SDL_Environment *env)
174{
175 char **result = NULL;
176
177 if (!env) {
178 SDL_InvalidParamError("env");
179 return NULL;
180 }
181
182 SDL_LockMutex(env->lock);
183 {
184 size_t count, length = 0;
185 void *iter;
186 const char *key, *value;
187
188 // First pass, get the size we need for all the strings
189 count = 0;
190 iter = NULL;
191 while (SDL_IterateHashTable(env->strings, (const void **)&key, (const void **)&value, &iter)) {
192 length += SDL_strlen(key) + 1 + SDL_strlen(value) + 1;
193 ++count;
194 }
195
196 // Allocate memory for the strings
197 result = (char **)SDL_malloc((count + 1) * sizeof(*result) + length);
198 char *string = (char *)(result + count + 1);
199
200 // Second pass, copy the strings
201 count = 0;
202 iter = NULL;
203 while (SDL_IterateHashTable(env->strings, (const void **)&key, (const void **)&value, &iter)) {
204 size_t len;
205
206 result[count] = string;
207 len = SDL_strlen(key);
208 SDL_memcpy(string, key, len);
209 string += len;
210 *string++ = '=';
211 len = SDL_strlen(value);
212 SDL_memcpy(string, value, len);
213 string += len;
214 *string++ = '\0';
215 ++count;
216 }
217 result[count] = NULL;
218 }
219 SDL_UnlockMutex(env->lock);
220
221 return result;
222}
223
224bool SDL_SetEnvironmentVariable(SDL_Environment *env, const char *name, const char *value, bool overwrite)
225{
226 bool result = false;
227
228 if (!env) {
229 return SDL_InvalidParamError("env");
230 } else if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {
231 return SDL_InvalidParamError("name");
232 } else if (!value) {
233 return SDL_InvalidParamError("value");
234 }
235
236 SDL_LockMutex(env->lock);
237 {
238 const void *existing_value;
239 bool insert = true;
240
241 if (SDL_FindInHashTable(env->strings, name, &existing_value)) {
242 if (!overwrite) {
243 result = true;
244 insert = false;
245 } else {
246 SDL_RemoveFromHashTable(env->strings, name);
247 }
248 }
249
250 if (insert) {
251 char *string = NULL;
252 if (SDL_asprintf(&string, "%s=%s", name, value) > 0) {
253 size_t len = SDL_strlen(name);
254 string[len] = '\0';
255 name = string;
256 value = string + len + 1;
257 result = SDL_InsertIntoHashTable(env->strings, name, value);
258 }
259 }
260 }
261 SDL_UnlockMutex(env->lock);
262
263 return result;
264}
265
266bool SDL_UnsetEnvironmentVariable(SDL_Environment *env, const char *name)
267{
268 bool result = false;
269
270 if (!env) {
271 return SDL_InvalidParamError("env");
272 } else if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {
273 return SDL_InvalidParamError("name");
274 }
275
276 SDL_LockMutex(env->lock);
277 {
278 const void *value;
279 if (SDL_FindInHashTable(env->strings, name, &value)) {
280 result = SDL_RemoveFromHashTable(env->strings, name);
281 } else {
282 result = true;
283 }
284 }
285 SDL_UnlockMutex(env->lock);
286
287 return result;
288}
289
290void SDL_DestroyEnvironment(SDL_Environment *env)
291{
292 if (!env || env == SDL_environment) {
293 return;
294 }
295
296 SDL_DestroyMutex(env->lock);
297 SDL_DestroyHashTable(env->strings);
298 SDL_free(env);
299}
300
301// Put a variable into the environment
302// Note: Name may not contain a '=' character. (Reference: http://www.unix.com/man-page/Linux/3/setenv/)
303#ifdef HAVE_LIBC_ENVIRONMENT
304#if defined(HAVE_SETENV)
305int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
306{
307 // Input validation
308 if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {
309 return -1;
310 }
311
312 SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));
313
314 return setenv(name, value, overwrite);
315}
316// We have a real environment table, but no real setenv? Fake it w/ putenv.
317#else
318int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
319{
320 char *new_variable;
321
322 // Input validation
323 if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {
324 return -1;
325 }
326
327 SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));
328
329 if (getenv(name) != NULL) {
330 if (!overwrite) {
331 return 0; // leave the existing one there.
332 }
333 }
334
335 // This leaks. Sorry. Get a better OS so we don't have to do this.
336 SDL_asprintf(&new_variable, "%s=%s", name, value);
337 if (!new_variable) {
338 return -1;
339 }
340 return putenv(new_variable);
341}
342#endif
343#elif defined(HAVE_WIN32_ENVIRONMENT)
344int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
345{
346 // Input validation
347 if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {
348 return -1;
349 }
350
351 SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));
352
353 if (!overwrite) {
354 if (GetEnvironmentVariableA(name, NULL, 0) > 0) {
355 return 0; // asked not to overwrite existing value.
356 }
357 }
358 if (!SetEnvironmentVariableA(name, value)) {
359 return -1;
360 }
361 return 0;
362}
363#else // roll our own
364
365int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)
366{
367 int added;
368 size_t len, i;
369 char **new_env;
370 char *new_variable;
371
372 // Input validation
373 if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {
374 return -1;
375 }
376
377 // See if it already exists
378 if (!overwrite && SDL_getenv_unsafe(name)) {
379 return 0;
380 }
381
382 SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));
383
384 // Allocate memory for the variable
385 len = SDL_strlen(name) + SDL_strlen(value) + 2;
386 new_variable = (char *)SDL_malloc(len);
387 if (!new_variable) {
388 return -1;
389 }
390
391 SDL_snprintf(new_variable, len, "%s=%s", name, value);
392 value = new_variable + SDL_strlen(name) + 1;
393 name = new_variable;
394
395 // Actually put it into the environment
396 added = 0;
397 i = 0;
398 if (environ) {
399 // Check to see if it's already there...
400 len = (value - name);
401 for (; environ[i]; ++i) {
402 if (SDL_strncmp(environ[i], name, len) == 0) {
403 // If we found it, just replace the entry
404 SDL_free(environ[i]);
405 environ[i] = new_variable;
406 added = 1;
407 break;
408 }
409 }
410 }
411
412 // Didn't find it in the environment, expand and add
413 if (!added) {
414 new_env = SDL_realloc(environ, (i + 2) * sizeof(char *));
415 if (new_env) {
416 environ = new_env;
417 environ[i++] = new_variable;
418 environ[i++] = (char *)0;
419 added = 1;
420 } else {
421 SDL_free(new_variable);
422 }
423 }
424 return added ? 0 : -1;
425}
426#endif // HAVE_LIBC_ENVIRONMENT
427
428#ifdef HAVE_LIBC_ENVIRONMENT
429#if defined(HAVE_UNSETENV)
430int SDL_unsetenv_unsafe(const char *name)
431{
432 // Input validation
433 if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {
434 return -1;
435 }
436
437 SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);
438
439 return unsetenv(name);
440}
441// We have a real environment table, but no unsetenv? Fake it w/ putenv.
442#else
443int SDL_unsetenv_unsafe(const char *name)
444{
445 // Input validation
446 if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {
447 return -1;
448 }
449
450 SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);
451
452 // Hope this environment uses the non-standard extension of removing the environment variable if it has no '='
453 return putenv(name);
454}
455#endif
456#elif defined(HAVE_WIN32_ENVIRONMENT)
457int SDL_unsetenv_unsafe(const char *name)
458{
459 // Input validation
460 if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {
461 return -1;
462 }
463
464 SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);
465
466 if (!SetEnvironmentVariableA(name, NULL)) {
467 return -1;
468 }
469 return 0;
470}
471#else
472int SDL_unsetenv_unsafe(const char *name)
473{
474 size_t len, i;
475
476 // Input validation
477 if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {
478 return -1;
479 }
480
481 SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);
482
483 if (environ) {
484 len = SDL_strlen(name);
485 for (i = 0; environ[i]; ++i) {
486 if ((SDL_strncmp(environ[i], name, len) == 0) &&
487 (environ[i][len] == '=')) {
488 // Just clear out this entry for now
489 *environ[i] = '\0';
490 break;
491 }
492 }
493 }
494 return 0;
495}
496#endif // HAVE_LIBC_ENVIRONMENT
497
498// Retrieve a variable named "name" from the environment
499#ifdef HAVE_LIBC_ENVIRONMENT
500const char *SDL_getenv_unsafe(const char *name)
501{
502#ifdef SDL_PLATFORM_ANDROID
503 // Make sure variables from the application manifest are available
504 Android_JNI_GetManifestEnvironmentVariables();
505#endif
506
507 // Input validation
508 if (!name || *name == '\0') {
509 return NULL;
510 }
511
512 return getenv(name);
513}
514#elif defined(HAVE_WIN32_ENVIRONMENT)
515const char *SDL_getenv_unsafe(const char *name)
516{
517 DWORD length, maxlen = 0;
518 char *string = NULL;
519 const char *result = NULL;
520
521 // Input validation
522 if (!name || *name == '\0') {
523 return NULL;
524 }
525
526 for ( ; ; ) {
527 SetLastError(ERROR_SUCCESS);
528 length = GetEnvironmentVariableA(name, string, maxlen);
529
530 if (length > maxlen) {
531 char *temp = (char *)SDL_realloc(string, length);
532 if (!temp) {
533 return NULL;
534 }
535 string = temp;
536 maxlen = length;
537 } else {
538 if (GetLastError() != ERROR_SUCCESS) {
539 if (string) {
540 SDL_free(string);
541 }
542 return NULL;
543 }
544 break;
545 }
546 }
547 if (string) {
548 result = SDL_GetPersistentString(string);
549 SDL_free(string);
550 }
551 return result;
552}
553#else
554const char *SDL_getenv_unsafe(const char *name)
555{
556 size_t len, i;
557 const char *value = NULL;
558
559 // Input validation
560 if (!name || *name == '\0') {
561 return NULL;
562 }
563
564 if (environ) {
565 len = SDL_strlen(name);
566 for (i = 0; environ[i]; ++i) {
567 if ((SDL_strncmp(environ[i], name, len) == 0) &&
568 (environ[i][len] == '=')) {
569 value = &environ[i][len + 1];
570 break;
571 }
572 }
573 }
574 return value;
575}
576#endif // HAVE_LIBC_ENVIRONMENT
577
578const char *SDL_getenv(const char *name)
579{
580 return SDL_GetEnvironmentVariable(SDL_GetEnvironment(), name);
581}