diff --git a/src/ctl/ctl.c b/src/ctl/ctl.c index d9cfb18eb..ff0425b1a 100644 --- a/src/ctl/ctl.c +++ b/src/ctl/ctl.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -33,8 +34,6 @@ #ifdef _WIN32 #define strtok_r strtok_s -#else -#include #endif #define MAX_CONFIG_FILE_LEN (1 << 20) /* 1 megabyte */ @@ -578,7 +577,7 @@ static umf_result_t ctl_load_config_helper(struct ctl *ctl, void *ctx, // we do not need to copy va_list before call as we know that for query_config_input // ctl_query will not call va_arg on it. Ref 7.15/3 of C99 standard ret = ctl_query(ctl, ctx, CTL_QUERY_CONFIG_INPUT, name, CTL_QUERY_WRITE, - value, 0, empty_args); + value, strlen(value) + 1, empty_args); if (ret != UMF_RESULT_SUCCESS && ctx != NULL) { goto end; @@ -621,7 +620,6 @@ umf_result_t ctl_load_config_from_string(struct ctl *ctl, void *ctx, * This function opens up the config file, allocates a buffer of size equal to * the size of the file, reads its content and sanitizes it for ctl_load_config. */ -#ifndef _WIN32 // TODO: implement for Windows umf_result_t ctl_load_config_from_file(struct ctl *ctl, void *ctx, const char *cfg_file) { umf_result_t ret = UMF_RESULT_ERROR_UNKNOWN; @@ -679,7 +677,6 @@ umf_result_t ctl_load_config_from_file(struct ctl *ctl, void *ctx, (void)fclose(fp); return ret; } -#endif /* * ctl_parse_ll -- (internal) parses and returns a long long signed integer diff --git a/src/ctl/ctl_internal.h b/src/ctl/ctl_internal.h index bd4f5b9b4..8f51f534e 100644 --- a/src/ctl/ctl_internal.h +++ b/src/ctl/ctl_internal.h @@ -121,7 +121,6 @@ struct ctl { int first_free; }; -void initialize_global_ctl(void); void ctl_init(void *(*Malloc)(size_t), void (*Free)(void *)); umf_result_t ctl_load_config_from_string(struct ctl *ctl, void *ctx, diff --git a/src/libumf.c b/src/libumf.c index c18d44064..62aae14a3 100644 --- a/src/libumf.c +++ b/src/libumf.c @@ -14,6 +14,7 @@ #include "base_alloc_global.h" #include "ctl/ctl_internal.h" #include "ipc_cache.h" +#include "libumf.h" #include "memory_pool_internal.h" #include "memory_provider_internal.h" #include "memspace_internal.h" @@ -37,7 +38,23 @@ static void initialize_init_mutex(void) { utils_mutex_init(&initMutex); } static umf_ctl_node_t CTL_NODE(umf)[] = {CTL_CHILD(provider), CTL_CHILD(pool), CTL_CHILD(logger), CTL_NODE_END}; -void initialize_global_ctl(void) { CTL_REGISTER_MODULE(NULL, umf); } +void initialize_ctl(void) { + ctl_init(umf_ba_global_alloc, umf_ba_global_free); + + CTL_REGISTER_MODULE(NULL, umf); + const char *env_var = getenv("UMF_CONF"); + if (env_var && env_var[0] != '\0') { + LOG_INFO("Loading UMF configuration from environment variable: %s", + env_var); + ctl_load_config_from_string(NULL, NULL, env_var); + } + + const char *file_var = getenv("UMF_CONF_FILE"); + if (file_var && file_var[0] != '\0') { + LOG_INFO("Loading UMF configuration from file: %s", file_var); + ctl_load_config_from_file(NULL, NULL, file_var); + } +} umf_result_t umfInit(void) { utils_init_once(&initMutexOnce, initialize_init_mutex); @@ -46,6 +63,8 @@ umf_result_t umfInit(void) { if (umfRefCount == 0) { utils_log_init(); + initialize_ctl(); + umf_result_t umf_result = umfMemoryTrackerCreate(&TRACKER); if (umf_result != UMF_RESULT_SUCCESS) { LOG_ERR("Failed to create memory tracker"); @@ -64,8 +83,6 @@ umf_result_t umfInit(void) { } LOG_DEBUG("UMF IPC cache initialized"); - ctl_init(umf_ba_global_alloc, umf_ba_global_free); - initialize_global_ctl(); } umfRefCount++; @@ -111,6 +128,8 @@ umf_result_t umfTearDown(void) { umfMemoryTrackerDestroy(t); LOG_DEBUG("UMF tracker destroyed"); + umfPoolCtlDefaultsDestroy(); + umf_ba_destroy_global(); LOG_DEBUG("UMF base allocator destroyed"); @@ -127,6 +146,7 @@ umf_result_t umfTearDown(void) { int umfGetCurrentVersion(void) { return UMF_VERSION_CURRENT; } umf_result_t umfCtlGet(const char *name, void *arg, size_t size, ...) { + libumfInit(); // ctx can be NULL when getting defaults if (name == NULL || arg == NULL || size == 0) { return UMF_RESULT_ERROR_INVALID_ARGUMENT; @@ -141,6 +161,7 @@ umf_result_t umfCtlGet(const char *name, void *arg, size_t size, ...) { } umf_result_t umfCtlSet(const char *name, void *arg, size_t size, ...) { + libumfInit(); // ctx can be NULL when setting defaults if (name == NULL || arg == NULL || size == 0) { return UMF_RESULT_ERROR_INVALID_ARGUMENT; @@ -154,6 +175,7 @@ umf_result_t umfCtlSet(const char *name, void *arg, size_t size, ...) { } umf_result_t umfCtlExec(const char *name, void *arg, size_t size, ...) { + libumfInit(); // arg can be NULL when executing a command // ctx can be NULL when executing defaults // size can depends on the arg diff --git a/src/memory_pool.c b/src/memory_pool.c index 4b0173c73..a617427a9 100644 --- a/src/memory_pool.c +++ b/src/memory_pool.c @@ -24,16 +24,21 @@ #include "utils_assert.h" #include "utils_concurrency.h" #include "utils_log.h" +#include "utlist.h" -#define UMF_DEFAULT_SIZE 100 -#define UMF_DEFAULT_LEN 100 +typedef struct ctl_default_entry_t { + char *name; + void *value; + size_t value_size; + umf_ctl_query_source_t source; + struct ctl_default_entry_t *next; +} ctl_default_entry_t; + +static ctl_default_entry_t *ctl_default_list = NULL; utils_mutex_t ctl_mtx; static UTIL_ONCE_FLAG mem_pool_ctl_initialized = UTIL_ONCE_FLAG_INIT; -char CTL_DEFAULT_ENTRIES[UMF_DEFAULT_SIZE][UMF_DEFAULT_LEN] = {0}; -char CTL_DEFAULT_VALUES[UMF_DEFAULT_SIZE][UMF_DEFAULT_LEN] = {0}; - static struct ctl umf_pool_ctl_root; static void pool_ctl_init(void); @@ -79,36 +84,76 @@ static umf_result_t CTL_SUBTREE_HANDLER(default)( utils_mutex_lock(&ctl_mtx); + ctl_default_entry_t *entry = NULL; + LL_FOREACH(ctl_default_list, entry) { + if (strcmp(entry->name, extra_name) == 0) { + break; + } + } + if (queryType == CTL_QUERY_WRITE) { - int i = 0; - for (; i < UMF_DEFAULT_SIZE; i++) { - if (CTL_DEFAULT_ENTRIES[i][0] == '\0' || - strcmp(CTL_DEFAULT_ENTRIES[i], extra_name) == 0) { - strncpy(CTL_DEFAULT_ENTRIES[i], extra_name, UMF_DEFAULT_LEN); - CTL_DEFAULT_ENTRIES[i][UMF_DEFAULT_LEN - 1] = '\0'; - strncpy(CTL_DEFAULT_VALUES[i], arg, UMF_DEFAULT_LEN); - CTL_DEFAULT_VALUES[i][UMF_DEFAULT_LEN - 1] = '\0'; - break; + bool is_new_entry = false; + if (entry == NULL) { + entry = umf_ba_global_alloc(sizeof(*entry)); + if (entry == NULL) { + utils_mutex_unlock(&ctl_mtx); + return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY; } + + entry->name = NULL; + entry->value = NULL; + entry->next = NULL; + is_new_entry = true; } - if (UMF_DEFAULT_SIZE == i) { - LOG_ERR("Default entries array is full"); + + size_t name_len = strlen(extra_name) + 1; + char *new_name = umf_ba_global_alloc(name_len); + if (new_name == NULL) { utils_mutex_unlock(&ctl_mtx); - return UMF_RESULT_ERROR_OUT_OF_RESOURCES; + return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY; } - } else if (queryType == CTL_QUERY_READ) { - int i = 0; - for (; i < UMF_DEFAULT_SIZE; i++) { - if (strcmp(CTL_DEFAULT_ENTRIES[i], extra_name) == 0) { - strncpy(arg, CTL_DEFAULT_VALUES[i], size); - break; + + memcpy(new_name, extra_name, name_len); + if (entry->name) { + umf_ba_global_free(entry->name); + } + entry->name = new_name; + + void *new_value = NULL; + if (size > 0) { + new_value = umf_ba_global_alloc(size); + if (new_value == NULL) { + utils_mutex_unlock(&ctl_mtx); + return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY; } + memcpy(new_value, arg, size); } - if (UMF_DEFAULT_SIZE == i) { + + if (entry->value) { + umf_ba_global_free(entry->value); + } + + entry->value = new_value; + entry->value_size = size; + entry->source = source; + + if (is_new_entry) { + LL_APPEND(ctl_default_list, entry); + } + } else if (queryType == CTL_QUERY_READ) { + if (entry == NULL) { LOG_WARN("Wrong path name: %s", extra_name); utils_mutex_unlock(&ctl_mtx); return UMF_RESULT_ERROR_INVALID_ARGUMENT; } + + if (entry->value_size > size) { + LOG_ERR("Provided buffer size %zu is smaller than field size %zu", + size, entry->value_size); + utils_mutex_unlock(&ctl_mtx); + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + memcpy(arg, entry->value, entry->value_size); } utils_mutex_unlock(&ctl_mtx); @@ -174,12 +219,11 @@ static const umf_pool_create_flags_t UMF_POOL_CREATE_FLAG_ALL = // windows do not allow to use uninitialized va_list so this function help us to initialize it. static umf_result_t default_ctl_helper(const umf_memory_pool_ops_t *ops, void *ctl, const char *name, void *arg, - ...) { + size_t size, ...) { va_list empty_args; - va_start(empty_args, arg); - umf_result_t ret = - ops->ext_ctl(ctl, CTL_QUERY_PROGRAMMATIC, name, arg, UMF_DEFAULT_LEN, - CTL_QUERY_WRITE, empty_args); + va_start(empty_args, size); + umf_result_t ret = ops->ext_ctl(ctl, CTL_QUERY_PROGRAMMATIC, name, arg, + size, CTL_QUERY_WRITE, empty_args); va_end(empty_args); return ret; } @@ -246,18 +290,23 @@ static umf_result_t umfPoolCreateInternal(const umf_memory_pool_ops_t *ops, } // Set default property "name" to pool if exists - for (int i = 0; i < UMF_DEFAULT_SIZE; i++) { - const char *pname = NULL; - ret = ops->get_name(NULL, &pname); - if (ret != UMF_RESULT_SUCCESS) { - LOG_ERR("Failed to get pool name"); - goto err_pool_init; - } - if (CTL_DEFAULT_ENTRIES[i][0] != '\0' && pname && - strstr(CTL_DEFAULT_ENTRIES[i], pname)) { - - default_ctl_helper(ops, pool->pool_priv, CTL_DEFAULT_ENTRIES[i], - CTL_DEFAULT_VALUES[i]); + const char *pname = NULL; + ret = ops->get_name(NULL, &pname); + if (ret != UMF_RESULT_SUCCESS) { + LOG_ERR("Failed to get pool name"); + goto err_pool_init; + } + assert(pname != NULL); + + size_t pname_len = strlen(pname); + ctl_default_entry_t *it = NULL; + LL_FOREACH(ctl_default_list, it) { + if (strlen(it->name) > pname_len + 1 && + strncmp(it->name, pname, pname_len) == 0 && + it->name[pname_len] == '.') { + const char *ctl_name = it->name + pname_len + 1; + default_ctl_helper(ops, pool->pool_priv, ctl_name, it->value, + it->value_size); } } @@ -454,3 +503,23 @@ umf_result_t umfPoolGetTag(umf_memory_pool_handle_t hPool, void **tag) { utils_mutex_unlock(&hPool->lock); return UMF_RESULT_SUCCESS; } + +void umfPoolCtlDefaultsDestroy(void) { + utils_init_once(&mem_pool_ctl_initialized, pool_ctl_init); + + utils_mutex_lock(&ctl_mtx); + + ctl_default_entry_t *entry = NULL, *tmp = NULL; + LL_FOREACH_SAFE(ctl_default_list, entry, tmp) { + LL_DELETE(ctl_default_list, entry); + if (entry->name) { + umf_ba_global_free(entry->name); + } + if (entry->value) { + umf_ba_global_free(entry->value); + } + umf_ba_global_free(entry); + } + + utils_mutex_unlock(&ctl_mtx); +} diff --git a/src/memory_pool_internal.h b/src/memory_pool_internal.h index 00f9a2a05..8c66cd4e7 100644 --- a/src/memory_pool_internal.h +++ b/src/memory_pool_internal.h @@ -47,6 +47,8 @@ typedef struct umf_memory_pool_t { extern umf_ctl_node_t CTL_NODE(pool)[]; +void umfPoolCtlDefaultsDestroy(void); + #ifdef __cplusplus } #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 42e159a75..da29448ee 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -221,6 +221,26 @@ if(LINUX) LIBS ${UMF_UTILS_FOR_TEST}) endif() +build_umf_test( + NAME ctl_env_app + SRCS ctl/ctl_env_app.cpp + LIBS ${UMF_UTILS_FOR_TEST} umf) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/ctl/ctl_env_config1.cfg + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/ctl) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/ctl/ctl_env_config2.cfg + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/ctl) + +add_umf_test( + NAME ctl_env_driver + SRCS ctl/ctl_env_driver.cpp + LIBS ${UMF_UTILS_FOR_TEST}) + +target_compile_definitions( + test_ctl_env_driver + PRIVATE CTL_ENV_APP="$" + CTL_CONF_FILE_DIR="${CMAKE_CURRENT_BINARY_DIR}/ctl") + add_umf_test( NAME coarse_lib SRCS coarse_lib.cpp diff --git a/test/ctl/ctl_env_app.cpp b/test/ctl/ctl_env_app.cpp new file mode 100644 index 000000000..45d5b7829 --- /dev/null +++ b/test/ctl/ctl_env_app.cpp @@ -0,0 +1,57 @@ +/* + * + * Copyright (C) 2025 Intel Corporation + * + * Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + */ + +#include +#include +#include +#include + +#include +#include + +static int test_env_defaults(int argc, char **argv) { + char buf[64] = {0}; + + if (argc % 2 != 0) { + std::cerr << "expected even number of arguments" << std::endl; + std::cerr << "Usage: env_defaults key1 value1 key2 value2 ..." + << std::endl; + return 1; + } + for (int i = 0; i < argc; i += 2) { + const char *key = argv[i]; + const char *value = argv[i + 1]; + if (umfCtlGet(key, buf, sizeof(buf)) != UMF_RESULT_SUCCESS) { + fprintf(stderr, "Failed to get control for '%s'\n", key); + return 1; + } + + if (strcmp(buf, value) != 0) { + std::cerr << "Expected value for '" << key << "' to be '" << value + << "', but got '" << buf << "'" << std::endl; + return 1; + } + } + return 0; +} + +int main(int argc, char **argv) { + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " args..." + << std::endl; + return 1; + } + const char *test_name = argv[1]; + argc -= 2; + argv += 2; + if (strcmp(test_name, "env_defaults") == 0) { + return test_env_defaults(argc, argv); + } + return 1; +} diff --git a/test/ctl/ctl_env_config1.cfg b/test/ctl/ctl_env_config1.cfg new file mode 100644 index 000000000..9831dc352 --- /dev/null +++ b/test/ctl/ctl_env_config1.cfg @@ -0,0 +1,4 @@ +# Copyright (C) 2025 Intel Corporation +# Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +umf.pool.default.test_pool.opt_one=opt_one_value1; # test comment diff --git a/test/ctl/ctl_env_config2.cfg b/test/ctl/ctl_env_config2.cfg new file mode 100644 index 000000000..ca2b52d62 --- /dev/null +++ b/test/ctl/ctl_env_config2.cfg @@ -0,0 +1,5 @@ +# Copyright (C) 2025 Intel Corporation +# Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +umf.pool.default.test_pool.opt_one=opt_one_value2; +umf.pool.default.test_pool.opt_two=opt_two_value2; diff --git a/test/ctl/ctl_env_driver.cpp b/test/ctl/ctl_env_driver.cpp new file mode 100644 index 000000000..bcc795354 --- /dev/null +++ b/test/ctl/ctl_env_driver.cpp @@ -0,0 +1,123 @@ +/* + * + * Copyright (C) 2025 Intel Corporation + * + * Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + */ + +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +#include +#include +#include + +#include "../common/base.hpp" +#include "gtest/gtest.h" + +using namespace umf_test; + +#ifndef CTL_ENV_APP +#define CTL_ENV_APP "./ctl_env_app" +#endif + +#ifndef CTL_CONF_FILE_DIR +#define CTL_CONF_FILE_DIR "./ctl" +#endif + +void set_env(std::pair env) { + const auto &name = env.first; + const auto &value = env.second; + + if (name.empty()) { + return; + } +#ifdef _WIN32 + _putenv_s(name.c_str(), value.c_str()); +#else + setenv(name.c_str(), value.c_str(), 1); +#endif +} + +static void run_case( + const std::vector> &env, + const std::vector &args) { + for (const auto &e : env) { + set_env(e); + } + +#ifdef _WIN32 + std::vector cargs; + cargs.push_back(CTL_ENV_APP); + for (const auto &s : args) { + cargs.push_back(s.c_str()); + } + + cargs.push_back(nullptr); + intptr_t status = _spawnv(_P_WAIT, CTL_ENV_APP, cargs.data()); + ASSERT_EQ(status, 0); +#else + pid_t pid = fork(); + if (pid == 0) { + std::vector cargs; + cargs.push_back(const_cast(CTL_ENV_APP)); + for (const auto &s : args) { + cargs.push_back(const_cast(s.c_str())); + } + cargs.push_back(nullptr); + execv(CTL_ENV_APP, cargs.data()); + std::cerr << "Failed to execute " << CTL_ENV_APP << std::endl; + _exit(127); + } + int status = 0; + waitpid(pid, &status, 0); + ASSERT_EQ(status, 0); +#endif + for (const auto &e : env) { + set_env({e.first, ""}); // Clear the environment variable + } +} + +TEST_F(test, ctl_env_defaults) { + run_case( + {{"UMF_CONF", "umf.pool.default.test_pool.opt_one=test_value"}}, + {"env_defaults", "umf.pool.default.test_pool.opt_one", "test_value"}); + + run_case({{"UMF_CONF", "umf.pool.default.test_pool.opt_one=second"}}, + {"env_defaults", "umf.pool.default.test_pool.opt_one", "second"}); +} + +TEST_F(test, ctl_env_file) { + std::string cfg1 = CTL_CONF_FILE_DIR "/ctl_env_config1.cfg"; + std::string cfg2 = CTL_CONF_FILE_DIR "/ctl_env_config2.cfg"; + + run_case({{"UMF_CONF_FILE", cfg1}}, + {"env_defaults", "umf.pool.default.test_pool.opt_one", + "opt_one_value1"}); + + run_case({{"UMF_CONF_FILE", cfg2}}, + {"env_defaults", "umf.pool.default.test_pool.opt_one", + "opt_one_value2", "umf.pool.default.test_pool.opt_two", + "opt_two_value2"}); +} + +TEST_F(test, ctl_env_plus_file) { + std::string cfg = CTL_CONF_FILE_DIR "/ctl_env_config2.cfg"; + + // it is expected that configuration from file will override configuration from environment variable + run_case({{"UMF_CONF_FILE", cfg}, + {"UMF_CONF", "umf.pool.default.test_pool.opt_one=first;umf.pool." + "default.test_pool.opt_three=second"}}, + {"env_defaults", "umf.pool.default.test_pool.opt_one", + "opt_one_value2", "umf.pool.default.test_pool.opt_two", + "opt_two_value2", "umf.pool.default.test_pool.opt_three", + "second"}); +} diff --git a/test/test_valgrind.sh b/test/test_valgrind.sh index cff45bdec..c877c4f44 100755 --- a/test/test_valgrind.sh +++ b/test/test_valgrind.sh @@ -128,6 +128,9 @@ for test in $TESTS; do echo "- SKIPPED" continue; # skip testing helper binaries used by the ipc_file_prov_* tests ;; + ./test/test_ctl_env_app) + continue; # this is not a standalone test + ;; ./test/test_memspace_host_all) FILTER='--gtest_filter="-*allocsSpreadAcrossAllNumaNodes"' ;;