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 <glib.h>
29 #include <locale.h>
30 #include <string.h>
31 #include <fcntl.h>
32 #include <glib/gstdio.h>
33
34 #ifdef G_OS_UNIX
35 #include <glib-unix.h>
36 #include <sys/types.h>
37 #include <sys/socket.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #endif
41
42 #ifdef G_OS_WIN32
43 #include <winsock2.h>
44 #include <io.h>
45 #define LINEEND "\r\n"
46 #else
47 #define LINEEND "\n"
48 #endif
49
50 /* MinGW builds are likely done using a BASH-style shell, so run the
51 * normal script there, as on non-Windows builds, as it is more likely
52 * that one will run 'make check' in such shells to test the code
53 */
54 #if defined (G_OS_WIN32) && defined (_MSC_VER)
55 #define SCRIPT_EXT ".bat"
56 #else
57 #define SCRIPT_EXT
58 #endif
59
60 static char *echo_prog_path;
61 static char *echo_script_path;
62
63 typedef struct {
64 GMainLoop *loop;
65 gboolean child_exited;
66 gboolean stdout_done;
67 GString *stdout_buf;
68 } SpawnAsyncMultithreadedData;
69
70 static gboolean
71 on_child_exited (GPid pid,
72 gint status,
73 gpointer datap)
74 {
75 SpawnAsyncMultithreadedData *data = datap;
76
77 data->child_exited = TRUE;
78 if (data->child_exited && data->stdout_done)
79 g_main_loop_quit (data->loop);
80
81 return G_SOURCE_REMOVE;
82 }
83
84 static gboolean
85 on_child_stdout (GIOChannel *channel,
86 GIOCondition condition,
87 gpointer datap)
88 {
89 char buf[1024];
90 GError *error = NULL;
91 gsize bytes_read;
92 SpawnAsyncMultithreadedData *data = datap;
93
94 if (condition & G_IO_IN)
95 {
96 GIOStatus status;
97 status = g_io_channel_read_chars (channel, buf, sizeof (buf), &bytes_read, &error);
98 g_assert_no_error (error);
99 g_string_append_len (data->stdout_buf, buf, (gssize) bytes_read);
100 if (status == G_IO_STATUS_EOF)
101 data->stdout_done = TRUE;
102 }
103 if (condition & G_IO_HUP)
104 data->stdout_done = TRUE;
105 if (condition & G_IO_ERR)
106 g_error ("Error reading from child stdin");
107
108 if (data->child_exited && data->stdout_done)
109 g_main_loop_quit (data->loop);
110
111 return !data->stdout_done;
112 }
113
114 static void
115 test_spawn_async (void)
116 {
117 int tnum = 1;
118 GError *error = NULL;
119 GPtrArray *argv;
120 char *arg;
121 GPid pid;
122 GMainContext *context;
123 GMainLoop *loop;
124 GIOChannel *channel;
125 GSource *source;
126 int child_stdout_fd;
127 SpawnAsyncMultithreadedData data;
128
129 context = g_main_context_new ();
130 loop = g_main_loop_new (context, TRUE);
131
132 arg = g_strdup_printf ("thread %d", tnum);
133
134 argv = g_ptr_array_new ();
135 g_ptr_array_add (argv, echo_prog_path);
136 g_ptr_array_add (argv, arg);
137 g_ptr_array_add (argv, NULL);
138
139 g_spawn_async_with_pipes (NULL, (char**)argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL,
140 &child_stdout_fd, NULL, &error);
141 g_assert_no_error (error);
142 g_ptr_array_free (argv, TRUE);
143
144 data.loop = loop;
145 data.stdout_done = FALSE;
146 data.child_exited = FALSE;
147 data.stdout_buf = g_string_new (0);
148
149 source = g_child_watch_source_new (pid);
150 g_source_set_callback (source, (GSourceFunc)on_child_exited, &data, NULL);
151 g_source_attach (source, context);
152 g_source_unref (source);
153
154 channel = g_io_channel_unix_new (child_stdout_fd);
155 source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR);
156 g_source_set_callback (source, (GSourceFunc)on_child_stdout, &data, NULL);
157 g_source_attach (source, context);
158 g_source_unref (source);
159
160 g_main_loop_run (loop);
161
162 g_assert (data.child_exited);
163 g_assert (data.stdout_done);
164 g_assert_cmpstr (data.stdout_buf->str, ==, arg);
165 g_string_free (data.stdout_buf, TRUE);
166
167 g_io_channel_unref (channel);
168 g_main_context_unref (context);
169 g_main_loop_unref (loop);
170
171 g_free (arg);
172 }
173
174 /* Windows close() causes failure through the Invalid Parameter Handler
175 * Routine if the file descriptor does not exist.
176 */
177 static void
178 safe_close (int fd)
179 {
180 if (fd >= 0)
181 close (fd);
182 }
183
184 /* Test g_spawn_async_with_fds() with a variety of different inputs */
185 static void
186 test_spawn_async_with_fds (void)
187 {
188 int tnum = 1;
189 GPtrArray *argv;
190 char *arg;
191 gsize i;
192
193 /* Each test has 3 variable parameters: stdin, stdout, stderr */
194 enum fd_type {
195 NO_FD, /* pass fd -1 (unset) */
196 FD_NEGATIVE, /* pass fd of negative value (equivalent to unset) */
197 PIPE, /* pass fd of new/unique pipe */
198 STDOUT_PIPE, /* pass the same pipe as stdout */
199 } tests[][3] = {
200 { NO_FD, NO_FD, NO_FD }, /* Test with no fds passed */
201 { NO_FD, FD_NEGATIVE, NO_FD }, /* Test another negative fd value */
202 { PIPE, PIPE, PIPE }, /* Test with unique fds passed */
203 { NO_FD, PIPE, STDOUT_PIPE }, /* Test the same fd for stdout + stderr */
204 };
205
206 arg = g_strdup_printf ("# thread %d\n", tnum);
207
208 argv = g_ptr_array_new ();
209 g_ptr_array_add (argv, echo_prog_path);
210 g_ptr_array_add (argv, arg);
211 g_ptr_array_add (argv, NULL);
212
213 for (i = 0; i < G_N_ELEMENTS (tests); i++)
214 {
215 GError *error = NULL;
216 GPid pid;
217 GMainContext *context;
218 GMainLoop *loop;
219 GIOChannel *channel = NULL;
220 GSource *source;
221 SpawnAsyncMultithreadedData data;
222 enum fd_type *fd_info = tests[i];
223 gint test_pipe[3][2];
224 int j;
225
226 for (j = 0; j < 3; j++)
227 {
228 switch (fd_info[j])
229 {
230 case NO_FD:
231 test_pipe[j][0] = -1;
232 test_pipe[j][1] = -1;
233 break;
234 case FD_NEGATIVE:
235 test_pipe[j][0] = -5;
236 test_pipe[j][1] = -5;
237 break;
238 case PIPE:
239 #ifdef G_OS_UNIX
240 g_unix_open_pipe (test_pipe[j], O_CLOEXEC, &error);
241 g_assert_no_error (error);
242 #else
243 g_assert_cmpint (_pipe (test_pipe[j], 4096, _O_BINARY), >=, 0);
244 #endif
245 break;
246 case STDOUT_PIPE:
247 g_assert_cmpint (j, ==, 2); /* only works for stderr */
248 test_pipe[j][0] = test_pipe[1][0];
249 test_pipe[j][1] = test_pipe[1][1];
250 break;
251 default:
252 g_assert_not_reached ();
253 }
254 }
255
256 context = g_main_context_new ();
257 loop = g_main_loop_new (context, TRUE);
258
259 g_spawn_async_with_fds (NULL, (char**)argv->pdata, NULL,
260 G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid,
261 test_pipe[0][0], test_pipe[1][1], test_pipe[2][1],
262 &error);
263 g_assert_no_error (error);
264 safe_close (test_pipe[0][0]);
265 safe_close (test_pipe[1][1]);
266 if (fd_info[2] != STDOUT_PIPE)
267 safe_close (test_pipe[2][1]);
268
269 data.loop = loop;
270 data.stdout_done = FALSE;
271 data.child_exited = FALSE;
272 data.stdout_buf = g_string_new (0);
273
274 source = g_child_watch_source_new (pid);
275 g_source_set_callback (source, (GSourceFunc)on_child_exited, &data, NULL);
276 g_source_attach (source, context);
277 g_source_unref (source);
278
279 if (test_pipe[1][0] >= 0)
280 {
281 channel = g_io_channel_unix_new (test_pipe[1][0]);
282 source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR);
283 g_source_set_callback (source, (GSourceFunc)on_child_stdout,
284 &data, NULL);
285 g_source_attach (source, context);
286 g_source_unref (source);
287 }
288 else
289 {
290 /* Don't check stdout data if we didn't pass a fd */
291 data.stdout_done = TRUE;
292 }
293
294 g_main_loop_run (loop);
295
296 g_assert_true (data.child_exited);
297
298 if (test_pipe[1][0] >= 0)
299 {
300 gchar *tmp = g_strdup_printf ("# thread %d" LINEEND, tnum);
301 /* Check for echo on stdout */
302 g_assert_true (data.stdout_done);
303 g_assert_cmpstr (data.stdout_buf->str, ==, tmp);
304 g_io_channel_unref (channel);
305 g_free (tmp);
306 }
307 g_string_free (data.stdout_buf, TRUE);
308
309 g_main_context_unref (context);
310 g_main_loop_unref (loop);
311 safe_close (test_pipe[0][1]);
312 safe_close (test_pipe[1][0]);
313 if (fd_info[2] != STDOUT_PIPE)
314 safe_close (test_pipe[2][0]);
315 }
316
317 g_ptr_array_free (argv, TRUE);
318 g_free (arg);
319 }
320
321 static void
322 test_spawn_async_with_invalid_fds (void)
323 {
324 const gchar *argv[] = { echo_prog_path, "thread 0", NULL };
325 gint source_fds[1000];
326 GError *local_error = NULL;
327 gboolean retval;
328 gsize i;
329
330 /* Create an identity mapping from [0, …, 999]. This is very likely going to
331 * conflict with the internal FDs, as it covers a lot of the FD space
332 * (including stdin, stdout and stderr, though we don’t care about them in
333 * this test).
334 *
335 * Skip the test if we somehow avoid a collision. */
336 for (i = 0; i < G_N_ELEMENTS (source_fds); i++)
337 source_fds[i] = i;
338
339 retval = g_spawn_async_with_pipes_and_fds (NULL, argv, NULL, G_SPAWN_DEFAULT,
340 NULL, NULL, -1, -1, -1,
341 source_fds, source_fds, G_N_ELEMENTS (source_fds),
342 NULL, NULL, NULL, NULL,
343 &local_error);
344 if (retval)
345 {
346 g_test_skip ("Skipping internal FDs check as test didn’t manage to trigger a collision");
347 return;
348 }
349 g_assert_false (retval);
350 g_assert_error (local_error, G_SPAWN_ERROR, G_SPAWN_ERROR_INVAL);
351 g_error_free (local_error);
352 }
353
354 static void
355 test_spawn_sync (void)
356 {
357 int tnum = 1;
358 GError *error = NULL;
359 char *arg = g_strdup_printf ("thread %d", tnum);
360 /* Include arguments with special symbols to test that they are correctly passed to child.
361 * This is tested on all platforms, but the most prone to failure is win32,
362 * where args are specially escaped during spawning.
363 */
364 const char * const argv[] = {
365 echo_prog_path,
366 arg,
367 "doublequotes\\\"after\\\\\"\"backslashes", /* this would be special escaped on win32 */
368 "\\\"\"doublequotes spaced after backslashes\\\\\"", /* this would be special escaped on win32 */
369 "even$$dollars",
370 "even%%percents",
371 "even\"\"doublequotes",
372 "even''singlequotes",
373 "even\\\\backslashes",
374 "even//slashes",
375 "$odd spaced$dollars$",
376 "%odd spaced%spercents%",
377 "\"odd spaced\"doublequotes\"",
378 "'odd spaced'singlequotes'",
379 "\\odd spaced\\backslashes\\", /* this wasn't handled correctly on win32 in glib <=2.58 */
380 "/odd spaced/slashes/",
381 NULL
382 };
383 char *joined_args_str = g_strjoinv ("", (char**)argv + 1);
384 char *stdout_str;
385 int estatus;
386
387 g_spawn_sync (NULL, (char**)argv, NULL, 0, NULL, NULL, &stdout_str, NULL, &estatus, &error);
388 g_assert_no_error (error);
389 g_assert_cmpstr (joined_args_str, ==, stdout_str);
390 g_free (arg);
391 g_free (stdout_str);
392 g_free (joined_args_str);
393 }
394
395 static void
396 init_networking (void)
397 {
398 #ifdef G_OS_WIN32
399 WSADATA wsadata;
400
401 if (WSAStartup (MAKEWORD (2, 0), &wsadata) != 0)
402 g_error ("Windows Sockets could not be initialized");
403 #endif
404 }
405
406 static void
407 test_spawn_stderr_socket (void)
408 {
409 GError *error = NULL;
410 GPtrArray *argv;
411 int estatus;
412 int fd;
413
414 g_test_summary ("Test calling g_spawn_sync() with its stderr FD set to a socket");
415
416 if (g_test_subprocess ())
417 {
418 init_networking ();
419 fd = socket (AF_INET, SOCK_STREAM, 0);
420 g_assert_cmpint (fd, >=, 0);
421 #ifdef G_OS_WIN32
422 fd = _open_osfhandle (fd, 0);
423 g_assert_cmpint (fd, >=, 0);
424 #endif
425 /* Set the socket as FD 2, stderr */
426 estatus = dup2 (fd, 2);
427 g_assert_cmpint (estatus, >=, 0);
428
429 argv = g_ptr_array_new ();
430 g_ptr_array_add (argv, echo_script_path);
431 g_ptr_array_add (argv, NULL);
432
433 g_spawn_sync (NULL, (char**) argv->pdata, NULL, 0, NULL, NULL, NULL, NULL, NULL, &error);
434 g_assert_no_error (error);
435 g_ptr_array_free (argv, TRUE);
436 g_close (fd, &error);
437 g_assert_no_error (error);
438 return;
439 }
440
441 g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
442 g_test_trap_assert_passed ();
443 }
444
445 /* Like test_spawn_sync but uses spawn flags that trigger the optimized
446 * posix_spawn codepath.
447 */
448 static void
449 test_posix_spawn (void)
450 {
451 int tnum = 1;
452 GError *error = NULL;
453 GPtrArray *argv;
454 char *arg;
455 char *stdout_str;
456 int estatus;
457 GSpawnFlags flags = G_SPAWN_CLOEXEC_PIPES | G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
458
459 arg = g_strdup_printf ("thread %d", tnum);
460
461 argv = g_ptr_array_new ();
462 g_ptr_array_add (argv, echo_prog_path);
463 g_ptr_array_add (argv, arg);
464 g_ptr_array_add (argv, NULL);
465
466 g_spawn_sync (NULL, (char**)argv->pdata, NULL, flags, NULL, NULL, &stdout_str, NULL, &estatus, &error);
467 g_assert_no_error (error);
468 g_assert_cmpstr (arg, ==, stdout_str);
469 g_free (arg);
470 g_free (stdout_str);
471 g_ptr_array_free (argv, TRUE);
472 }
473
474 static void
475 test_spawn_script (void)
476 {
477 GError *error = NULL;
478 GPtrArray *argv;
479 char *stdout_str;
480 int estatus;
481
482 argv = g_ptr_array_new ();
483 g_ptr_array_add (argv, echo_script_path);
484 g_ptr_array_add (argv, NULL);
485
486 g_spawn_sync (NULL, (char**)argv->pdata, NULL, 0, NULL, NULL, &stdout_str, NULL, &estatus, &error);
487 g_assert_no_error (error);
488 g_assert_cmpstr ("echo" LINEEND, ==, stdout_str);
489 g_free (stdout_str);
490 g_ptr_array_free (argv, TRUE);
491 }
492
493 /* Test that spawning a non-existent executable returns %G_SPAWN_ERROR_NOENT. */
494 static void
495 test_spawn_nonexistent (void)
496 {
497 GError *error = NULL;
498 GPtrArray *argv = NULL;
499 gchar *stdout_str = NULL;
500 gint wait_status = -1;
501
502 argv = g_ptr_array_new ();
503 g_ptr_array_add (argv, "this does not exist");
504 g_ptr_array_add (argv, NULL);
505
506 g_spawn_sync (NULL, (char**) argv->pdata, NULL, 0, NULL, NULL, &stdout_str,
507 NULL, &wait_status, &error);
508 g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT);
509 g_assert_null (stdout_str);
510 g_assert_cmpint (wait_status, ==, -1);
511
512 g_ptr_array_free (argv, TRUE);
513
514 g_clear_error (&error);
515 }
516
517 /* Test that FD assignments in a spawned process don’t overwrite and break the
518 * child_err_report_fd which is used to report error information back from the
519 * intermediate child process to the parent.
520 *
521 * https://gitlab.gnome.org/GNOME/glib/-/issues/2097 */
522 static void
523 test_spawn_fd_assignment_clash (void)
524 {
525 int tmp_fd;
526 guint i;
527 #define N_FDS 10
528 gint source_fds[N_FDS];
529 gint target_fds[N_FDS];
530 const gchar *argv[] = { "/nonexistent", NULL };
531 gboolean retval;
532 GError *local_error = NULL;
533 struct stat statbuf;
534
535 /* Open a temporary file and duplicate its FD several times so we have several
536 * FDs to remap in the child process. */
537 tmp_fd = g_file_open_tmp ("glib-spawn-test-XXXXXX", NULL, NULL);
538 g_assert_cmpint (tmp_fd, >=, 0);
539
540 for (i = 0; i < (N_FDS - 1); ++i)
541 {
542 int source;
543 #ifdef F_DUPFD_CLOEXEC
544 source = fcntl (tmp_fd, F_DUPFD_CLOEXEC, 3);
545 #else
546 source = dup (tmp_fd);
547 #endif
548 g_assert_cmpint (source, >=, 0);
549 source_fds[i] = source;
550 target_fds[i] = source + N_FDS;
551 }
552
553 source_fds[i] = tmp_fd;
554 target_fds[i] = tmp_fd + N_FDS;
555
556 /* Print out the FD map. */
557 g_test_message ("FD map:");
558 for (i = 0; i < N_FDS; i++)
559 g_test_message (" • %d → %d", source_fds[i], target_fds[i]);
560
561 /* Spawn the subprocess. This should fail because the executable doesn’t
562 * exist. */
563 retval = g_spawn_async_with_pipes_and_fds (NULL, argv, NULL, G_SPAWN_DEFAULT,
564 NULL, NULL, -1, -1, -1,
565 source_fds, target_fds, N_FDS,
566 NULL, NULL, NULL, NULL,
567 &local_error);
568 g_assert_error (local_error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT);
569 g_assert_false (retval);
570
571 g_clear_error (&local_error);
572
573 /* Check nothing was written to the temporary file, as would happen if the FD
574 * mapping was messed up to conflict with the child process error reporting FD.
575 * See https://gitlab.gnome.org/GNOME/glib/-/issues/2097 */
576 g_assert_no_errno (fstat (tmp_fd, &statbuf));
577 g_assert_cmpuint (statbuf.st_size, ==, 0);
578
579 /* Clean up. */
580 for (i = 0; i < N_FDS; i++)
581 g_close (source_fds[i], NULL);
582 }
583
584 int
585 main (int argc,
586 char *argv[])
587 {
588 char *dirname;
589 int ret;
590
591 setlocale (LC_ALL, "");
592
593 g_test_init (&argc, &argv, NULL);
594
595 dirname = g_path_get_dirname (argv[0]);
596 echo_prog_path = g_build_filename (dirname, "test-spawn-echo" EXEEXT, NULL);
597 echo_script_path = g_build_filename (dirname, "echo-script" SCRIPT_EXT, NULL);
598 if (!g_file_test (echo_script_path, G_FILE_TEST_EXISTS))
599 {
600 g_free (echo_script_path);
601 echo_script_path = g_test_build_filename (G_TEST_DIST, "echo-script" SCRIPT_EXT, NULL);
602 }
603 g_free (dirname);
604
605 g_assert (g_file_test (echo_prog_path, G_FILE_TEST_EXISTS));
606 g_assert (g_file_test (echo_script_path, G_FILE_TEST_EXISTS));
607
608 g_test_add_func ("/gthread/spawn-single-sync", test_spawn_sync);
609 g_test_add_func ("/gthread/spawn-stderr-socket", test_spawn_stderr_socket);
610 g_test_add_func ("/gthread/spawn-single-async", test_spawn_async);
611 g_test_add_func ("/gthread/spawn-single-async-with-fds", test_spawn_async_with_fds);
612 g_test_add_func ("/gthread/spawn-async-with-invalid-fds", test_spawn_async_with_invalid_fds);
613 g_test_add_func ("/gthread/spawn-script", test_spawn_script);
614 g_test_add_func ("/gthread/spawn/nonexistent", test_spawn_nonexistent);
615 g_test_add_func ("/gthread/spawn-posix-spawn", test_posix_spawn);
616 g_test_add_func ("/gthread/spawn/fd-assignment-clash", test_spawn_fd_assignment_clash);
617
618 ret = g_test_run();
619
620 g_free (echo_script_path);
621 g_free (echo_prog_path);
622
623 return ret;
624 }