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_length;
gsize stdin_offset; gsize stdin_offset;
/* Not actually GString. Just borrowing the struct. */ GInputStream *stdin_buf;
GString stdout_string; GMemoryOutputStream *stdout_buf;
GString stderr_string; GMemoryOutputStream *stderr_buf;
GBytes *unref_this_later;
gchar *free_this_later;
GCancellable *cancellable; GCancellable *cancellable;
GSource *cancellable_source; GSource *cancellable_source;
gboolean completion_reported; guint outstanding_ops;
gboolean reported_error;
} CommunicateState; } 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 static void
g_subprocess_communicate_made_progress (GObject *source_object, g_subprocess_communicate_made_progress (GObject *source_object,
GAsyncResult *result, GAsyncResult *result,
@ -1235,91 +1218,17 @@ g_subprocess_communicate_made_progress (GObject *source_object,
state = g_task_get_task_data (task); state = g_task_get_task_data (task);
source = source_object; 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; (void) g_output_stream_splice_finish ((GOutputStream*)source, result, &error);
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;
}
} }
else if (source == subprocess) else if (source == subprocess)
{ {
if (g_subprocess_wait_finish (subprocess, result, &error)) (void) 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);
}
} }
else else
g_assert_not_reached (); 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 * We might be seeing an error as a result of the cancellation
* done when the process quits. * 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_cancellable_cancel (state->cancellable);
g_task_return_error (task, error); g_task_return_error (task, error);
} }
else else
g_error_free (error); 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); g_object_unref (task);
} }
@ -1360,25 +1273,20 @@ g_subprocess_communicate_state_free (gpointer data)
{ {
CommunicateState *state = data; CommunicateState *state = data;
g_free (state->stdout_string.str); g_clear_object (&state->stdin_buf);
g_free (state->stderr_string.str); g_clear_object (&state->stdout_buf);
g_free (state->free_this_later); g_clear_object (&state->stderr_buf);
if (!g_source_is_destroyed (state->cancellable_source)) if (!g_source_is_destroyed (state->cancellable_source))
g_source_destroy (state->cancellable_source); g_source_destroy (state->cancellable_source);
g_source_unref (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); g_slice_free (CommunicateState, state);
} }
static CommunicateState * static CommunicateState *
g_subprocess_communicate_internal (GSubprocess *subprocess, g_subprocess_communicate_internal (GSubprocess *subprocess,
GBytes *stdin_bytes, GBytes *stdin_buf,
const gchar *stdin_data,
gssize stdin_length,
GCancellable *cancellable, GCancellable *cancellable,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
gpointer user_data) gpointer user_data)
@ -1390,24 +1298,6 @@ g_subprocess_communicate_internal (GSubprocess *subprocess,
state = g_slice_new0 (CommunicateState); state = g_slice_new0 (CommunicateState);
g_task_set_task_data (task, state, g_subprocess_communicate_state_free); 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 (); state->cancellable = g_cancellable_new ();
if (cancellable) if (cancellable)
@ -1418,78 +1308,78 @@ g_subprocess_communicate_internal (GSubprocess *subprocess,
g_source_attach (state->cancellable_source, g_main_context_get_thread_default ()); g_source_attach (state->cancellable_source, g_main_context_get_thread_default ());
} }
if (subprocess->stdin_pipe && state->stdin_length) if (subprocess->stdin_pipe)
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)); 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) if (subprocess->stdout_pipe)
{ {
ensure_string_allocated (&state->stdout_string); state->stdout_buf = (GMemoryOutputStream*)g_memory_output_stream_new_resizable ();
g_output_stream_splice_async ((GOutputStream*)state->stdout_buf, subprocess->stdout_pipe,
g_input_stream_read_async (subprocess->stdout_pipe, state->stdout_string.str, COMMUNICATE_READ_SIZE, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
G_PRIORITY_DEFAULT - 1, state->cancellable, G_PRIORITY_DEFAULT, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task)); g_subprocess_communicate_made_progress, g_object_ref (task));
state->outstanding_ops++;
} }
if (subprocess->stderr_pipe) if (subprocess->stderr_pipe)
{ {
ensure_string_allocated (&state->stderr_string); state->stderr_buf = (GMemoryOutputStream*)g_memory_output_stream_new_resizable ();
g_output_stream_splice_async ((GOutputStream*)state->stderr_buf, subprocess->stderr_pipe,
g_input_stream_read_async (subprocess->stderr_pipe, state->stderr_string.str, COMMUNICATE_READ_SIZE, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
G_PRIORITY_DEFAULT - 1, state->cancellable, G_PRIORITY_DEFAULT, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task)); g_subprocess_communicate_made_progress, g_object_ref (task));
state->outstanding_ops++;
} }
g_subprocess_wait_async (subprocess, state->cancellable, g_subprocess_wait_async (subprocess, state->cancellable,
g_subprocess_communicate_made_progress, g_object_ref (task)); g_subprocess_communicate_made_progress, g_object_ref (task));
state->outstanding_ops++;
return state; return state;
} }
/** /**
* g_Subprocess_communicate: * g_subprocess_communicate:
* @self: a #GSubprocess * @self: a #GSubprocess
* @stdin_data: data to send to the stdin of the subprocess, or %NULL * @stdin_buf: data to send to the stdin of the subprocess, or %NULL
* @stdin_length: the length of @stdin_data, or -1
* @cancellable: a #GCancellable * @cancellable: a #GCancellable
* @stdout_data: (out): data read from the subprocess stdout * @stdout_buf: (out): data read from the subprocess stdout
* @stdout_length: (out): the length of @stdout_data returned * @stderr_buf: (out): data read from the subprocess stderr
* @stderr_data: (out): data read from the subprocess stderr
* @stderr_length: (out): the length of @stderr_data returned
* @error: a pointer to a %NULL #GError pointer, or %NULL * @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 * %G_SUBPROCESS_FLAGS_STDIN_PIPE. The given data is fed to the
* stdin of the subprocess and the pipe is closed (ie: EOF). * stdin of the subprocess and the pipe is closed (ie: EOF).
* *
* At the same time (as not to cause blocking when dealing with large * At the same time (as not to cause blocking when dealing with large
* amounts of data), if %G_SUBPROCESS_FLAGS_STDOUT_PIPE or * amounts of data), if %G_SUBPROCESS_FLAGS_STDOUT_PIPE or
* %G_SUBPROCESS_FLAGS_STDERR_PIPE were used, reads from those streams. * %G_SUBPROCESS_FLAGS_STDERR_PIPE were used, reads from those
* The data that was read is returned in @stdout_data and/or * streams. The data that was read is returned in @stdout and/or
* @stderr_data. * the @stderr.
*
* @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.
* *
* If the subprocess was created with %G_SUBPROCESS_FLAGS_STDOUT_PIPE, * If the subprocess was created with %G_SUBPROCESS_FLAGS_STDOUT_PIPE,
* @stdout_data will contain the data read from stdout, plus a * @stdout_buf will contain the data read from stdout. Otherwise, for
* terminating nul character; it will always be non-%NULL (ie: * subprocesses not created with %G_SUBPROCESS_FLAGS_STDOUT_PIPE,
* containing at least the nul). @stdout_length will be the length of * @stdout_buf will be set to %NULL. Similar provisions apply to
* the data, excluding the added nul. For subprocesses not created with * @stderr_buf and %G_SUBPROCESS_FLAGS_STDERR_PIPE.
* %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.
* *
* As usual, any output variable may be given as %NULL to ignore it. * 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 * If you desire the stdout and stderr data to be interleaved, create
* the subprocess with %G_SUBPROCESS_FLAGS_STDOUT_PIPE and * the subprocess with %G_SUBPROCESS_FLAGS_STDOUT_PIPE and
* %G_SUBPROCESS_FLAGS_STDERR_MERGE. The merged result will be returned * %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 * In case of any error (including cancellation), %FALSE will be
* returned with @error set. Some or all of the stdin data may have * 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 * Returns: %TRUE if successful
* *
* Since: 2.36 * Since: 2.40
**/ **/
gboolean gboolean
g_subprocess_communicate (GSubprocess *subprocess, g_subprocess_communicate (GSubprocess *subprocess,
const gchar *stdin_data, GBytes *stdin_buf,
gssize stdin_length,
GCancellable *cancellable, GCancellable *cancellable,
gchar **stdout_data, GBytes **stdout_buf,
gsize *stdout_length, GBytes **stderr_buf,
gchar **stderr_data,
gsize *stderr_length,
GError **error) GError **error)
{ {
GAsyncResult *result = NULL; GAsyncResult *result = NULL;
gboolean success; gboolean success;
g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE); 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_buf == NULL || (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE), FALSE);
g_return_val_if_fail (stdin_data == 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 (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_subprocess_sync_setup (); g_subprocess_sync_setup ();
g_subprocess_communicate_internal (subprocess, NULL, stdin_data, stdin_length, g_subprocess_communicate_internal (subprocess, stdin_buf, cancellable, g_subprocess_sync_done, &result);
cancellable, g_subprocess_sync_done, &result);
g_subprocess_sync_complete (&result); g_subprocess_sync_complete (&result);
success = g_subprocess_communicate_finish (subprocess, result, success = g_subprocess_communicate_finish (subprocess, result, stdout_buf, stderr_buf, error);
stdout_data, stdout_length,
stderr_data, stderr_length, error);
g_object_unref (result); g_object_unref (result);
return success; 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 void
g_subprocess_communicate_async (GSubprocess *subprocess, g_subprocess_communicate_async (GSubprocess *subprocess,
const gchar *stdin_data, GBytes *stdin_buf,
gssize stdin_length,
GCancellable *cancellable, GCancellable *cancellable,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
gpointer user_data) gpointer user_data)
{ {
g_return_if_fail (G_IS_SUBPROCESS (subprocess)); g_return_if_fail (G_IS_SUBPROCESS (subprocess));
g_return_if_fail (stdin_length == 0 || stdin_data != NULL); g_return_if_fail (stdin_buf == NULL || (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE));
g_return_if_fail (stdin_data == NULL || (subprocess->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE));
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); 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 gboolean
g_subprocess_communicate_finish (GSubprocess *subprocess, g_subprocess_communicate_finish (GSubprocess *subprocess,
GAsyncResult *result, GAsyncResult *result,
gchar **stdout_data, GBytes **stdout_buf,
gsize *stdout_length, GBytes **stderr_buf,
gchar **stderr_data,
gsize *stderr_length,
GError **error) GError **error)
{ {
gboolean success;
CommunicateState *state; CommunicateState *state;
gboolean success;
GTask *task;
g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE); 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 (g_task_is_valid (result, subprocess), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
task = G_TASK (result); g_object_ref (result);
state = g_task_get_task_data (task);
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 (success)
{ {
if (stdout_data) if (stdout_buf)
{ *stdout_buf = g_memory_output_stream_steal_as_bytes (state->stdout_buf);
gchar *string; if (stderr_buf)
*stderr_buf = g_memory_output_stream_steal_as_bytes (state->stderr_buf);
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;
} }
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); 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; return success;
} }

View File

@ -124,18 +124,14 @@ gint g_subprocess_get_term_sig (GSubprocess *s
GLIB_AVAILABLE_IN_2_40 GLIB_AVAILABLE_IN_2_40
gboolean g_subprocess_communicate (GSubprocess *subprocess, gboolean g_subprocess_communicate (GSubprocess *subprocess,
const gchar *stdin_data, GBytes *stdin_buf,
gssize stdin_length,
GCancellable *cancellable, GCancellable *cancellable,
gchar **stdout_data, GBytes **stdout_buf,
gsize *stdout_length, GBytes **stderr_buf,
gchar **stderr_data,
gsize *stderr_length,
GError **error); GError **error);
GLIB_AVAILABLE_IN_2_40 GLIB_AVAILABLE_IN_2_40
void g_subprocess_communicate_async (GSubprocess *subprocess, void g_subprocess_communicate_async (GSubprocess *subprocess,
const gchar *stdin_data, GBytes *stdin_buf,
gssize stdin_length,
GCancellable *cancellable, GCancellable *cancellable,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
gpointer user_data); gpointer user_data);
@ -143,30 +139,8 @@ void g_subprocess_communicate_async (GSubprocess *s
GLIB_AVAILABLE_IN_2_40 GLIB_AVAILABLE_IN_2_40
gboolean g_subprocess_communicate_finish (GSubprocess *subprocess, gboolean g_subprocess_communicate_finish (GSubprocess *subprocess,
GAsyncResult *result, GAsyncResult *result,
gchar **stdout_data, GBytes **stdout_buf,
gsize *stdout_length, GBytes **stderr_buf,
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,
GError **error); GError **error);
G_END_DECLS G_END_DECLS

View File

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

View File

@ -101,7 +101,7 @@ void g_subprocess_launcher_take_stderr_fd (GSubpro
gint fd); gint fd);
GLIB_AVAILABLE_IN_2_40 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 source_fd,
gint target_fd); gint target_fd);

View File

@ -545,6 +545,69 @@ test_multi_1 (void)
g_object_unref (third); 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 static gboolean
send_terminate (gpointer user_data) 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); args = get_test_subprocess_args ("write-to-fds", basic_fd_str, needdup_fd_str, NULL);
launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
g_subprocess_launcher_pass_fd (launcher, basic_pipefds[1], basic_pipefds[1]); g_subprocess_launcher_take_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, needdup_pipefds[1], needdup_pipefds[1] + 1);
proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error);
g_ptr_array_free (args, TRUE); g_ptr_array_free (args, TRUE);
g_assert_no_error (local_error); g_assert_no_error (local_error);
(void) close (basic_pipefds[1]);
(void) close (needdup_pipefds[1]);
g_free (basic_fd_str); g_free (basic_fd_str);
g_free (needdup_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-utf8", test_cat_utf8);
g_test_add_func ("/gsubprocess/cat-eof", test_cat_eof); g_test_add_func ("/gsubprocess/cat-eof", test_cat_eof);
g_test_add_func ("/gsubprocess/multi1", test_multi_1); 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); g_test_add_func ("/gsubprocess/terminate", test_terminate);
#ifdef G_OS_UNIX #ifdef G_OS_UNIX
g_test_add_func ("/gsubprocess/stdout-file", test_stdout_file); g_test_add_func ("/gsubprocess/stdout-file", test_stdout_file);