1 /*
2  * Copyright (C) 2011 Red Hat, Inc.
3  *
4  * This work is provided "as is"; redistribution and modification
5  * in whole or in part, in any medium, physical or electronic is
6  * permitted without restriction.
7  *
8  * This work is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11  *
12  * In no event shall the authors or contributors be liable for any
13  * direct, indirect, incidental, special, exemplary, or consequential
14  * damages (including, but not limited to, procurement of substitute
15  * goods or services; loss of use, data, or profits; or business
16  * interruption) however caused and on any theory of liability, whether
17  * in contract, strict liability, or tort (including negligence or
18  * otherwise) arising in any way out of the use of this software, even
19  * if advised of the possibility of such damage.
20  *
21  * Author: Colin Walters <walters@verbum.org>
22  */
23 
24 #include "config.h"
25 
26 #include <glib.h>
27 #include <locale.h>
28 #include <string.h>
29 #include <fcntl.h>
30 
31 #ifdef G_OS_UNIX
32 #include <glib-unix.h>
33 #include <glib/gstdio.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <unistd.h>
37 #endif
38 
39 #ifdef G_OS_WIN32
40 #include <io.h>
41 #define LINEEND "\r\n"
42 #else
43 #define LINEEND "\n"
44 #endif
45 
46 /* MinGW builds are likely done using a BASH-style shell, so run the
47  * normal script there, as on non-Windows builds, as it is more likely
48  * that one will run 'make check' in such shells to test the code
49  */
50 #if defined (G_OS_WIN32) && defined (_MSC_VER)
51 #define SCRIPT_EXT ".bat"
52 #else
53 #define SCRIPT_EXT
54 #endif
55 
56 static char *echo_prog_path;
57 static char *echo_script_path;
58 
59 typedef struct {
60   GMainLoop *loop;
61   gboolean child_exited;
62   gboolean stdout_done;
63   GString *stdout_buf;
64 } SpawnAsyncMultithreadedData;
65 
66 static gboolean
on_child_exited(GPid pid,gint status,gpointer datap)67 on_child_exited (GPid     pid,
68 		 gint     status,
69 		 gpointer datap)
70 {
71   SpawnAsyncMultithreadedData *data = datap;
72 
73   data->child_exited = TRUE;
74   if (data->child_exited && data->stdout_done)
75     g_main_loop_quit (data->loop);
76 
77   return G_SOURCE_REMOVE;
78 }
79 
80 static gboolean
on_child_stdout(GIOChannel * channel,GIOCondition condition,gpointer datap)81 on_child_stdout (GIOChannel   *channel,
82 		 GIOCondition  condition,
83 		 gpointer      datap)
84 {
85   char buf[1024];
86   GError *error = NULL;
87   gsize bytes_read;
88   SpawnAsyncMultithreadedData *data = datap;
89 
90   if (condition & G_IO_IN)
91     {
92       GIOStatus status;
93       status = g_io_channel_read_chars (channel, buf, sizeof (buf), &bytes_read, &error);
94       g_assert_no_error (error);
95       g_string_append_len (data->stdout_buf, buf, (gssize) bytes_read);
96       if (status == G_IO_STATUS_EOF)
97 	data->stdout_done = TRUE;
98     }
99   if (condition & G_IO_HUP)
100     data->stdout_done = TRUE;
101   if (condition & G_IO_ERR)
102     g_error ("Error reading from child stdin");
103 
104   if (data->child_exited && data->stdout_done)
105     g_main_loop_quit (data->loop);
106 
107   return !data->stdout_done;
108 }
109 
110 static void
test_spawn_async(void)111 test_spawn_async (void)
112 {
113   int tnum = 1;
114   GError *error = NULL;
115   GPtrArray *argv;
116   char *arg;
117   GPid pid;
118   GMainContext *context;
119   GMainLoop *loop;
120   GIOChannel *channel;
121   GSource *source;
122   int child_stdout_fd;
123   SpawnAsyncMultithreadedData data;
124 
125   context = g_main_context_new ();
126   loop = g_main_loop_new (context, TRUE);
127 
128   arg = g_strdup_printf ("thread %d", tnum);
129 
130   argv = g_ptr_array_new ();
131   g_ptr_array_add (argv, echo_prog_path);
132   g_ptr_array_add (argv, arg);
133   g_ptr_array_add (argv, NULL);
134 
135   g_spawn_async_with_pipes (NULL, (char**)argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL,
136 			    &child_stdout_fd, NULL, &error);
137   g_assert_no_error (error);
138   g_ptr_array_free (argv, TRUE);
139 
140   data.loop = loop;
141   data.stdout_done = FALSE;
142   data.child_exited = FALSE;
143   data.stdout_buf = g_string_new (0);
144 
145   source = g_child_watch_source_new (pid);
146   g_source_set_callback (source, (GSourceFunc)on_child_exited, &data, NULL);
147   g_source_attach (source, context);
148   g_source_unref (source);
149 
150   channel = g_io_channel_unix_new (child_stdout_fd);
151   source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR);
152   g_source_set_callback (source, (GSourceFunc)on_child_stdout, &data, NULL);
153   g_source_attach (source, context);
154   g_source_unref (source);
155 
156   g_main_loop_run (loop);
157 
158   g_assert (data.child_exited);
159   g_assert (data.stdout_done);
160   g_assert_cmpstr (data.stdout_buf->str, ==, arg);
161   g_string_free (data.stdout_buf, TRUE);
162 
163   g_io_channel_unref (channel);
164   g_main_context_unref (context);
165   g_main_loop_unref (loop);
166 
167   g_free (arg);
168 }
169 
170 /* Windows close() causes failure through the Invalid Parameter Handler
171  * Routine if the file descriptor does not exist.
172  */
173 static void
safe_close(int fd)174 safe_close (int fd)
175 {
176   if (fd >= 0)
177     close (fd);
178 }
179 
180 /* Test g_spawn_async_with_fds() with a variety of different inputs */
181 static void
test_spawn_async_with_fds(void)182 test_spawn_async_with_fds (void)
183 {
184   int tnum = 1;
185   GPtrArray *argv;
186   char *arg;
187   gsize i;
188 
189   /* Each test has 3 variable parameters: stdin, stdout, stderr */
190   enum fd_type {
191     NO_FD,        /* pass fd -1 (unset) */
192     FD_NEGATIVE,  /* pass fd of negative value (equivalent to unset) */
193     PIPE,         /* pass fd of new/unique pipe */
194     STDOUT_PIPE,  /* pass the same pipe as stdout */
195   } tests[][3] = {
196     { NO_FD, NO_FD, NO_FD },       /* Test with no fds passed */
197     { NO_FD, FD_NEGATIVE, NO_FD }, /* Test another negative fd value */
198     { PIPE, PIPE, PIPE },          /* Test with unique fds passed */
199     { NO_FD, PIPE, STDOUT_PIPE },  /* Test the same fd for stdout + stderr */
200   };
201 
202   arg = g_strdup_printf ("thread %d", tnum);
203 
204   argv = g_ptr_array_new ();
205   g_ptr_array_add (argv, echo_prog_path);
206   g_ptr_array_add (argv, arg);
207   g_ptr_array_add (argv, NULL);
208 
209   for (i = 0; i < G_N_ELEMENTS (tests); i++)
210     {
211       GError *error = NULL;
212       GPid pid;
213       GMainContext *context;
214       GMainLoop *loop;
215       GIOChannel *channel = NULL;
216       GSource *source;
217       SpawnAsyncMultithreadedData data;
218       enum fd_type *fd_info = tests[i];
219       gint test_pipe[3][2];
220       int j;
221 
222       for (j = 0; j < 3; j++)
223         {
224           switch (fd_info[j])
225             {
226             case NO_FD:
227               test_pipe[j][0] = -1;
228               test_pipe[j][1] = -1;
229               break;
230             case FD_NEGATIVE:
231               test_pipe[j][0] = -5;
232               test_pipe[j][1] = -5;
233               break;
234             case PIPE:
235 #ifdef G_OS_UNIX
236               g_unix_open_pipe (test_pipe[j], FD_CLOEXEC, &error);
237               g_assert_no_error (error);
238 #else
239               g_assert_cmpint (_pipe (test_pipe[j], 4096, _O_BINARY), >=, 0);
240 #endif
241               break;
242             case STDOUT_PIPE:
243               g_assert_cmpint (j, ==, 2); /* only works for stderr */
244               test_pipe[j][0] = test_pipe[1][0];
245               test_pipe[j][1] = test_pipe[1][1];
246               break;
247             default:
248               g_assert_not_reached ();
249             }
250         }
251 
252       context = g_main_context_new ();
253       loop = g_main_loop_new (context, TRUE);
254 
255       g_spawn_async_with_fds (NULL, (char**)argv->pdata, NULL,
256 			      G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid,
257 			      test_pipe[0][0], test_pipe[1][1], test_pipe[2][1],
258 			      &error);
259       g_assert_no_error (error);
260       safe_close (test_pipe[0][0]);
261       safe_close (test_pipe[1][1]);
262       if (fd_info[2] != STDOUT_PIPE)
263         safe_close (test_pipe[2][1]);
264 
265       data.loop = loop;
266       data.stdout_done = FALSE;
267       data.child_exited = FALSE;
268       data.stdout_buf = g_string_new (0);
269 
270       source = g_child_watch_source_new (pid);
271       g_source_set_callback (source, (GSourceFunc)on_child_exited, &data, NULL);
272       g_source_attach (source, context);
273       g_source_unref (source);
274 
275       if (test_pipe[1][0] >= 0)
276         {
277           channel = g_io_channel_unix_new (test_pipe[1][0]);
278           source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR);
279           g_source_set_callback (source, (GSourceFunc)on_child_stdout,
280                                  &data, NULL);
281           g_source_attach (source, context);
282           g_source_unref (source);
283         }
284       else
285         {
286           /* Don't check stdout data if we didn't pass a fd */
287           data.stdout_done = TRUE;
288         }
289 
290       g_main_loop_run (loop);
291 
292       g_assert_true (data.child_exited);
293 
294       if (test_pipe[1][0] >= 0)
295         {
296           /* Check for echo on stdout */
297           g_assert_true (data.stdout_done);
298           g_assert_cmpstr (data.stdout_buf->str, ==, arg);
299           g_io_channel_unref (channel);
300         }
301       g_string_free (data.stdout_buf, TRUE);
302 
303       g_main_context_unref (context);
304       g_main_loop_unref (loop);
305       safe_close (test_pipe[0][1]);
306       safe_close (test_pipe[1][0]);
307       if (fd_info[2] != STDOUT_PIPE)
308         safe_close (test_pipe[2][0]);
309     }
310 
311   g_ptr_array_free (argv, TRUE);
312   g_free (arg);
313 }
314 
315 static void
test_spawn_sync(void)316 test_spawn_sync (void)
317 {
318   int tnum = 1;
319   GError *error = NULL;
320   char *arg = g_strdup_printf ("thread %d", tnum);
321   /* Include arguments with special symbols to test that they are correctly passed to child.
322    * This is tested on all platforms, but the most prone to failure is win32,
323    * where args are specially escaped during spawning.
324    */
325   const char * const argv[] = {
326     echo_prog_path,
327     arg,
328     "doublequotes\\\"after\\\\\"\"backslashes", /* this would be special escaped on win32 */
329     "\\\"\"doublequotes spaced after backslashes\\\\\"", /* this would be special escaped on win32 */
330     "even$$dollars",
331     "even%%percents",
332     "even\"\"doublequotes",
333     "even''singlequotes",
334     "even\\\\backslashes",
335     "even//slashes",
336     "$odd spaced$dollars$",
337     "%odd spaced%spercents%",
338     "\"odd spaced\"doublequotes\"",
339     "'odd spaced'singlequotes'",
340     "\\odd spaced\\backslashes\\", /* this wasn't handled correctly on win32 in glib <=2.58 */
341     "/odd spaced/slashes/",
342     NULL
343   };
344   char *joined_args_str = g_strjoinv ("", (char**)argv + 1);
345   char *stdout_str;
346   int estatus;
347 
348   g_spawn_sync (NULL, (char**)argv, NULL, 0, NULL, NULL, &stdout_str, NULL, &estatus, &error);
349   g_assert_no_error (error);
350   g_assert_cmpstr (joined_args_str, ==, stdout_str);
351   g_free (arg);
352   g_free (stdout_str);
353   g_free (joined_args_str);
354 }
355 
356 /* Like test_spawn_sync but uses spawn flags that trigger the optimized
357  * posix_spawn codepath.
358  */
359 static void
test_posix_spawn(void)360 test_posix_spawn (void)
361 {
362   int tnum = 1;
363   GError *error = NULL;
364   GPtrArray *argv;
365   char *arg;
366   char *stdout_str;
367   int estatus;
368   GSpawnFlags flags = G_SPAWN_CLOEXEC_PIPES | G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
369 
370   arg = g_strdup_printf ("thread %d", tnum);
371 
372   argv = g_ptr_array_new ();
373   g_ptr_array_add (argv, echo_prog_path);
374   g_ptr_array_add (argv, arg);
375   g_ptr_array_add (argv, NULL);
376 
377   g_spawn_sync (NULL, (char**)argv->pdata, NULL, flags, NULL, NULL, &stdout_str, NULL, &estatus, &error);
378   g_assert_no_error (error);
379   g_assert_cmpstr (arg, ==, stdout_str);
380   g_free (arg);
381   g_free (stdout_str);
382   g_ptr_array_free (argv, TRUE);
383 }
384 
385 static void
test_spawn_script(void)386 test_spawn_script (void)
387 {
388   GError *error = NULL;
389   GPtrArray *argv;
390   char *stdout_str;
391   int estatus;
392 
393   argv = g_ptr_array_new ();
394   g_ptr_array_add (argv, echo_script_path);
395   g_ptr_array_add (argv, NULL);
396 
397   g_spawn_sync (NULL, (char**)argv->pdata, NULL, 0, NULL, NULL, &stdout_str, NULL, &estatus, &error);
398   g_assert_no_error (error);
399   g_assert_cmpstr ("echo" LINEEND, ==, stdout_str);
400   g_free (stdout_str);
401   g_ptr_array_free (argv, TRUE);
402 }
403 
404 /* Test that spawning a non-existent executable returns %G_SPAWN_ERROR_NOENT. */
405 static void
test_spawn_nonexistent(void)406 test_spawn_nonexistent (void)
407 {
408   GError *error = NULL;
409   GPtrArray *argv = NULL;
410   gchar *stdout_str = NULL;
411   gint wait_status = -1;
412 
413   argv = g_ptr_array_new ();
414   g_ptr_array_add (argv, "this does not exist");
415   g_ptr_array_add (argv, NULL);
416 
417   g_spawn_sync (NULL, (char**) argv->pdata, NULL, 0, NULL, NULL, &stdout_str,
418                 NULL, &wait_status, &error);
419   g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT);
420   g_assert_null (stdout_str);
421   g_assert_cmpint (wait_status, ==, -1);
422 
423   g_ptr_array_free (argv, TRUE);
424 
425   g_clear_error (&error);
426 }
427 
428 /* Test that FD assignments in a spawned process don’t overwrite and break the
429  * child_err_report_fd which is used to report error information back from the
430  * intermediate child process to the parent.
431  *
432  * https://gitlab.gnome.org/GNOME/glib/-/issues/2097 */
433 static void
test_spawn_fd_assignment_clash(void)434 test_spawn_fd_assignment_clash (void)
435 {
436 #if defined(G_OS_UNIX) && defined(F_DUPFD_CLOEXEC)
437   int tmp_fd;
438   guint i;
439   const guint n_fds = 10;
440   gint source_fds[n_fds];
441   gint target_fds[n_fds];
442   const gchar *argv[] = { "/nonexistent", NULL };
443   gboolean retval;
444   GError *local_error = NULL;
445   struct stat statbuf;
446 
447   /* Open a temporary file and duplicate its FD several times so we have several
448    * FDs to remap in the child process. */
449   tmp_fd = g_file_open_tmp ("glib-spawn-test-XXXXXX", NULL, NULL);
450   g_assert_cmpint (tmp_fd, >=, 0);
451 
452   for (i = 0; i < (n_fds - 1); ++i)
453     {
454       int source = fcntl (tmp_fd, F_DUPFD_CLOEXEC, 3);
455       g_assert_cmpint (source, >=, 0);
456       source_fds[i] = source;
457       target_fds[i] = source + n_fds;
458     }
459 
460   source_fds[i] = tmp_fd;
461   target_fds[i] = tmp_fd + n_fds;
462 
463   /* Print out the FD map. */
464   g_test_message ("FD map:");
465   for (i = 0; i < n_fds; i++)
466     g_test_message (" • %d → %d", source_fds[i], target_fds[i]);
467 
468   /* Spawn the subprocess. This should fail because the executable doesn’t
469    * exist. */
470   retval = g_spawn_async_with_pipes_and_fds (NULL, argv, NULL, G_SPAWN_DEFAULT,
471                                              NULL, NULL, -1, -1, -1,
472                                              source_fds, target_fds, n_fds,
473                                              NULL, NULL, NULL, NULL,
474                                              &local_error);
475   g_assert_error (local_error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT);
476   g_assert_false (retval);
477 
478   g_clear_error (&local_error);
479 
480   /* Check nothing was written to the temporary file, as would happen if the FD
481    * mapping was messed up to conflict with the child process error reporting FD.
482    * See https://gitlab.gnome.org/GNOME/glib/-/issues/2097 */
483   g_assert_no_errno (fstat (tmp_fd, &statbuf));
484   g_assert_cmpuint (statbuf.st_size, ==, 0);
485 
486   /* Clean up. */
487   for (i = 0; i < n_fds; i++)
488     g_close (source_fds[i], NULL);
489 #else  /* !G_OS_UNIX */
490   g_test_skip ("FD redirection only supported on Unix with F_DUPFD_CLOEXEC");
491 #endif  /* !G_OS_UNIX */
492 }
493 
494 int
main(int argc,char * argv[])495 main (int   argc,
496       char *argv[])
497 {
498   char *dirname;
499   int ret;
500 
501   setlocale (LC_ALL, "");
502 
503   g_test_init (&argc, &argv, NULL);
504 
505   dirname = g_path_get_dirname (argv[0]);
506   echo_prog_path = g_build_filename (dirname, "test-spawn-echo" EXEEXT, NULL);
507   if (!g_file_test (echo_prog_path, G_FILE_TEST_EXISTS))
508     {
509       g_free (echo_prog_path);
510       echo_prog_path = g_build_filename (dirname, "lt-test-spawn-echo" EXEEXT, NULL);
511     }
512   echo_script_path = g_build_filename (dirname, "echo-script" SCRIPT_EXT, NULL);
513   if (!g_file_test (echo_script_path, G_FILE_TEST_EXISTS))
514     {
515       g_free (echo_script_path);
516       echo_script_path = g_test_build_filename (G_TEST_DIST, "echo-script" SCRIPT_EXT, NULL);
517     }
518   g_free (dirname);
519 
520   g_assert (g_file_test (echo_prog_path, G_FILE_TEST_EXISTS));
521   g_assert (g_file_test (echo_script_path, G_FILE_TEST_EXISTS));
522 
523   g_test_add_func ("/gthread/spawn-single-sync", test_spawn_sync);
524   g_test_add_func ("/gthread/spawn-single-async", test_spawn_async);
525   g_test_add_func ("/gthread/spawn-single-async-with-fds", test_spawn_async_with_fds);
526   g_test_add_func ("/gthread/spawn-script", test_spawn_script);
527   g_test_add_func ("/gthread/spawn/nonexistent", test_spawn_nonexistent);
528   g_test_add_func ("/gthread/spawn-posix-spawn", test_posix_spawn);
529   g_test_add_func ("/gthread/spawn/fd-assignment-clash", test_spawn_fd_assignment_clash);
530 
531   ret = g_test_run();
532 
533   g_free (echo_script_path);
534   g_free (echo_prog_path);
535 
536   return ret;
537 }
538