diff --git a/LibC/stdlib.cpp b/LibC/stdlib.cpp index cabcda573da..74d7d07939d 100644 --- a/LibC/stdlib.cpp +++ b/LibC/stdlib.cpp @@ -46,6 +46,16 @@ void abort() raise(SIGABRT); } +static HashTable s_malloced_environment_variables; + +static void free_environment_variable_if_needed(const char* var) +{ + if (!s_malloced_environment_variables.contains((char*)var)) + return; + free((void*)var); + s_malloced_environment_variables.remove((char*)var); +} + char* getenv(const char* name) { size_t vl = strlen(name); @@ -89,9 +99,22 @@ int unsetenv(const char* name) // Shuffle the existing array down by one. memmove(&environ[skip], &environ[skip+1], ((environ_size-1)-skip) * sizeof(environ[0])); environ[environ_size-1] = nullptr; + + free_environment_variable_if_needed(name); return 0; } +int setenv(const char* name, const char* value, int overwrite) +{ + if (!overwrite && !getenv(name)) + return 0; + auto length = strlen(name) + strlen(value) + 2; + auto* var = (char*)malloc(length); + snprintf(var, length, "%s=%s", name, value); + s_malloced_environment_variables.set(var); + return putenv(var); +} + int putenv(char* new_var) { char* new_eq = strchr(new_var, '='); @@ -111,6 +134,7 @@ int putenv(char* new_var) continue; // can't match if (strncmp(new_var, old_var, new_var_len) == 0) { + free_environment_variable_if_needed(old_var); environ[environ_size] = new_var; return 0; } diff --git a/LibC/stdlib.h b/LibC/stdlib.h index 946a1d82748..a310ee5e11e 100644 --- a/LibC/stdlib.h +++ b/LibC/stdlib.h @@ -18,6 +18,7 @@ void* realloc(void* ptr, size_t); char* getenv(const char* name); int putenv(char*); int unsetenv(const char*); +int setenv(const char* name, const char* value, int overwrite); int atoi(const char*); long atol(const char*); long long atoll(const char*); diff --git a/Shell/main.cpp b/Shell/main.cpp index b596fc2c931..47a2bc2ef92 100644 --- a/Shell/main.cpp +++ b/Shell/main.cpp @@ -72,13 +72,7 @@ static int sh_export(int argc, char** argv) return 1; } - // FIXME: Yes, this leaks. - // Maybe LibCore should grow a CEnvironment which is secretly a map to char*, - // so it can keep track of the environment pointers as needed? - const auto& s = String::format("%s=%s", parts[0].characters(), parts[1].characters()); - char *ev = strndup(s.characters(), s.length()); - putenv(ev); - return 0; + return setenv(parts[0].characters(), parts[1].characters(), 1); } static int sh_unset(int argc, char** argv) @@ -494,9 +488,7 @@ int main(int argc, char** argv) if (pw) { g.username = pw->pw_name; g.home = pw->pw_dir; - const auto& s = String::format("HOME=%s", pw->pw_dir); - char *ev = strndup(s.characters(), s.length()); - putenv(ev); + setenv("HOME", pw->pw_dir, 1); } endpwent(); }