gsubprocess: Fix up communicate

We weren't closing the streams after we were done reading or writing,
which is kind of essential.  The easy way to fix this is to just use
g_output_stream_splice() to a GMemoryOutputStream rather than
hand-rolling it.  This results in a substantial reduction of code
complexity.

A second serious issue is that we were marking the task as complete when
the process exits, but that's racy - there could still be data to read
from stdout.  Fix this by just refcounting outstanding operations.

This code, not surprisingly, looks a lot like the "multi" test.

Next, because processes output binary data, I'd be forced to annotate
the char*/length pairs as (array) (element-type uint8).  But rather than
doing that, it's *far* simpler to just use GBytes.

We need a version of this that actually validates as UTF-8, that will be
in the next patch.
This commit is contained in:
Colin Walters 2013-10-15 00:12:22 +01:00 committed by Ryan Lortie
parent 5b48dc40cc
commit 0e1a3ee345
5 changed files with 198 additions and 344 deletions

View File

@ -1189,34 +1189,17 @@ typedef struct
gsize stdin_length;
gsize stdin_offset;
/* Not actually GString. Just borrowing the struct. */
GString stdout_string;
GString stderr_string;
GBytes *unref_this_later;
gchar *free_this_later;
GInputStream *stdin_buf;
GMemoryOutputStream *stdout_buf;
GMemoryOutputStream *stderr_buf;
GCancellable *cancellable;
GSource *cancellable_source;
gboolean completion_reported;
guint outstanding_ops;
gboolean reported_error;
} CommunicateState;
static void
ensure_string_allocated (GString *str)
{
/* This will work because the first time we will set it to
* COMMUNICATE_READ_SIZE and then all future attempts will grow by at
* least that much (as a result of multiplying the existing value by
* 2).
*/
if (str->len + COMMUNICATE_READ_SIZE > str->allocated_len)
{
str->allocated_len = MAX(COMMUNICATE_READ_SIZE, str->allocated_len * 2);
str->str = g_realloc (str->str, str->allocated_len);
}
}
static void
g_subprocess_communicate_made_progress (GObject *source_object,
GAsyncResult *result,
@ -1235,91 +1218,17 @@ g_subprocess_communicate_made_progress (GObject *source_object,
state = g_task_get_task_data (task);
source = source_object;
if (source == subprocess->stdin_pipe)
state->outstanding_ops--;
if (source == subprocess->stdin_pipe ||
source == state->stdout_buf ||
source == state->stderr_buf)
{
gssize s;
s = g_output_stream_write_finish (subprocess->stdin_pipe, result, &error);
g_assert (s != 0);
if (s != -1)
{
g_assert (0 < s && s < state->stdin_length);
g_assert (state->stdin_offset + s <= state->stdin_length);
state->stdin_offset += s;
if (state->stdin_offset != state->stdin_length)
{
/* write more... */
g_output_stream_write_async (subprocess->stdin_pipe,
state->stdin_data + state->stdin_offset,
state->stdin_length - state->stdin_offset,
G_PRIORITY_DEFAULT,
state->cancellable,
g_subprocess_communicate_made_progress,
task);
return;
}
}
}
else if (source == subprocess->stdout_pipe)
{
gssize s;
s = g_input_stream_read_finish (subprocess->stdout_pipe, result, &error);
g_assert (s <= COMMUNICATE_READ_SIZE);
/* If s is 0 then we have EOF and should not read more, but should
* continue to try the other event sources.
*
* If s is -1 then error will be set and we deal with that below.
*
* Only have to handle the result > 0 case.
*/
if (s > 0)
{
state->stdout_string.len += s;
ensure_string_allocated (&state->stdout_string);
g_input_stream_read_async (subprocess->stdout_pipe, state->stdout_string.str + state->stdout_string.len,
COMMUNICATE_READ_SIZE, G_PRIORITY_DEFAULT - 1, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task));
return;
}
}
else if (source == subprocess->stderr_pipe)
{
gssize s;
s = g_input_stream_read_finish (subprocess->stdout_pipe, result, &error);
g_assert (s <= COMMUNICATE_READ_SIZE);
/* As above... */
if (s > 0)
{
state->stderr_string.len += s;
ensure_string_allocated (&state->stderr_string);
g_input_stream_read_async (subprocess->stderr_pipe, state->stderr_string.str + state->stderr_string.len,
COMMUNICATE_READ_SIZE, G_PRIORITY_DEFAULT - 1, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task));
return;
}
(void) g_output_stream_splice_finish ((GOutputStream*)source, result, &error);
}
else if (source == subprocess)
{
if (g_subprocess_wait_finish (subprocess, result, &error))
{
/* It is not possible that we had a successful completion if
* the task was already completed because we flag our own
* cancellable in that case.
*/
g_assert (!state->completion_reported);
state->completion_reported = TRUE;
g_task_return_boolean (task, TRUE);
}
(void) g_subprocess_wait_finish (subprocess, result, &error);
}
else
g_assert_not_reached ();
@ -1331,17 +1240,21 @@ g_subprocess_communicate_made_progress (GObject *source_object,
* We might be seeing an error as a result of the cancellation
* done when the process quits.
*/
if (!state->completion_reported)
if (!state->reported_error)
{
state->completion_reported = TRUE;
state->reported_error = TRUE;
g_cancellable_cancel (state->cancellable);
g_task_return_error (task, error);
}
else
g_error_free (error);
}
else if (state->outstanding_ops == 0)
{
g_task_return_boolean (task, TRUE);
}
/* And drop the original ref */
g_object_unref (task);
}
@ -1360,25 +1273,20 @@ g_subprocess_communicate_state_free (gpointer data)
{
CommunicateState *state = data;
g_free (state->stdout_string.str);
g_free (state->stderr_string.str);
g_free (state->free_this_later);
g_clear_object (&state->stdin_buf);
g_clear_object (&state->stdout_buf);
g_clear_object (&state->stderr_buf);
if (!g_source_is_destroyed (state->cancellable_source))
g_source_destroy (state->cancellable_source);
g_source_unref (state->cancellable_source);
if (state->unref_this_later)
g_bytes_unref (state->unref_this_later);
g_slice_free (CommunicateState, state);
}
static CommunicateState *
g_subprocess_communicate_internal (GSubprocess *subprocess,
GBytes *stdin_bytes,
const gchar *stdin_data,
gssize stdin_length,
GBytes *stdin_buf,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
@ -1390,24 +1298,6 @@ g_subprocess_communicate_internal (GSubprocess *subprocess,
state = g_slice_new0 (CommunicateState);
g_task_set_task_data (task, state, g_subprocess_communicate_state_free);
if (stdin_bytes)
{
g_assert (!stdin_data && !stdin_length && (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE));
state->stdin_data = g_bytes_get_data (stdin_bytes, &state->stdin_length);
state->unref_this_later = g_bytes_ref (stdin_bytes);
}
else if (stdin_data)
{
g_assert (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE);
if (stdin_length < 0)
state->stdin_length = strlen (stdin_data);
else
state->stdin_length = stdin_length;
state->free_this_later = g_memdup (stdin_data, state->stdin_length);
state->stdin_data = state->free_this_later;
}
state->cancellable = g_cancellable_new ();
if (cancellable)
@ -1418,78 +1308,78 @@ g_subprocess_communicate_internal (GSubprocess *subprocess,
g_source_attach (state->cancellable_source, g_main_context_get_thread_default ());
}
if (subprocess->stdin_pipe && state->stdin_length)
g_output_stream_write_async (subprocess->stdin_pipe, state->stdin_data, state->stdin_length, G_PRIORITY_DEFAULT,
state->cancellable, g_subprocess_communicate_made_progress, g_object_ref (task));
if (subprocess->stdin_pipe)
{
g_assert (stdin_buf != NULL);
state->stdin_buf = g_memory_input_stream_new_from_bytes (stdin_buf);
g_output_stream_splice_async (subprocess->stdin_pipe, (GInputStream*)state->stdin_buf,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
G_PRIORITY_DEFAULT, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task));
state->outstanding_ops++;
}
if (subprocess->stdout_pipe)
{
ensure_string_allocated (&state->stdout_string);
g_input_stream_read_async (subprocess->stdout_pipe, state->stdout_string.str, COMMUNICATE_READ_SIZE,
G_PRIORITY_DEFAULT - 1, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task));
state->stdout_buf = (GMemoryOutputStream*)g_memory_output_stream_new_resizable ();
g_output_stream_splice_async ((GOutputStream*)state->stdout_buf, subprocess->stdout_pipe,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
G_PRIORITY_DEFAULT, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task));
state->outstanding_ops++;
}
if (subprocess->stderr_pipe)
{
ensure_string_allocated (&state->stderr_string);
g_input_stream_read_async (subprocess->stderr_pipe, state->stderr_string.str, COMMUNICATE_READ_SIZE,
G_PRIORITY_DEFAULT - 1, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task));
state->stderr_buf = (GMemoryOutputStream*)g_memory_output_stream_new_resizable ();
g_output_stream_splice_async ((GOutputStream*)state->stderr_buf, subprocess->stderr_pipe,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
G_PRIORITY_DEFAULT, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task));
state->outstanding_ops++;
}
g_subprocess_wait_async (subprocess, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task));
state->outstanding_ops++;
return state;
}
/**
* g_Subprocess_communicate:
* g_subprocess_communicate:
* @self: a #GSubprocess
* @stdin_data: data to send to the stdin of the subprocess, or %NULL
* @stdin_length: the length of @stdin_data, or -1
* @stdin_buf: data to send to the stdin of the subprocess, or %NULL
* @cancellable: a #GCancellable
* @stdout_data: (out): data read from the subprocess stdout
* @stdout_length: (out): the length of @stdout_data returned
* @stderr_data: (out): data read from the subprocess stderr
* @stderr_length: (out): the length of @stderr_data returned
* @stdout_buf: (out): data read from the subprocess stdout
* @stderr_buf: (out): data read from the subprocess stderr
* @error: a pointer to a %NULL #GError pointer, or %NULL
*
* Communicate with the subprocess until it terminates.
* Communicate with the subprocess until it terminates, and all input
* and output has been completed.
*
* If @stdin_data is given, the subprocess must have been created with
* If @stdin is given, the subprocess must have been created with
* %G_SUBPROCESS_FLAGS_STDIN_PIPE. The given data is fed to the
* stdin of the subprocess and the pipe is closed (ie: EOF).
*
* At the same time (as not to cause blocking when dealing with large
* amounts of data), if %G_SUBPROCESS_FLAGS_STDOUT_PIPE or
* %G_SUBPROCESS_FLAGS_STDERR_PIPE were used, reads from those streams.
* The data that was read is returned in @stdout_data and/or
* @stderr_data.
*
* @stdin_length specifies the length of @stdin_data. If it is -1 then
* @stdin_data is taken to be a nul-terminated string. If the
* subprocess was not created with %G_SUBPROCESS_FLAGS_STDIN_PIPE then
* you must pass %NULL for @stdin_data and 0 for @stdin_length.
* %G_SUBPROCESS_FLAGS_STDERR_PIPE were used, reads from those
* streams. The data that was read is returned in @stdout and/or
* the @stderr.
*
* If the subprocess was created with %G_SUBPROCESS_FLAGS_STDOUT_PIPE,
* @stdout_data will contain the data read from stdout, plus a
* terminating nul character; it will always be non-%NULL (ie:
* containing at least the nul). @stdout_length will be the length of
* the data, excluding the added nul. For subprocesses not created with
* %G_SUBPROCESS_FLAGS_STDOUT_PIPE, @stdout_data will be set to %NULL
* and @stdout_length will be set to zero. stderr is handled in the
* same way.
* @stdout_buf will contain the data read from stdout. Otherwise, for
* subprocesses not created with %G_SUBPROCESS_FLAGS_STDOUT_PIPE,
* @stdout_buf will be set to %NULL. Similar provisions apply to
* @stderr_buf and %G_SUBPROCESS_FLAGS_STDERR_PIPE.
*
* As usual, any output variable may be given as %NULL to ignore it.
*
* If you desire the stdout and stderr data to be interleaved, create
* the subprocess with %G_SUBPROCESS_FLAGS_STDOUT_PIPE and
* %G_SUBPROCESS_FLAGS_STDERR_MERGE. The merged result will be returned
* in @stdout_data and @stderr_data will be set to %NULL.
* in @stdout_buf and @stderr_buf will be set to %NULL.
*
* In case of any error (including cancellation), %FALSE will be
* returned with @error set. Some or all of the stdin data may have
@ -1509,181 +1399,95 @@ g_subprocess_communicate_internal (GSubprocess *subprocess,
*
* Returns: %TRUE if successful
*
* Since: 2.36
* Since: 2.40
**/
gboolean
g_subprocess_communicate (GSubprocess *subprocess,
const gchar *stdin_data,
gssize stdin_length,
GBytes *stdin_buf,
GCancellable *cancellable,
gchar **stdout_data,
gsize *stdout_length,
gchar **stderr_data,
gsize *stderr_length,
GBytes **stdout_buf,
GBytes **stderr_buf,
GError **error)
{
GAsyncResult *result = NULL;
gboolean success;
g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE);
g_return_val_if_fail (stdin_length == 0 || stdin_data != NULL, FALSE);
g_return_val_if_fail (stdin_data == NULL || (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE), FALSE);
g_return_val_if_fail (stdin_buf == NULL || (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE), FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_subprocess_sync_setup ();
g_subprocess_communicate_internal (subprocess, NULL, stdin_data, stdin_length,
cancellable, g_subprocess_sync_done, &result);
g_subprocess_communicate_internal (subprocess, stdin_buf, cancellable, g_subprocess_sync_done, &result);
g_subprocess_sync_complete (&result);
success = g_subprocess_communicate_finish (subprocess, result,
stdout_data, stdout_length,
stderr_data, stderr_length, error);
success = g_subprocess_communicate_finish (subprocess, result, stdout_buf, stderr_buf, error);
g_object_unref (result);
return success;
}
/**
* g_subprocess_communicate_async:
* @subprocess: Self
* @stdin_buf: Input data
* @cancellable: Cancellable
* @callback: Callback
* @user_data: User data
*
* Asynchronous version of g_subprocess_communicate(). Complete
* invocation with g_subprocess_communicate_finish().
*/
void
g_subprocess_communicate_async (GSubprocess *subprocess,
const gchar *stdin_data,
gssize stdin_length,
GBytes *stdin_buf,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (G_IS_SUBPROCESS (subprocess));
g_return_if_fail (stdin_length == 0 || stdin_data != NULL);
g_return_if_fail (stdin_data == NULL || (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE));
g_return_if_fail (stdin_buf == NULL || (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_subprocess_communicate_internal (subprocess, NULL, stdin_data, stdin_length, cancellable, callback, user_data);
g_subprocess_communicate_internal (subprocess, stdin_buf, cancellable, callback, user_data);
}
/**
* g_subprocess_communicate_finish:
* @subprocess: Self
* @result: Result
* @stdout_buf: (out): Return location for stdout data
* @stderr_buf: (out): Return location for stderr data
* @error: Error
*
* Complete an invocation of g_subprocess_communicate_async().
*/
gboolean
g_subprocess_communicate_finish (GSubprocess *subprocess,
GAsyncResult *result,
gchar **stdout_data,
gsize *stdout_length,
gchar **stderr_data,
gsize *stderr_length,
GBytes **stdout_buf,
GBytes **stderr_buf,
GError **error)
{
gboolean success;
CommunicateState *state;
gboolean success;
GTask *task;
g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE);
g_return_val_if_fail (g_task_is_valid (result, subprocess), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
task = G_TASK (result);
state = g_task_get_task_data (task);
g_object_ref (result);
success = g_task_propagate_boolean (task, error);
state = g_task_get_task_data ((GTask*)result);
success = g_task_propagate_boolean ((GTask*)result, error);
if (success)
{
if (stdout_data)
{
gchar *string;
string = g_realloc (state->stdout_string.str, state->stdout_string.len + 1);
string[state->stdout_string.len] = '\0';
state->stdout_string.str = NULL;
*stdout_data = string;
}
if (stdout_length)
*stdout_length = state->stdout_string.len;
if (stderr_data)
{
gchar *string;
string = g_realloc (state->stderr_string.str, state->stderr_string.len + 1);
string[state->stderr_string.len] = '\0';
state->stderr_string.str = NULL;
*stderr_data = string;
}
if (stderr_length)
*stderr_length = state->stderr_string.len;
if (stdout_buf)
*stdout_buf = g_memory_output_stream_steal_as_bytes (state->stdout_buf);
if (stderr_buf)
*stderr_buf = g_memory_output_stream_steal_as_bytes (state->stderr_buf);
}
return success;
}
gboolean
g_subprocess_communicate_bytes (GSubprocess *subprocess,
GBytes *stdin_bytes,
GCancellable *cancellable,
GBytes **stdout_bytes,
GBytes **stderr_bytes,
GError **error)
{
GAsyncResult *result = NULL;
gboolean success;
g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE);
g_return_val_if_fail (stdin_bytes == NULL || (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE), FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_subprocess_sync_setup ();
g_subprocess_communicate_internal (subprocess, stdin_bytes, NULL, 0, cancellable, g_subprocess_sync_done, &result);
g_subprocess_sync_complete (&result);
success = g_subprocess_communicate_bytes_finish (subprocess, result, stdout_bytes, stderr_bytes, error);
g_object_unref (result);
return success;
}
void
g_subprocess_communicate_bytes_async (GSubprocess *subprocess,
GBytes *stdin_bytes,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (G_IS_SUBPROCESS (subprocess));
g_return_if_fail (stdin_bytes == NULL || (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
g_subprocess_communicate_internal (subprocess, stdin_bytes, NULL, 0, cancellable, callback, user_data);
}
gboolean
g_subprocess_communicate_bytes_finish (GSubprocess *subprocess,
GAsyncResult *result,
GBytes **stdout_bytes,
GBytes **stderr_bytes,
GError **error)
{
gboolean success;
gchar *stdout_data;
gsize stdout_length;
gchar *stderr_data;
gsize stderr_length;
g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE);
g_return_val_if_fail (g_task_is_valid (result, subprocess), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
success = g_subprocess_communicate_finish (subprocess, result,
stdout_bytes ? &stdout_data : NULL,
stdout_bytes ? &stdout_length : NULL,
stderr_bytes ? &stderr_data : NULL,
stderr_bytes ? &stderr_length : NULL,
error);
if (success)
{
if (stdout_bytes)
*stdout_bytes = g_bytes_new_take (stdout_data, stdout_length);
if (stderr_bytes)
*stderr_bytes = g_bytes_new_take (stderr_data, stderr_length);
}
return success;
}

View File

@ -124,18 +124,14 @@ gint g_subprocess_get_term_sig (GSubprocess *s
GLIB_AVAILABLE_IN_2_40
gboolean g_subprocess_communicate (GSubprocess *subprocess,
const gchar *stdin_data,
gssize stdin_length,
GBytes *stdin_buf,
GCancellable *cancellable,
gchar **stdout_data,
gsize *stdout_length,
gchar **stderr_data,
gsize *stderr_length,
GBytes **stdout_buf,
GBytes **stderr_buf,
GError **error);
GLIB_AVAILABLE_IN_2_40
void g_subprocess_communicate_async (GSubprocess *subprocess,
const gchar *stdin_data,
gssize stdin_length,
GBytes *stdin_buf,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
@ -143,30 +139,8 @@ void g_subprocess_communicate_async (GSubprocess *s
GLIB_AVAILABLE_IN_2_40
gboolean g_subprocess_communicate_finish (GSubprocess *subprocess,
GAsyncResult *result,
gchar **stdout_data,
gsize *stdout_length,
gchar **stderr_data,
gsize *stderr_length,
GError **error);
GLIB_AVAILABLE_IN_2_40
gboolean g_subprocess_communicate_bytes (GSubprocess *subprocess,
GBytes *stdin_bytes,
GCancellable *cancellable,
GBytes **stdout_bytes,
GBytes **stderr_bytes,
GError **error);
GLIB_AVAILABLE_IN_2_40
void g_subprocess_communicate_bytes_async (GSubprocess *subprocess,
GBytes *stdin_bytes,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GLIB_AVAILABLE_IN_2_40
gboolean g_subprocess_communicate_bytes_finish (GSubprocess *subprocess,
GAsyncResult *result,
GBytes **stdout_bytes,
GBytes **stderr_bytes,
GBytes **stdout_buf,
GBytes **stderr_buf,
GError **error);
G_END_DECLS

View File

@ -130,6 +130,7 @@ static void
g_subprocess_launcher_finalize (GObject *object)
{
GSubprocessLauncher *self = G_SUBPROCESS_LAUNCHER (object);
guint i;
g_strfreev (self->envp);
g_free (self->cwd);
@ -148,8 +149,18 @@ g_subprocess_launcher_finalize (GObject *object)
if (self->stderr_fd != -1)
close (self->stderr_fd);
g_clear_pointer (&self->basic_fd_assignments, g_array_unref);
g_clear_pointer (&self->needdup_fd_assignments, g_array_unref);
if (self->basic_fd_assignments)
{
for (i = 0; i < self->basic_fd_assignments->len; i++)
(void) close (g_array_index (self->basic_fd_assignments, int, i));
g_array_unref (self->basic_fd_assignments);
}
if (self->needdup_fd_assignments)
{
for (i = 0; i < self->needdup_fd_assignments->len; i += 2)
(void) close (g_array_index (self->needdup_fd_assignments, int, i));
g_array_unref (self->needdup_fd_assignments);
}
#endif
if (self->child_setup_destroy_notify)
@ -570,23 +581,26 @@ g_subprocess_launcher_take_stderr_fd (GSubprocessLauncher *self,
}
/**
* g_subprocess_launcher_pass_fd:
* g_subprocess_launcher_take_fd:
* @self: a #GSubprocessLauncher
* @source_fd: File descriptor in parent process
* @target_fd: Target descriptor for child process
*
* Pass an arbitrary file descriptor from parent process to
* the child. By default, all file descriptors from the parent
* will be closed. This function allows you to create (for example)
* a custom pipe() or socketpair() before launching the process, and
* choose the target descriptor in the child.
* Transfer an arbitrary file descriptor from parent process to the
* child. This function takes "ownership" of the fd; it will be closed
* in the parent when @self is freed.
*
* By default, all file descriptors from the parent will be closed.
* This function allows you to create (for example) a custom pipe() or
* socketpair() before launching the process, and choose the target
* descriptor in the child.
*
* An example use case is GNUPG, which has a command line argument
* --passphrase-fd providing a file descriptor number where it expects
* the passphrase to be written.
*/
void
g_subprocess_launcher_pass_fd (GSubprocessLauncher *self,
g_subprocess_launcher_take_fd (GSubprocessLauncher *self,
gint source_fd,
gint target_fd)
{

View File

@ -101,7 +101,7 @@ void g_subprocess_launcher_take_stderr_fd (GSubpro
gint fd);
GLIB_AVAILABLE_IN_2_40
void g_subprocess_launcher_pass_fd (GSubprocessLauncher *self,
void g_subprocess_launcher_take_fd (GSubprocessLauncher *self,
gint source_fd,
gint target_fd);

View File

@ -545,6 +545,69 @@ test_multi_1 (void)
g_object_unref (third);
}
typedef struct {
gboolean running;
GError *error;
} TestAsyncCommunicateData;
static void
on_communicate_complete (GObject *proc,
GAsyncResult *result,
gpointer user_data)
{
TestAsyncCommunicateData *data = user_data;
GBytes *stdout;
const guint8 *stdout_data;
gsize stdout_len;
data->running = FALSE;
(void) g_subprocess_communicate_finish ((GSubprocess*)proc, result,
&stdout, NULL, &data->error);
g_assert_no_error (data->error);
stdout_data = g_bytes_get_data (stdout, &stdout_len);
g_assert_cmpint (stdout_len, ==, 11);
g_assert (memcmp (stdout_data, "hello world", 11) == 0);
g_bytes_unref (stdout);
}
static void
test_communicate (void)
{
GError *local_error = NULL;
GError **error = &local_error;
GPtrArray *args;
TestAsyncCommunicateData data = { 0, };
GSubprocess *proc;
GCancellable *cancellable = NULL;
GBytes *input;
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_assert_no_error (local_error);
g_ptr_array_free (args, TRUE);
input = g_bytes_new_static ("hello world", strlen ("hello world"));
data.error = local_error;
g_subprocess_communicate_async (proc, input,
cancellable,
on_communicate_complete,
&data);
data.running = TRUE;
while (data.running)
g_main_context_iteration (NULL, TRUE);
g_assert_no_error (local_error);
g_object_unref (proc);
}
static gboolean
send_terminate (gpointer user_data)
{
@ -788,14 +851,12 @@ test_pass_fd (void)
args = get_test_subprocess_args ("write-to-fds", basic_fd_str, needdup_fd_str, NULL);
launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
g_subprocess_launcher_pass_fd (launcher, basic_pipefds[1], basic_pipefds[1]);
g_subprocess_launcher_pass_fd (launcher, needdup_pipefds[1], needdup_pipefds[1] + 1);
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);
proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error);
g_ptr_array_free (args, TRUE);
g_assert_no_error (local_error);
(void) close (basic_pipefds[1]);
(void) close (needdup_pipefds[1]);
g_free (basic_fd_str);
g_free (needdup_fd_str);
@ -843,6 +904,7 @@ main (int argc, char **argv)
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);
g_test_add_func ("/gsubprocess/communicate", test_communicate);
g_test_add_func ("/gsubprocess/terminate", test_terminate);
#ifdef G_OS_UNIX
g_test_add_func ("/gsubprocess/stdout-file", test_stdout_file);