2012-05-17 20:37:17 +02:00
|
|
|
|
#include <gio/gio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#ifdef G_OS_UNIX
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#include <glib-unix.h>
|
|
|
|
|
#include <gio/gunixinputstream.h>
|
2021-12-14 20:36:31 +01:00
|
|
|
|
#include <gio/gunixoutputstream.h>
|
2012-05-17 20:37:17 +02:00
|
|
|
|
#include <gio/gfiledescriptorbased.h>
|
GSubprocessLauncher: allow to close passed FDs
By default, when using g_subprocess_launcher_take_fd() to pass an
FD to a child, the GSubprocessLauncher object also takes ownership
of the FD in the parent, and closes it during finalize(). This is
a reasonable assumption in the majority of the cases, but sometimes
it isn't a good idea.
An example is when creating a GSubprocessLauncher in JavaScript:
here, the destruction process is managed by the Garbage Collector,
which means that those sockets will remain opened for some time
after all the references to the object has been droped. This means
that it could be not possible to detect when the child has closed
that same FD, because in order to make that work, both FDs
instances (the one in the parent and the one in the children) must
be closed. This can be a problem in, as an example, a process that
launches a child that communicates with Wayland using an specific
socket (like when using the new API MetaWaylandClient).
Of course, it isn't a valid solution to manually call close() in
the parent process just after the call to spawn(), because the FD
number could be reused in the time between it is manually closed,
and when the object is destroyed and closes again that FD. If that
happens, it will close an incorrect FD.
One solution could be to call run_dispose() from Javascript on the
GSubprocessLauncher object, to force freeing the resources.
Unfortunately, the current code frees them in the finalize()
method, not in dispose() (this is fixed in !1670 (merged) ) but it
isn't a very elegant solution.
This proposal adds a new method, g_subprocess_launcher_close(),
that allows to close the FDs passed to the child. To avoid problems,
after closing an FD with this method, no more spawns are allowed.
Fix: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1677
2020-10-04 14:34:41 +02:00
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <fcntl.h>
|
2012-05-17 20:37:17 +02:00
|
|
|
|
#endif
|
|
|
|
|
|
2023-08-14 10:27:07 +02:00
|
|
|
|
#ifdef __linux__
|
|
|
|
|
#include <sys/ptrace.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-02-12 00:51:53 +01:00
|
|
|
|
/* 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"
|
|
|
|
|
|
2013-11-05 10:33:09 +01:00
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
|
#define LINEEND "\r\n"
|
|
|
|
|
#define EXEEXT ".exe"
|
2019-02-12 00:51:53 +01:00
|
|
|
|
#define SPLICELEN (TOTAL_HELLOS * (strlen (HELLO_WORLD) + 1)) /* because \r */
|
2013-11-05 10:33:09 +01:00
|
|
|
|
#else
|
|
|
|
|
#define LINEEND "\n"
|
|
|
|
|
#define EXEEXT
|
2019-02-12 00:51:53 +01:00
|
|
|
|
#define SPLICELEN (TOTAL_HELLOS * strlen (HELLO_WORLD))
|
2013-11-05 10:33:09 +01:00
|
|
|
|
#endif
|
|
|
|
|
|
2021-11-17 13:27:46 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
|
#define TESTPROG "gsubprocess-testprog.exe"
|
|
|
|
|
#else
|
|
|
|
|
#define TESTPROG "gsubprocess-testprog"
|
|
|
|
|
#endif
|
|
|
|
|
|
2012-05-17 20:37:17 +02:00
|
|
|
|
static GPtrArray *
|
|
|
|
|
get_test_subprocess_args (const char *mode,
|
|
|
|
|
...) G_GNUC_NULL_TERMINATED;
|
|
|
|
|
|
|
|
|
|
static GPtrArray *
|
|
|
|
|
get_test_subprocess_args (const char *mode,
|
|
|
|
|
...)
|
|
|
|
|
{
|
|
|
|
|
GPtrArray *ret;
|
2013-10-17 23:39:48 +02:00
|
|
|
|
char *path;
|
2012-05-17 20:37:17 +02:00
|
|
|
|
va_list args;
|
|
|
|
|
gpointer arg;
|
|
|
|
|
|
|
|
|
|
ret = g_ptr_array_new_with_free_func (g_free);
|
|
|
|
|
|
2021-11-17 13:27:46 +01:00
|
|
|
|
path = g_test_build_filename (G_TEST_BUILT, TESTPROG, NULL);
|
2013-10-17 23:39:48 +02:00
|
|
|
|
g_ptr_array_add (ret, path);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_true (g_subprocess_get_successful (proc));
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
g_object_unref (proc);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-23 18:08:08 +01:00
|
|
|
|
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);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_true (ret);
|
2013-12-23 18:08:08 +01:00
|
|
|
|
g_assert_no_error (error);
|
|
|
|
|
|
|
|
|
|
g_object_unref (source);
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2013-12-23 18:08:08 +01:00
|
|
|
|
g_subprocess_wait_check_async (proc, NULL, check_ready, NULL);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
2021-11-17 13:27:46 +01:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
2012-05-17 20:37:17 +02:00
|
|
|
|
#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);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-18 11:41:46 +02:00
|
|
|
|
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);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_false (ret);
|
2017-08-18 11:41:46 +02:00
|
|
|
|
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 };
|
|
|
|
|
|
2021-05-13 23:12:29 +02:00
|
|
|
|
g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=786456");
|
2017-08-18 11:41:46 +02:00
|
|
|
|
|
|
|
|
|
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);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_false (ret);
|
2017-08-18 11:41:46 +02:00
|
|
|
|
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 };
|
|
|
|
|
|
2021-05-13 23:12:29 +02:00
|
|
|
|
g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=786456");
|
2017-08-18 11:41:46 +02:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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;
|
2018-04-20 18:10:18 +02:00
|
|
|
|
GInputStream *stdout_stream;
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
stdout_stream = g_subprocess_get_stdout_pipe (proc);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
result = splice_to_string (stdout_stream, error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
g_assert_no_error (local_error);
|
|
|
|
|
|
2013-11-05 10:33:09 +01:00
|
|
|
|
g_assert_cmpstr (result, ==, "hello" LINEEND "world!" LINEEND);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
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;
|
2018-04-20 18:10:18 +02:00
|
|
|
|
GInputStream *stdout_stream;
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
stdout_stream = g_subprocess_get_stdout_pipe (proc);
|
|
|
|
|
result = splice_to_string (stdout_stream, error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2014-12-03 11:57:29 +01:00
|
|
|
|
g_assert_cmpmem (g_bytes_get_data (output_buf, NULL),
|
|
|
|
|
g_bytes_get_size (output_buf),
|
|
|
|
|
"hello, world!", 13);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2013-11-05 10:33:09 +01:00
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
|
g_test_skip ("This test has not been ported to Win32");
|
|
|
|
|
return;
|
|
|
|
|
#endif
|
|
|
|
|
|
2012-05-17 20:37:17 +02:00
|
|
|
|
/* Spawn 'cat' */
|
|
|
|
|
cat = g_subprocess_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error, "cat", NULL);
|
|
|
|
|
g_assert_no_error (error);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_nonnull (cat);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
/* 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);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_true (result);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
/* 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);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_false (s);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
/* Check that the process has exited as a result of the EOF */
|
|
|
|
|
result = g_subprocess_wait (cat, NULL, &error);
|
|
|
|
|
g_assert_no_error (error);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_true (g_subprocess_get_if_exited (cat));
|
2012-05-17 20:37:17 +02:00
|
|
|
|
g_assert_cmpint (g_subprocess_get_exit_status (cat), ==, 0);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_true (result);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2019-02-12 00:51:53 +01:00
|
|
|
|
if (data->counter >= TOTAL_HELLOS || data->caught_error)
|
2012-05-17 20:37:17 +02:00
|
|
|
|
{
|
|
|
|
|
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;
|
2019-02-12 00:51:53 +01:00
|
|
|
|
if (!g_output_stream_write_all (data->first_stdin, HELLO_WORLD,
|
|
|
|
|
strlen (HELLO_WORLD), &bytes_written,
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
Distinguish more clearly between wait status and exit status
On Unix platforms, wait() and friends yield an integer that encodes
how the process exited. Confusingly, this is usually not the same as
the integer passed to exit() or returned from main(): conceptually it's
an integer encoding of this tagged union:
enum { EXITED, SIGNALLED, ... } tag;
union {
int exit_status; /* if EXITED */
struct {
int terminating_signal;
bool core_dumped;
} terminating_signal; /* if SIGNALLED */
...
} detail;
Meanwhile, on Windows, wait statuses and exit statuses are
interchangeable.
I find that it's clearer what is going on if we are consistent about
referring to the result of wait() as a "wait status", and the value
passed to exit() as an "exit status".
GSubprocess already gets this right: g_subprocess_get_status() returns
the wait status, while g_subprocess_get_exit_status() genuinely returns
the exit status. However, the GSpawn family of APIs has tended to
conflate the two.
Confusingly, g_spawn_check_exit_status() has always checked a wait
status, and it would not be correct to pass an exit status to it; so
let's deprecate it in favour of g_spawn_check_wait_status(), which
does the same thing that g_spawn_check_exit_status() always did.
Code that needs backwards-compatibility with older GLib can use:
#if !GLIB_CHECK_VERSION(2, 69, 0)
#define g_spawn_check_wait_status(x) (g_spawn_check_exit_status (x))
#endif
Signed-off-by: Simon McVittie <smcv@collabora.com>
2021-06-14 13:51:01 +02:00
|
|
|
|
g_spawn_check_wait_status (g_subprocess_get_status (subprocess), &error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_false (data.caught_error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
g_assert_no_error (data.error);
|
|
|
|
|
|
2019-02-12 00:51:53 +01:00
|
|
|
|
g_assert_cmpint (g_memory_output_stream_get_data_size ((GMemoryOutputStream*)membuf), ==, SPLICELEN);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-15 01:12:22 +02:00
|
|
|
|
typedef struct {
|
2018-02-09 13:25:54 +01:00
|
|
|
|
GSubprocessFlags flags;
|
2013-10-15 16:04:54 +02:00
|
|
|
|
gboolean is_utf8;
|
2013-10-15 01:12:22 +02:00
|
|
|
|
gboolean running;
|
|
|
|
|
GError *error;
|
|
|
|
|
} TestAsyncCommunicateData;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
on_communicate_complete (GObject *proc,
|
|
|
|
|
GAsyncResult *result,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
TestAsyncCommunicateData *data = user_data;
|
2018-02-09 13:25:54 +01:00
|
|
|
|
GBytes *stdout_bytes = NULL, *stderr_bytes = NULL;
|
|
|
|
|
char *stdout_str = NULL, *stderr_str = NULL;
|
2013-10-15 01:12:22 +02:00
|
|
|
|
const guint8 *stdout_data;
|
|
|
|
|
gsize stdout_len;
|
|
|
|
|
|
|
|
|
|
data->running = FALSE;
|
2013-10-15 16:04:54 +02:00
|
|
|
|
if (data->is_utf8)
|
|
|
|
|
(void) g_subprocess_communicate_utf8_finish ((GSubprocess*)proc, result,
|
2018-02-09 13:25:54 +01:00
|
|
|
|
&stdout_str, &stderr_str, &data->error);
|
2013-10-15 16:04:54 +02:00
|
|
|
|
else
|
|
|
|
|
(void) g_subprocess_communicate_finish ((GSubprocess*)proc, result,
|
2018-02-09 13:25:54 +01:00
|
|
|
|
&stdout_bytes, &stderr_bytes, &data->error);
|
2013-11-09 08:53:13 +01:00
|
|
|
|
if (data->error)
|
2013-10-15 16:04:54 +02:00
|
|
|
|
return;
|
2013-10-15 01:12:22 +02:00
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
if (data->flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE)
|
2013-10-15 16:04:54 +02:00
|
|
|
|
{
|
2018-02-09 13:25:54 +01:00
|
|
|
|
if (data->is_utf8)
|
|
|
|
|
{
|
2022-11-18 17:34:06 +01:00
|
|
|
|
g_assert_nonnull (stdout_str);
|
2018-02-09 13:25:54 +01:00
|
|
|
|
stdout_data = (guint8*)stdout_str;
|
|
|
|
|
stdout_len = strlen (stdout_str);
|
|
|
|
|
}
|
|
|
|
|
else
|
2022-11-18 17:34:06 +01:00
|
|
|
|
{
|
|
|
|
|
g_assert_nonnull (stdout_bytes);
|
|
|
|
|
stdout_data = g_bytes_get_data (stdout_bytes, &stdout_len);
|
|
|
|
|
}
|
2018-02-09 13:25:54 +01:00
|
|
|
|
|
2019-02-12 00:51:53 +01:00
|
|
|
|
g_assert_cmpmem (stdout_data, stdout_len, "# hello world" LINEEND, 13 + strlen (LINEEND));
|
2018-02-09 13:25:54 +01:00
|
|
|
|
}
|
|
|
|
|
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);
|
2013-10-15 16:04:54 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-02-09 13:25:54 +01:00
|
|
|
|
g_assert_null (stderr_str);
|
|
|
|
|
g_assert_null (stderr_bytes);
|
2013-10-15 16:04:54 +02:00
|
|
|
|
}
|
2013-10-15 01:12:22 +02:00
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
g_clear_pointer (&stdout_bytes, g_bytes_unref);
|
|
|
|
|
g_clear_pointer (&stderr_bytes, g_bytes_unref);
|
2013-10-15 16:04:54 +02:00
|
|
|
|
g_free (stdout_str);
|
2018-02-09 13:25:54 +01:00
|
|
|
|
g_free (stderr_str);
|
2013-10-15 01:12:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
/* Test g_subprocess_communicate_async() works correctly with a variety of flags,
|
|
|
|
|
* as passed in via @test_data. */
|
2013-10-15 01:12:22 +02:00
|
|
|
|
static void
|
2018-02-09 13:25:54 +01:00
|
|
|
|
test_communicate_async (gconstpointer test_data)
|
2013-10-15 01:12:22 +02:00
|
|
|
|
{
|
2018-02-09 13:25:54 +01:00
|
|
|
|
GSubprocessFlags flags = GPOINTER_TO_INT (test_data);
|
2013-11-09 08:53:13 +01:00
|
|
|
|
GError *error = NULL;
|
2013-10-15 01:12:22 +02:00
|
|
|
|
GPtrArray *args;
|
2020-11-18 21:07:23 +01:00
|
|
|
|
TestAsyncCommunicateData data = { flags, 0, 0, NULL };
|
2013-10-15 01:12:22 +02:00
|
|
|
|
GSubprocess *proc;
|
|
|
|
|
GCancellable *cancellable = NULL;
|
|
|
|
|
GBytes *input;
|
2013-10-15 16:04:54 +02:00
|
|
|
|
const char *hellostring;
|
2013-10-15 01:12:22 +02:00
|
|
|
|
|
|
|
|
|
args = get_test_subprocess_args ("cat", NULL);
|
|
|
|
|
proc = g_subprocess_newv ((const gchar* const*)args->pdata,
|
2018-02-09 13:25:54 +01:00
|
|
|
|
G_SUBPROCESS_FLAGS_STDIN_PIPE | flags,
|
2013-11-09 08:53:13 +01:00
|
|
|
|
&error);
|
|
|
|
|
g_assert_no_error (error);
|
2013-10-15 01:12:22 +02:00
|
|
|
|
g_ptr_array_free (args, TRUE);
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
/* 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";
|
2013-10-15 16:04:54 +02:00
|
|
|
|
input = g_bytes_new_static (hellostring, strlen (hellostring));
|
2013-10-15 01:12:22 +02:00
|
|
|
|
|
|
|
|
|
g_subprocess_communicate_async (proc, input,
|
|
|
|
|
cancellable,
|
|
|
|
|
on_communicate_complete,
|
|
|
|
|
&data);
|
|
|
|
|
|
|
|
|
|
data.running = TRUE;
|
|
|
|
|
while (data.running)
|
|
|
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
|
|
2013-11-09 08:53:13 +01:00
|
|
|
|
g_assert_no_error (data.error);
|
2013-10-15 01:12:22 +02:00
|
|
|
|
|
2013-11-09 08:54:28 +01:00
|
|
|
|
g_bytes_unref (input);
|
2013-10-15 01:12:22 +02:00
|
|
|
|
g_object_unref (proc);
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
/* Test g_subprocess_communicate() works correctly with a variety of flags,
|
|
|
|
|
* as passed in via @test_data. */
|
2013-10-15 16:04:54 +02:00
|
|
|
|
static void
|
2018-02-09 13:25:54 +01:00
|
|
|
|
test_communicate (gconstpointer test_data)
|
2013-12-23 18:08:08 +01:00
|
|
|
|
{
|
2018-02-09 13:25:54 +01:00
|
|
|
|
GSubprocessFlags flags = GPOINTER_TO_INT (test_data);
|
2013-12-23 18:08:08 +01:00
|
|
|
|
GError *error = NULL;
|
|
|
|
|
GPtrArray *args;
|
|
|
|
|
GSubprocess *proc;
|
|
|
|
|
GCancellable *cancellable = NULL;
|
|
|
|
|
GBytes *input;
|
|
|
|
|
const gchar *hellostring;
|
2018-02-09 13:25:54 +01:00
|
|
|
|
GBytes *stdout_bytes, *stderr_bytes;
|
2013-12-23 18:08:08 +01:00
|
|
|
|
const gchar *stdout_data;
|
|
|
|
|
gsize stdout_len;
|
|
|
|
|
|
|
|
|
|
args = get_test_subprocess_args ("cat", NULL);
|
|
|
|
|
proc = g_subprocess_newv ((const gchar* const*)args->pdata,
|
2018-02-09 13:25:54 +01:00
|
|
|
|
G_SUBPROCESS_FLAGS_STDIN_PIPE | flags,
|
2013-12-23 18:08:08 +01:00
|
|
|
|
&error);
|
|
|
|
|
g_assert_no_error (error);
|
|
|
|
|
g_ptr_array_free (args, TRUE);
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
/* 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";
|
2013-12-23 18:08:08 +01:00
|
|
|
|
input = g_bytes_new_static (hellostring, strlen (hellostring));
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
g_subprocess_communicate (proc, input, cancellable, &stdout_bytes, &stderr_bytes, &error);
|
2013-12-23 18:08:08 +01:00
|
|
|
|
g_assert_no_error (error);
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
if (flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE)
|
|
|
|
|
{
|
|
|
|
|
stdout_data = g_bytes_get_data (stdout_bytes, &stdout_len);
|
2019-02-12 00:51:53 +01:00
|
|
|
|
g_assert_cmpmem (stdout_data, stdout_len, "# hello world" LINEEND, 13 + strlen (LINEEND));
|
2018-02-09 13:25:54 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
g_assert_null (stdout_bytes);
|
|
|
|
|
if (flags & G_SUBPROCESS_FLAGS_STDERR_PIPE)
|
|
|
|
|
g_assert_nonnull (stderr_bytes);
|
|
|
|
|
else
|
|
|
|
|
g_assert_null (stderr_bytes);
|
2017-08-23 12:24:32 +02:00
|
|
|
|
|
2013-12-23 18:08:08 +01:00
|
|
|
|
g_bytes_unref (input);
|
2018-02-09 13:25:54 +01:00
|
|
|
|
g_clear_pointer (&stdout_bytes, g_bytes_unref);
|
|
|
|
|
g_clear_pointer (&stderr_bytes, g_bytes_unref);
|
2013-12-23 18:08:08 +01:00
|
|
|
|
g_object_unref (proc);
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 15:56:35 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
/* Test g_subprocess_communicate_utf8_async() works correctly with a variety of
|
|
|
|
|
* flags, as passed in via @test_data. */
|
2013-12-23 18:08:08 +01:00
|
|
|
|
static void
|
2018-02-09 13:25:54 +01:00
|
|
|
|
test_communicate_utf8_async (gconstpointer test_data)
|
2013-10-15 16:04:54 +02:00
|
|
|
|
{
|
2018-02-09 13:25:54 +01:00
|
|
|
|
GSubprocessFlags flags = GPOINTER_TO_INT (test_data);
|
2013-11-09 08:53:13 +01:00
|
|
|
|
GError *error = NULL;
|
2013-10-15 16:04:54 +02:00
|
|
|
|
GPtrArray *args;
|
2020-11-18 21:07:23 +01:00
|
|
|
|
TestAsyncCommunicateData data = { flags, 0, 0, NULL };
|
2013-10-15 16:04:54 +02:00
|
|
|
|
GSubprocess *proc;
|
|
|
|
|
GCancellable *cancellable = NULL;
|
|
|
|
|
|
|
|
|
|
args = get_test_subprocess_args ("cat", NULL);
|
|
|
|
|
proc = g_subprocess_newv ((const gchar* const*)args->pdata,
|
2018-02-09 13:25:54 +01:00
|
|
|
|
G_SUBPROCESS_FLAGS_STDIN_PIPE | flags,
|
2013-11-09 08:53:13 +01:00
|
|
|
|
&error);
|
|
|
|
|
g_assert_no_error (error);
|
2013-10-15 16:04:54 +02:00
|
|
|
|
g_ptr_array_free (args, TRUE);
|
|
|
|
|
|
|
|
|
|
data.is_utf8 = TRUE;
|
2018-02-09 13:25:54 +01:00
|
|
|
|
g_subprocess_communicate_utf8_async (proc, "# hello world\n",
|
2013-10-15 16:04:54 +02:00
|
|
|
|
cancellable,
|
|
|
|
|
on_communicate_complete,
|
|
|
|
|
&data);
|
|
|
|
|
|
|
|
|
|
data.running = TRUE;
|
|
|
|
|
while (data.running)
|
|
|
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
|
|
2013-11-09 08:53:13 +01:00
|
|
|
|
g_assert_no_error (data.error);
|
2013-10-15 16:04:54 +02:00
|
|
|
|
|
|
|
|
|
g_object_unref (proc);
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-12 15:02:30 +02:00
|
|
|
|
/* Test g_subprocess_communicate_utf8_async() can be cancelled correctly. */
|
2018-08-27 15:56:35 +02:00
|
|
|
|
static void
|
|
|
|
|
test_communicate_utf8_cancelled_async (gconstpointer test_data)
|
|
|
|
|
{
|
|
|
|
|
GSubprocessFlags flags = GPOINTER_TO_INT (test_data);
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
GPtrArray *args;
|
2020-11-18 21:07:23 +01:00
|
|
|
|
TestAsyncCommunicateData data = { flags, 0, 0, NULL };
|
2018-08-27 15:56:35 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
/* Test g_subprocess_communicate_utf8() works correctly with a variety of flags,
|
|
|
|
|
* as passed in via @test_data. */
|
2013-12-23 18:08:08 +01:00
|
|
|
|
static void
|
2018-02-09 13:25:54 +01:00
|
|
|
|
test_communicate_utf8 (gconstpointer test_data)
|
2013-12-23 18:08:08 +01:00
|
|
|
|
{
|
2018-02-09 13:25:54 +01:00
|
|
|
|
GSubprocessFlags flags = GPOINTER_TO_INT (test_data);
|
2013-12-23 18:08:08 +01:00
|
|
|
|
GError *error = NULL;
|
|
|
|
|
GPtrArray *args;
|
|
|
|
|
GSubprocess *proc;
|
|
|
|
|
GCancellable *cancellable = NULL;
|
|
|
|
|
const gchar *stdin_buf;
|
2018-02-09 13:25:54 +01:00
|
|
|
|
gchar *stdout_buf, *stderr_buf;
|
2013-12-23 18:08:08 +01:00
|
|
|
|
|
|
|
|
|
args = get_test_subprocess_args ("cat", NULL);
|
|
|
|
|
proc = g_subprocess_newv ((const gchar* const*)args->pdata,
|
2018-02-09 13:25:54 +01:00
|
|
|
|
G_SUBPROCESS_FLAGS_STDIN_PIPE | flags,
|
2013-12-23 18:08:08 +01:00
|
|
|
|
&error);
|
|
|
|
|
g_assert_no_error (error);
|
|
|
|
|
g_ptr_array_free (args, TRUE);
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
/* 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";
|
2013-12-23 18:08:08 +01:00
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
g_subprocess_communicate_utf8 (proc, stdin_buf, cancellable, &stdout_buf, &stderr_buf, &error);
|
2013-12-23 18:08:08 +01:00
|
|
|
|
g_assert_no_error (error);
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
if (flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE)
|
2019-02-12 00:51:53 +01:00
|
|
|
|
g_assert_cmpstr (stdout_buf, ==, "# hello world" LINEEND);
|
2018-02-09 13:25:54 +01:00
|
|
|
|
else
|
|
|
|
|
g_assert_null (stdout_buf);
|
|
|
|
|
if (flags & G_SUBPROCESS_FLAGS_STDERR_PIPE)
|
|
|
|
|
g_assert_nonnull (stderr_buf);
|
|
|
|
|
else g_assert_null (stderr_buf);
|
|
|
|
|
|
2013-12-23 18:08:08 +01:00
|
|
|
|
g_free (stdout_buf);
|
2018-02-09 13:25:54 +01:00
|
|
|
|
g_free (stderr_buf);
|
2013-12-23 18:08:08 +01:00
|
|
|
|
g_object_unref (proc);
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 15:56:35 +02:00
|
|
|
|
/* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-04 03:25:16 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-15 16:04:54 +02:00
|
|
|
|
static void
|
2018-10-11 00:29:17 +02:00
|
|
|
|
test_communicate_utf8_async_invalid (void)
|
2013-10-15 16:04:54 +02:00
|
|
|
|
{
|
2018-02-09 13:25:54 +01:00
|
|
|
|
GSubprocessFlags flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE;
|
2013-11-09 08:53:13 +01:00
|
|
|
|
GError *error = NULL;
|
2013-10-15 16:04:54 +02:00
|
|
|
|
GPtrArray *args;
|
2020-11-18 21:07:23 +01:00
|
|
|
|
TestAsyncCommunicateData data = { flags, 0, 0, NULL };
|
2013-10-15 16:04:54 +02:00
|
|
|
|
GSubprocess *proc;
|
|
|
|
|
GCancellable *cancellable = NULL;
|
|
|
|
|
|
|
|
|
|
args = get_test_subprocess_args ("cat", NULL);
|
|
|
|
|
proc = g_subprocess_newv ((const gchar* const*)args->pdata,
|
2018-02-09 13:25:54 +01:00
|
|
|
|
G_SUBPROCESS_FLAGS_STDIN_PIPE | flags,
|
2013-11-09 08:53:13 +01:00
|
|
|
|
&error);
|
|
|
|
|
g_assert_no_error (error);
|
2013-10-15 16:04:54 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2013-11-09 08:53:13 +01:00
|
|
|
|
g_assert_error (data.error, G_IO_ERROR, G_IO_ERROR_FAILED);
|
|
|
|
|
g_error_free (data.error);
|
2013-10-15 16:04:54 +02:00
|
|
|
|
|
|
|
|
|
g_object_unref (proc);
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-11 00:29:17 +02:00
|
|
|
|
/* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-16 21:49:10 +02:00
|
|
|
|
static void
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_true (g_subprocess_get_if_signaled (subprocess));
|
|
|
|
|
g_assert_cmpint (g_subprocess_get_term_sig (subprocess), ==, 9);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
#endif
|
Distinguish more clearly between wait status and exit status
On Unix platforms, wait() and friends yield an integer that encodes
how the process exited. Confusingly, this is usually not the same as
the integer passed to exit() or returned from main(): conceptually it's
an integer encoding of this tagged union:
enum { EXITED, SIGNALLED, ... } tag;
union {
int exit_status; /* if EXITED */
struct {
int terminating_signal;
bool core_dumped;
} terminating_signal; /* if SIGNALLED */
...
} detail;
Meanwhile, on Windows, wait statuses and exit statuses are
interchangeable.
I find that it's clearer what is going on if we are consistent about
referring to the result of wait() as a "wait status", and the value
passed to exit() as an "exit status".
GSubprocess already gets this right: g_subprocess_get_status() returns
the wait status, while g_subprocess_get_exit_status() genuinely returns
the exit status. However, the GSpawn family of APIs has tended to
conflate the two.
Confusingly, g_spawn_check_exit_status() has always checked a wait
status, and it would not be correct to pass an exit status to it; so
let's deprecate it in favour of g_spawn_check_wait_status(), which
does the same thing that g_spawn_check_exit_status() always did.
Code that needs backwards-compatibility with older GLib can use:
#if !GLIB_CHECK_VERSION(2, 69, 0)
#define g_spawn_check_wait_status(x) (g_spawn_check_exit_status (x))
#endif
Signed-off-by: Simon McVittie <smcv@collabora.com>
2021-06-14 13:51:01 +02:00
|
|
|
|
g_spawn_check_wait_status (g_subprocess_get_status (subprocess), &error);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_nonnull (error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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;
|
2019-06-01 00:35:27 +02:00
|
|
|
|
const gchar *id;
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2019-06-01 00:35:27 +02:00
|
|
|
|
id = g_subprocess_get_identifier (proc);
|
|
|
|
|
g_assert_nonnull (id);
|
|
|
|
|
|
2012-05-17 20:37:17 +02:00
|
|
|
|
loop = g_main_loop_new (NULL, TRUE);
|
|
|
|
|
|
|
|
|
|
g_subprocess_wait_async (proc, NULL, on_request_quit_exited, loop);
|
|
|
|
|
|
2023-04-16 21:49:10 +02:00
|
|
|
|
g_timeout_add_seconds_once (3, send_terminate, proc);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
|
|
|
|
|
|
g_main_loop_unref (loop);
|
|
|
|
|
g_object_unref (proc);
|
|
|
|
|
}
|
|
|
|
|
|
2013-12-23 18:08:08 +01:00
|
|
|
|
#ifdef G_OS_UNIX
|
2023-04-16 21:49:10 +02:00
|
|
|
|
static void
|
2013-12-23 18:08:08 +01:00
|
|
|
|
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);
|
|
|
|
|
|
2023-04-16 21:49:10 +02:00
|
|
|
|
g_timeout_add_seconds_once (3, send_signal, proc);
|
2013-12-23 18:08:08 +01:00
|
|
|
|
|
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
|
|
|
|
|
|
g_main_loop_unref (loop);
|
|
|
|
|
g_object_unref (proc);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2013-11-28 07:12:26 +01:00
|
|
|
|
static void
|
|
|
|
|
test_env (void)
|
|
|
|
|
{
|
|
|
|
|
GError *local_error = NULL;
|
|
|
|
|
GError **error = &local_error;
|
|
|
|
|
GSubprocessLauncher *launcher;
|
|
|
|
|
GSubprocess *proc;
|
|
|
|
|
GPtrArray *args;
|
2018-04-20 18:10:18 +02:00
|
|
|
|
GInputStream *stdout_stream;
|
2013-11-28 07:12:26 +01:00
|
|
|
|
gchar *result;
|
2019-02-12 00:53:38 +01:00
|
|
|
|
gchar *envp[] = { NULL, "ONE=1", "TWO=1", "THREE=3", "FOUR=1", NULL };
|
2013-11-28 07:12:26 +01:00
|
|
|
|
gchar **split;
|
|
|
|
|
|
2019-02-12 00:53:38 +01:00
|
|
|
|
envp[0] = g_strdup_printf ("PATH=%s", g_getenv ("PATH"));
|
2013-11-28 07:12:26 +01:00
|
|
|
|
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"));
|
|
|
|
|
|
2013-11-28 07:36:29 +01:00
|
|
|
|
proc = g_subprocess_launcher_spawn (launcher, error, args->pdata[0], "env", NULL);
|
2013-11-28 07:12:26 +01:00
|
|
|
|
g_ptr_array_free (args, TRUE);
|
|
|
|
|
g_assert_no_error (local_error);
|
2019-02-12 00:53:38 +01:00
|
|
|
|
g_free (envp[0]);
|
2013-11-28 07:12:26 +01:00
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
stdout_stream = g_subprocess_get_stdout_pipe (proc);
|
2013-11-28 07:12:26 +01:00
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
result = splice_to_string (stdout_stream, error);
|
2019-02-12 00:51:53 +01:00
|
|
|
|
split = g_strsplit (result, LINEEND, -1);
|
2013-11-28 07:12:26 +01:00
|
|
|
|
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);
|
2017-08-23 12:24:32 +02:00
|
|
|
|
g_object_unref (launcher);
|
2013-11-28 07:12:26 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-30 12:02:14 +02:00
|
|
|
|
/* 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;
|
2018-04-20 18:10:18 +02:00
|
|
|
|
GInputStream *stdout_stream;
|
2017-03-30 12:02:14 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
stdout_stream = g_subprocess_get_stdout_pipe (proc);
|
2017-03-30 12:02:14 +02:00
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
result = splice_to_string (stdout_stream, error);
|
2019-02-12 00:51:53 +01:00
|
|
|
|
split = g_strsplit (result, LINEEND, -1);
|
2017-03-30 12:02:14 +02:00
|
|
|
|
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);
|
2017-08-23 12:24:32 +02:00
|
|
|
|
g_object_unref (launcher);
|
2017-03-30 12:02:14 +02:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-28 18:59:11 +01:00
|
|
|
|
static void
|
|
|
|
|
test_cwd (void)
|
|
|
|
|
{
|
|
|
|
|
GError *local_error = NULL;
|
|
|
|
|
GSubprocessLauncher *launcher;
|
|
|
|
|
GSubprocess *proc;
|
|
|
|
|
GPtrArray *args;
|
2018-04-20 18:10:18 +02:00
|
|
|
|
GInputStream *stdout_stream;
|
2013-11-28 18:59:11 +01:00
|
|
|
|
gchar *result;
|
2022-06-27 16:00:39 +02:00
|
|
|
|
gsize result_len;
|
|
|
|
|
const gchar *tmpdir = g_get_tmp_dir ();
|
|
|
|
|
gchar *tmpdir_basename = NULL, *result_basename = NULL;
|
2013-11-28 18:59:11 +01:00
|
|
|
|
|
|
|
|
|
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);
|
2022-06-27 16:00:39 +02:00
|
|
|
|
g_subprocess_launcher_set_cwd (launcher, tmpdir);
|
2013-11-28 18:59:11 +01:00
|
|
|
|
|
2022-06-27 16:00:39 +02:00
|
|
|
|
proc = g_subprocess_launcher_spawnv (launcher, (const char * const *)args->pdata, &local_error);
|
2013-11-28 18:59:11 +01:00
|
|
|
|
g_ptr_array_free (args, TRUE);
|
|
|
|
|
g_assert_no_error (local_error);
|
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
stdout_stream = g_subprocess_get_stdout_pipe (proc);
|
2013-11-28 18:59:11 +01:00
|
|
|
|
|
2022-06-27 16:00:39 +02:00
|
|
|
|
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);
|
2013-11-28 18:59:11 +01:00
|
|
|
|
|
|
|
|
|
g_free (result);
|
|
|
|
|
g_object_unref (proc);
|
2017-08-23 12:24:32 +02:00
|
|
|
|
g_object_unref (launcher);
|
2013-11-28 18:59:11 +01:00
|
|
|
|
}
|
2012-05-17 20:37:17 +02:00
|
|
|
|
#ifdef G_OS_UNIX
|
GSubprocessLauncher: allow to close passed FDs
By default, when using g_subprocess_launcher_take_fd() to pass an
FD to a child, the GSubprocessLauncher object also takes ownership
of the FD in the parent, and closes it during finalize(). This is
a reasonable assumption in the majority of the cases, but sometimes
it isn't a good idea.
An example is when creating a GSubprocessLauncher in JavaScript:
here, the destruction process is managed by the Garbage Collector,
which means that those sockets will remain opened for some time
after all the references to the object has been droped. This means
that it could be not possible to detect when the child has closed
that same FD, because in order to make that work, both FDs
instances (the one in the parent and the one in the children) must
be closed. This can be a problem in, as an example, a process that
launches a child that communicates with Wayland using an specific
socket (like when using the new API MetaWaylandClient).
Of course, it isn't a valid solution to manually call close() in
the parent process just after the call to spawn(), because the FD
number could be reused in the time between it is manually closed,
and when the object is destroyed and closes again that FD. If that
happens, it will close an incorrect FD.
One solution could be to call run_dispose() from Javascript on the
GSubprocessLauncher object, to force freeing the resources.
Unfortunately, the current code frees them in the finalize()
method, not in dispose() (this is fixed in !1670 (merged) ) but it
isn't a very elegant solution.
This proposal adds a new method, g_subprocess_launcher_close(),
that allows to close the FDs passed to the child. To avoid problems,
after closing an FD with this method, no more spawns are allowed.
Fix: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1677
2020-10-04 14:34:41 +02:00
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_subprocess_launcher_close (void)
|
|
|
|
|
{
|
|
|
|
|
GError *local_error = NULL;
|
|
|
|
|
GError **error = &local_error;
|
|
|
|
|
GSubprocessLauncher *launcher;
|
|
|
|
|
GSubprocess *proc;
|
|
|
|
|
GPtrArray *args;
|
2021-02-19 19:09:42 +01:00
|
|
|
|
int fd, fd2;
|
GSubprocessLauncher: allow to close passed FDs
By default, when using g_subprocess_launcher_take_fd() to pass an
FD to a child, the GSubprocessLauncher object also takes ownership
of the FD in the parent, and closes it during finalize(). This is
a reasonable assumption in the majority of the cases, but sometimes
it isn't a good idea.
An example is when creating a GSubprocessLauncher in JavaScript:
here, the destruction process is managed by the Garbage Collector,
which means that those sockets will remain opened for some time
after all the references to the object has been droped. This means
that it could be not possible to detect when the child has closed
that same FD, because in order to make that work, both FDs
instances (the one in the parent and the one in the children) must
be closed. This can be a problem in, as an example, a process that
launches a child that communicates with Wayland using an specific
socket (like when using the new API MetaWaylandClient).
Of course, it isn't a valid solution to manually call close() in
the parent process just after the call to spawn(), because the FD
number could be reused in the time between it is manually closed,
and when the object is destroyed and closes again that FD. If that
happens, it will close an incorrect FD.
One solution could be to call run_dispose() from Javascript on the
GSubprocessLauncher object, to force freeing the resources.
Unfortunately, the current code frees them in the finalize()
method, not in dispose() (this is fixed in !1670 (merged) ) but it
isn't a very elegant solution.
This proposal adds a new method, g_subprocess_launcher_close(),
that allows to close the FDs passed to the child. To avoid problems,
after closing an FD with this method, no more spawns are allowed.
Fix: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1677
2020-10-04 14:34:41 +02:00
|
|
|
|
gboolean is_open;
|
|
|
|
|
|
2021-02-19 19:09:42 +01:00
|
|
|
|
/* 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);
|
GSubprocessLauncher: allow to close passed FDs
By default, when using g_subprocess_launcher_take_fd() to pass an
FD to a child, the GSubprocessLauncher object also takes ownership
of the FD in the parent, and closes it during finalize(). This is
a reasonable assumption in the majority of the cases, but sometimes
it isn't a good idea.
An example is when creating a GSubprocessLauncher in JavaScript:
here, the destruction process is managed by the Garbage Collector,
which means that those sockets will remain opened for some time
after all the references to the object has been droped. This means
that it could be not possible to detect when the child has closed
that same FD, because in order to make that work, both FDs
instances (the one in the parent and the one in the children) must
be closed. This can be a problem in, as an example, a process that
launches a child that communicates with Wayland using an specific
socket (like when using the new API MetaWaylandClient).
Of course, it isn't a valid solution to manually call close() in
the parent process just after the call to spawn(), because the FD
number could be reused in the time between it is manually closed,
and when the object is destroyed and closes again that FD. If that
happens, it will close an incorrect FD.
One solution could be to call run_dispose() from Javascript on the
GSubprocessLauncher object, to force freeing the resources.
Unfortunately, the current code frees them in the finalize()
method, not in dispose() (this is fixed in !1670 (merged) ) but it
isn't a very elegant solution.
This proposal adds a new method, g_subprocess_launcher_close(),
that allows to close the FDs passed to the child. To avoid problems,
after closing an FD with this method, no more spawns are allowed.
Fix: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1677
2020-10-04 14:34:41 +02:00
|
|
|
|
launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
|
2021-02-19 19:09:42 +01:00
|
|
|
|
g_subprocess_launcher_take_fd (launcher, fd, fd2);
|
|
|
|
|
|
GSubprocessLauncher: allow to close passed FDs
By default, when using g_subprocess_launcher_take_fd() to pass an
FD to a child, the GSubprocessLauncher object also takes ownership
of the FD in the parent, and closes it during finalize(). This is
a reasonable assumption in the majority of the cases, but sometimes
it isn't a good idea.
An example is when creating a GSubprocessLauncher in JavaScript:
here, the destruction process is managed by the Garbage Collector,
which means that those sockets will remain opened for some time
after all the references to the object has been droped. This means
that it could be not possible to detect when the child has closed
that same FD, because in order to make that work, both FDs
instances (the one in the parent and the one in the children) must
be closed. This can be a problem in, as an example, a process that
launches a child that communicates with Wayland using an specific
socket (like when using the new API MetaWaylandClient).
Of course, it isn't a valid solution to manually call close() in
the parent process just after the call to spawn(), because the FD
number could be reused in the time between it is manually closed,
and when the object is destroyed and closes again that FD. If that
happens, it will close an incorrect FD.
One solution could be to call run_dispose() from Javascript on the
GSubprocessLauncher object, to force freeing the resources.
Unfortunately, the current code frees them in the finalize()
method, not in dispose() (this is fixed in !1670 (merged) ) but it
isn't a very elegant solution.
This proposal adds a new method, g_subprocess_launcher_close(),
that allows to close the FDs passed to the child. To avoid problems,
after closing an FD with this method, no more spawns are allowed.
Fix: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1677
2020-10-04 14:34:41 +02:00
|
|
|
|
is_open = fcntl (fd, F_GETFD) != -1;
|
|
|
|
|
g_assert_true (is_open);
|
2021-02-19 19:09:42 +01:00
|
|
|
|
is_open = fcntl (fd2, F_GETFD) != -1;
|
|
|
|
|
g_assert_true (is_open);
|
|
|
|
|
|
GSubprocessLauncher: allow to close passed FDs
By default, when using g_subprocess_launcher_take_fd() to pass an
FD to a child, the GSubprocessLauncher object also takes ownership
of the FD in the parent, and closes it during finalize(). This is
a reasonable assumption in the majority of the cases, but sometimes
it isn't a good idea.
An example is when creating a GSubprocessLauncher in JavaScript:
here, the destruction process is managed by the Garbage Collector,
which means that those sockets will remain opened for some time
after all the references to the object has been droped. This means
that it could be not possible to detect when the child has closed
that same FD, because in order to make that work, both FDs
instances (the one in the parent and the one in the children) must
be closed. This can be a problem in, as an example, a process that
launches a child that communicates with Wayland using an specific
socket (like when using the new API MetaWaylandClient).
Of course, it isn't a valid solution to manually call close() in
the parent process just after the call to spawn(), because the FD
number could be reused in the time between it is manually closed,
and when the object is destroyed and closes again that FD. If that
happens, it will close an incorrect FD.
One solution could be to call run_dispose() from Javascript on the
GSubprocessLauncher object, to force freeing the resources.
Unfortunately, the current code frees them in the finalize()
method, not in dispose() (this is fixed in !1670 (merged) ) but it
isn't a very elegant solution.
This proposal adds a new method, g_subprocess_launcher_close(),
that allows to close the FDs passed to the child. To avoid problems,
after closing an FD with this method, no more spawns are allowed.
Fix: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1677
2020-10-04 14:34:41 +02:00
|
|
|
|
g_subprocess_launcher_close (launcher);
|
2021-02-19 19:09:42 +01:00
|
|
|
|
|
GSubprocessLauncher: allow to close passed FDs
By default, when using g_subprocess_launcher_take_fd() to pass an
FD to a child, the GSubprocessLauncher object also takes ownership
of the FD in the parent, and closes it during finalize(). This is
a reasonable assumption in the majority of the cases, but sometimes
it isn't a good idea.
An example is when creating a GSubprocessLauncher in JavaScript:
here, the destruction process is managed by the Garbage Collector,
which means that those sockets will remain opened for some time
after all the references to the object has been droped. This means
that it could be not possible to detect when the child has closed
that same FD, because in order to make that work, both FDs
instances (the one in the parent and the one in the children) must
be closed. This can be a problem in, as an example, a process that
launches a child that communicates with Wayland using an specific
socket (like when using the new API MetaWaylandClient).
Of course, it isn't a valid solution to manually call close() in
the parent process just after the call to spawn(), because the FD
number could be reused in the time between it is manually closed,
and when the object is destroyed and closes again that FD. If that
happens, it will close an incorrect FD.
One solution could be to call run_dispose() from Javascript on the
GSubprocessLauncher object, to force freeing the resources.
Unfortunately, the current code frees them in the finalize()
method, not in dispose() (this is fixed in !1670 (merged) ) but it
isn't a very elegant solution.
This proposal adds a new method, g_subprocess_launcher_close(),
that allows to close the FDs passed to the child. To avoid problems,
after closing an FD with this method, no more spawns are allowed.
Fix: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1677
2020-10-04 14:34:41 +02:00
|
|
|
|
is_open = fcntl (fd, F_GETFD) != -1;
|
|
|
|
|
g_assert_false (is_open);
|
2021-02-19 19:09:42 +01:00
|
|
|
|
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. */
|
GSubprocessLauncher: allow to close passed FDs
By default, when using g_subprocess_launcher_take_fd() to pass an
FD to a child, the GSubprocessLauncher object also takes ownership
of the FD in the parent, and closes it during finalize(). This is
a reasonable assumption in the majority of the cases, but sometimes
it isn't a good idea.
An example is when creating a GSubprocessLauncher in JavaScript:
here, the destruction process is managed by the Garbage Collector,
which means that those sockets will remain opened for some time
after all the references to the object has been droped. This means
that it could be not possible to detect when the child has closed
that same FD, because in order to make that work, both FDs
instances (the one in the parent and the one in the children) must
be closed. This can be a problem in, as an example, a process that
launches a child that communicates with Wayland using an specific
socket (like when using the new API MetaWaylandClient).
Of course, it isn't a valid solution to manually call close() in
the parent process just after the call to spawn(), because the FD
number could be reused in the time between it is manually closed,
and when the object is destroyed and closes again that FD. If that
happens, it will close an incorrect FD.
One solution could be to call run_dispose() from Javascript on the
GSubprocessLauncher object, to force freeing the resources.
Unfortunately, the current code frees them in the finalize()
method, not in dispose() (this is fixed in !1670 (merged) ) but it
isn't a very elegant solution.
This proposal adds a new method, g_subprocess_launcher_close(),
that allows to close the FDs passed to the child. To avoid problems,
after closing an FD with this method, no more spawns are allowed.
Fix: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1677
2020-10-04 14:34:41 +02:00
|
|
|
|
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);
|
2021-02-19 19:09:42 +01:00
|
|
|
|
|
|
|
|
|
close (fd2);
|
GSubprocessLauncher: allow to close passed FDs
By default, when using g_subprocess_launcher_take_fd() to pass an
FD to a child, the GSubprocessLauncher object also takes ownership
of the FD in the parent, and closes it during finalize(). This is
a reasonable assumption in the majority of the cases, but sometimes
it isn't a good idea.
An example is when creating a GSubprocessLauncher in JavaScript:
here, the destruction process is managed by the Garbage Collector,
which means that those sockets will remain opened for some time
after all the references to the object has been droped. This means
that it could be not possible to detect when the child has closed
that same FD, because in order to make that work, both FDs
instances (the one in the parent and the one in the children) must
be closed. This can be a problem in, as an example, a process that
launches a child that communicates with Wayland using an specific
socket (like when using the new API MetaWaylandClient).
Of course, it isn't a valid solution to manually call close() in
the parent process just after the call to spawn(), because the FD
number could be reused in the time between it is manually closed,
and when the object is destroyed and closes again that FD. If that
happens, it will close an incorrect FD.
One solution could be to call run_dispose() from Javascript on the
GSubprocessLauncher object, to force freeing the resources.
Unfortunately, the current code frees them in the finalize()
method, not in dispose() (this is fixed in !1670 (merged) ) but it
isn't a very elegant solution.
This proposal adds a new method, g_subprocess_launcher_close(),
that allows to close the FDs passed to the child. To avoid problems,
after closing an FD with this method, no more spawns are allowed.
Fix: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1677
2020-10-04 14:34:41 +02:00
|
|
|
|
g_object_unref (launcher);
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-17 20:37:17 +02:00
|
|
|
|
static void
|
|
|
|
|
test_stdout_file (void)
|
|
|
|
|
{
|
|
|
|
|
GError *local_error = NULL;
|
|
|
|
|
GError **error = &local_error;
|
|
|
|
|
GSubprocessLauncher *launcher;
|
|
|
|
|
GSubprocess *proc;
|
|
|
|
|
GPtrArray *args;
|
|
|
|
|
GFile *tmpfile;
|
|
|
|
|
GFileIOStream *iostream;
|
2018-04-20 18:10:18 +02:00
|
|
|
|
GOutputStream *stdin_stream;
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
stdin_stream = g_subprocess_get_stdin_pipe (proc);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
g_output_stream_write_all (stdin_stream, test_data, strlen (test_data), NULL, NULL, error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
g_assert_no_error (local_error);
|
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
g_output_stream_close (stdin_stream, NULL, error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
2013-11-09 08:54:28 +01:00
|
|
|
|
g_object_unref (tmpfile);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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;
|
2018-04-20 18:10:18 +02:00
|
|
|
|
GOutputStream *stdin_stream;
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
stdin_stream = g_subprocess_get_stdin_pipe (proc);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
g_output_stream_write_all (stdin_stream, test_data, strlen (test_data), NULL, NULL, error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
g_assert_no_error (local_error);
|
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
g_output_stream_close (stdin_stream, NULL, error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
2013-11-09 08:54:28 +01:00
|
|
|
|
g_object_unref (tmpfile);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2018-04-20 18:10:18 +02:00
|
|
|
|
GOutputStream *stdin_stream;
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
stdin_stream = g_subprocess_get_stdin_pipe (proc);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
g_output_stream_write_all (stdin_stream, test_data, strlen (test_data), NULL, NULL, error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
g_assert_no_error (local_error);
|
|
|
|
|
|
2018-04-20 18:10:18 +02:00
|
|
|
|
g_output_stream_close (stdin_stream, NULL, error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
2013-11-09 08:54:28 +01:00
|
|
|
|
g_object_unref (tmpfile);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2021-12-14 20:36:29 +01:00
|
|
|
|
do_test_pass_fd (GSubprocessFlags flags,
|
|
|
|
|
GSpawnChildSetupFunc child_setup)
|
2012-05-17 20:37:17 +02:00
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
|
2023-08-16 15:31:02 +02:00
|
|
|
|
g_unix_open_pipe (basic_pipefds, O_CLOEXEC, error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
g_assert_no_error (local_error);
|
2023-08-16 15:31:02 +02:00
|
|
|
|
g_unix_open_pipe (needdup_pipefds, O_CLOEXEC, error);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
2021-12-14 20:36:29 +01:00
|
|
|
|
launcher = g_subprocess_launcher_new (flags);
|
2013-10-15 01:12:22 +02:00
|
|
|
|
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);
|
2021-12-14 20:36:29 +01:00
|
|
|
|
if (child_setup != NULL)
|
|
|
|
|
g_subprocess_launcher_set_child_setup (launcher, child_setup, NULL, NULL);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-14 20:36:29 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-14 20:36:31 +01:00
|
|
|
|
static void
|
|
|
|
|
do_test_fd_conflation (GSubprocessFlags flags,
|
2021-12-14 20:36:32 +01:00
|
|
|
|
GSpawnChildSetupFunc child_setup,
|
|
|
|
|
gboolean test_child_err_report_fd)
|
2021-12-14 20:36:31 +01:00
|
|
|
|
{
|
|
|
|
|
char success_message[] = "Yay success!";
|
|
|
|
|
GError *error = NULL;
|
|
|
|
|
GOutputStream *output_stream;
|
|
|
|
|
GSubprocessLauncher *launcher;
|
|
|
|
|
GSubprocess *proc;
|
|
|
|
|
GPtrArray *args;
|
|
|
|
|
int unused_pipefds[2];
|
|
|
|
|
int pipefds[2];
|
2021-12-14 20:36:32 +01:00
|
|
|
|
int fd_to_pass_to_child;
|
2021-12-14 20:36:31 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-16 15:31:02 +02:00
|
|
|
|
g_unix_open_pipe (unused_pipefds, O_CLOEXEC, &error);
|
2021-12-14 20:36:31 +01:00
|
|
|
|
g_assert_no_error (error);
|
|
|
|
|
|
2023-08-16 15:31:02 +02:00
|
|
|
|
g_unix_open_pipe (pipefds, O_CLOEXEC, &error);
|
2021-12-14 20:36:31 +01:00
|
|
|
|
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.
|
|
|
|
|
*
|
2021-12-14 20:36:32 +01:00
|
|
|
|
* This test also has an alternate mode of operation where it instead tests
|
|
|
|
|
* for conflation with gspawn's child_err_report_fd, glib#2506.
|
|
|
|
|
*
|
2021-12-14 20:36:31 +01:00
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2021-12-14 20:36:32 +01:00
|
|
|
|
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 */;
|
|
|
|
|
|
2021-12-14 20:36:31 +01:00
|
|
|
|
launcher = g_subprocess_launcher_new (flags);
|
2021-12-14 20:36:32 +01:00
|
|
|
|
g_subprocess_launcher_take_fd (launcher, pipefds[0] /* 5 */, fd_to_pass_to_child);
|
2021-12-14 20:36:31 +01:00
|
|
|
|
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);
|
2021-12-14 20:36:32 +01:00
|
|
|
|
fd_str = g_strdup_printf ("%d", fd_to_pass_to_child);
|
2021-12-14 20:36:31 +01:00
|
|
|
|
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]);
|
|
|
|
|
|
2021-12-14 20:36:32 +01:00
|
|
|
|
/* If doing our normal test:
|
|
|
|
|
*
|
|
|
|
|
* So now pipefds[0] should be inherited into the subprocess as
|
2021-12-14 20:36:31 +01:00
|
|
|
|
* 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.
|
2021-12-14 20:36:32 +01:00
|
|
|
|
*
|
|
|
|
|
* 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.
|
2021-12-14 20:36:31 +01:00
|
|
|
|
*/
|
|
|
|
|
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)
|
|
|
|
|
{
|
2021-12-14 20:36:32 +01:00
|
|
|
|
do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, NULL, FALSE);
|
2021-12-14 20:36:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
test_fd_conflation_empty_child_setup (void)
|
|
|
|
|
{
|
|
|
|
|
/* Using a child setup function forces gspawn to use fork/exec
|
|
|
|
|
* rather than posix_spawn.
|
|
|
|
|
*/
|
2021-12-14 20:36:32 +01:00
|
|
|
|
do_test_fd_conflation (G_SUBPROCESS_FLAGS_NONE, empty_child_setup, FALSE);
|
2021-12-14 20:36:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
*/
|
2021-12-14 20:36:32 +01:00
|
|
|
|
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);
|
2021-12-14 20:36:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 10:27:07 +02:00
|
|
|
|
#ifdef __linux__
|
|
|
|
|
|
2023-05-16 12:38:47 +02:00
|
|
|
|
/* 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 |
|
2023-10-26 09:56:53 +02:00
|
|
|
|
#ifdef HAVE_PTRACE_O_EXITKILL
|
2023-05-16 12:38:47 +02:00
|
|
|
|
PTRACE_O_EXITKILL |
|
2023-09-19 12:24:09 +02:00
|
|
|
|
#endif
|
2023-05-16 12:38:47 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-14 10:27:07 +02:00
|
|
|
|
#endif /* __linux__ */
|
|
|
|
|
|
2023-05-16 12:38:47 +02:00
|
|
|
|
static void
|
|
|
|
|
test_exit_status_trapped (void)
|
|
|
|
|
{
|
2023-08-14 10:27:07 +02:00
|
|
|
|
#ifdef __linux__
|
2023-05-16 12:38:47 +02:00
|
|
|
|
GPtrArray *args = NULL;
|
|
|
|
|
pid_t test_child;
|
2023-08-14 10:27:07 +02:00
|
|
|
|
#endif
|
2023-05-16 12:38:47 +02:00
|
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
2023-08-14 10:27:07 +02:00
|
|
|
|
#ifdef __linux__
|
2023-05-16 12:38:47 +02:00
|
|
|
|
/* 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);
|
2023-08-14 10:27:07 +02:00
|
|
|
|
#else
|
|
|
|
|
g_test_skip ("ptrace() support for this test is only tested on Linux");
|
|
|
|
|
#endif
|
2023-05-16 12:38:47 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif /* G_OS_UNIX */
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
2014-03-04 15:08:24 +01:00
|
|
|
|
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");
|
|
|
|
|
|
2020-06-12 15:02:30 +02:00
|
|
|
|
/* and set a different one */
|
2014-03-04 15:08:24 +01:00
|
|
|
|
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);
|
2019-03-05 11:57:29 +01:00
|
|
|
|
g_assert_nonnull (proc);
|
2014-03-04 15:08:24 +01:00
|
|
|
|
|
|
|
|
|
g_subprocess_communicate_utf8 (proc, NULL, NULL, &out, NULL, &error);
|
|
|
|
|
g_assert_no_error (error);
|
|
|
|
|
|
2019-02-12 00:51:53 +01:00
|
|
|
|
g_assert_cmpstr (out, ==, "C=D" LINEEND "E=F" LINEEND);
|
2014-03-04 15:08:24 +01:00
|
|
|
|
g_free (out);
|
|
|
|
|
|
|
|
|
|
g_object_unref (proc);
|
2017-08-23 12:24:32 +02:00
|
|
|
|
g_object_unref (launcher);
|
|
|
|
|
g_ptr_array_unref (args);
|
2014-03-04 15:08:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
2012-05-17 20:37:17 +02:00
|
|
|
|
int
|
|
|
|
|
main (int argc, char **argv)
|
|
|
|
|
{
|
2018-02-09 13:25:54 +01:00
|
|
|
|
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;
|
|
|
|
|
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
2021-11-17 13:27:46 +01:00
|
|
|
|
g_test_add_func ("/gsubprocess/search-path-from-envp", test_search_path_from_envp);
|
2013-12-23 18:08:08 +01:00
|
|
|
|
g_test_add_func ("/gsubprocess/signal", test_signal);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
#endif
|
|
|
|
|
g_test_add_func ("/gsubprocess/exit1", test_exit1);
|
2017-08-18 11:41:46 +02:00
|
|
|
|
g_test_add_func ("/gsubprocess/exit1/cancel", test_exit1_cancel);
|
|
|
|
|
g_test_add_func ("/gsubprocess/exit1/cancel_in_cb", test_exit1_cancel_in_cb);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
2018-02-09 13:25:54 +01:00
|
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
2018-08-27 15:56:35 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
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);
|
|
|
|
|
|
2018-08-27 15:56:35 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
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);
|
|
|
|
|
|
2018-08-27 15:56:35 +02:00
|
|
|
|
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);
|
|
|
|
|
|
2018-02-09 13:25:54 +01:00
|
|
|
|
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);
|
2018-08-27 15:56:35 +02:00
|
|
|
|
|
|
|
|
|
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);
|
2018-02-09 13:25:54 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-10-11 00:29:17 +02:00
|
|
|
|
g_test_add_func ("/gsubprocess/communicate/utf8/async/invalid", test_communicate_utf8_async_invalid);
|
2018-02-09 13:25:54 +01:00
|
|
|
|
g_test_add_func ("/gsubprocess/communicate/utf8/invalid", test_communicate_utf8_invalid);
|
|
|
|
|
g_test_add_func ("/gsubprocess/communicate/nothing", test_communicate_nothing);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
g_test_add_func ("/gsubprocess/terminate", test_terminate);
|
2013-11-28 07:12:26 +01:00
|
|
|
|
g_test_add_func ("/gsubprocess/env", test_env);
|
2017-03-30 12:02:14 +02:00
|
|
|
|
g_test_add_func ("/gsubprocess/env/inherit", test_env_inherit);
|
2013-11-28 18:59:11 +01:00
|
|
|
|
g_test_add_func ("/gsubprocess/cwd", test_cwd);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
#ifdef G_OS_UNIX
|
GSubprocessLauncher: allow to close passed FDs
By default, when using g_subprocess_launcher_take_fd() to pass an
FD to a child, the GSubprocessLauncher object also takes ownership
of the FD in the parent, and closes it during finalize(). This is
a reasonable assumption in the majority of the cases, but sometimes
it isn't a good idea.
An example is when creating a GSubprocessLauncher in JavaScript:
here, the destruction process is managed by the Garbage Collector,
which means that those sockets will remain opened for some time
after all the references to the object has been droped. This means
that it could be not possible to detect when the child has closed
that same FD, because in order to make that work, both FDs
instances (the one in the parent and the one in the children) must
be closed. This can be a problem in, as an example, a process that
launches a child that communicates with Wayland using an specific
socket (like when using the new API MetaWaylandClient).
Of course, it isn't a valid solution to manually call close() in
the parent process just after the call to spawn(), because the FD
number could be reused in the time between it is manually closed,
and when the object is destroyed and closes again that FD. If that
happens, it will close an incorrect FD.
One solution could be to call run_dispose() from Javascript on the
GSubprocessLauncher object, to force freeing the resources.
Unfortunately, the current code frees them in the finalize()
method, not in dispose() (this is fixed in !1670 (merged) ) but it
isn't a very elegant solution.
This proposal adds a new method, g_subprocess_launcher_close(),
that allows to close the FDs passed to the child. To avoid problems,
after closing an FD with this method, no more spawns are allowed.
Fix: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1677
2020-10-04 14:34:41 +02:00
|
|
|
|
g_test_add_func ("/gsubprocess/launcher-close", test_subprocess_launcher_close);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
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);
|
2021-12-14 20:36:29 +01:00
|
|
|
|
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);
|
2021-12-14 20:36:31 +01:00
|
|
|
|
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);
|
2021-12-14 20:36:32 +01:00
|
|
|
|
g_test_add_func ("/gsubprocess/fd-conflation/child-err-report-fd", test_fd_conflation_child_err_report_fd);
|
2023-05-16 12:38:47 +02:00
|
|
|
|
g_test_add_func ("/gsubprocess/exit-status/trapped", test_exit_status_trapped);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
#endif
|
2014-03-04 15:08:24 +01:00
|
|
|
|
g_test_add_func ("/gsubprocess/launcher-environment", test_launcher_environment);
|
2012-05-17 20:37:17 +02:00
|
|
|
|
|
|
|
|
|
return g_test_run ();
|
|
|
|
|
}
|