Path: blob/master/thirdparty/sdl/stdlib/SDL_getenv.c
10279 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#include "SDL_getenv_c.h"2324#if defined(SDL_PLATFORM_WINDOWS)25#include "../core/windows/SDL_windows.h"26#endif2728#ifdef SDL_PLATFORM_ANDROID29#include "../core/android/SDL_android.h"30#endif3132#if defined(SDL_PLATFORM_WINDOWS)33#define HAVE_WIN32_ENVIRONMENT34#elif defined(HAVE_GETENV) && \35(defined(HAVE_SETENV) || defined(HAVE_PUTENV)) && \36(defined(HAVE_UNSETENV) || defined(HAVE_PUTENV))37#define HAVE_LIBC_ENVIRONMENT38#if defined(SDL_PLATFORM_MACOS)39#include <crt_externs.h>40#define environ (*_NSGetEnviron())41#elif defined(SDL_PLATFORM_FREEBSD)42#include <dlfcn.h>43#define environ ((char **)dlsym(RTLD_DEFAULT, "environ"))44#else45extern char **environ;46#endif47#else48#define HAVE_LOCAL_ENVIRONMENT49static char **environ;50#endif5152#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_FREEBSD)53#include <stdlib.h>54#endif5556struct SDL_Environment57{58SDL_Mutex *lock; // !!! FIXME: reuse SDL_HashTable's lock.59SDL_HashTable *strings;60};61static SDL_Environment *SDL_environment;6263SDL_Environment *SDL_GetEnvironment(void)64{65if (!SDL_environment) {66SDL_environment = SDL_CreateEnvironment(true);67}68return SDL_environment;69}7071bool SDL_InitEnvironment(void)72{73return (SDL_GetEnvironment() != NULL);74}7576void SDL_QuitEnvironment(void)77{78SDL_Environment *env = SDL_environment;7980if (env) {81SDL_environment = NULL;82SDL_DestroyEnvironment(env);83}84}8586SDL_Environment *SDL_CreateEnvironment(bool populated)87{88SDL_Environment *env = SDL_calloc(1, sizeof(*env));89if (!env) {90return NULL;91}9293env->strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashKey, NULL);94if (!env->strings) {95SDL_free(env);96return NULL;97}9899// 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!100env->lock = SDL_CreateMutex();101102if (populated) {103#ifdef SDL_PLATFORM_WINDOWS104LPWCH strings = GetEnvironmentStringsW();105if (strings) {106for (LPWCH string = strings; *string; string += SDL_wcslen(string) + 1) {107char *variable = WIN_StringToUTF8W(string);108if (!variable) {109continue;110}111112char *value = SDL_strchr(variable, '=');113if (!value || value == variable) {114SDL_free(variable);115continue;116}117*value++ = '\0';118119SDL_InsertIntoHashTable(env->strings, variable, value, true);120}121FreeEnvironmentStringsW(strings);122}123#else124#ifdef SDL_PLATFORM_ANDROID125// Make sure variables from the application manifest are available126Android_JNI_GetManifestEnvironmentVariables();127#endif128char **strings = environ;129if (strings) {130for (int i = 0; strings[i]; ++i) {131char *variable = SDL_strdup(strings[i]);132if (!variable) {133continue;134}135136char *value = SDL_strchr(variable, '=');137if (!value || value == variable) {138SDL_free(variable);139continue;140}141*value++ = '\0';142143SDL_InsertIntoHashTable(env->strings, variable, value, true);144}145}146#endif // SDL_PLATFORM_WINDOWS147}148149return env;150}151152const char *SDL_GetEnvironmentVariable(SDL_Environment *env, const char *name)153{154#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_FREEBSD)155return getenv(name);156#else157const char *result = NULL;158159if (!env) {160return NULL;161} else if (!name || *name == '\0') {162return NULL;163}164165SDL_LockMutex(env->lock);166{167const char *value;168169if (SDL_FindInHashTable(env->strings, name, (const void **)&value)) {170result = SDL_GetPersistentString(value);171}172}173SDL_UnlockMutex(env->lock);174175return result;176#endif177}178179typedef struct CountEnvStringsData180{181size_t count;182size_t length;183} CountEnvStringsData;184185static bool SDLCALL CountEnvStrings(void *userdata, const SDL_HashTable *table, const void *key, const void *value)186{187CountEnvStringsData *data = (CountEnvStringsData *) userdata;188data->length += SDL_strlen((const char *) key) + 1 + SDL_strlen((const char *) value) + 1;189data->count++;190return true; // keep iterating.191}192193typedef struct CopyEnvStringsData194{195char **result;196char *string;197size_t count;198} CopyEnvStringsData;199200static bool SDLCALL CopyEnvStrings(void *userdata, const SDL_HashTable *table, const void *vkey, const void *vvalue)201{202CopyEnvStringsData *data = (CopyEnvStringsData *) userdata;203const char *key = (const char *) vkey;204const char *value = (const char *) vvalue;205size_t len;206207len = SDL_strlen(key);208data->result[data->count] = data->string;209SDL_memcpy(data->string, key, len);210data->string += len;211*(data->string++) = '=';212213len = SDL_strlen(value);214SDL_memcpy(data->string, value, len);215data->string += len;216*(data->string++) = '\0';217data->count++;218219return true; // keep iterating.220}221222char **SDL_GetEnvironmentVariables(SDL_Environment *env)223{224char **result = NULL;225226if (!env) {227SDL_InvalidParamError("env");228return NULL;229}230231SDL_LockMutex(env->lock);232{233// First pass, get the size we need for all the strings234CountEnvStringsData countdata = { 0, 0 };235SDL_IterateHashTable(env->strings, CountEnvStrings, &countdata);236237// Allocate memory for the strings238result = (char **)SDL_malloc((countdata.count + 1) * sizeof(*result) + countdata.length);239if (result) {240// Second pass, copy the strings241char *string = (char *)(result + countdata.count + 1);242CopyEnvStringsData cpydata = { result, string, 0 };243SDL_IterateHashTable(env->strings, CopyEnvStrings, &cpydata);244SDL_assert(countdata.count == cpydata.count);245result[cpydata.count] = NULL;246}247}248SDL_UnlockMutex(env->lock);249250return result;251}252253bool SDL_SetEnvironmentVariable(SDL_Environment *env, const char *name, const char *value, bool overwrite)254{255#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_FREEBSD)256return setenv(name, value, overwrite);257#else258bool result = false;259260if (!env) {261return SDL_InvalidParamError("env");262} else if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {263return SDL_InvalidParamError("name");264} else if (!value) {265return SDL_InvalidParamError("value");266}267268SDL_LockMutex(env->lock);269{270char *string = NULL;271if (SDL_asprintf(&string, "%s=%s", name, value) > 0) {272const size_t len = SDL_strlen(name);273string[len] = '\0';274const char *origname = name;275name = string;276value = string + len + 1;277result = SDL_InsertIntoHashTable(env->strings, name, value, overwrite);278if (!result) {279SDL_free(string);280if (!overwrite) {281const void *existing_value = NULL;282// !!! FIXME: InsertIntoHashTable does this lookup too, maybe we should have a means to report that, to avoid duplicate work?283if (SDL_FindInHashTable(env->strings, origname, &existing_value)) {284result = true; // it already existed, and we refused to overwrite it. Call it success.285}286}287}288}289}290SDL_UnlockMutex(env->lock);291292return result;293#endif294}295296bool SDL_UnsetEnvironmentVariable(SDL_Environment *env, const char *name)297{298#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_FREEBSD)299return unsetenv(name);300#else301bool result = false;302303if (!env) {304return SDL_InvalidParamError("env");305} else if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {306return SDL_InvalidParamError("name");307}308309SDL_LockMutex(env->lock);310{311const void *value;312if (SDL_FindInHashTable(env->strings, name, &value)) {313result = SDL_RemoveFromHashTable(env->strings, name);314} else {315result = true;316}317}318SDL_UnlockMutex(env->lock);319320return result;321#endif322}323324void SDL_DestroyEnvironment(SDL_Environment *env)325{326if (!env || env == SDL_environment) {327return;328}329330SDL_DestroyMutex(env->lock);331SDL_DestroyHashTable(env->strings);332SDL_free(env);333}334335// Put a variable into the environment336// Note: Name may not contain a '=' character. (Reference: http://www.unix.com/man-page/Linux/3/setenv/)337#ifdef HAVE_LIBC_ENVIRONMENT338#if defined(HAVE_SETENV)339int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)340{341// Input validation342if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {343return -1;344}345346SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));347348return setenv(name, value, overwrite);349}350// We have a real environment table, but no real setenv? Fake it w/ putenv.351#else352int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)353{354char *new_variable;355356// Input validation357if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {358return -1;359}360361SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));362363if (getenv(name) != NULL) {364if (!overwrite) {365return 0; // leave the existing one there.366}367}368369// This leaks. Sorry. Get a better OS so we don't have to do this.370SDL_asprintf(&new_variable, "%s=%s", name, value);371if (!new_variable) {372return -1;373}374return putenv(new_variable);375}376#endif377#elif defined(HAVE_WIN32_ENVIRONMENT)378int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)379{380// Input validation381if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {382return -1;383}384385SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));386387if (!overwrite) {388if (GetEnvironmentVariableA(name, NULL, 0) > 0) {389return 0; // asked not to overwrite existing value.390}391}392if (!SetEnvironmentVariableA(name, value)) {393return -1;394}395return 0;396}397#else // roll our own398399int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)400{401int added;402size_t len, i;403char **new_env;404char *new_variable;405406// Input validation407if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {408return -1;409}410411// See if it already exists412if (!overwrite && SDL_getenv_unsafe(name)) {413return 0;414}415416SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));417418// Allocate memory for the variable419len = SDL_strlen(name) + SDL_strlen(value) + 2;420new_variable = (char *)SDL_malloc(len);421if (!new_variable) {422return -1;423}424425SDL_snprintf(new_variable, len, "%s=%s", name, value);426value = new_variable + SDL_strlen(name) + 1;427name = new_variable;428429// Actually put it into the environment430added = 0;431i = 0;432if (environ) {433// Check to see if it's already there...434len = (value - name);435for (; environ[i]; ++i) {436if (SDL_strncmp(environ[i], name, len) == 0) {437// If we found it, just replace the entry438SDL_free(environ[i]);439environ[i] = new_variable;440added = 1;441break;442}443}444}445446// Didn't find it in the environment, expand and add447if (!added) {448new_env = SDL_realloc(environ, (i + 2) * sizeof(char *));449if (new_env) {450environ = new_env;451environ[i++] = new_variable;452environ[i++] = (char *)0;453added = 1;454} else {455SDL_free(new_variable);456}457}458return added ? 0 : -1;459}460#endif // HAVE_LIBC_ENVIRONMENT461462#ifdef HAVE_LIBC_ENVIRONMENT463#if defined(HAVE_UNSETENV)464int SDL_unsetenv_unsafe(const char *name)465{466// Input validation467if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {468return -1;469}470471SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);472473return unsetenv(name);474}475// We have a real environment table, but no unsetenv? Fake it w/ putenv.476#else477int SDL_unsetenv_unsafe(const char *name)478{479// Input validation480if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {481return -1;482}483484SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);485486// Hope this environment uses the non-standard extension of removing the environment variable if it has no '='487return putenv(name);488}489#endif490#elif defined(HAVE_WIN32_ENVIRONMENT)491int SDL_unsetenv_unsafe(const char *name)492{493// Input validation494if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {495return -1;496}497498SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);499500if (!SetEnvironmentVariableA(name, NULL)) {501return -1;502}503return 0;504}505#else506int SDL_unsetenv_unsafe(const char *name)507{508size_t len, i;509510// Input validation511if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {512return -1;513}514515SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);516517if (environ) {518len = SDL_strlen(name);519for (i = 0; environ[i]; ++i) {520if ((SDL_strncmp(environ[i], name, len) == 0) &&521(environ[i][len] == '=')) {522// Just clear out this entry for now523*environ[i] = '\0';524break;525}526}527}528return 0;529}530#endif // HAVE_LIBC_ENVIRONMENT531532// Retrieve a variable named "name" from the environment533#ifdef HAVE_LIBC_ENVIRONMENT534const char *SDL_getenv_unsafe(const char *name)535{536#ifdef SDL_PLATFORM_ANDROID537// Make sure variables from the application manifest are available538Android_JNI_GetManifestEnvironmentVariables();539#endif540541// Input validation542if (!name || *name == '\0') {543return NULL;544}545546return getenv(name);547}548#elif defined(HAVE_WIN32_ENVIRONMENT)549const char *SDL_getenv_unsafe(const char *name)550{551DWORD length, maxlen = 0;552char *string = NULL;553const char *result = NULL;554555// Input validation556if (!name || *name == '\0') {557return NULL;558}559560for ( ; ; ) {561SetLastError(ERROR_SUCCESS);562length = GetEnvironmentVariableA(name, string, maxlen);563564if (length > maxlen) {565char *temp = (char *)SDL_realloc(string, length);566if (!temp) {567return NULL;568}569string = temp;570maxlen = length;571} else {572if (GetLastError() != ERROR_SUCCESS) {573if (string) {574SDL_free(string);575}576return NULL;577}578break;579}580}581if (string) {582result = SDL_GetPersistentString(string);583SDL_free(string);584}585return result;586}587#else588const char *SDL_getenv_unsafe(const char *name)589{590size_t len, i;591const char *value = NULL;592593// Input validation594if (!name || *name == '\0') {595return NULL;596}597598if (environ) {599len = SDL_strlen(name);600for (i = 0; environ[i]; ++i) {601if ((SDL_strncmp(environ[i], name, len) == 0) &&602(environ[i][len] == '=')) {603value = &environ[i][len + 1];604break;605}606}607}608return value;609}610#endif // HAVE_LIBC_ENVIRONMENT611612const char *SDL_getenv(const char *name)613{614return SDL_GetEnvironmentVariable(SDL_GetEnvironment(), name);615}616617618