Path: blob/master/thirdparty/sdl/stdlib/SDL_getenv.c
21362 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>43static char **get_environ_rtld(void)44{45char ***environ_rtld = (char ***)dlsym(RTLD_DEFAULT, "environ");46return environ_rtld ? *environ_rtld : NULL;47}48#define environ (get_environ_rtld())49#else50extern char **environ;51#endif52#else53#define HAVE_LOCAL_ENVIRONMENT54static char **environ;55#endif5657#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_FREEBSD)58#include <stdlib.h>59#endif6061struct SDL_Environment62{63SDL_Mutex *lock; // !!! FIXME: reuse SDL_HashTable's lock.64SDL_HashTable *strings;65};66static SDL_Environment *SDL_environment;6768SDL_Environment *SDL_GetEnvironment(void)69{70if (!SDL_environment) {71SDL_environment = SDL_CreateEnvironment(true);72}73return SDL_environment;74}7576bool SDL_InitEnvironment(void)77{78return (SDL_GetEnvironment() != NULL);79}8081void SDL_QuitEnvironment(void)82{83SDL_Environment *env = SDL_environment;8485if (env) {86SDL_environment = NULL;87SDL_DestroyEnvironment(env);88}89}9091SDL_Environment *SDL_CreateEnvironment(bool populated)92{93SDL_Environment *env = SDL_calloc(1, sizeof(*env));94if (!env) {95return NULL;96}9798env->strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashKey, NULL);99if (!env->strings) {100SDL_free(env);101return NULL;102}103104// 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!105env->lock = SDL_CreateMutex();106107if (populated) {108#ifdef SDL_PLATFORM_WINDOWS109LPWCH strings = GetEnvironmentStringsW();110if (strings) {111for (LPWCH string = strings; *string; string += SDL_wcslen(string) + 1) {112char *variable = WIN_StringToUTF8W(string);113if (!variable) {114continue;115}116117char *value = SDL_strchr(variable, '=');118if (!value || value == variable) {119SDL_free(variable);120continue;121}122*value++ = '\0';123124SDL_InsertIntoHashTable(env->strings, variable, value, true);125}126FreeEnvironmentStringsW(strings);127}128#else129#ifdef SDL_PLATFORM_ANDROID130// Make sure variables from the application manifest are available131Android_JNI_GetManifestEnvironmentVariables();132#endif133char **strings = environ;134if (strings) {135for (int i = 0; strings[i]; ++i) {136char *variable = SDL_strdup(strings[i]);137if (!variable) {138continue;139}140141char *value = SDL_strchr(variable, '=');142if (!value || value == variable) {143SDL_free(variable);144continue;145}146*value++ = '\0';147148SDL_InsertIntoHashTable(env->strings, variable, value, true);149}150}151#endif // SDL_PLATFORM_WINDOWS152}153154return env;155}156157const char *SDL_GetEnvironmentVariable(SDL_Environment *env, const char *name)158{159#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_FREEBSD)160return getenv(name);161#else162const char *result = NULL;163164if (!env) {165return NULL;166} else if (!name || *name == '\0') {167return NULL;168}169170SDL_LockMutex(env->lock);171{172const char *value;173174if (SDL_FindInHashTable(env->strings, name, (const void **)&value)) {175result = SDL_GetPersistentString(value);176}177}178SDL_UnlockMutex(env->lock);179180return result;181#endif182}183184typedef struct CountEnvStringsData185{186size_t count;187size_t length;188} CountEnvStringsData;189190static bool SDLCALL CountEnvStrings(void *userdata, const SDL_HashTable *table, const void *key, const void *value)191{192CountEnvStringsData *data = (CountEnvStringsData *) userdata;193data->length += SDL_strlen((const char *) key) + 1 + SDL_strlen((const char *) value) + 1;194data->count++;195return true; // keep iterating.196}197198typedef struct CopyEnvStringsData199{200char **result;201char *string;202size_t count;203} CopyEnvStringsData;204205static bool SDLCALL CopyEnvStrings(void *userdata, const SDL_HashTable *table, const void *vkey, const void *vvalue)206{207CopyEnvStringsData *data = (CopyEnvStringsData *) userdata;208const char *key = (const char *) vkey;209const char *value = (const char *) vvalue;210size_t len;211212len = SDL_strlen(key);213data->result[data->count] = data->string;214SDL_memcpy(data->string, key, len);215data->string += len;216*(data->string++) = '=';217218len = SDL_strlen(value);219SDL_memcpy(data->string, value, len);220data->string += len;221*(data->string++) = '\0';222data->count++;223224return true; // keep iterating.225}226227char **SDL_GetEnvironmentVariables(SDL_Environment *env)228{229char **result = NULL;230231if (!env) {232SDL_InvalidParamError("env");233return NULL;234}235236SDL_LockMutex(env->lock);237{238// First pass, get the size we need for all the strings239CountEnvStringsData countdata = { 0, 0 };240SDL_IterateHashTable(env->strings, CountEnvStrings, &countdata);241242// Allocate memory for the strings243result = (char **)SDL_malloc((countdata.count + 1) * sizeof(*result) + countdata.length);244if (result) {245// Second pass, copy the strings246char *string = (char *)(result + countdata.count + 1);247CopyEnvStringsData cpydata = { result, string, 0 };248SDL_IterateHashTable(env->strings, CopyEnvStrings, &cpydata);249SDL_assert(countdata.count == cpydata.count);250result[cpydata.count] = NULL;251}252}253SDL_UnlockMutex(env->lock);254255return result;256}257258bool SDL_SetEnvironmentVariable(SDL_Environment *env, const char *name, const char *value, bool overwrite)259{260#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_FREEBSD)261return setenv(name, value, overwrite);262#else263bool result = false;264265if (!env) {266return SDL_InvalidParamError("env");267} else if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {268return SDL_InvalidParamError("name");269} else if (!value) {270return SDL_InvalidParamError("value");271}272273SDL_LockMutex(env->lock);274{275char *string = NULL;276if (SDL_asprintf(&string, "%s=%s", name, value) > 0) {277const size_t len = SDL_strlen(name);278string[len] = '\0';279const char *origname = name;280name = string;281value = string + len + 1;282result = SDL_InsertIntoHashTable(env->strings, name, value, overwrite);283if (!result) {284SDL_free(string);285if (!overwrite) {286const void *existing_value = NULL;287// !!! FIXME: InsertIntoHashTable does this lookup too, maybe we should have a means to report that, to avoid duplicate work?288if (SDL_FindInHashTable(env->strings, origname, &existing_value)) {289result = true; // it already existed, and we refused to overwrite it. Call it success.290}291}292}293}294}295SDL_UnlockMutex(env->lock);296297return result;298#endif299}300301bool SDL_UnsetEnvironmentVariable(SDL_Environment *env, const char *name)302{303#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_FREEBSD)304return unsetenv(name);305#else306bool result = false;307308if (!env) {309return SDL_InvalidParamError("env");310} else if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {311return SDL_InvalidParamError("name");312}313314SDL_LockMutex(env->lock);315{316const void *value;317if (SDL_FindInHashTable(env->strings, name, &value)) {318result = SDL_RemoveFromHashTable(env->strings, name);319} else {320result = true;321}322}323SDL_UnlockMutex(env->lock);324325return result;326#endif327}328329void SDL_DestroyEnvironment(SDL_Environment *env)330{331if (!env || env == SDL_environment) {332return;333}334335SDL_DestroyMutex(env->lock);336SDL_DestroyHashTable(env->strings);337SDL_free(env);338}339340// Put a variable into the environment341// Note: Name may not contain a '=' character. (Reference: http://www.unix.com/man-page/Linux/3/setenv/)342#ifdef HAVE_LIBC_ENVIRONMENT343#if defined(HAVE_SETENV)344int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)345{346// Input validation347if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {348return -1;349}350351SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));352353return setenv(name, value, overwrite);354}355// We have a real environment table, but no real setenv? Fake it w/ putenv.356#else357int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)358{359char *new_variable;360361// Input validation362if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {363return -1;364}365366SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));367368if (getenv(name) != NULL) {369if (!overwrite) {370return 0; // leave the existing one there.371}372}373374// This leaks. Sorry. Get a better OS so we don't have to do this.375SDL_asprintf(&new_variable, "%s=%s", name, value);376if (!new_variable) {377return -1;378}379return putenv(new_variable);380}381#endif382#elif defined(HAVE_WIN32_ENVIRONMENT)383int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)384{385// Input validation386if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {387return -1;388}389390SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));391392if (!overwrite) {393if (GetEnvironmentVariableA(name, NULL, 0) > 0) {394return 0; // asked not to overwrite existing value.395}396}397if (!SetEnvironmentVariableA(name, value)) {398return -1;399}400return 0;401}402#else // roll our own403404int SDL_setenv_unsafe(const char *name, const char *value, int overwrite)405{406int added;407size_t len, i;408char **new_env;409char *new_variable;410411// Input validation412if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL || !value) {413return -1;414}415416// See if it already exists417if (!overwrite && SDL_getenv_unsafe(name)) {418return 0;419}420421SDL_SetEnvironmentVariable(SDL_GetEnvironment(), name, value, (overwrite != 0));422423// Allocate memory for the variable424len = SDL_strlen(name) + SDL_strlen(value) + 2;425new_variable = (char *)SDL_malloc(len);426if (!new_variable) {427return -1;428}429430SDL_snprintf(new_variable, len, "%s=%s", name, value);431value = new_variable + SDL_strlen(name) + 1;432name = new_variable;433434// Actually put it into the environment435added = 0;436i = 0;437if (environ) {438// Check to see if it's already there...439len = (value - name);440for (; environ[i]; ++i) {441if (SDL_strncmp(environ[i], name, len) == 0) {442// If we found it, just replace the entry443SDL_free(environ[i]);444environ[i] = new_variable;445added = 1;446break;447}448}449}450451// Didn't find it in the environment, expand and add452if (!added) {453new_env = SDL_realloc(environ, (i + 2) * sizeof(char *));454if (new_env) {455environ = new_env;456environ[i++] = new_variable;457environ[i++] = (char *)0;458added = 1;459} else {460SDL_free(new_variable);461}462}463return added ? 0 : -1;464}465#endif // HAVE_LIBC_ENVIRONMENT466467#ifdef HAVE_LIBC_ENVIRONMENT468#if defined(HAVE_UNSETENV)469int SDL_unsetenv_unsafe(const char *name)470{471// Input validation472if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {473return -1;474}475476SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);477478return unsetenv(name);479}480// We have a real environment table, but no unsetenv? Fake it w/ putenv.481#else482int SDL_unsetenv_unsafe(const char *name)483{484// Input validation485if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {486return -1;487}488489SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);490491// Hope this environment uses the non-standard extension of removing the environment variable if it has no '='492return putenv(name);493}494#endif495#elif defined(HAVE_WIN32_ENVIRONMENT)496int SDL_unsetenv_unsafe(const char *name)497{498// Input validation499if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {500return -1;501}502503SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);504505if (!SetEnvironmentVariableA(name, NULL)) {506return -1;507}508return 0;509}510#else511int SDL_unsetenv_unsafe(const char *name)512{513size_t len, i;514515// Input validation516if (!name || *name == '\0' || SDL_strchr(name, '=') != NULL) {517return -1;518}519520SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), name);521522if (environ) {523len = SDL_strlen(name);524for (i = 0; environ[i]; ++i) {525if ((SDL_strncmp(environ[i], name, len) == 0) &&526(environ[i][len] == '=')) {527// Just clear out this entry for now528*environ[i] = '\0';529break;530}531}532}533return 0;534}535#endif // HAVE_LIBC_ENVIRONMENT536537// Retrieve a variable named "name" from the environment538#ifdef HAVE_LIBC_ENVIRONMENT539const char *SDL_getenv_unsafe(const char *name)540{541#ifdef SDL_PLATFORM_ANDROID542// Make sure variables from the application manifest are available543Android_JNI_GetManifestEnvironmentVariables();544#endif545546// Input validation547if (!name || *name == '\0') {548return NULL;549}550551return getenv(name);552}553#elif defined(HAVE_WIN32_ENVIRONMENT)554const char *SDL_getenv_unsafe(const char *name)555{556DWORD length, maxlen = 0;557char *string = NULL;558const char *result = NULL;559560// Input validation561if (!name || *name == '\0') {562return NULL;563}564565for ( ; ; ) {566SetLastError(ERROR_SUCCESS);567length = GetEnvironmentVariableA(name, string, maxlen);568569if (length > maxlen) {570char *temp = (char *)SDL_realloc(string, length);571if (!temp) {572return NULL;573}574string = temp;575maxlen = length;576} else {577if (GetLastError() != ERROR_SUCCESS) {578if (string) {579SDL_free(string);580}581return NULL;582}583break;584}585}586if (string) {587result = SDL_GetPersistentString(string);588SDL_free(string);589}590return result;591}592#else593const char *SDL_getenv_unsafe(const char *name)594{595size_t len, i;596const char *value = NULL;597598// Input validation599if (!name || *name == '\0') {600return NULL;601}602603if (environ) {604len = SDL_strlen(name);605for (i = 0; environ[i]; ++i) {606if ((SDL_strncmp(environ[i], name, len) == 0) &&607(environ[i][len] == '=')) {608value = &environ[i][len + 1];609break;610}611}612}613return value;614}615#endif // HAVE_LIBC_ENVIRONMENT616617const char *SDL_getenv(const char *name)618{619return SDL_GetEnvironmentVariable(SDL_GetEnvironment(), name);620}621622623