Branch data Line data Source code
1 : : /* GIO - GLib Input, Output and Streaming Library
2 : : *
3 : : * Copyright 2026 Red Hat, Inc.
4 : : *
5 : : * SPDX-License-Identifier: LGPL-2.1-or-later
6 : : *
7 : : * This library is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU Lesser General Public
9 : : * License as published by the Free Software Foundation; either
10 : : * version 2.1 of the License, or (at your option) any later version.
11 : : *
12 : : * This library is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : : * Lesser General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU Lesser General
18 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 : : */
20 : :
21 : : #include <glib/glib.h>
22 : : #include <glib/gstdio.h>
23 : : #include <sys/stat.h>
24 : : #include <sys/types.h>
25 : :
26 : : #include "gio.h"
27 : : #include "gmemorymonitorpsi.h"
28 : :
29 : : typedef struct
30 : : {
31 : : char *mock_psi_path;
32 : : char *mock_proc_path;
33 : : char *mock_write_env_path;
34 : : char *mock_write_env_no_write;
35 : : char *mock_socket_dir;
36 : : char *mock_socket_path;
37 : : GSocket *mock_write_socket;
38 : :
39 : : GMutex socket_mutex;
40 : : GCond socket_cond;
41 : : } SetupData;
42 : :
43 : : static void
44 : 4 : tests_setup (SetupData *setup_data,
45 : : gconstpointer data)
46 : : {
47 : 4 : char *proc_contents = NULL;
48 : 4 : GError *local_error = NULL;
49 : :
50 : 4 : setup_data->mock_psi_path = g_build_filename (g_get_tmp_dir (), "cgroup", NULL);
51 : 4 : g_assert_no_errno (mkfifo (setup_data->mock_psi_path, S_IRUSR | S_IWUSR));
52 : :
53 : 4 : setup_data->mock_proc_path = g_build_filename (g_get_tmp_dir (), "psi-proc", NULL);
54 : 4 : proc_contents = g_strdup_printf ("0::%s", setup_data->mock_psi_path);
55 : 4 : g_file_set_contents_full (setup_data->mock_proc_path, proc_contents,
56 : : -1, G_FILE_SET_CONTENTS_NONE,
57 : : S_IRUSR | S_IWUSR, &local_error);
58 : 4 : g_assert_no_error (local_error);
59 : 4 : g_clear_error (&local_error);
60 : :
61 : 4 : setup_data->mock_write_env_path = g_build_filename (g_get_tmp_dir (), "write-env", NULL);
62 : 4 : g_file_set_contents_full (setup_data->mock_write_env_path, "",
63 : : -1, G_FILE_SET_CONTENTS_NONE,
64 : : S_IRUSR | S_IWUSR, &local_error);
65 : :
66 : 4 : setup_data->mock_write_env_no_write = g_build_filename (g_get_tmp_dir (), "write-env-no-write", NULL);
67 : 4 : g_file_set_contents_full (setup_data->mock_write_env_no_write, "",
68 : : -1, G_FILE_SET_CONTENTS_NONE,
69 : : S_IRUSR | S_IWUSR, &local_error);
70 : :
71 : 4 : g_free (proc_contents);
72 : 4 : }
73 : :
74 : : static void
75 : 1 : tests_unix_domain_setup (SetupData *setup_data,
76 : : gconstpointer data)
77 : : {
78 : 1 : char *proc_contents = NULL;
79 : : GSocketAddress *socket_addr;
80 : 1 : GError *local_error = NULL;
81 : :
82 : 1 : setup_data->mock_psi_path = g_build_filename (g_get_tmp_dir (), "cgroup", NULL);
83 : 1 : g_assert_no_errno (mkfifo (setup_data->mock_psi_path, S_IRUSR | S_IWUSR));
84 : :
85 : 1 : setup_data->mock_proc_path = g_build_filename (g_get_tmp_dir (), "psi-proc", NULL);
86 : 1 : proc_contents = g_strdup_printf ("0::%s", setup_data->mock_psi_path);
87 : 1 : g_file_set_contents_full (setup_data->mock_proc_path, proc_contents,
88 : : -1, G_FILE_SET_CONTENTS_NONE,
89 : : S_IRUSR | S_IWUSR, &local_error);
90 : 1 : g_assert_no_error (local_error);
91 : 1 : g_clear_error (&local_error);
92 : :
93 : 1 : g_free (proc_contents);
94 : :
95 : 1 : setup_data->mock_socket_dir = g_dir_make_tmp ("memory-monitor-psi-env-set-XXXXXX", &local_error);
96 : 1 : g_assert_no_error (local_error);
97 : 1 : setup_data->mock_socket_path = g_build_filename (setup_data->mock_socket_dir,
98 : : "memory-monitor-socket", NULL);
99 : 1 : setup_data->mock_write_socket = g_socket_new (G_SOCKET_FAMILY_UNIX,
100 : : G_SOCKET_TYPE_STREAM,
101 : : 0, &local_error);
102 : 1 : g_assert_no_error (local_error);
103 : 1 : socket_addr = g_unix_socket_address_new (setup_data->mock_socket_path);
104 : 1 : g_socket_bind (setup_data->mock_write_socket,
105 : : socket_addr,
106 : : TRUE, &local_error);
107 : 1 : g_object_unref (socket_addr);
108 : 1 : g_assert_no_error (local_error);
109 : :
110 : 1 : g_socket_listen (setup_data->mock_write_socket, &local_error);
111 : 1 : g_assert_no_error (local_error);
112 : :
113 : 1 : g_clear_error (&local_error);
114 : :
115 : 1 : g_mutex_init (&setup_data->socket_mutex);
116 : 1 : g_cond_init (&setup_data->socket_cond);
117 : 1 : }
118 : :
119 : : static void
120 : 1 : tests_unix_domain_teardown (SetupData *setup_data,
121 : : gconstpointer data)
122 : : {
123 : 1 : g_unlink (setup_data->mock_proc_path);
124 : 1 : g_free (setup_data->mock_proc_path);
125 : 1 : g_unlink (setup_data->mock_psi_path);
126 : 1 : g_free (setup_data->mock_psi_path);
127 : 1 : g_socket_close (setup_data->mock_write_socket, NULL);
128 : 1 : g_object_unref (setup_data->mock_write_socket);
129 : 1 : g_unlink (setup_data->mock_socket_path);
130 : 1 : g_rmdir (setup_data->mock_socket_dir);
131 : 1 : g_free (setup_data->mock_socket_path);
132 : 1 : g_free (setup_data->mock_socket_dir);
133 : 1 : g_cond_clear (&setup_data->socket_cond);
134 : 1 : g_mutex_clear (&setup_data->socket_mutex);
135 : 1 : }
136 : :
137 : : static void
138 : 4 : tests_teardown (SetupData *setup_data,
139 : : gconstpointer data)
140 : : {
141 : 4 : g_unlink (setup_data->mock_proc_path);
142 : 4 : g_free (setup_data->mock_proc_path);
143 : 4 : g_unlink (setup_data->mock_psi_path);
144 : 4 : g_free (setup_data->mock_psi_path);
145 : 4 : g_unlink (setup_data->mock_write_env_path);
146 : 4 : g_free (setup_data->mock_write_env_path);
147 : 4 : g_unlink (setup_data->mock_write_env_no_write);
148 : 4 : g_free (setup_data->mock_write_env_no_write);
149 : 4 : }
150 : :
151 : : static void
152 : 2 : memory_monitor_psi_signal_cb (GMemoryMonitor *m,
153 : : GMemoryMonitorWarningLevel level,
154 : : gpointer user_data)
155 : : {
156 : 2 : int *out_warning_level = user_data;
157 : :
158 : 2 : *out_warning_level = level;
159 : :
160 : 2 : g_main_context_wakeup (NULL);
161 : 2 : }
162 : :
163 : : static void
164 : 1 : test_receive_signals (SetupData *setup, gconstpointer data)
165 : : {
166 : : GMemoryMonitorPsi *monitor;
167 : : unsigned long warning_id;
168 : 1 : int warning_level = -1;
169 : 1 : GError *local_error = NULL;
170 : :
171 : 1 : g_setenv ("MEMORY_PRESSURE_WATCH", setup->mock_psi_path, TRUE);
172 : 1 : g_setenv ("MEMORY_PRESSURE_WRITE", "c29tZSAxNTAwMDAgMTAwMDAwMA==", TRUE);
173 : :
174 : 1 : g_file_set_contents (setup->mock_psi_path,
175 : : "some 150000 1000000",
176 : : -1,
177 : : &local_error);
178 : 1 : g_assert_no_error (local_error);
179 : :
180 : 1 : monitor = g_object_new (G_TYPE_MEMORY_MONITOR_PSI,
181 : : "proc-path", setup->mock_proc_path,
182 : : NULL);
183 : 1 : warning_id = g_signal_connect (monitor, "low-memory-warning",
184 : : G_CALLBACK (memory_monitor_psi_signal_cb), &warning_level);
185 : 1 : g_initable_init (G_INITABLE (monitor), NULL, &local_error);
186 : 1 : g_assert_no_error (local_error);
187 : :
188 : 2 : while (warning_level == -1)
189 : 1 : g_main_context_iteration (NULL, TRUE);
190 : :
191 : 1 : g_assert_true (warning_level == 50);
192 : :
193 : 1 : g_signal_handler_disconnect (monitor, warning_id);
194 : 1 : g_object_unref (monitor);
195 : 1 : }
196 : :
197 : : static void
198 : 1 : test_write_env (SetupData *setup, gconstpointer data)
199 : : {
200 : : GMemoryMonitorPsi *monitor;
201 : : unsigned long warning_id;
202 : 1 : int warning_level = -1;
203 : 1 : GError *local_error = NULL;
204 : 1 : gchar *read_data = NULL;
205 : 1 : gsize read_data_len = 0;
206 : 1 : gchar *base64_data = NULL;
207 : : /* The contents is generated by running "dd if=/dev/random of=/foo/bar bs=128 count=1" */
208 : 1 : const gchar *env_write = "hVh9eV0BZsh/GWEs9V6zKyO3VUe2MnxTxLZbA7BRo7GJKHAAayIRsgO8EI4m8iFskhx/O4cAfpyL"
209 : : "YQ8LFHMG5XBuBmqD9spZghu0eVYF2X8lCCT+iBlEFw5dWiSa6XwJN++AmHi1tp4uJj0UBR7CwV5c"
210 : : "+ms9ZDUqOQrvlFHUSxw=";
211 : :
212 : 1 : g_setenv ("MEMORY_PRESSURE_WATCH", setup->mock_write_env_path, TRUE);
213 : 1 : g_setenv ("MEMORY_PRESSURE_WRITE", env_write, TRUE);
214 : :
215 : 1 : monitor = g_object_new (G_TYPE_MEMORY_MONITOR_PSI,
216 : : "proc-path", setup->mock_proc_path,
217 : : NULL);
218 : 1 : warning_id = g_signal_connect (monitor, "low-memory-warning",
219 : : G_CALLBACK (memory_monitor_psi_signal_cb), &warning_level);
220 : 1 : g_initable_init (G_INITABLE (monitor), NULL, &local_error);
221 : 1 : g_assert_no_error (local_error);
222 : :
223 : 1 : g_signal_handler_disconnect (monitor, warning_id);
224 : 1 : g_object_unref (monitor);
225 : :
226 : : /* Verify the write contents */
227 : 1 : g_file_get_contents (setup->mock_write_env_path, &read_data, &read_data_len, &local_error);
228 : 1 : g_assert_no_error (local_error);
229 : 1 : base64_data = g_base64_encode ((guchar *) read_data, read_data_len);
230 : 1 : g_assert_cmpstr (base64_data, ==, env_write);
231 : 1 : g_assert_cmpuint (read_data_len, ==, 128);
232 : 1 : g_free (read_data);
233 : 1 : g_free (base64_data);
234 : 1 : g_clear_error (&local_error);
235 : 1 : }
236 : :
237 : : static void
238 : 1 : test_write_env_no_write (SetupData *setup, gconstpointer data)
239 : : {
240 : : GMemoryMonitorPsi *monitor;
241 : : unsigned long warning_id;
242 : 1 : int warning_level = -1;
243 : 1 : GError *local_error = NULL;
244 : 1 : gchar *read_data = NULL;
245 : 1 : gsize read_data_len = 0;
246 : 1 : gchar *base64_data = NULL;
247 : :
248 : 1 : g_setenv ("MEMORY_PRESSURE_WATCH", setup->mock_write_env_no_write, TRUE);
249 : 1 : g_unsetenv ("MEMORY_PRESSURE_WRITE");
250 : :
251 : 1 : monitor = g_object_new (G_TYPE_MEMORY_MONITOR_PSI,
252 : : "proc-path", setup->mock_proc_path,
253 : : NULL);
254 : 1 : warning_id = g_signal_connect (monitor, "low-memory-warning",
255 : : G_CALLBACK (memory_monitor_psi_signal_cb), &warning_level);
256 : 1 : g_initable_init (G_INITABLE (monitor), NULL, &local_error);
257 : 1 : g_assert_no_error (local_error);
258 : :
259 : 1 : g_signal_handler_disconnect (monitor, warning_id);
260 : 1 : g_object_unref (monitor);
261 : :
262 : : /* Verify the write contents */
263 : 1 : g_file_get_contents (setup->mock_write_env_no_write, &read_data, &read_data_len, &local_error);
264 : 1 : g_assert_no_error (local_error);
265 : 1 : base64_data = g_base64_encode ((guchar *) read_data, read_data_len);
266 : 1 : g_assert_cmpuint (read_data_len, ==, 0);
267 : 1 : g_free (read_data);
268 : 1 : g_free (base64_data);
269 : 1 : g_clear_error (&local_error);
270 : 1 : }
271 : :
272 : : static void
273 : 1 : test_write_env_null (SetupData *setup, gconstpointer data)
274 : : {
275 : : GMemoryMonitorPsi *monitor;
276 : : unsigned long warning_id;
277 : 1 : int warning_level = -1;
278 : 1 : GError *local_error = NULL;
279 : :
280 : : /* The contents is generated by running "dd if=/dev/random of=/foo/bar bs=128 count=1" */
281 : 1 : const gchar *env_write = "hVh9eV0BZsh/GWEs9V6zKyO3VUe2MnxTxLZbA7BRo7GJKHAAayIRsgO8EI4m8iFskhx/O4cAfpyL"
282 : : "YQ8LFHMG5XBuBmqD9spZghu0eVYF2X8lCCT+iBlEFw5dWiSa6XwJN++AmHi1tp4uJj0UBR7CwV5c"
283 : : "+ms9ZDUqOQrvlFHUSxw=";
284 : :
285 : 1 : g_setenv ("MEMORY_PRESSURE_WATCH", "/dev/null", TRUE);
286 : 1 : g_setenv ("MEMORY_PRESSURE_WRITE", env_write, TRUE);
287 : :
288 : 1 : monitor = g_object_new (G_TYPE_MEMORY_MONITOR_PSI,
289 : : "proc-path", setup->mock_proc_path,
290 : : NULL);
291 : 1 : warning_id = g_signal_connect (monitor, "low-memory-warning",
292 : : G_CALLBACK (memory_monitor_psi_signal_cb), &warning_level);
293 : 1 : g_initable_init (G_INITABLE (monitor), NULL, &local_error);
294 : 1 : g_assert_no_error (local_error);
295 : :
296 : 1 : g_signal_handler_disconnect (monitor, warning_id);
297 : 1 : g_object_unref (monitor);
298 : :
299 : : /* warning_level shouldn't be set */
300 : 1 : g_assert_cmpint (warning_level, ==, -1);
301 : :
302 : 1 : g_clear_error (&local_error);
303 : 1 : }
304 : :
305 : : static gpointer
306 : 1 : test_write_env_unix_domain_socket_thread (gpointer user_data)
307 : : {
308 : 1 : SetupData *setup = user_data;
309 : 1 : GError *local_error = NULL;
310 : 1 : GSocket *socket = NULL;
311 : : gchar read_data[256];
312 : 1 : gssize ret_size = 0;
313 : :
314 : 1 : g_mutex_lock (&setup->socket_mutex);
315 : 1 : g_cond_signal (&setup->socket_cond);
316 : 1 : g_mutex_unlock (&setup->socket_mutex);
317 : :
318 : 1 : socket = g_socket_accept (setup->mock_write_socket,
319 : : NULL, &local_error);
320 : 1 : g_assert_no_error (local_error);
321 : :
322 : 1 : ret_size = g_socket_receive (socket, read_data, sizeof (read_data), NULL, &local_error);
323 : :
324 : 1 : g_assert_no_error (local_error);
325 : 1 : g_assert_cmpint (ret_size, ==, 128);
326 : :
327 : 1 : g_socket_send (socket, read_data, ret_size, NULL, &local_error);
328 : :
329 : 1 : g_socket_close (socket, &local_error);
330 : 1 : g_assert_no_error (local_error);
331 : 1 : g_object_unref (socket);
332 : :
333 : 1 : return NULL;
334 : : }
335 : :
336 : : static void
337 : 1 : test_write_env_unix_domain_socket (SetupData *setup, gconstpointer data)
338 : : {
339 : : GMemoryMonitorPsi *monitor;
340 : 1 : int warning_level = -1;
341 : 1 : GError *local_error = NULL;
342 : 1 : GThread *thread = NULL;
343 : :
344 : : /* The contents is generated by running "dd if=/dev/random of=/foo/bar bs=128 count=1" */
345 : 1 : const gchar *env_write = "hVh9eV0BZsh/GWEs9V6zKyO3VUe2MnxTxLZbA7BRo7GJKHAAayIRsgO8EI4m8iFskhx/O4cAfpyL"
346 : : "YQ8LFHMG5XBuBmqD9spZghu0eVYF2X8lCCT+iBlEFw5dWiSa6XwJN++AmHi1tp4uJj0UBR7CwV5c"
347 : : "+ms9ZDUqOQrvlFHUSxw=";
348 : :
349 : 1 : g_setenv ("MEMORY_PRESSURE_WATCH", setup->mock_socket_path, TRUE);
350 : 1 : g_setenv ("MEMORY_PRESSURE_WRITE", env_write, TRUE);
351 : :
352 : 1 : g_mutex_lock (&setup->socket_mutex);
353 : :
354 : 1 : thread = g_thread_new ("test_write_env_unix_domain_socket_thread",
355 : : test_write_env_unix_domain_socket_thread, setup);
356 : :
357 : 1 : g_cond_wait (&setup->socket_cond, &setup->socket_mutex);
358 : :
359 : 1 : monitor = g_object_new (G_TYPE_MEMORY_MONITOR_PSI,
360 : : "proc-path", setup->mock_proc_path,
361 : : NULL);
362 : 1 : g_initable_init (G_INITABLE (monitor), NULL, &local_error);
363 : :
364 : 1 : g_mutex_unlock (&setup->socket_mutex);
365 : :
366 : 1 : g_assert_no_error (local_error);
367 : :
368 : 1 : g_signal_connect (monitor, "low-memory-warning",
369 : : G_CALLBACK (memory_monitor_psi_signal_cb), &warning_level);
370 : 1 : g_assert_no_error (local_error);
371 : :
372 : 1 : g_thread_join (thread);
373 : :
374 : 1 : while (warning_level == -1)
375 : 0 : g_main_context_iteration (NULL, TRUE);
376 : :
377 : 1 : g_assert_cmpint (warning_level, ==, 50);
378 : :
379 : 1 : g_object_unref (monitor);
380 : 1 : g_clear_error (&local_error);
381 : 1 : }
382 : :
383 : : int
384 : 1 : main (int argc, char **argv)
385 : : {
386 : 1 : g_setenv ("GIO_USE_MEMORY_MONITOR", "psi", TRUE);
387 : :
388 : 1 : g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
389 : 1 : g_test_add ("/memory-monitor-psi-env-set/receive-signal", SetupData, NULL,
390 : : tests_setup, test_receive_signals, tests_teardown);
391 : :
392 : 1 : g_test_add ("/memory-monitor-psi-env-set/write-env-no-write", SetupData, NULL,
393 : : tests_setup, test_write_env_no_write, tests_teardown);
394 : :
395 : 1 : g_test_add ("/memory-monitor-psi-env-set/write-env", SetupData, NULL,
396 : : tests_setup, test_write_env, tests_teardown);
397 : :
398 : 1 : g_test_add ("/memory-monitor-psi-env-set/write-env-null", SetupData, NULL,
399 : : tests_setup, test_write_env_null, tests_teardown);
400 : :
401 : 1 : g_test_add ("/memory-monitor-psi-env-set/write-env-unix-domain-socket", SetupData, NULL,
402 : : tests_unix_domain_setup, test_write_env_unix_domain_socket, tests_unix_domain_teardown);
403 : :
404 : 1 : return g_test_run ();
405 : : }
|