| //===----------------------------------------------------------------------===// |
| // |
| // 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" |
| |
| 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; |
| } |
| |
| } // namespace internal |
| } // namespace LIBC_NAMESPACE_DECL |