#include "glib-private.h" #include #include #ifdef G_OS_UNIX #include #include #include #include #include #include #include #endif #ifdef __linux__ #include #endif /* We write 2^1 + 2^2 ... + 2^10 or 2047 copies of "Hello World!\n" * ultimately */ #define TOTAL_HELLOS 2047 #define HELLO_WORLD "hello world!\n" #ifdef G_OS_WIN32 #define LINEEND "\r\n" #define EXEEXT ".exe" #define SPLICELEN (TOTAL_HELLOS * (strlen (HELLO_WORLD) + 1)) /* because \r */ #else #define LINEEND "\n" #define EXEEXT #define SPLICELEN (TOTAL_HELLOS * strlen (HELLO_WORLD)) #endif #ifdef G_OS_WIN32 #define TESTPROG "gsubprocess-testprog.exe" #else #define TESTPROG "gsubprocess-testprog" #endif static GPtrArray * get_test_subprocess_args (const char *mode, ...) G_GNUC_NULL_TERMINATED; static GPtrArray * get_test_subprocess_args (const char *mode, ...) { GPtrArray *ret; char *path; va_list args; gpointer arg; ret = g_ptr_array_new_with_free_func (g_free); path = g_test_build_filename (G_TEST_BUILT, TESTPROG, NULL); g_ptr_array_add (ret, path); g_ptr_array_add (ret, g_strdup (mode)); va_start (args, mode); while ((arg = va_arg (args, gpointer)) != NULL) g_ptr_array_add (ret, g_strdup (arg)); va_end (args); g_ptr_array_add (ret, NULL); return ret; } static void test_noop (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; args = get_test_subprocess_args ("noop", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_assert_no_error (local_error); g_assert_true (g_subprocess_get_successful (proc)); g_object_unref (proc); } static void check_ready (GObject *source, GAsyncResult *res, gpointer user_data) { gboolean ret; GError *error = NULL; ret = g_subprocess_wait_check_finish (G_SUBPROCESS (source), res, &error); g_assert_true (ret); g_assert_no_error (error); g_object_unref (source); } static void test_noop_all_to_null (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; args = get_test_subprocess_args ("noop", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_STDOUT_SILENCE | G_SUBPROCESS_FLAGS_STDERR_SILENCE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_subprocess_wait_check_async (proc, NULL, check_ready, NULL); } static void test_noop_no_wait (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; args = get_test_subprocess_args ("noop", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_object_unref (proc); } static void test_noop_stdin_inherit (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; args = get_test_subprocess_args ("noop", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_STDIN_INHERIT, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_assert_no_error (local_error); g_object_unref (proc); } #ifdef G_OS_UNIX static void test_search_path (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; proc = g_subprocess_new (G_SUBPROCESS_FLAGS_NONE, error, "true", NULL); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_assert_no_error (local_error); g_object_unref (proc); } static void test_search_path_from_envp (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; const char *path; path = g_test_get_dir (G_TEST_BUILT); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_SEARCH_PATH_FROM_ENVP); g_subprocess_launcher_setenv (launcher, "PATH", path, TRUE); proc = g_subprocess_launcher_spawn (launcher, error, TESTPROG, "exit1", NULL); g_assert_no_error (local_error); g_object_unref (launcher); g_subprocess_wait_check (proc, NULL, error); g_assert_error (local_error, G_SPAWN_EXIT_ERROR, 1); g_clear_error (error); g_object_unref (proc); } #endif static void test_exit1 (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; args = get_test_subprocess_args ("exit1", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_assert_error (local_error, G_SPAWN_EXIT_ERROR, 1); g_clear_error (error); g_object_unref (proc); } typedef struct { GMainLoop *loop; GCancellable *cancellable; gboolean cb_called; } TestExit1CancelData; static gboolean test_exit1_cancel_idle_quit_cb (gpointer user_data) { GMainLoop *loop = user_data; g_main_loop_quit (loop); return G_SOURCE_REMOVE; } static void test_exit1_cancel_wait_check_cb (GObject *source, GAsyncResult *result, gpointer user_data) { GSubprocess *subprocess = G_SUBPROCESS (source); TestExit1CancelData *data = user_data; gboolean ret; GError *error = NULL; g_assert_false (data->cb_called); data->cb_called = TRUE; ret = g_subprocess_wait_check_finish (subprocess, result, &error); g_assert_false (ret); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_clear_error (&error); g_idle_add (test_exit1_cancel_idle_quit_cb, data->loop); } static void test_exit1_cancel (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; TestExit1CancelData data = { 0 }; g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=786456"); args = get_test_subprocess_args ("exit1", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); data.loop = g_main_loop_new (NULL, FALSE); data.cancellable = g_cancellable_new (); g_subprocess_wait_check_async (proc, data.cancellable, test_exit1_cancel_wait_check_cb, &data); g_subprocess_wait_check (proc, NULL, error); g_assert_error (local_error, G_SPAWN_EXIT_ERROR, 1); g_clear_error (error); g_cancellable_cancel (data.cancellable); g_main_loop_run (data.loop); g_object_unref (proc); g_main_loop_unref (data.loop); g_clear_object (&data.cancellable); } static void test_exit1_cancel_in_cb_wait_check_cb (GObject *source, GAsyncResult *result, gpointer user_data) { GSubprocess *subprocess = G_SUBPROCESS (source); TestExit1CancelData *data = user_data; gboolean ret; GError *error = NULL; g_assert_false (data->cb_called); data->cb_called = TRUE; ret = g_subprocess_wait_check_finish (subprocess, result, &error); g_assert_false (ret); g_assert_error (error, G_SPAWN_EXIT_ERROR, 1); g_clear_error (&error); g_cancellable_cancel (data->cancellable); g_idle_add (test_exit1_cancel_idle_quit_cb, data->loop); } static void test_exit1_cancel_in_cb (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocess *proc; TestExit1CancelData data = { 0 }; g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=786456"); args = get_test_subprocess_args ("exit1", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); data.loop = g_main_loop_new (NULL, FALSE); data.cancellable = g_cancellable_new (); g_subprocess_wait_check_async (proc, data.cancellable, test_exit1_cancel_in_cb_wait_check_cb, &data); g_subprocess_wait_check (proc, NULL, error); g_assert_error (local_error, G_SPAWN_EXIT_ERROR, 1); g_clear_error (error); g_main_loop_run (data.loop); g_object_unref (proc); g_main_loop_unref (data.loop); g_clear_object (&data.cancellable); } static gchar * splice_to_string (GInputStream *stream, GError **error) { GMemoryOutputStream *buffer = NULL; char *ret = NULL; buffer = (GMemoryOutputStream*)g_memory_output_stream_new (NULL, 0, g_realloc, g_free); if (g_output_stream_splice ((GOutputStream*)buffer, stream, 0, NULL, error) < 0) goto out; if (!g_output_stream_write ((GOutputStream*)buffer, "\0", 1, NULL, error)) goto out; if (!g_output_stream_close ((GOutputStream*)buffer, NULL, error)) goto out; ret = g_memory_output_stream_steal_data (buffer); out: g_clear_object (&buffer); return ret; } static void test_echo1 (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; GPtrArray *args; GInputStream *stdout_stream; gchar *result; args = get_test_subprocess_args ("echo", "hello", "world!", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_STDOUT_PIPE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdout_stream = g_subprocess_get_stdout_pipe (proc); result = splice_to_string (stdout_stream, error); g_assert_no_error (local_error); g_assert_cmpstr (result, ==, "hello" LINEEND "world!" LINEEND); g_free (result); g_object_unref (proc); } #ifdef G_OS_UNIX static void test_echo_merged (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; GPtrArray *args; GInputStream *stdout_stream; gchar *result; args = get_test_subprocess_args ("echo-stdout-and-stderr", "merge", "this", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdout_stream = g_subprocess_get_stdout_pipe (proc); result = splice_to_string (stdout_stream, error); g_assert_no_error (local_error); g_assert_cmpstr (result, ==, "merge\nmerge\nthis\nthis\n"); g_free (result); g_object_unref (proc); } #endif typedef struct { guint events_pending; GMainLoop *loop; } TestCatData; static void test_cat_on_input_splice_complete (GObject *object, GAsyncResult *result, gpointer user_data) { TestCatData *data = user_data; GError *error = NULL; (void)g_output_stream_splice_finish ((GOutputStream*)object, result, &error); g_assert_no_error (error); data->events_pending--; if (data->events_pending == 0) g_main_loop_quit (data->loop); } static void test_cat_utf8 (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; GPtrArray *args; GBytes *input_buf; GBytes *output_buf; GInputStream *input_buf_stream = NULL; GOutputStream *output_buf_stream = NULL; GOutputStream *stdin_stream = NULL; GInputStream *stdout_stream = NULL; TestCatData data; memset (&data, 0, sizeof (data)); data.loop = g_main_loop_new (NULL, TRUE); args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdin_stream = g_subprocess_get_stdin_pipe (proc); stdout_stream = g_subprocess_get_stdout_pipe (proc); input_buf = g_bytes_new_static ("hello, world!", strlen ("hello, world!")); input_buf_stream = g_memory_input_stream_new_from_bytes (input_buf); g_bytes_unref (input_buf); output_buf_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); g_output_stream_splice_async (stdin_stream, input_buf_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, G_PRIORITY_DEFAULT, NULL, test_cat_on_input_splice_complete, &data); data.events_pending++; g_output_stream_splice_async (output_buf_stream, stdout_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, G_PRIORITY_DEFAULT, NULL, test_cat_on_input_splice_complete, &data); data.events_pending++; g_main_loop_run (data.loop); g_subprocess_wait_check (proc, NULL, error); g_assert_no_error (local_error); output_buf = g_memory_output_stream_steal_as_bytes ((GMemoryOutputStream*)output_buf_stream); g_assert_cmpmem (g_bytes_get_data (output_buf, NULL), g_bytes_get_size (output_buf), "hello, world!", 13); g_bytes_unref (output_buf); g_main_loop_unref (data.loop); g_object_unref (input_buf_stream); g_object_unref (output_buf_stream); g_object_unref (proc); } static gpointer cancel_soon (gpointer user_data) { GCancellable *cancellable = user_data; g_usleep (G_TIME_SPAN_SECOND); g_cancellable_cancel (cancellable); g_object_unref (cancellable); return NULL; } static void test_cat_eof (void) { GCancellable *cancellable; GError *error = NULL; GSubprocess *cat; gboolean result; gchar buffer; gssize s; #ifdef G_OS_WIN32 g_test_skip ("This test has not been ported to Win32"); return; #endif /* Spawn 'cat' */ cat = g_subprocess_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error, "cat", NULL); g_assert_no_error (error); g_assert_nonnull (cat); /* Make sure that reading stdout blocks (until we cancel) */ cancellable = g_cancellable_new (); g_thread_unref (g_thread_new ("cancel thread", cancel_soon, g_object_ref (cancellable))); s = g_input_stream_read (g_subprocess_get_stdout_pipe (cat), &buffer, sizeof buffer, cancellable, &error); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_assert_cmpint (s, ==, -1); g_object_unref (cancellable); g_clear_error (&error); /* Close the stream (EOF on cat's stdin) */ result = g_output_stream_close (g_subprocess_get_stdin_pipe (cat), NULL, &error); g_assert_no_error (error); g_assert_true (result); /* Now check that reading cat's stdout gets us an EOF (since it quit) */ s = g_input_stream_read (g_subprocess_get_stdout_pipe (cat), &buffer, sizeof buffer, NULL, &error); g_assert_no_error (error); g_assert_false (s); /* Check that the process has exited as a result of the EOF */ result = g_subprocess_wait (cat, NULL, &error); g_assert_no_error (error); g_assert_true (g_subprocess_get_if_exited (cat)); g_assert_cmpint (g_subprocess_get_exit_status (cat), ==, 0); g_assert_true (result); g_object_unref (cat); } typedef struct { guint events_pending; gboolean caught_error; GError *error; GMainLoop *loop; gint counter; GOutputStream *first_stdin; } TestMultiSpliceData; static void on_one_multi_splice_done (GObject *obj, GAsyncResult *res, gpointer user_data) { TestMultiSpliceData *data = user_data; if (!data->caught_error) { if (g_output_stream_splice_finish ((GOutputStream*)obj, res, &data->error) < 0) data->caught_error = TRUE; } data->events_pending--; if (data->events_pending == 0) g_main_loop_quit (data->loop); } static gboolean on_idle_multisplice (gpointer user_data) { TestMultiSpliceData *data = user_data; if (data->counter >= TOTAL_HELLOS || data->caught_error) { if (!g_output_stream_close (data->first_stdin, NULL, &data->error)) data->caught_error = TRUE; data->events_pending--; if (data->events_pending == 0) { g_main_loop_quit (data->loop); } return FALSE; } else { int i; for (i = 0; i < data->counter; i++) { gsize bytes_written; if (!g_output_stream_write_all (data->first_stdin, HELLO_WORLD, strlen (HELLO_WORLD), &bytes_written, NULL, &data->error)) { data->caught_error = TRUE; return FALSE; } } data->counter *= 2; return TRUE; } } static void on_subprocess_exited (GObject *object, GAsyncResult *result, gpointer user_data) { GSubprocess *subprocess = G_SUBPROCESS (object); TestMultiSpliceData *data = user_data; GError *error = NULL; if (!g_subprocess_wait_finish (subprocess, result, &error)) { if (!data->caught_error) { data->caught_error = TRUE; g_propagate_error (&data->error, error); } } g_spawn_check_wait_status (g_subprocess_get_status (subprocess), &error); g_assert_no_error (error); data->events_pending--; if (data->events_pending == 0) g_main_loop_quit (data->loop); } static void test_multi_1 (void) { GError *local_error = NULL; GError **error = &local_error; GPtrArray *args; GSubprocessLauncher *launcher; GSubprocess *first; GSubprocess *second; GSubprocess *third; GOutputStream *first_stdin; GInputStream *first_stdout; GOutputStream *second_stdin; GInputStream *second_stdout; GOutputStream *third_stdin; GInputStream *third_stdout; GOutputStream *membuf; TestMultiSpliceData data; int splice_flags = G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET; args = get_test_subprocess_args ("cat", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE); first = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_assert_no_error (local_error); second = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_assert_no_error (local_error); third = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_assert_no_error (local_error); g_ptr_array_free (args, TRUE); membuf = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); first_stdin = g_subprocess_get_stdin_pipe (first); first_stdout = g_subprocess_get_stdout_pipe (first); second_stdin = g_subprocess_get_stdin_pipe (second); second_stdout = g_subprocess_get_stdout_pipe (second); third_stdin = g_subprocess_get_stdin_pipe (third); third_stdout = g_subprocess_get_stdout_pipe (third); memset (&data, 0, sizeof (data)); data.loop = g_main_loop_new (NULL, TRUE); data.counter = 1; data.first_stdin = first_stdin; data.events_pending++; g_output_stream_splice_async (second_stdin, first_stdout, splice_flags, G_PRIORITY_DEFAULT, NULL, on_one_multi_splice_done, &data); data.events_pending++; g_output_stream_splice_async (third_stdin, second_stdout, splice_flags, G_PRIORITY_DEFAULT, NULL, on_one_multi_splice_done, &data); data.events_pending++; g_output_stream_splice_async (membuf, third_stdout, splice_flags, G_PRIORITY_DEFAULT, NULL, on_one_multi_splice_done, &data); data.events_pending++; g_timeout_add (250, on_idle_multisplice, &data); data.events_pending++; g_subprocess_wait_async (first, NULL, on_subprocess_exited, &data); data.events_pending++; g_subprocess_wait_async (second, NULL, on_subprocess_exited, &data); data.events_pending++; g_subprocess_wait_async (third, NULL, on_subprocess_exited, &data); g_main_loop_run (data.loop); g_assert_false (data.caught_error); g_assert_no_error (data.error); g_assert_cmpint (g_memory_output_stream_get_data_size ((GMemoryOutputStream*)membuf), ==, SPLICELEN); g_main_loop_unref (data.loop); g_object_unref (membuf); g_object_unref (launcher); g_object_unref (first); g_object_unref (second); g_object_unref (third); } typedef struct { GSubprocessFlags flags; gboolean is_utf8; gboolean running; GError *error; } TestAsyncCommunicateData; static void on_communicate_complete (GObject *proc, GAsyncResult *result, gpointer user_data) { TestAsyncCommunicateData *data = user_data; GBytes *stdout_bytes = NULL, *stderr_bytes = NULL; char *stdout_str = NULL, *stderr_str = NULL; const guint8 *stdout_data; gsize stdout_len; data->running = FALSE; if (data->is_utf8) (void) g_subprocess_communicate_utf8_finish ((GSubprocess*)proc, result, &stdout_str, &stderr_str, &data->error); else (void) g_subprocess_communicate_finish ((GSubprocess*)proc, result, &stdout_bytes, &stderr_bytes, &data->error); if (data->error) return; if (data->flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE) { if (data->is_utf8) { g_assert_nonnull (stdout_str); stdout_data = (guint8*)stdout_str; stdout_len = strlen (stdout_str); } else { g_assert_nonnull (stdout_bytes); stdout_data = g_bytes_get_data (stdout_bytes, &stdout_len); } g_assert_cmpmem (stdout_data, stdout_len, "# hello world" LINEEND, 13 + strlen (LINEEND)); } else { g_assert_null (stdout_str); g_assert_null (stdout_bytes); } if (data->flags & G_SUBPROCESS_FLAGS_STDERR_PIPE) { if (data->is_utf8) g_assert_nonnull (stderr_str); else g_assert_nonnull (stderr_bytes); } else { g_assert_null (stderr_str); g_assert_null (stderr_bytes); } g_clear_pointer (&stdout_bytes, g_bytes_unref); g_clear_pointer (&stderr_bytes, g_bytes_unref); g_free (stdout_str); g_free (stderr_str); } /* Test g_subprocess_communicate_async() works correctly with a variety of flags, * as passed in via @test_data. */ static void test_communicate_async (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GError *error = NULL; GPtrArray *args; TestAsyncCommunicateData data = { flags, 0, 0, NULL }; GSubprocess *proc; GCancellable *cancellable = NULL; GBytes *input; const char *hellostring; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); /* Include a leading hash and trailing newline so that if this gets onto the * test’s stdout, it doesn’t mess up TAP output. */ hellostring = "# hello world\n"; input = g_bytes_new_static (hellostring, strlen (hellostring)); g_subprocess_communicate_async (proc, input, cancellable, on_communicate_complete, &data); data.running = TRUE; while (data.running) g_main_context_iteration (NULL, TRUE); g_assert_no_error (data.error); g_bytes_unref (input); g_object_unref (proc); } /* Test g_subprocess_communicate() works correctly with a variety of flags, * as passed in via @test_data. */ static void test_communicate (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GError *error = NULL; GPtrArray *args; GSubprocess *proc; GCancellable *cancellable = NULL; GBytes *input; const gchar *hellostring; GBytes *stdout_bytes, *stderr_bytes; const gchar *stdout_data; gsize stdout_len; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); /* Include a leading hash and trailing newline so that if this gets onto the * test’s stdout, it doesn’t mess up TAP output. */ hellostring = "# hello world\n"; input = g_bytes_new_static (hellostring, strlen (hellostring)); g_subprocess_communicate (proc, input, cancellable, &stdout_bytes, &stderr_bytes, &error); g_assert_no_error (error); if (flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE) { stdout_data = g_bytes_get_data (stdout_bytes, &stdout_len); g_assert_cmpmem (stdout_data, stdout_len, "# hello world" LINEEND, 13 + strlen (LINEEND)); } else g_assert_null (stdout_bytes); if (flags & G_SUBPROCESS_FLAGS_STDERR_PIPE) g_assert_nonnull (stderr_bytes); else g_assert_null (stderr_bytes); g_bytes_unref (input); g_clear_pointer (&stdout_bytes, g_bytes_unref); g_clear_pointer (&stderr_bytes, g_bytes_unref); g_object_unref (proc); } typedef struct { GSubprocess *proc; GCancellable *cancellable; gboolean is_utf8; gboolean running; GError *error; } TestCancelledCommunicateData; static gboolean on_test_communicate_cancelled_idle (gpointer user_data) { TestCancelledCommunicateData *data = user_data; GBytes *input; const gchar *hellostring; GBytes *stdout_bytes = NULL, *stderr_bytes = NULL; gchar *stdout_buf = NULL, *stderr_buf = NULL; /* Include a leading hash and trailing newline so that if this gets onto the * test’s stdout, it doesn’t mess up TAP output. */ hellostring = "# hello world\n"; input = g_bytes_new_static (hellostring, strlen (hellostring)); if (data->is_utf8) g_subprocess_communicate_utf8 (data->proc, hellostring, data->cancellable, &stdout_buf, &stderr_buf, &data->error); else g_subprocess_communicate (data->proc, input, data->cancellable, &stdout_bytes, &stderr_bytes, &data->error); data->running = FALSE; if (data->is_utf8) { g_assert_null (stdout_buf); g_assert_null (stderr_buf); } else { g_assert_null (stdout_bytes); g_assert_null (stderr_bytes); } g_bytes_unref (input); return G_SOURCE_REMOVE; } /* Test g_subprocess_communicate() can be cancelled correctly */ static void test_communicate_cancelled (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GPtrArray *args; GSubprocess *proc; GCancellable *cancellable = NULL; GError *error = NULL; TestCancelledCommunicateData data = { 0 }; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); cancellable = g_cancellable_new (); data.proc = proc; data.cancellable = cancellable; data.error = error; g_cancellable_cancel (cancellable); g_idle_add (on_test_communicate_cancelled_idle, &data); data.running = TRUE; while (data.running) g_main_context_iteration (NULL, TRUE); g_assert_error (data.error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_clear_error (&data.error); g_object_unref (cancellable); g_object_unref (proc); } static void on_communicate_cancelled_complete (GObject *proc, GAsyncResult *result, gpointer user_data) { TestAsyncCommunicateData *data = user_data; GBytes *stdout_bytes = NULL, *stderr_bytes = NULL; char *stdout_str = NULL, *stderr_str = NULL; data->running = FALSE; if (data->is_utf8) (void) g_subprocess_communicate_utf8_finish ((GSubprocess*)proc, result, &stdout_str, &stderr_str, &data->error); else (void) g_subprocess_communicate_finish ((GSubprocess*)proc, result, &stdout_bytes, &stderr_bytes, &data->error); if (data->is_utf8) { g_assert_null (stdout_str); g_assert_null (stderr_str); } else { g_assert_null (stdout_bytes); g_assert_null (stderr_bytes); } } /* Test g_subprocess_communicate_async() can be cancelled correctly, * as passed in via @test_data. */ static void test_communicate_cancelled_async (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GError *error = NULL; GPtrArray *args; TestAsyncCommunicateData data = { 0 }; GSubprocess *proc; GCancellable *cancellable = NULL; GBytes *input; const char *hellostring; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); /* Include a leading hash and trailing newline so that if this gets onto the * test’s stdout, it doesn’t mess up TAP output. */ hellostring = "# hello world\n"; input = g_bytes_new_static (hellostring, strlen (hellostring)); cancellable = g_cancellable_new (); g_subprocess_communicate_async (proc, input, cancellable, on_communicate_cancelled_complete, &data); g_cancellable_cancel (cancellable); data.running = TRUE; while (data.running) g_main_context_iteration (NULL, TRUE); g_assert_error (data.error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_clear_error (&data.error); g_bytes_unref (input); g_object_unref (cancellable); g_object_unref (proc); } /* Test g_subprocess_communicate_utf8_async() works correctly with a variety of * flags, as passed in via @test_data. */ static void test_communicate_utf8_async (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GError *error = NULL; GPtrArray *args; TestAsyncCommunicateData data = { flags, 0, 0, NULL }; GSubprocess *proc; GCancellable *cancellable = NULL; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); data.is_utf8 = TRUE; g_subprocess_communicate_utf8_async (proc, "# hello world\n", cancellable, on_communicate_complete, &data); data.running = TRUE; while (data.running) g_main_context_iteration (NULL, TRUE); g_assert_no_error (data.error); g_object_unref (proc); } /* Test g_subprocess_communicate_utf8_async() can be cancelled correctly. */ static void test_communicate_utf8_cancelled_async (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GError *error = NULL; GPtrArray *args; TestAsyncCommunicateData data = { flags, 0, 0, NULL }; GSubprocess *proc; GCancellable *cancellable = NULL; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); cancellable = g_cancellable_new (); data.is_utf8 = TRUE; g_subprocess_communicate_utf8_async (proc, "# hello world\n", cancellable, on_communicate_cancelled_complete, &data); g_cancellable_cancel (cancellable); data.running = TRUE; while (data.running) g_main_context_iteration (NULL, TRUE); g_assert_error (data.error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_clear_error (&data.error); g_object_unref (cancellable); g_object_unref (proc); } /* Test g_subprocess_communicate_utf8() works correctly with a variety of flags, * as passed in via @test_data. */ static void test_communicate_utf8 (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GError *error = NULL; GPtrArray *args; GSubprocess *proc; GCancellable *cancellable = NULL; const gchar *stdin_buf; gchar *stdout_buf, *stderr_buf; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); /* Include a leading hash and trailing newline so that if this gets onto the * test’s stdout, it doesn’t mess up TAP output. */ stdin_buf = "# hello world\n"; g_subprocess_communicate_utf8 (proc, stdin_buf, cancellable, &stdout_buf, &stderr_buf, &error); g_assert_no_error (error); if (flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE) g_assert_cmpstr (stdout_buf, ==, "# hello world" LINEEND); else g_assert_null (stdout_buf); if (flags & G_SUBPROCESS_FLAGS_STDERR_PIPE) g_assert_nonnull (stderr_buf); else g_assert_null (stderr_buf); g_free (stdout_buf); g_free (stderr_buf); g_object_unref (proc); } /* Test g_subprocess_communicate_utf8() can be cancelled correctly */ static void test_communicate_utf8_cancelled (gconstpointer test_data) { GSubprocessFlags flags = GPOINTER_TO_INT (test_data); GPtrArray *args; GSubprocess *proc; GCancellable *cancellable = NULL; GError *error = NULL; TestCancelledCommunicateData data = { 0 }; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); cancellable = g_cancellable_new (); data.proc = proc; data.cancellable = cancellable; data.error = error; g_cancellable_cancel (cancellable); g_idle_add (on_test_communicate_cancelled_idle, &data); data.is_utf8 = TRUE; data.running = TRUE; while (data.running) g_main_context_iteration (NULL, TRUE); g_assert_error (data.error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_clear_error (&data.error); g_object_unref (cancellable); g_object_unref (proc); } static void test_communicate_nothing (void) { GError *error = NULL; GPtrArray *args; GSubprocess *proc; GCancellable *cancellable = NULL; gchar *stdout_buf; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); g_subprocess_communicate_utf8 (proc, "", cancellable, &stdout_buf, NULL, &error); g_assert_no_error (error); g_assert_cmpstr (stdout_buf, ==, ""); g_free (stdout_buf); g_object_unref (proc); } static void test_communicate_utf8_async_invalid (void) { GSubprocessFlags flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE; GError *error = NULL; GPtrArray *args; TestAsyncCommunicateData data = { flags, 0, 0, NULL }; GSubprocess *proc; GCancellable *cancellable = NULL; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &error); g_assert_no_error (error); g_ptr_array_free (args, TRUE); data.is_utf8 = TRUE; g_subprocess_communicate_utf8_async (proc, "\xFF\xFF", cancellable, on_communicate_complete, &data); data.running = TRUE; while (data.running) g_main_context_iteration (NULL, TRUE); g_assert_error (data.error, G_IO_ERROR, G_IO_ERROR_FAILED); g_error_free (data.error); g_object_unref (proc); } /* Test that invalid UTF-8 received using g_subprocess_communicate_utf8() * results in an error. */ static void test_communicate_utf8_invalid (void) { GSubprocessFlags flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE; GError *local_error = NULL; gboolean ret; GPtrArray *args; gchar *stdout_str = NULL, *stderr_str = NULL; GSubprocess *proc; args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_newv ((const gchar* const*)args->pdata, G_SUBPROCESS_FLAGS_STDIN_PIPE | flags, &local_error); g_assert_no_error (local_error); g_ptr_array_free (args, TRUE); ret = g_subprocess_communicate_utf8 (proc, "\xFF\xFF", NULL, &stdout_str, &stderr_str, &local_error); g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_FAILED); g_error_free (local_error); g_assert_false (ret); g_assert_null (stdout_str); g_assert_null (stderr_str); g_object_unref (proc); } static void send_terminate (gpointer user_data) { GSubprocess *proc = user_data; g_subprocess_force_exit (proc); } static void on_request_quit_exited (GObject *object, GAsyncResult *result, gpointer user_data) { GSubprocess *subprocess = G_SUBPROCESS (object); GError *error = NULL; g_subprocess_wait_finish (subprocess, result, &error); g_assert_no_error (error); #ifdef G_OS_UNIX g_assert_true (g_subprocess_get_if_signaled (subprocess)); g_assert_cmpint (g_subprocess_get_term_sig (subprocess), ==, 9); #endif g_spawn_check_wait_status (g_subprocess_get_status (subprocess), &error); g_assert_nonnull (error); g_clear_error (&error); g_main_loop_quit ((GMainLoop*)user_data); } static void test_terminate (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; GPtrArray *args; GMainLoop *loop; const gchar *id; args = get_test_subprocess_args ("sleep-forever", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); id = g_subprocess_get_identifier (proc); g_assert_nonnull (id); loop = g_main_loop_new (NULL, TRUE); g_subprocess_wait_async (proc, NULL, on_request_quit_exited, loop); g_timeout_add_seconds_once (3, send_terminate, proc); g_main_loop_run (loop); g_main_loop_unref (loop); g_object_unref (proc); } #ifdef G_OS_UNIX static void send_signal (gpointer user_data) { GSubprocess *proc = user_data; g_subprocess_send_signal (proc, SIGKILL); } static void test_signal (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocess *proc; GPtrArray *args; GMainLoop *loop; args = get_test_subprocess_args ("sleep-forever", NULL); proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); loop = g_main_loop_new (NULL, TRUE); g_subprocess_wait_async (proc, NULL, on_request_quit_exited, loop); g_timeout_add_seconds_once (3, send_signal, proc); g_main_loop_run (loop); g_main_loop_unref (loop); g_object_unref (proc); } #endif static void test_env (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GInputStream *stdout_stream; gchar *result; gchar *envp[] = { NULL, "ONE=1", "TWO=1", "THREE=3", "FOUR=1", NULL }; gchar **split; envp[0] = g_strdup_printf ("PATH=%s", g_getenv ("PATH")); args = get_test_subprocess_args ("env", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); g_subprocess_launcher_set_flags (launcher, G_SUBPROCESS_FLAGS_STDOUT_PIPE); g_subprocess_launcher_set_environ (launcher, envp); g_subprocess_launcher_setenv (launcher, "TWO", "2", TRUE); g_subprocess_launcher_setenv (launcher, "THREE", "1", FALSE); g_subprocess_launcher_unsetenv (launcher, "FOUR"); g_assert_null (g_subprocess_launcher_getenv (launcher, "FOUR")); proc = g_subprocess_launcher_spawn (launcher, error, args->pdata[0], "env", NULL); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_free (envp[0]); stdout_stream = g_subprocess_get_stdout_pipe (proc); result = splice_to_string (stdout_stream, error); split = g_strsplit (result, LINEEND, -1); g_assert_cmpstr (g_environ_getenv (split, "ONE"), ==, "1"); g_assert_cmpstr (g_environ_getenv (split, "TWO"), ==, "2"); g_assert_cmpstr (g_environ_getenv (split, "THREE"), ==, "3"); g_assert_null (g_environ_getenv (split, "FOUR")); g_strfreev (split); g_free (result); g_object_unref (proc); g_object_unref (launcher); } /* Test that explicitly inheriting and modifying the parent process’ * environment works. */ static void test_env_inherit (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GInputStream *stdout_stream; gchar *result; gchar **split; g_setenv ("TEST_ENV_INHERIT1", "1", TRUE); g_setenv ("TEST_ENV_INHERIT2", "2", TRUE); args = get_test_subprocess_args ("env", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); g_subprocess_launcher_set_flags (launcher, G_SUBPROCESS_FLAGS_STDOUT_PIPE); g_subprocess_launcher_set_environ (launcher, NULL); g_subprocess_launcher_setenv (launcher, "TWO", "2", TRUE); g_subprocess_launcher_unsetenv (launcher, "TEST_ENV_INHERIT1"); g_assert_null (g_subprocess_launcher_getenv (launcher, "TEST_ENV_INHERIT1")); g_assert_cmpstr (g_subprocess_launcher_getenv (launcher, "TEST_ENV_INHERIT2"), ==, "2"); g_assert_cmpstr (g_subprocess_launcher_getenv (launcher, "TWO"), ==, "2"); proc = g_subprocess_launcher_spawn (launcher, error, args->pdata[0], "env", NULL); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdout_stream = g_subprocess_get_stdout_pipe (proc); result = splice_to_string (stdout_stream, error); split = g_strsplit (result, LINEEND, -1); g_assert_null (g_environ_getenv (split, "TEST_ENV_INHERIT1")); g_assert_cmpstr (g_environ_getenv (split, "TEST_ENV_INHERIT2"), ==, "2"); g_assert_cmpstr (g_environ_getenv (split, "TWO"), ==, "2"); g_strfreev (split); g_free (result); g_object_unref (proc); g_object_unref (launcher); } static void test_cwd (void) { GError *local_error = NULL; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GInputStream *stdout_stream; gchar *result; gsize result_len; const gchar *tmpdir = g_get_tmp_dir (); gchar *tmpdir_basename = NULL, *result_basename = NULL; args = get_test_subprocess_args ("cwd", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); g_subprocess_launcher_set_flags (launcher, G_SUBPROCESS_FLAGS_STDOUT_PIPE); g_subprocess_launcher_set_cwd (launcher, tmpdir); proc = g_subprocess_launcher_spawnv (launcher, (const char * const *)args->pdata, &local_error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdout_stream = g_subprocess_get_stdout_pipe (proc); result = splice_to_string (stdout_stream, &local_error); g_assert_no_error (local_error); result_len = strlen (result); /* The result should end with a line ending */ g_assert_cmpstr (result + result_len - strlen (LINEEND), ==, LINEEND); /* Not sure if the testprog guarantees to return an absolute path for the cwd, * so only compare the basenames. */ tmpdir_basename = g_path_get_basename (tmpdir); result_basename = g_path_get_basename (g_strstrip (result)); g_assert_cmpstr (tmpdir_basename, ==, result_basename); g_free (tmpdir_basename); g_free (result_basename); g_free (result); g_object_unref (proc); g_object_unref (launcher); } #ifdef G_OS_UNIX static void test_subprocess_launcher_close (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; int fd, fd2; gboolean is_open; /* Open two arbitrary FDs. One of them, @fd, will be transferred to the * launcher, and the other’s FD integer will be used as its target FD, giving * the mapping `fd → fd2` if a child process were to be spawned. * * The launcher will then be closed, which should close @fd but *not* @fd2, * as the value of @fd2 is only valid as an FD in a child process. (A child * process is not actually spawned in this test.) */ fd = dup (0); fd2 = dup (0); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); g_subprocess_launcher_take_fd (launcher, fd, fd2); is_open = fcntl (fd, F_GETFD) != -1; g_assert_true (is_open); is_open = fcntl (fd2, F_GETFD) != -1; g_assert_true (is_open); g_subprocess_launcher_close (launcher); is_open = fcntl (fd, F_GETFD) != -1; g_assert_false (is_open); is_open = fcntl (fd2, F_GETFD) != -1; g_assert_true (is_open); /* Now test that actually trying to spawn the child gives %G_IO_ERROR_CLOSED, * as g_subprocess_launcher_close() has been called. */ args = get_test_subprocess_args ("cat", NULL); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_null (proc); g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_CLOSED); g_clear_error (error); close (fd2); g_object_unref (launcher); } static void test_stdout_file (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GFile *tmpfile; GFileIOStream *iostream; GOutputStream *stdin_stream; const char *test_data = "this is some test data\n"; char *tmp_contents; char *tmp_file_path; tmpfile = g_file_new_tmp ("gsubprocessXXXXXX", &iostream, error); g_assert_no_error (local_error); g_clear_object (&iostream); tmp_file_path = g_file_get_path (tmpfile); args = get_test_subprocess_args ("cat", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE); g_subprocess_launcher_set_stdout_file_path (launcher, tmp_file_path); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); stdin_stream = g_subprocess_get_stdin_pipe (proc); g_output_stream_write_all (stdin_stream, test_data, strlen (test_data), NULL, NULL, error); g_assert_no_error (local_error); g_output_stream_close (stdin_stream, NULL, error); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_object_unref (launcher); g_object_unref (proc); g_file_load_contents (tmpfile, NULL, &tmp_contents, NULL, NULL, error); g_assert_no_error (local_error); g_assert_cmpstr (test_data, ==, tmp_contents); g_free (tmp_contents); (void) g_file_delete (tmpfile, NULL, NULL); g_object_unref (tmpfile); g_free (tmp_file_path); } static void test_stdout_fd (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GFile *tmpfile; GFileIOStream *iostream; GFileDescriptorBased *descriptor_stream; GOutputStream *stdin_stream; const char *test_data = "this is some test data\n"; char *tmp_contents; tmpfile = g_file_new_tmp ("gsubprocessXXXXXX", &iostream, error); g_assert_no_error (local_error); args = get_test_subprocess_args ("cat", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE); descriptor_stream = G_FILE_DESCRIPTOR_BASED (g_io_stream_get_output_stream (G_IO_STREAM (iostream))); g_subprocess_launcher_take_stdout_fd (launcher, dup (g_file_descriptor_based_get_fd (descriptor_stream))); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_clear_object (&iostream); stdin_stream = g_subprocess_get_stdin_pipe (proc); g_output_stream_write_all (stdin_stream, test_data, strlen (test_data), NULL, NULL, error); g_assert_no_error (local_error); g_output_stream_close (stdin_stream, NULL, error); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_object_unref (launcher); g_object_unref (proc); g_file_load_contents (tmpfile, NULL, &tmp_contents, NULL, NULL, error); g_assert_no_error (local_error); g_assert_cmpstr (test_data, ==, tmp_contents); g_free (tmp_contents); (void) g_file_delete (tmpfile, NULL, NULL); g_object_unref (tmpfile); } static void child_setup (gpointer user_data) { dup2 (GPOINTER_TO_INT (user_data), 1); } static void test_child_setup (void) { GError *local_error = NULL; GError **error = &local_error; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; GFile *tmpfile; GFileIOStream *iostream; GOutputStream *stdin_stream; const char *test_data = "this is some test data\n"; char *tmp_contents; int fd; tmpfile = g_file_new_tmp ("gsubprocessXXXXXX", &iostream, error); g_assert_no_error (local_error); fd = g_file_descriptor_based_get_fd (G_FILE_DESCRIPTOR_BASED (g_io_stream_get_output_stream (G_IO_STREAM (iostream)))); args = get_test_subprocess_args ("cat", NULL); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE); g_subprocess_launcher_set_child_setup (launcher, child_setup, GINT_TO_POINTER (fd), NULL); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_clear_object (&iostream); stdin_stream = g_subprocess_get_stdin_pipe (proc); g_output_stream_write_all (stdin_stream, test_data, strlen (test_data), NULL, NULL, error); g_assert_no_error (local_error); g_output_stream_close (stdin_stream, NULL, error); g_assert_no_error (local_error); g_subprocess_wait_check (proc, NULL, error); g_object_unref (launcher); g_object_unref (proc); g_file_load_contents (tmpfile, NULL, &tmp_contents, NULL, NULL, error); g_assert_no_error (local_error); g_assert_cmpstr (test_data, ==, tmp_contents); g_free (tmp_contents); (void) g_file_delete (tmpfile, NULL, NULL); g_object_unref (tmpfile); } static void do_test_pass_fd (GSubprocessFlags flags, GSpawnChildSetupFunc child_setup) { GError *local_error = NULL; GError **error = &local_error; GInputStream *child_input; GDataInputStream *child_datainput; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; int basic_pipefds[2]; int needdup_pipefds[2]; char *buf; gsize len; char *basic_fd_str; char *needdup_fd_str; g_unix_open_pipe (basic_pipefds, O_CLOEXEC, error); g_assert_no_error (local_error); g_unix_open_pipe (needdup_pipefds, O_CLOEXEC, error); g_assert_no_error (local_error); basic_fd_str = g_strdup_printf ("%d", basic_pipefds[1]); needdup_fd_str = g_strdup_printf ("%d", needdup_pipefds[1] + 1); args = get_test_subprocess_args ("write-to-fds", basic_fd_str, needdup_fd_str, NULL); launcher = g_subprocess_launcher_new (flags); g_subprocess_launcher_take_fd (launcher, basic_pipefds[1], basic_pipefds[1]); g_subprocess_launcher_take_fd (launcher, needdup_pipefds[1], needdup_pipefds[1] + 1); if (child_setup != NULL) g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); g_ptr_array_free (args, TRUE); g_assert_no_error (local_error); g_free (basic_fd_str); g_free (needdup_fd_str); child_input = g_unix_input_stream_new (basic_pipefds[0], TRUE); child_datainput = g_data_input_stream_new (child_input); buf = g_data_input_stream_read_line_utf8 (child_datainput, &len, NULL, error); g_assert_no_error (local_error); g_assert_cmpstr (buf, ==, "hello world"); g_object_unref (child_datainput); g_object_unref (child_input); g_free (buf); child_input = g_unix_input_stream_new (needdup_pipefds[0], TRUE); child_datainput = g_data_input_stream_new (child_input); buf = g_data_input_stream_read_line_utf8 (child_datainput, &len, NULL, error); g_assert_no_error (local_error); g_assert_cmpstr (buf, ==, "hello world"); g_free (buf); g_object_unref (child_datainput); g_object_unref (child_input); g_object_unref (launcher); g_object_unref (proc); } static void test_pass_fd (void) { do_test_pass_fd (G_SUBPROCESS_FLAGS_NONE, NULL); } static void empty_child_setup (gpointer user_data) { } static void test_pass_fd_empty_child_setup (void) { /* Using a child setup function forces gspawn to use fork/exec * rather than posix_spawn. */ do_test_pass_fd (G_SUBPROCESS_FLAGS_NONE, empty_child_setup); } static void test_pass_fd_inherit_fds (void) { /* Try to test the optimized posix_spawn codepath instead of * fork/exec. Currently this requires using INHERIT_FDS since gspawn's * posix_spawn codepath does not currently handle closing * non-inherited fds. Note that using INHERIT_FDS means our testing of * g_subprocess_launcher_take_fd() is less-comprehensive than when * using G_SUBPROCESS_FLAGS_NONE. */ do_test_pass_fd (G_SUBPROCESS_FLAGS_INHERIT_FDS, NULL); } static void do_test_fd_conflation (GSubprocessFlags flags, GSpawnChildSetupFunc child_setup, gboolean test_child_err_report_fd) { char success_message[] = "Yay success!"; GError *error = NULL; GOutputStream *output_stream; GSubprocessLauncher *launcher; GSubprocess *proc; GPtrArray *args; int unused_pipefds[2]; int pipefds[2]; int fd_to_pass_to_child; gsize bytes_written; gboolean success; char *fd_str; /* This test must run in a new process because it is extremely sensitive to * order of opened fds. */ if (!g_test_subprocess ()) { g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_INHERIT_STDOUT | G_TEST_SUBPROCESS_INHERIT_STDERR); g_test_trap_assert_passed (); return; } g_unix_open_pipe (unused_pipefds, O_CLOEXEC, &error); g_assert_no_error (error); g_unix_open_pipe (pipefds, O_CLOEXEC, &error); g_assert_no_error (error); /* The fds should be sequential since we are in a new process. */ g_assert_cmpint (unused_pipefds[0] /* 3 */, ==, unused_pipefds[1] - 1); g_assert_cmpint (unused_pipefds[1] /* 4 */, ==, pipefds[0] - 1); g_assert_cmpint (pipefds[0] /* 5 */, ==, pipefds[1] /* 6 */ - 1); /* Because GSubprocess allows arbitrary remapping of fds, it has to be careful * to avoid fd conflation issues, e.g. it should properly handle 5 -> 4 and * 4 -> 5 at the same time. GIO previously attempted to handle this by naively * dup'ing the source fds, but this was not good enough because it was * possible that the dup'ed result could still conflict with one of the target * fds. For example: * * source_fd 5 -> target_fd 9, source_fd 3 -> target_fd 7 * * dup(5) -> dup returns 8 * dup(3) -> dup returns 9 * * After dup'ing, we wind up with: 8 -> 9, 9 -> 7. That means that after we * dup2(8, 9), we have clobbered fd 9 before we dup2(9, 7). The end result is * we have remapped 5 -> 9 as expected, but then remapped 5 -> 7 instead of * 3 -> 7 as the application intended. * * This issue has been fixed in the simplest way possible, by passing a * minimum fd value when using F_DUPFD_CLOEXEC that is higher than any of the * target fds, to guarantee all source fds are different than all target fds, * eliminating any possibility of conflation. * * Anyway, that is why we have the unused_pipefds here. We need to open fds in * a certain order in order to trick older GSubprocess into conflating the * fds. The primary goal of this test is to ensure this particular conflation * issue is not reintroduced. See glib#2503. * * This test also has an alternate mode of operation where it instead tests * for conflation with gspawn's child_err_report_fd, glib#2506. * * Be aware this test is necessarily extremely fragile. To reproduce these * bugs, it relies on internals of gspawn and gmain that will likely change * in the future, eventually causing this test to no longer test the bugs * it was originally designed to test. That is OK! If the test fails, at * least you know *something* is wrong. */ if (test_child_err_report_fd) fd_to_pass_to_child = pipefds[1] + 2 /* 8 */; else fd_to_pass_to_child = pipefds[1] + 3 /* 9 */; launcher = g_subprocess_launcher_new (flags); g_subprocess_launcher_take_fd (launcher, pipefds[0] /* 5 */, fd_to_pass_to_child); g_subprocess_launcher_take_fd (launcher, unused_pipefds[0] /* 3 */, pipefds[1] + 1 /* 7 */); if (child_setup != NULL) g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL); fd_str = g_strdup_printf ("%d", fd_to_pass_to_child); args = get_test_subprocess_args ("read-from-fd", fd_str, NULL); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, &error); g_assert_no_error (error); g_assert_nonnull (proc); g_ptr_array_free (args, TRUE); g_object_unref (launcher); g_free (fd_str); /* Close the read ends of the pipes. */ close (unused_pipefds[0]); close (pipefds[0]); /* Also close the write end of the unused pipe. */ close (unused_pipefds[1]); /* If doing our normal test: * * So now pipefds[0] should be inherited into the subprocess as * pipefds[1] + 2, and unused_pipefds[0] should be inherited as * pipefds[1] + 1. We will write to pipefds[1] and the subprocess will verify * that it reads the expected data. But older broken GIO will accidentally * clobber pipefds[1] + 2 with pipefds[1] + 1! This will cause the subprocess * to hang trying to read from the wrong pipe. * * If testing conflation with child_err_report_fd: * * We are actually already done. The real test succeeded if we made it this * far without hanging while spawning the child. But let's continue with our * write and read anyway, to ensure things are good. */ output_stream = g_unix_output_stream_new (pipefds[1], TRUE); success = g_output_stream_write_all (output_stream, success_message, sizeof (success_message), &bytes_written, NULL, &error); g_assert_no_error (error); g_assert_cmpint (bytes_written, ==, sizeof (success_message)); g_assert_true (success); g_object_unref (output_stream); success = g_subprocess_wait_check (proc, NULL, &error); g_assert_no_error (error); g_object_unref (proc); } static void test_fd_conflation (void) { do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, NULL, FALSE); } static void test_fd_conflation_empty_child_setup (void) { /* Using a child setup function forces gspawn to use fork/exec * rather than posix_spawn. */ do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, empty_child_setup, FALSE); } static void test_fd_conflation_inherit_fds (void) { /* Try to test the optimized posix_spawn codepath instead of * fork/exec. Currently this requires using INHERIT_FDS since gspawn's * posix_spawn codepath does not currently handle closing * non-inherited fds. */ do_test_fd_conflation (G_SUBPROCESS_FLAGS_INHERIT_FDS, NULL, FALSE); } static void test_fd_conflation_child_err_report_fd (void) { /* Using a child setup function forces gspawn to use fork/exec * rather than posix_spawn. */ do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, empty_child_setup, TRUE); } #ifdef __linux__ /* Handle ptrace events on @main_child, and assert that when it exits, it does * so with status %EXIT_SUCCESS, rather than signalling. Other than that, this * just calls %PTRACE_CONT for all trace events. */ static void trace_children (pid_t main_child) { int wstatus; g_assert_no_errno (waitpid (main_child, &wstatus, 0)); g_assert_no_errno (ptrace (PTRACE_SETOPTIONS, main_child, NULL, (PTRACE_O_TRACEFORK | #ifdef HAVE_PTRACE_O_EXITKILL PTRACE_O_EXITKILL | #endif PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC))); g_assert_no_errno (ptrace (PTRACE_CONT, main_child, NULL, 0)); while (TRUE) { pid_t pid; int wstatus; int stop_signum; int ptrace_event; pid = waitpid (-1, &wstatus, 0); if (pid == -1 && errno == ECHILD) break; g_assert_cmpint (errno, ==, 0); g_assert_cmpint (pid, >=, 0); if (WIFSTOPPED (wstatus)) stop_signum = WSTOPSIG (wstatus); else stop_signum = 0; switch (stop_signum) { case SIGTRAP: ptrace_event = (wstatus >> 16) & 0xffff; switch (ptrace_event) { case 0: g_assert_no_errno (ptrace (PTRACE_CONT, pid, NULL, stop_signum)); break; default: g_assert_no_errno (ptrace (PTRACE_CONT, pid, NULL, 0)); break; } break; case SIGSTOP: g_assert_no_errno (ptrace (PTRACE_CONT, pid, NULL, 0)); break; default: if (!WIFEXITED (wstatus) && !WIFSIGNALED (wstatus)) g_assert_no_errno (ptrace (PTRACE_CONT, pid, NULL, stop_signum)); break; } if (pid == main_child) { g_assert_false (WIFSIGNALED (wstatus)); if (WIFEXITED (wstatus)) { g_assert_cmpint (WEXITSTATUS (wstatus), ==, EXIT_SUCCESS); break; } } } } #endif /* __linux__ */ static void test_exit_status_trapped (void) { #ifndef _GLIB_ADDRESS_SANITIZER #ifdef __linux__ GPtrArray *args = NULL; pid_t test_child; #endif g_test_summary ("Test that exit status is reported correctly for ptrace()d child processes"); g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3433"); #ifdef __linux__ /* Call fork() directly here, rather than using #GSubprocess, so that we can * safely call waitpid() on it ourselves without interfering with the internals * of #GSubprocess. * See https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3433#note_1749055 */ args = get_test_subprocess_args ("sleep-and-kill", NULL); test_child = fork (); if (test_child == 0) { /* Between fork() and exec() we can only call async-signal-safe functions. */ if (ptrace (PTRACE_TRACEME, 0, NULL, NULL) < 0) abort (); g_assert_no_errno (execvp (args->pdata[0], (char * const *) args->pdata)); } trace_children (test_child); g_clear_pointer (&args, g_ptr_array_unref); #else g_test_skip ("ptrace() support for this test is only tested on Linux"); #endif #else /* if defined (_GLIB_ADDRESS_SANITIZER) */ g_test_skip ("LeakSanitizer does not work under ptrace"); (void) trace_children; #endif /* _GLIB_ADDRESS_SANITIZER */ } #endif /* G_OS_UNIX */ static void test_launcher_environment (void) { GSubprocessLauncher *launcher; GError *error = NULL; GSubprocess *proc; GPtrArray *args; gchar *out; g_setenv ("A", "B", TRUE); g_setenv ("C", "D", TRUE); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); /* unset a variable */ g_subprocess_launcher_unsetenv (launcher, "A"); /* and set a different one */ g_subprocess_launcher_setenv (launcher, "E", "F", TRUE); args = get_test_subprocess_args ("printenv", "A", "C", "E", NULL); proc = g_subprocess_launcher_spawnv (launcher, (const gchar **) args->pdata, &error); g_assert_no_error (error); g_assert_nonnull (proc); g_subprocess_communicate_utf8 (proc, NULL, NULL, &out, NULL, &error); g_assert_no_error (error); g_assert_cmpstr (out, ==, "C=D" LINEEND "E=F" LINEEND); g_free (out); g_object_unref (proc); g_object_unref (launcher); g_ptr_array_unref (args); } int main (int argc, char **argv) { const struct { const gchar *subtest; GSubprocessFlags flags; } flags_vectors[] = { { "", G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE }, { "/no-pipes", G_SUBPROCESS_FLAGS_NONE }, { "/separate-stderr", G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE }, { "/stdout-only", G_SUBPROCESS_FLAGS_STDOUT_PIPE }, { "/stderr-only", G_SUBPROCESS_FLAGS_STDERR_PIPE }, { "/stdout-silence", G_SUBPROCESS_FLAGS_STDOUT_SILENCE }, }; gsize i; g_test_init (&argc, &argv, NULL); g_test_add_func ("/gsubprocess/noop", test_noop); g_test_add_func ("/gsubprocess/noop-all-to-null", test_noop_all_to_null); g_test_add_func ("/gsubprocess/noop-no-wait", test_noop_no_wait); g_test_add_func ("/gsubprocess/noop-stdin-inherit", test_noop_stdin_inherit); #ifdef G_OS_UNIX g_test_add_func ("/gsubprocess/search-path", test_search_path); g_test_add_func ("/gsubprocess/search-path-from-envp", test_search_path_from_envp); g_test_add_func ("/gsubprocess/signal", test_signal); #endif g_test_add_func ("/gsubprocess/exit1", test_exit1); g_test_add_func ("/gsubprocess/exit1/cancel", test_exit1_cancel); g_test_add_func ("/gsubprocess/exit1/cancel_in_cb", test_exit1_cancel_in_cb); g_test_add_func ("/gsubprocess/echo1", test_echo1); #ifdef G_OS_UNIX g_test_add_func ("/gsubprocess/echo-merged", test_echo_merged); #endif g_test_add_func ("/gsubprocess/cat-utf8", test_cat_utf8); g_test_add_func ("/gsubprocess/cat-eof", test_cat_eof); g_test_add_func ("/gsubprocess/multi1", test_multi_1); /* Add various tests for g_subprocess_communicate() with different flags. */ for (i = 0; i < G_N_ELEMENTS (flags_vectors); i++) { gchar *test_path = NULL; test_path = g_strdup_printf ("/gsubprocess/communicate%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate); g_free (test_path); test_path = g_strdup_printf ("/gsubprocess/communicate/cancelled%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate_cancelled); g_free (test_path); test_path = g_strdup_printf ("/gsubprocess/communicate/async%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate_async); g_free (test_path); test_path = g_strdup_printf ("/gsubprocess/communicate/async/cancelled%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate_cancelled_async); g_free (test_path); test_path = g_strdup_printf ("/gsubprocess/communicate/utf8%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate_utf8); g_free (test_path); test_path = g_strdup_printf ("/gsubprocess/communicate/utf8/cancelled%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate_utf8_cancelled); g_free (test_path); test_path = g_strdup_printf ("/gsubprocess/communicate/utf8/async%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate_utf8_async); g_free (test_path); test_path = g_strdup_printf ("/gsubprocess/communicate/utf8/async/cancelled%s", flags_vectors[i].subtest); g_test_add_data_func (test_path, GINT_TO_POINTER (flags_vectors[i].flags), test_communicate_utf8_cancelled_async); g_free (test_path); } g_test_add_func ("/gsubprocess/communicate/utf8/async/invalid", test_communicate_utf8_async_invalid); g_test_add_func ("/gsubprocess/communicate/utf8/invalid", test_communicate_utf8_invalid); g_test_add_func ("/gsubprocess/communicate/nothing", test_communicate_nothing); g_test_add_func ("/gsubprocess/terminate", test_terminate); g_test_add_func ("/gsubprocess/env", test_env); g_test_add_func ("/gsubprocess/env/inherit", test_env_inherit); g_test_add_func ("/gsubprocess/cwd", test_cwd); #ifdef G_OS_UNIX g_test_add_func ("/gsubprocess/launcher-close", test_subprocess_launcher_close); g_test_add_func ("/gsubprocess/stdout-file", test_stdout_file); g_test_add_func ("/gsubprocess/stdout-fd", test_stdout_fd); g_test_add_func ("/gsubprocess/child-setup", test_child_setup); g_test_add_func ("/gsubprocess/pass-fd/basic", test_pass_fd); g_test_add_func ("/gsubprocess/pass-fd/empty-child-setup", test_pass_fd_empty_child_setup); g_test_add_func ("/gsubprocess/pass-fd/inherit-fds", test_pass_fd_inherit_fds); g_test_add_func ("/gsubprocess/fd-conflation/basic", test_fd_conflation); g_test_add_func ("/gsubprocess/fd-conflation/empty-child-setup", test_fd_conflation_empty_child_setup); g_test_add_func ("/gsubprocess/fd-conflation/inherit-fds", test_fd_conflation_inherit_fds); g_test_add_func ("/gsubprocess/fd-conflation/child-err-report-fd", test_fd_conflation_child_err_report_fd); g_test_add_func ("/gsubprocess/exit-status/trapped", test_exit_status_trapped); #endif g_test_add_func ("/gsubprocess/launcher-environment", test_launcher_environment); return g_test_run (); }