| //===----------------------------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// Implementation of internal environment management utilities. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "src/stdlib/environ_internal.h" |
| #include "config/app.h" |
| #include "src/__support/CPP/new.h" |
| #include "src/__support/CPP/string_view.h" |
| #include "src/__support/alloc-checker.h" |
| #include "src/__support/macros/config.h" |
| #include "src/string/memory_utils/inline_memcpy.h" |
| |
| namespace LIBC_NAMESPACE_DECL { |
| namespace internal { |
| |
| // Minimum initial capacity for the environment array when first allocated. |
| // This avoids frequent reallocations for small environments. |
| constexpr size_t MIN_ENVIRON_CAPACITY = 32; |
| |
| // Growth factor for environment array capacity when expanding. |
| // When capacity is exceeded, new_capacity = old_capacity * |
| // ENVIRON_GROWTH_FACTOR. |
| constexpr size_t ENVIRON_GROWTH_FACTOR = 2; |
| |
| void EnvironmentManager::init_once() { |
| if (initialized) |
| return; |
| |
| // Count entries in the startup environ. |
| char **env_ptr = reinterpret_cast<char **>(app.env_ptr); |
| if (env_ptr) { |
| size_t c = 0; |
| for (char **env = env_ptr; *env != nullptr; env++) |
| c++; |
| count = c; |
| } |
| |
| initialized = true; |
| } |
| |
| EnvironmentManager &EnvironmentManager::get_instance() { |
| static EnvironmentManager mgr; |
| mgr.init_once(); |
| return mgr; |
| } |
| |
| char **EnvironmentManager::get_array() { |
| if (is_ours) |
| return storage; |
| return reinterpret_cast<char **>(app.env_ptr); |
| } |
| |
| EnvironmentManager::iterator EnvironmentManager::begin() { return get_array(); } |
| |
| EnvironmentManager::iterator EnvironmentManager::end() { |
| return get_array() + count; |
| } |
| |
| size_t EnvironmentManager::size() const { return count; } |
| |
| char *EnvironmentManager::get(cpp::string_view name) { |
| cpp::optional<size_t> idx = find_var(name); |
| if (!idx) |
| return nullptr; |
| return get_array()[*idx] + name.size() + 1; |
| } |
| |
| cpp::optional<size_t> EnvironmentManager::find_var(cpp::string_view name) { |
| char **env_array = get_array(); |
| if (!env_array) |
| return cpp::nullopt; |
| |
| for (size_t i = 0; i < count; i++) { |
| cpp::string_view current(env_array[i]); |
| if (current.starts_with(name) && current.size() > name.size() && |
| current[name.size()] == '=') |
| return i; |
| } |
| |
| return cpp::nullopt; |
| } |
| |
| // Helper: allocate new storage and ownership arrays of the given capacity, |
| // copy the first `copy_count` entries from old_storage/old_ownership, and |
| // initialize the remaining ownership slots to default (not-owned). |
| // Returns nullopt on allocation failure; the old arrays are untouched. |
| cpp::optional<EnvironmentManager::AllocResult> |
| EnvironmentManager::alloc_and_copy(size_t new_capacity, char **old_storage, |
| EnvStringOwnership *old_ownership, |
| size_t copy_count) { |
| AllocChecker ac; |
| char **new_storage = new (ac) char *[new_capacity + 1]; |
| if (!ac) |
| return cpp::nullopt; |
| |
| EnvStringOwnership *new_ownership = |
| new (ac) EnvStringOwnership[new_capacity + 1]; |
| if (!ac) { |
| delete[] new_storage; |
| return cpp::nullopt; |
| } |
| |
| for (size_t i = 0; i < copy_count; i++) { |
| new_storage[i] = old_storage ? old_storage[i] : nullptr; |
| new_ownership[i] = old_ownership ? old_ownership[i] : EnvStringOwnership(); |
| } |
| new_storage[copy_count] = nullptr; |
| |
| return AllocResult{new_storage, new_ownership}; |
| } |
| |
| bool EnvironmentManager::ensure_capacity(size_t needed) { |
| // If we're still using the startup environ (pointed to by app.env_ptr), |
| // we must transition to our own managed storage. This allows us to |
| // track ownership of strings and safely expand the array. |
| if (!is_ours) { |
| char **old_env = reinterpret_cast<char **>(app.env_ptr); |
| |
| // Allocate new array with room to grow. |
| size_t new_capacity = needed < MIN_ENVIRON_CAPACITY |
| ? MIN_ENVIRON_CAPACITY |
| : needed * ENVIRON_GROWTH_FACTOR; |
| |
| auto result = alloc_and_copy(new_capacity, old_env, nullptr, count); |
| if (!result) |
| return false; |
| |
| auto [new_storage, new_ownership] = *result; |
| storage = new_storage; |
| ownership = new_ownership; |
| capacity = new_capacity; |
| is_ours = true; |
| |
| // Update the global environ pointer. |
| app.env_ptr = reinterpret_cast<uintptr_t *>(storage); |
| |
| return true; |
| } |
| |
| // We already own the environment array. Check if it's large enough. |
| if (needed <= capacity) |
| return true; |
| |
| // Grow capacity. We avoid realloc to ensure that failures don't leave the |
| // manager in an inconsistent state. |
| size_t new_capacity = needed * ENVIRON_GROWTH_FACTOR; |
| |
| auto result = alloc_and_copy(new_capacity, storage, ownership, count); |
| if (!result) |
| return false; |
| |
| delete[] storage; |
| delete[] ownership; |
| |
| auto [new_storage, new_ownership] = *result; |
| storage = new_storage; |
| ownership = new_ownership; |
| capacity = new_capacity; |
| |
| // Update the global environ pointer. |
| app.env_ptr = reinterpret_cast<uintptr_t *>(storage); |
| |
| return true; |
| } |
| |
| int EnvironmentManager::set(cpp::string_view name, cpp::string_view value, |
| bool overwrite) { |
| cpp::optional<size_t> idx = find_var(name); |
| |
| // If the variable exists and we're not overwriting, do nothing. |
| if (idx && !overwrite) |
| return 0; |
| |
| // Ensure we have capacity. If the variable doesn't exist, we need one |
| // more slot. |
| size_t needed = idx ? count : count + 1; |
| if (!ensure_capacity(needed)) |
| return -1; |
| |
| // Build the "name=value" string. |
| size_t name_len = name.size(); |
| size_t value_len = value.size(); |
| size_t total_len = name_len + 1 + value_len + 1; // name + '=' + value + '\0' |
| |
| AllocChecker ac; |
| char *new_string = new (ac) char[total_len]; |
| if (!ac) |
| return -1; |
| |
| inline_memcpy(new_string, name.data(), name_len); |
| new_string[name_len] = '='; |
| inline_memcpy(new_string + name_len + 1, value.data(), value_len); |
| new_string[name_len + 1 + value_len] = '\0'; |
| |
| char **env_array = get_array(); |
| |
| if (idx) { |
| // Replace existing variable. Free old string if we own it. |
| if (ownership[*idx].can_free()) |
| delete[] env_array[*idx]; |
| |
| env_array[*idx] = new_string; |
| ownership[*idx].allocated_by_us = true; |
| } else { |
| // Add new variable at the end. |
| env_array[count] = new_string; |
| ownership[count].allocated_by_us = true; |
| count++; |
| env_array[count] = nullptr; // Maintain null terminator. |
| } |
| |
| return 0; |
| } |
| |
| } // namespace internal |
| } // namespace LIBC_NAMESPACE_DECL |