mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-02-04 10:16:17 +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_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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user