diff --git a/src/compiler/propagation/type_primitive_core.cc b/src/compiler/propagation/type_primitive_core.cc index 41935090c..95c01db5b 100644 --- a/src/compiler/propagation/type_primitive_core.cc +++ b/src/compiler/propagation/type_primitive_core.cc @@ -270,6 +270,7 @@ TYPE_PRIMITIVE_ANY(debug_set_memory_limit) TYPE_PRIMITIVE_ANY(dump_heap) TYPE_PRIMITIVE_ANY(serial_print_heap_report) TYPE_PRIMITIVE_ANY(get_env) +TYPE_PRIMITIVE_NULL(set_env) TYPE_PRIMITIVE_ANY(literal_index) TYPE_PRIMITIVE_ANY(firmware_map) TYPE_PRIMITIVE_ANY(firmware_unmap) diff --git a/src/os.h b/src/os.h index f0066ff7b..cc2b4fc87 100644 --- a/src/os.h +++ b/src/os.h @@ -241,7 +241,11 @@ class OS { static word get_heap_tag(); static void heap_summary_report(int max_pages, const char* marker); - static const char* getenv(const char* variable); + // Returns a malloced string. + static char* getenv(const char* variable); + // Returns true for OK. + static bool setenv(const char* variable, const char* value); + static bool unsetenv(const char* variable); #ifdef TOIT_FREERTOS static bool use_spiram_for_heap() { return use_spiram_for_heap_; } diff --git a/src/os_esp32.cc b/src/os_esp32.cc index 726d61a9a..0f67142af 100644 --- a/src/os_esp32.cc +++ b/src/os_esp32.cc @@ -800,7 +800,7 @@ void OS::heap_summary_report(int max_pages, const char* marker) {} #endif // def TOIT_CMPCTMALLOC -const char* OS::getenv(const char* variable) { +char* OS::getenv(const char* variable) { // Unimplemented on purpose. // We currently prefer not to expose environment variables on embedded devices. // There is no technical reason for it, so if circumstances change, one can @@ -808,6 +808,16 @@ const char* OS::getenv(const char* variable) { UNIMPLEMENTED(); } +bool OS::setenv(const char* variable, const char* value) { + // Unimplemented on purpose. + UNIMPLEMENTED(); +} + +bool OS::unsetenv(const char* variable) { + // Unimplemented on purpose. + UNIMPLEMENTED(); +} + bool OS::set_real_time(struct timespec* time) { if (clock_settime(CLOCK_REALTIME, time) == 0) return true; struct timeval timeofday{}; diff --git a/src/os_posix.cc b/src/os_posix.cc index 579c8d31e..96dcec4bd 100644 --- a/src/os_posix.cc +++ b/src/os_posix.cc @@ -255,8 +255,22 @@ void OS::out_of_memory(const char* reason) { abort(); } -const char* OS::getenv(const char* variable) { - return ::getenv(variable); +char* OS::getenv(const char* variable) { + // Getenv/setenv are not guaranteed to be reentrant. + Locker scope(global_mutex_); + char* result = ::getenv(variable); + if (result == null) return null; + return strdup(result); +} + +bool OS::setenv(const char* variable, const char* value) { + Locker scope(global_mutex_); + return ::setenv(variable, value, 1) == 0; +} + +bool OS::unsetenv(const char* variable) { + Locker scope(global_mutex_); + return ::unsetenv(variable) == 0; } bool OS::set_real_time(struct timespec* time) { diff --git a/src/os_win.cc b/src/os_win.cc index 2515e63e5..08281af58 100644 --- a/src/os_win.cc +++ b/src/os_win.cc @@ -256,8 +256,43 @@ void OS::out_of_memory(const char* reason) { abort(); } -const char* OS::getenv(const char* variable) { - return ::getenv(variable); +static wchar_t* malloced_wide_string(const char* string) { + word length = Utils::utf_8_to_16(unsigned_cast(string), strlen(string)); + wchar_t* result = reinterpret_cast(malloc((length + 1) * sizeof(wchar_t))); + Utils::utf_8_to_16(unsigned_cast(string), strlen(string), result, length); + result[length] = '\0'; + return result; +} + +char* OS::getenv(const char* variable) { + wchar_t* wide_variable = malloced_wide_string(variable); + + const int BUFFER_SIZE = 32767; + wchar_t buffer[BUFFER_SIZE]; + int wide_length = GetEnvironmentVariableW(wide_variable, buffer, BUFFER_SIZE); + free(wide_variable); + if (wide_length == 0 || wide_length > BUFFER_SIZE) return null; + word size = Utils::utf_16_to_8(buffer, wide_length, null, 0); + char* result = unvoid_cast(malloc(size + 1)); + Utils::utf_16_to_8(buffer, wide_length, unsigned_cast(result), size); + result[size] = '\0'; + return result; +} + +bool OS::setenv(const char* variable, const char* value) { + wchar_t* wide_variable = malloced_wide_string(variable); + wchar_t* wide_value = malloced_wide_string(value); + bool ok = SetEnvironmentVariableW(wide_variable, wide_value); + free(wide_variable); + free(wide_value); + return ok; +} + +bool OS::unsetenv(const char* variable) { + wchar_t* wide_variable = malloced_wide_string(variable); + bool ok = SetEnvironmentVariableW(wide_variable, null); + free(wide_variable); + return ok; } bool OS::set_real_time(struct timespec* time) { diff --git a/src/primitive.h b/src/primitive.h index 6419a7830..3db07a2b3 100644 --- a/src/primitive.h +++ b/src/primitive.h @@ -237,6 +237,7 @@ namespace toit { PRIMITIVE(dump_heap, 1) \ PRIMITIVE(serial_print_heap_report, 2) \ PRIMITIVE(get_env, 1) \ + PRIMITIVE(set_env, 2) \ PRIMITIVE(literal_index, 1) \ PRIMITIVE(word_size, 0) \ PRIMITIVE(firmware_map, 1) \ diff --git a/src/primitive_core.cc b/src/primitive_core.cc index 3f10815ff..9ee95bdfa 100644 --- a/src/primitive_core.cc +++ b/src/primitive_core.cc @@ -2244,7 +2244,7 @@ PRIMITIVE(dump_heap) { #else ARGS(int, padding); if (padding < 0 || padding > 0x10000) OUT_OF_RANGE; -#if defined(TOIT_LINUX) +#ifdef TOIT_LINUX if (heap_caps_iterate_tagged_memory_areas == null) { // This always happens on the server unless we are running with // cmpctmalloc (using LD_PRELOAD), which supports iterating the heap in @@ -2290,16 +2290,31 @@ PRIMITIVE(serial_print_heap_report) { } PRIMITIVE(get_env) { -#if defined (TOIT_FREERTOS) +#ifdef TOIT_FREERTOS // FreeRTOS supports environment variables, but we prefer not to expose them. UNIMPLEMENTED_PRIMITIVE; #else ARGS(cstring, key); - // TODO(florian): getenv is not reentrant. - // We should have a lock around `getenv` and `setenv`. - const char* result = OS::getenv(key); + char* result = OS::getenv(key); if (result == null) return process->program()->null_object(); - return process->allocate_string_or_error(result, strlen(result)); + Object* string_or_error = process->allocate_string_or_error(result, strlen(result)); + free(result); + return string_or_error; +#endif +} + +PRIMITIVE(set_env) { +#ifdef TOIT_FREERTOS + // FreeRTOS supports environment variables, but we prefer not to expose them. + UNIMPLEMENTED_PRIMITIVE; +#else + ARGS(cstring, key, cstring, value); + if (value) { + OS::setenv(key, value); + } else { + OS::unsetenv(key); + } + return process->program()->null_object(); #endif }