1 /*
2 * Copyright (C) 2011 Red Hat, Inc.
3 *
4 * SPDX-License-Identifier: LicenseRef-old-glib-tests
5 *
6 * This work is provided "as is"; redistribution and modification
7 * in whole or in part, in any medium, physical or electronic is
8 * permitted without restriction.
9 *
10 * This work is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 *
14 * In no event shall the authors or contributors be liable for any
15 * direct, indirect, incidental, special, exemplary, or consequential
16 * damages (including, but not limited to, procurement of substitute
17 * goods or services; loss of use, data, or profits; or business
18 * interruption) however caused and on any theory of liability, whether
19 * in contract, strict liability, or tort (including negligence or
20 * otherwise) arising in any way out of the use of this software, even
21 * if advised of the possibility of such damage.
22 *
23 * Author: Colin Walters <walters@verbum.org>
24 */
25
26 #include "config.h"
27
28 #include <stdlib.h>
29
30 #include <glib.h>
31 #include <string.h>
32
33 #include <sys/types.h>
34
35 static char *echo_prog_path;
36
37 #ifdef G_OS_WIN32
38 static char *sleep_prog_path;
39 #endif
40
41 #ifdef G_OS_UNIX
42 #include <unistd.h>
43 #endif
44
45 #ifdef G_OS_WIN32
46 #include <windows.h>
47 #endif
48
49 typedef struct
50 {
51 GMainLoop *main_loop;
52 gint *n_alive; /* (atomic) */
53 gint ttl; /* seconds */
54 GMainLoop *thread_main_loop; /* (nullable) */
55 } SpawnChildsData;
56
57 static GPid
58 get_a_child (gint ttl)
59 {
60 GPid pid;
61
62 #ifdef G_OS_WIN32
63 STARTUPINFO si;
64 PROCESS_INFORMATION pi;
65 gchar *cmdline;
66 wchar_t *cmdline_utf16;
67
68 memset (&si, 0, sizeof (si));
69 si.cb = sizeof (&si);
70 memset (&pi, 0, sizeof (pi));
71
72 cmdline = g_strdup_printf ("%s %d", sleep_prog_path, ttl);
73
74 cmdline_utf16 = g_utf8_to_utf16 (cmdline, -1, NULL, NULL, NULL);
75 g_assert_nonnull (cmdline_utf16);
76
77 if (!CreateProcess (NULL, cmdline_utf16, NULL, NULL,
78 FALSE, 0, NULL, NULL, &si, &pi))
79 g_error ("CreateProcess failed: %s",
80 g_win32_error_message (GetLastError ()));
81
82 g_free (cmdline_utf16);
83 g_free (cmdline);
84
85 CloseHandle (pi.hThread);
86 pid = pi.hProcess;
87
88 return pid;
89 #else
90 pid = fork ();
91 if (pid < 0)
92 exit (1);
93
94 if (pid > 0)
95 return pid;
96
97 sleep (ttl);
98 _exit (0);
99 #endif /* G_OS_WIN32 */
100 }
101
102 static void
103 child_watch_callback (GPid pid, gint status, gpointer user_data)
104 {
105 SpawnChildsData *data = user_data;
106
107 g_test_message ("Child %" G_PID_FORMAT " (ttl %d) exited, status %d",
108 pid, data->ttl, status);
109
110 g_spawn_close_pid (pid);
111
112 if (g_atomic_int_dec_and_test (data->n_alive))
113 g_main_loop_quit (data->main_loop);
114 if (data->thread_main_loop != NULL)
115 g_main_loop_quit (data->thread_main_loop);
116 }
117
118 static gpointer
119 start_thread (gpointer user_data)
120 {
121 GMainLoop *new_main_loop;
122 GSource *source;
123 GPid pid;
124 SpawnChildsData *data = user_data;
125 gint ttl = data->ttl;
126 GMainContext *new_main_context = NULL;
127
128 new_main_context = g_main_context_new ();
129 new_main_loop = g_main_loop_new (new_main_context, FALSE);
130 data->thread_main_loop = new_main_loop;
131
132 pid = get_a_child (ttl);
133 source = g_child_watch_source_new (pid);
134 g_source_set_callback (source,
135 (GSourceFunc) child_watch_callback, data, NULL);
136 g_source_attach (source, g_main_loop_get_context (new_main_loop));
137 g_source_unref (source);
138
139 g_test_message ("Created pid: %" G_PID_FORMAT " (ttl %d)", pid, ttl);
140
141 g_main_loop_run (new_main_loop);
142 g_main_loop_unref (new_main_loop);
143 g_main_context_unref (new_main_context);
144
145 return NULL;
146 }
147
148 static gboolean
149 quit_loop (gpointer data)
150 {
151 GMainLoop *main_loop = data;
152
153 g_main_loop_quit (main_loop);
154
155 return TRUE;
156 }
157
158 static void
159 test_spawn_childs (void)
160 {
161 GPid pid;
162 GMainLoop *main_loop = NULL;
163 SpawnChildsData child1_data = { 0, }, child2_data = { 0, };
164 gint n_alive;
165 guint timeout_id;
166
167 main_loop = g_main_loop_new (NULL, FALSE);
168
169 #ifdef G_OS_WIN32
170 g_assert_no_errno (system ("cd ."));
171 #else
172 g_assert_no_errno (system ("true"));
173 #endif
174
175 n_alive = 2;
176 timeout_id = g_timeout_add_seconds (30, quit_loop, main_loop);
177
178 child1_data.main_loop = main_loop;
179 child1_data.ttl = 1;
180 child1_data.n_alive = &n_alive;
181 pid = get_a_child (child1_data.ttl);
182 g_child_watch_add (pid,
183 (GChildWatchFunc) child_watch_callback,
184 &child1_data);
185
186 child2_data.main_loop = main_loop;
187 child2_data.ttl = 2;
188 child2_data.n_alive = &n_alive;
189 pid = get_a_child (child2_data.ttl);
190 g_child_watch_add (pid,
191 (GChildWatchFunc) child_watch_callback,
192 &child2_data);
193
194 g_main_loop_run (main_loop);
195 g_main_loop_unref (main_loop);
196 g_source_remove (timeout_id);
197
198 g_assert_cmpint (g_atomic_int_get (&n_alive), ==, 0);
199 }
200
201 static void
202 test_spawn_childs_threads (void)
203 {
204 GMainLoop *main_loop = NULL;
205 SpawnChildsData thread1_data = { 0, }, thread2_data = { 0, };
206 gint n_alive;
207 guint timeout_id;
208 GThread *thread1, *thread2;
209
210 main_loop = g_main_loop_new (NULL, FALSE);
211
212 #ifdef G_OS_WIN32
213 g_assert_no_errno (system ("cd ."));
214 #else
215 g_assert_no_errno (system ("true"));
216 #endif
217
218 n_alive = 2;
219 timeout_id = g_timeout_add_seconds (30, quit_loop, main_loop);
220
221 thread1_data.main_loop = main_loop;
222 thread1_data.n_alive = &n_alive;
223 thread1_data.ttl = 1; /* seconds */
224 thread1 = g_thread_new (NULL, start_thread, &thread1_data);
225
226 thread2_data.main_loop = main_loop;
227 thread2_data.n_alive = &n_alive;
228 thread2_data.ttl = 2; /* seconds */
229 thread2 = g_thread_new (NULL, start_thread, &thread2_data);
230
231 g_main_loop_run (main_loop);
232 g_main_loop_unref (main_loop);
233 g_source_remove (timeout_id);
234
235 g_assert_cmpint (g_atomic_int_get (&n_alive), ==, 0);
236
237 g_thread_join (g_steal_pointer (&thread2));
238 g_thread_join (g_steal_pointer (&thread1));
239 }
240
241 static void
242 multithreaded_test_run (GThreadFunc function)
243 {
244 guint i;
245 GPtrArray *threads = g_ptr_array_new ();
246 guint n_threads;
247
248 /* Limit to 64, otherwise we may hit file descriptor limits and such */
249 n_threads = MIN (g_get_num_processors () * 2, 64);
250
251 for (i = 0; i < n_threads; i++)
252 {
253 GThread *thread;
254
255 thread = g_thread_new ("test", function, GUINT_TO_POINTER (i));
256 g_ptr_array_add (threads, thread);
257 }
258
259 for (i = 0; i < n_threads; i++)
260 {
261 gpointer ret;
262 ret = g_thread_join (g_ptr_array_index (threads, i));
263 g_assert_cmpint (GPOINTER_TO_UINT (ret), ==, i);
264 }
265 g_ptr_array_free (threads, TRUE);
266 }
267
268 static gpointer
269 test_spawn_sync_multithreaded_instance (gpointer data)
270 {
271 guint tnum = GPOINTER_TO_UINT (data);
272 GError *error = NULL;
273 GPtrArray *argv;
274 char *arg;
275 char *stdout_str;
276 int estatus;
277
278 arg = g_strdup_printf ("thread %u", tnum);
279
280 argv = g_ptr_array_new ();
281 g_ptr_array_add (argv, echo_prog_path);
282 g_ptr_array_add (argv, arg);
283 g_ptr_array_add (argv, NULL);
284
285 g_spawn_sync (NULL, (char**)argv->pdata, NULL, G_SPAWN_DEFAULT, NULL, NULL, &stdout_str, NULL, &estatus, &error);
286 g_assert_no_error (error);
287 g_assert_cmpstr (arg, ==, stdout_str);
288 g_free (arg);
289 g_free (stdout_str);
290 g_ptr_array_free (argv, TRUE);
291
292 return GUINT_TO_POINTER (tnum);
293 }
294
295 static void
296 test_spawn_sync_multithreaded (void)
297 {
298 multithreaded_test_run (test_spawn_sync_multithreaded_instance);
299 }
300
301 typedef struct {
302 GMainLoop *loop;
303 gboolean child_exited;
304 gboolean stdout_done;
305 GString *stdout_buf;
306 } SpawnAsyncMultithreadedData;
307
308 static gboolean
309 on_child_exited (GPid pid,
310 gint status,
311 gpointer datap)
312 {
313 SpawnAsyncMultithreadedData *data = datap;
314
315 data->child_exited = TRUE;
316 if (data->child_exited && data->stdout_done)
317 g_main_loop_quit (data->loop);
318
319 return G_SOURCE_REMOVE;
320 }
321
322 static gboolean
323 on_child_stdout (GIOChannel *channel,
324 GIOCondition condition,
325 gpointer datap)
326 {
327 char buf[1024];
328 GError *error = NULL;
329 gsize bytes_read;
330 GIOStatus status;
331 SpawnAsyncMultithreadedData *data = datap;
332
333 read:
334 status = g_io_channel_read_chars (channel, buf, sizeof (buf), &bytes_read, &error);
335 if (status == G_IO_STATUS_NORMAL)
336 {
337 g_string_append_len (data->stdout_buf, buf, (gssize) bytes_read);
338 if (bytes_read == sizeof (buf))
339 goto read;
340 }
341 else if (status == G_IO_STATUS_EOF)
342 {
343 g_string_append_len (data->stdout_buf, buf, (gssize) bytes_read);
344 data->stdout_done = TRUE;
345 }
346 else if (status == G_IO_STATUS_ERROR)
347 {
348 g_error ("Error reading from child stdin: %s", error->message);
349 }
350
351 if (data->child_exited && data->stdout_done)
352 g_main_loop_quit (data->loop);
353
354 return !data->stdout_done;
355 }
356
357 static gpointer
358 test_spawn_async_multithreaded_instance (gpointer thread_data)
359 {
360 guint tnum = GPOINTER_TO_UINT (thread_data);
361 GError *error = NULL;
362 GPtrArray *argv;
363 char *arg;
364 GPid pid;
365 GMainContext *context;
366 GMainLoop *loop;
367 GIOChannel *channel;
368 GSource *source;
369 int child_stdout_fd;
370 SpawnAsyncMultithreadedData data;
371
372 context = g_main_context_new ();
373 loop = g_main_loop_new (context, TRUE);
374
375 arg = g_strdup_printf ("thread %u", tnum);
376
377 argv = g_ptr_array_new ();
378 g_ptr_array_add (argv, echo_prog_path);
379 g_ptr_array_add (argv, arg);
380 g_ptr_array_add (argv, NULL);
381
382 g_spawn_async_with_pipes (NULL, (char**)argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL,
383 &child_stdout_fd, NULL, &error);
384 g_assert_no_error (error);
385 g_ptr_array_free (argv, TRUE);
386
387 data.loop = loop;
388 data.stdout_done = FALSE;
389 data.child_exited = FALSE;
390 data.stdout_buf = g_string_new (0);
391
392 source = g_child_watch_source_new (pid);
393 g_source_set_callback (source, (GSourceFunc)on_child_exited, &data, NULL);
394 g_source_attach (source, context);
395 g_source_unref (source);
396
397 channel = g_io_channel_unix_new (child_stdout_fd);
398 source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP);
399 g_source_set_callback (source, (GSourceFunc)on_child_stdout, &data, NULL);
400 g_source_attach (source, context);
401 g_source_unref (source);
402
403 g_main_loop_run (loop);
404
405 g_assert_true (data.child_exited);
406 g_assert_true (data.stdout_done);
407 g_assert_cmpstr (data.stdout_buf->str, ==, arg);
408 g_string_free (data.stdout_buf, TRUE);
409
410 g_io_channel_unref (channel);
411 g_main_context_unref (context);
412 g_main_loop_unref (loop);
413
414 g_free (arg);
415
416 return GUINT_TO_POINTER (tnum);
417 }
418
419 static void
420 test_spawn_async_multithreaded (void)
421 {
422 multithreaded_test_run (test_spawn_async_multithreaded_instance);
423 }
424
425 int
426 main (int argc,
427 char *argv[])
428 {
429 char *dirname;
430 int ret;
431
432 g_test_init (&argc, &argv, NULL);
433
434 dirname = g_path_get_dirname (argv[0]);
435 echo_prog_path = g_build_filename (dirname, "test-spawn-echo" EXEEXT, NULL);
436
437 g_assert (g_file_test (echo_prog_path, G_FILE_TEST_EXISTS));
438 #ifdef G_OS_WIN32
439 sleep_prog_path = g_build_filename (dirname, "test-spawn-sleep" EXEEXT, NULL);
440 g_assert (g_file_test (sleep_prog_path, G_FILE_TEST_EXISTS));
441 #endif
442
443 g_clear_pointer (&dirname, g_free);
444
445 g_test_add_func ("/gthread/spawn-childs", test_spawn_childs);
446 g_test_add_func ("/gthread/spawn-childs-threads", test_spawn_childs_threads);
447 g_test_add_func ("/gthread/spawn-sync", test_spawn_sync_multithreaded);
448 g_test_add_func ("/gthread/spawn-async", test_spawn_async_multithreaded);
449
450 ret = g_test_run();
451
452 g_free (echo_prog_path);
453
454 #ifdef G_OS_WIN32
455 g_free (sleep_prog_path);
456 #endif
457
458 return ret;
459 }