mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-02-09 20:35:49 +01:00
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:
parent
5b48dc40cc
commit
0e1a3ee345
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user