| /* SPDX-License-Identifier: GPL-2.0-or-later */ | |
| /* | |
| * envlist tests | |
| * | |
| * Copyright 2026 Virtuozzo International GmbH | |
| * | |
| * Authors: | |
| * Denis V. Lunev <den@openvz.org> | |
| */ | |
| #include "qemu/osdep.h" | |
| #include "qemu/envlist.h" | |
| static void free_environ(char **env) | |
| { | |
| char **p; | |
| for (p = env; *p != NULL; p++) { | |
| g_free(*p); | |
| } | |
| g_free(env); | |
| } | |
| static const char *find_env(char **env, const char *name) | |
| { | |
| size_t name_len = strlen(name); | |
| char **p; | |
| for (p = env; *p != NULL; p++) { | |
| if (strncmp(*p, name, name_len) == 0 && (*p)[name_len] == '=') { | |
| return *p + name_len + 1; | |
| } | |
| } | |
| return NULL; | |
| } | |
| static void test_envlist_basic(void) | |
| { | |
| envlist_t *el = envlist_create(); | |
| char **env; | |
| size_t count; | |
| /* empty list */ | |
| env = envlist_to_environ(el, &count); | |
| g_assert_cmpuint(count, ==, 0); | |
| g_assert_null(env[0]); | |
| free_environ(env); | |
| /* add */ | |
| g_assert_cmpint(envlist_setenv(el, "A=1"), ==, 0); | |
| g_assert_cmpint(envlist_setenv(el, "B=2"), ==, 0); | |
| env = envlist_to_environ(el, &count); | |
| g_assert_cmpuint(count, ==, 2); | |
| g_assert_cmpstr(find_env(env, "A"), ==, "1"); | |
| g_assert_cmpstr(find_env(env, "B"), ==, "2"); | |
| free_environ(env); | |
| /* replace */ | |
| g_assert_cmpint(envlist_setenv(el, "A=42"), ==, 0); | |
| env = envlist_to_environ(el, &count); | |
| g_assert_cmpuint(count, ==, 2); | |
| g_assert_cmpstr(find_env(env, "A"), ==, "42"); | |
| g_assert_cmpstr(find_env(env, "B"), ==, "2"); | |
| free_environ(env); | |
| /* unset existing */ | |
| g_assert_cmpint(envlist_unsetenv(el, "A"), ==, 0); | |
| env = envlist_to_environ(el, &count); | |
| g_assert_cmpuint(count, ==, 1); | |
| g_assert_null(find_env(env, "A")); | |
| g_assert_cmpstr(find_env(env, "B"), ==, "2"); | |
| free_environ(env); | |
| /* unset non-existing is a no-op success */ | |
| g_assert_cmpint(envlist_unsetenv(el, "NOPE"), ==, 0); | |
| env = envlist_to_environ(el, &count); | |
| g_assert_cmpuint(count, ==, 1); | |
| free_environ(env); | |
| envlist_free(el); | |
| } | |
| /* | |
| * envlist_setenv() inserts at the head; envlist_to_environ() walks | |
| * head-to-tail, so the last setenv comes out first. | |
| */ | |
| static void test_envlist_head_insertion_order(void) | |
| { | |
| envlist_t *el = envlist_create(); | |
| char **env; | |
| size_t count; | |
| g_assert_cmpint(envlist_setenv(el, "A=1"), ==, 0); | |
| g_assert_cmpint(envlist_setenv(el, "B=2"), ==, 0); | |
| g_assert_cmpint(envlist_setenv(el, "C=3"), ==, 0); | |
| env = envlist_to_environ(el, &count); | |
| g_assert_cmpuint(count, ==, 3); | |
| g_assert_cmpstr(env[0], ==, "C=3"); | |
| g_assert_cmpstr(env[1], ==, "B=2"); | |
| g_assert_cmpstr(env[2], ==, "A=1"); | |
| g_assert_null(env[3]); | |
| free_environ(env); | |
| envlist_free(el); | |
| } | |
| static void test_envlist_einval(void) | |
| { | |
| envlist_t *el = envlist_create(); | |
| /* NULL list */ | |
| g_assert_cmpint(envlist_setenv(NULL, "A=1"), ==, EINVAL); | |
| g_assert_cmpint(envlist_unsetenv(NULL, "A"), ==, EINVAL); | |
| /* NULL string */ | |
| g_assert_cmpint(envlist_setenv(el, NULL), ==, EINVAL); | |
| g_assert_cmpint(envlist_unsetenv(el, NULL), ==, EINVAL); | |
| /* setenv: missing '=' */ | |
| g_assert_cmpint(envlist_setenv(el, "NOEQ"), ==, EINVAL); | |
| /* unsetenv: name must not contain '=' */ | |
| g_assert_cmpint(envlist_unsetenv(el, "A=B"), ==, EINVAL); | |
| envlist_free(el); | |
| } | |
| /* | |
| * Regression: envlist_unsetenv("FOO") must not remove an entry named | |
| * "FOOBAR" -- the previous strncmp(entry, name, strlen(name)) lookup | |
| * prefix-matched. To trigger the bug, the longer-named entry has to | |
| * be ahead of the target in the list: envlist_setenv() inserts at | |
| * the head, so we add FOO first and FOOBAR last. | |
| */ | |
| static void test_envlist_unsetenv_no_prefix_match(void) | |
| { | |
| envlist_t *el = envlist_create(); | |
| char **env; | |
| size_t count; | |
| g_assert_cmpint(envlist_setenv(el, "FOO=y"), ==, 0); | |
| g_assert_cmpint(envlist_setenv(el, "FOOBAR=x"), ==, 0); | |
| g_assert_cmpint(envlist_unsetenv(el, "FOO"), ==, 0); | |
| env = envlist_to_environ(el, &count); | |
| g_assert_cmpuint(count, ==, 1); | |
| g_assert_cmpstr(find_env(env, "FOOBAR"), ==, "x"); | |
| g_assert_null(find_env(env, "FOO")); | |
| free_environ(env); | |
| envlist_free(el); | |
| } | |
| /* | |
| * envlist_setenv() must not replace a prior FOOBAR=... entry when | |
| * setting FOO=... The pre-fix code happened to be safe here only | |
| * because it included the trailing '=' byte in its strncmp length; | |
| * this test pins down the post-fix contract that the name boundary | |
| * is a property of the entry, not of the encoded form. | |
| */ | |
| static void test_envlist_setenv_no_prefix_match(void) | |
| { | |
| envlist_t *el = envlist_create(); | |
| char **env; | |
| size_t count; | |
| g_assert_cmpint(envlist_setenv(el, "FOOBAR=x"), ==, 0); | |
| g_assert_cmpint(envlist_setenv(el, "FOO=y"), ==, 0); | |
| env = envlist_to_environ(el, &count); | |
| g_assert_cmpuint(count, ==, 2); | |
| g_assert_cmpstr(find_env(env, "FOOBAR"), ==, "x"); | |
| g_assert_cmpstr(find_env(env, "FOO"), ==, "y"); | |
| free_environ(env); | |
| envlist_free(el); | |
| } | |
| int main(int argc, char *argv[]) | |
| { | |
| g_test_init(&argc, &argv, NULL); | |
| g_test_add_func("/envlist/basic", test_envlist_basic); | |
| g_test_add_func("/envlist/head_insertion_order", | |
| test_envlist_head_insertion_order); | |
| g_test_add_func("/envlist/einval", test_envlist_einval); | |
| g_test_add_func("/envlist/unsetenv_no_prefix_match", | |
| test_envlist_unsetenv_no_prefix_match); | |
| g_test_add_func("/envlist/setenv_no_prefix_match", | |
| test_envlist_setenv_no_prefix_match); | |
| return g_test_run(); | |
| } |