master
c 196 lines 5.42 KB
Raw
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 /*
3 * envlist tests
4 *
5 * Copyright 2026 Virtuozzo International GmbH
6 *
7 * Authors:
8 * Denis V. Lunev <den@openvz.org>
9 */
10
11 #include "qemu/osdep.h"
12 #include "qemu/envlist.h"
13
14 static void free_environ(char **env)
15 {
16 char **p;
17
18 for (p = env; *p != NULL; p++) {
19 g_free(*p);
20 }
21 g_free(env);
22 }
23
24 static const char *find_env(char **env, const char *name)
25 {
26 size_t name_len = strlen(name);
27 char **p;
28
29 for (p = env; *p != NULL; p++) {
30 if (strncmp(*p, name, name_len) == 0 && (*p)[name_len] == '=') {
31 return *p + name_len + 1;
32 }
33 }
34 return NULL;
35 }
36
37 static void test_envlist_basic(void)
38 {
39 envlist_t *el = envlist_create();
40 char **env;
41 size_t count;
42
43 /* empty list */
44 env = envlist_to_environ(el, &count);
45 g_assert_cmpuint(count, ==, 0);
46 g_assert_null(env[0]);
47 free_environ(env);
48
49 /* add */
50 g_assert_cmpint(envlist_setenv(el, "A=1"), ==, 0);
51 g_assert_cmpint(envlist_setenv(el, "B=2"), ==, 0);
52
53 env = envlist_to_environ(el, &count);
54 g_assert_cmpuint(count, ==, 2);
55 g_assert_cmpstr(find_env(env, "A"), ==, "1");
56 g_assert_cmpstr(find_env(env, "B"), ==, "2");
57 free_environ(env);
58
59 /* replace */
60 g_assert_cmpint(envlist_setenv(el, "A=42"), ==, 0);
61 env = envlist_to_environ(el, &count);
62 g_assert_cmpuint(count, ==, 2);
63 g_assert_cmpstr(find_env(env, "A"), ==, "42");
64 g_assert_cmpstr(find_env(env, "B"), ==, "2");
65 free_environ(env);
66
67 /* unset existing */
68 g_assert_cmpint(envlist_unsetenv(el, "A"), ==, 0);
69 env = envlist_to_environ(el, &count);
70 g_assert_cmpuint(count, ==, 1);
71 g_assert_null(find_env(env, "A"));
72 g_assert_cmpstr(find_env(env, "B"), ==, "2");
73 free_environ(env);
74
75 /* unset non-existing is a no-op success */
76 g_assert_cmpint(envlist_unsetenv(el, "NOPE"), ==, 0);
77 env = envlist_to_environ(el, &count);
78 g_assert_cmpuint(count, ==, 1);
79 free_environ(env);
80
81 envlist_free(el);
82 }
83
84 /*
85 * envlist_setenv() inserts at the head; envlist_to_environ() walks
86 * head-to-tail, so the last setenv comes out first.
87 */
88 static void test_envlist_head_insertion_order(void)
89 {
90 envlist_t *el = envlist_create();
91 char **env;
92 size_t count;
93
94 g_assert_cmpint(envlist_setenv(el, "A=1"), ==, 0);
95 g_assert_cmpint(envlist_setenv(el, "B=2"), ==, 0);
96 g_assert_cmpint(envlist_setenv(el, "C=3"), ==, 0);
97
98 env = envlist_to_environ(el, &count);
99 g_assert_cmpuint(count, ==, 3);
100 g_assert_cmpstr(env[0], ==, "C=3");
101 g_assert_cmpstr(env[1], ==, "B=2");
102 g_assert_cmpstr(env[2], ==, "A=1");
103 g_assert_null(env[3]);
104
105 free_environ(env);
106 envlist_free(el);
107 }
108
109 static void test_envlist_einval(void)
110 {
111 envlist_t *el = envlist_create();
112
113 /* NULL list */
114 g_assert_cmpint(envlist_setenv(NULL, "A=1"), ==, EINVAL);
115 g_assert_cmpint(envlist_unsetenv(NULL, "A"), ==, EINVAL);
116
117 /* NULL string */
118 g_assert_cmpint(envlist_setenv(el, NULL), ==, EINVAL);
119 g_assert_cmpint(envlist_unsetenv(el, NULL), ==, EINVAL);
120
121 /* setenv: missing '=' */
122 g_assert_cmpint(envlist_setenv(el, "NOEQ"), ==, EINVAL);
123
124 /* unsetenv: name must not contain '=' */
125 g_assert_cmpint(envlist_unsetenv(el, "A=B"), ==, EINVAL);
126
127 envlist_free(el);
128 }
129
130 /*
131 * Regression: envlist_unsetenv("FOO") must not remove an entry named
132 * "FOOBAR" -- the previous strncmp(entry, name, strlen(name)) lookup
133 * prefix-matched. To trigger the bug, the longer-named entry has to
134 * be ahead of the target in the list: envlist_setenv() inserts at
135 * the head, so we add FOO first and FOOBAR last.
136 */
137 static void test_envlist_unsetenv_no_prefix_match(void)
138 {
139 envlist_t *el = envlist_create();
140 char **env;
141 size_t count;
142
143 g_assert_cmpint(envlist_setenv(el, "FOO=y"), ==, 0);
144 g_assert_cmpint(envlist_setenv(el, "FOOBAR=x"), ==, 0);
145
146 g_assert_cmpint(envlist_unsetenv(el, "FOO"), ==, 0);
147
148 env = envlist_to_environ(el, &count);
149 g_assert_cmpuint(count, ==, 1);
150 g_assert_cmpstr(find_env(env, "FOOBAR"), ==, "x");
151 g_assert_null(find_env(env, "FOO"));
152
153 free_environ(env);
154 envlist_free(el);
155 }
156
157 /*
158 * envlist_setenv() must not replace a prior FOOBAR=... entry when
159 * setting FOO=... The pre-fix code happened to be safe here only
160 * because it included the trailing '=' byte in its strncmp length;
161 * this test pins down the post-fix contract that the name boundary
162 * is a property of the entry, not of the encoded form.
163 */
164 static void test_envlist_setenv_no_prefix_match(void)
165 {
166 envlist_t *el = envlist_create();
167 char **env;
168 size_t count;
169
170 g_assert_cmpint(envlist_setenv(el, "FOOBAR=x"), ==, 0);
171 g_assert_cmpint(envlist_setenv(el, "FOO=y"), ==, 0);
172
173 env = envlist_to_environ(el, &count);
174 g_assert_cmpuint(count, ==, 2);
175 g_assert_cmpstr(find_env(env, "FOOBAR"), ==, "x");
176 g_assert_cmpstr(find_env(env, "FOO"), ==, "y");
177
178 free_environ(env);
179 envlist_free(el);
180 }
181
182 int main(int argc, char *argv[])
183 {
184 g_test_init(&argc, &argv, NULL);
185
186 g_test_add_func("/envlist/basic", test_envlist_basic);
187 g_test_add_func("/envlist/head_insertion_order",
188 test_envlist_head_insertion_order);
189 g_test_add_func("/envlist/einval", test_envlist_einval);
190 g_test_add_func("/envlist/unsetenv_no_prefix_match",
191 test_envlist_unsetenv_no_prefix_match);
192 g_test_add_func("/envlist/setenv_no_prefix_match",
193 test_envlist_setenv_no_prefix_match);
194
195 return g_test_run();
196 }