/* GIO - GLib Input, Output and Streaming Library
*
- * Copyright © 2012 Red Hat, Inc.
- * Copyright © 2012-2013 Canonical Limited
+ * Copyright © 2012, 2013 Red Hat, Inc.
+ * Copyright © 2012, 2013 Canonical Limited
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* SECTION:gsubprocess
* @title: GSubprocess
* @short_description: Child processes
+ * @include: gio/gio.h
* @see_also: #GSubprocessLauncher
*
* #GSubprocess allows the creation of and interaction with child
* comprehensive API for asynchronous I/O, such
* g_output_stream_splice_async(). This makes GSubprocess
* significantly more powerful and flexible than equivalent APIs in
- * some other languages such as the <literal>subprocess.py</literal>
+ * some other languages such as the `subprocess.py`
* included with Python. For example, using #GSubprocess one could
* create two child processes, reading standard output from the first,
* processing it, and writing to the input stream of the second, all
* without blocking the main loop.
*
* A powerful g_subprocess_communicate() API is provided similar to the
- * <literal>communicate()</literal> method of
- * <literal>subprocess.py</literal>. This enables very easy interaction
- * with a subprocess that has been opened with pipes.
+ * `communicate()` method of `subprocess.py`. This enables very easy
+ * interaction with a subprocess that has been opened with pipes.
*
* #GSubprocess defaults to tight control over the file descriptors open
* in the child process, avoiding dangling-fd issues that are caused by
* change of working directory, child setup functions, etc).
*
* A typical use of #GSubprocess will involve calling
- * g_subprocess_new(), followed by g_subprocess_wait() or
- * g_subprocess_wait_sync(). After the process exits, the status can be
+ * g_subprocess_new(), followed by g_subprocess_wait_async() or
+ * g_subprocess_wait(). After the process exits, the status can be
* checked using functions such as g_subprocess_get_if_exited() (which
* are similar to the familiar WIFEXITED-style POSIX macros).
*
- * Since: 2.36
+ * Since: 2.40
**/
#include "config.h"
#include <fcntl.h>
#endif
#ifdef G_OS_WIN32
-#define _WIN32_WINNT 0x0500
#include <windows.h>
+#include <io.h>
#include "giowin32-priv.h"
#endif
#define O_BINARY 0
#endif
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#else
+#define HAVE_O_CLOEXEC 1
+#endif
+
#define COMMUNICATE_READ_SIZE 4096
/* A GSubprocess can have two possible states: running and not.
N_PROPS
};
+#ifdef G_OS_UNIX
typedef struct
{
gint fds[3];
}
}
+static int
+dupfd_cloexec (int parent_fd)
+{
+ int fd;
+#ifdef F_DUPFD_CLOEXEC
+ do
+ fd = fcntl (parent_fd, F_DUPFD_CLOEXEC, 3);
+ while (fd == -1 && errno == EINTR);
+#else
+ /* OS X Snow Lion and earlier don't have F_DUPFD_CLOEXEC:
+ * https://bugzilla.gnome.org/show_bug.cgi?id=710962
+ */
+ int result, flags;
+ do
+ fd = fcntl (parent_fd, F_DUPFD, 3);
+ while (fd == -1 && errno == EINTR);
+ flags = fcntl (fd, F_GETFD, 0);
+ if (flags != -1)
+ {
+ flags |= FD_CLOEXEC;
+ do
+ result = fcntl (fd, F_SETFD, flags);
+ while (result == -1 && errno == EINTR);
+ }
+#endif
+ return fd;
+}
+
/**
* Based on code derived from
* gnome-terminal:src/terminal-screen.c:terminal_screen_child_setup(),
gint parent_fd = g_array_index (child_data->needdup_fd_assignments, int, i);
gint new_parent_fd;
- do
- new_parent_fd = fcntl (parent_fd, F_DUPFD_CLOEXEC, 3);
- while (parent_fd == -1 && errno == EINTR);
+ new_parent_fd = dupfd_cloexec (parent_fd);
g_array_index (child_data->needdup_fd_assignments, int, i) = new_parent_fd;
}
if (child_data->child_setup_func)
child_data->child_setup_func (child_data->child_setup_data);
}
+#endif
static GInputStream *
platform_input_stream_from_spawn_fd (gint fd)
g_free (display_name);
/* fall through... */
}
+#ifndef HAVE_O_CLOEXEC
+ else
+ fcntl (my_fd, F_SETFD, FD_CLOEXEC);
+#endif
return my_fd;
}
while (tasks)
{
g_task_return_boolean (tasks->data, TRUE);
+ g_object_unref (tasks->data);
tasks = g_slist_delete_link (tasks, tasks);
}
GError **error)
{
GSubprocess *self = G_SUBPROCESS (initable);
+#ifdef G_OS_UNIX
ChildData child_data = { { -1, -1, -1 }, 0 };
+#endif
gint *pipe_ptrs[3] = { NULL, NULL, NULL };
gint pipe_fds[3] = { -1, -1, -1 };
gint close_fds[3] = { -1, -1, -1 };
spawn_flags |= G_SPAWN_STDERR_TO_DEV_NULL;
else if (self->flags & G_SUBPROCESS_FLAGS_STDERR_PIPE)
pipe_ptrs[2] = &pipe_fds[2];
+#ifdef G_OS_UNIX
else if (self->flags & G_SUBPROCESS_FLAGS_STDERR_MERGE)
/* This will work because stderr gets setup after stdout. */
child_data.fds[2] = 1;
-#ifdef G_OS_UNIX
else if (self->launcher)
{
if (self->launcher->stderr_fd != -1)
spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
spawn_flags |= G_SPAWN_CLOEXEC_PIPES;
+#ifdef G_OS_UNIX
child_data.child_setup_func = self->launcher ? self->launcher->child_setup_func : NULL;
child_data.child_setup_data = self->launcher ? self->launcher->child_setup_user_data : NULL;
+#endif
+
success = g_spawn_async_with_pipes (self->launcher ? self->launcher->cwd : NULL,
self->argv,
self->launcher ? self->launcher->envp : NULL,
spawn_flags,
+#ifdef G_OS_UNIX
child_setup, &child_data,
+#else
+ NULL, NULL,
+#endif
&self->pid,
pipe_ptrs[0], pipe_ptrs[1], pipe_ptrs[2],
error);
identifier = (guint64) self->pid;
#endif
- s = snprintf (self->identifier, sizeof self->identifier, "%"G_GUINT64_FORMAT, identifier);
+ s = g_snprintf (self->identifier, sizeof self->identifier, "%"G_GUINT64_FORMAT, identifier);
g_assert (0 < s && s < sizeof self->identifier);
}
g_source_unref (source);
}
+#ifdef G_OS_UNIX
out:
+#endif
/* we don't need this past init... */
self->launcher = NULL;
g_clear_object (&self->stdin_pipe);
g_clear_object (&self->stdout_pipe);
g_clear_object (&self->stderr_pipe);
- g_free (self->argv);
+ g_strfreev (self->argv);
+
+ g_mutex_clear (&self->pending_waits_lock);
G_OBJECT_CLASS (g_subprocess_parent_class)->finalize (object);
}
static void
g_subprocess_init (GSubprocess *self)
{
+ g_mutex_init (&self->pending_waits_lock);
}
static void
/**
* g_subprocess_new: (skip)
+ * @flags: flags that define the behaviour of the subprocess
+ * @error: (allow-none): return location for an error, or %NULL
+ * @argv0: first commandline argument to pass to the subprocess
+ * @...: more commandline arguments, followed by %NULL
*
- * Create a new process with the given flags and varargs argument list.
+ * Create a new process with the given flags and varargs argument
+ * list. By default, matching the g_spawn_async() defaults, the
+ * child's stdin will be set to the system null device, and
+ * stdout/stderr will be inherited from the parent. You can use
+ * @flags to control this behavior.
*
* The argument list must be terminated with %NULL.
*
* Returns: A newly created #GSubprocess, or %NULL on error (and @error
* will be set)
*
- * Since: 2.36
+ * Since: 2.40
*/
GSubprocess *
g_subprocess_new (GSubprocessFlags flags,
while ((arg = va_arg (ap, const gchar *)))
g_ptr_array_add (args, (gchar *) arg);
g_ptr_array_add (args, NULL);
+ va_end (ap);
result = g_subprocess_newv ((const gchar * const *) args->pdata, flags, error);
/**
* g_subprocess_newv:
+ * @argv: (array zero-terminated=1) (element-type utf8): commandline arguments for the subprocess
+ * @flags: flags that define the behaviour of the subprocess
+ * @error: (allow-none): return location for an error, or %NULL
*
* Create a new process with the given flags and argument list.
*
* Returns: A newly created #GSubprocess, or %NULL on error (and @error
* will be set)
*
- * Since: 2.36
+ * Since: 2.40
* Rename to: g_subprocess_new
*/
GSubprocess *
NULL);
}
+/**
+ * g_subprocess_get_identifier:
+ * @subprocess: a #GSubprocess
+ *
+ * On UNIX, returns the process ID as a decimal string.
+ * On Windows, returns the result of GetProcessId() also as a string.
+ */
const gchar *
-g_subprocess_get_identifier (GSubprocess *self)
+g_subprocess_get_identifier (GSubprocess *subprocess)
{
- g_return_val_if_fail (G_IS_SUBPROCESS (self), NULL);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), NULL);
- if (self->pid)
- return self->identifier;
+ if (subprocess->pid)
+ return subprocess->identifier;
else
return NULL;
}
+/**
+ * g_subprocess_get_stdin_pipe:
+ * @subprocess: a #GSubprocess
+ *
+ * Gets the #GOutputStream that you can write to in order to give data
+ * to the stdin of @subprocess.
+ *
+ * The process must have been created with
+ * %G_SUBPROCESS_FLAGS_STDIN_PIPE.
+ *
+ * Returns: (transfer none): the stdout pipe
+ *
+ * Since: 2.40
+ **/
GOutputStream *
-g_subprocess_get_stdin_pipe (GSubprocess *self)
+g_subprocess_get_stdin_pipe (GSubprocess *subprocess)
{
- g_return_val_if_fail (G_IS_SUBPROCESS (self), NULL);
- g_return_val_if_fail (self->stdin_pipe, NULL);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), NULL);
+ g_return_val_if_fail (subprocess->stdin_pipe, NULL);
- return self->stdin_pipe;
+ return subprocess->stdin_pipe;
}
+/**
+ * g_subprocess_get_stdout_pipe:
+ * @subprocess: a #GSubprocess
+ *
+ * Gets the #GInputStream from which to read the stdout output of
+ * @subprocess.
+ *
+ * The process must have been created with
+ * %G_SUBPROCESS_FLAGS_STDOUT_PIPE.
+ *
+ * Returns: (transfer none): the stdout pipe
+ *
+ * Since: 2.40
+ **/
GInputStream *
-g_subprocess_get_stdout_pipe (GSubprocess *self)
+g_subprocess_get_stdout_pipe (GSubprocess *subprocess)
{
- g_return_val_if_fail (G_IS_SUBPROCESS (self), NULL);
- g_return_val_if_fail (self->stdout_pipe, NULL);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), NULL);
+ g_return_val_if_fail (subprocess->stdout_pipe, NULL);
- return self->stdout_pipe;
+ return subprocess->stdout_pipe;
}
+/**
+ * g_subprocess_get_stderr_pipe:
+ * @subprocess: a #GSubprocess
+ *
+ * Gets the #GInputStream from which to read the stderr output of
+ * @subprocess.
+ *
+ * The process must have been created with
+ * %G_SUBPROCESS_FLAGS_STDERR_PIPE.
+ *
+ * Returns: (transfer none): the stderr pipe
+ *
+ * Since: 2.40
+ **/
GInputStream *
-g_subprocess_get_stderr_pipe (GSubprocess *self)
+g_subprocess_get_stderr_pipe (GSubprocess *subprocess)
{
- g_return_val_if_fail (G_IS_SUBPROCESS (self), NULL);
- g_return_val_if_fail (self->stderr_pipe, NULL);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), NULL);
+ g_return_val_if_fail (subprocess->stderr_pipe, NULL);
- return self->stderr_pipe;
+ return subprocess->stderr_pipe;
}
static void
g_object_unref (task);
}
+/**
+ * g_subprocess_wait_async:
+ * @subprocess: a #GSubprocess
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the operation is complete
+ * @user_data: user_data for @callback
+ *
+ * Wait for the subprocess to terminate.
+ *
+ * This is the asynchronous version of g_subprocess_wait().
+ *
+ * Since: 2.40
+ */
void
-g_subprocess_wait_async (GSubprocess *self,
+g_subprocess_wait_async (GSubprocess *subprocess,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
- task = g_task_new (self, cancellable, callback, user_data);
+ task = g_task_new (subprocess, cancellable, callback, user_data);
- g_mutex_lock (&self->pending_waits_lock);
- if (self->pid)
+ g_mutex_lock (&subprocess->pending_waits_lock);
+ if (subprocess->pid)
{
/* Only bother with cancellable if we're putting it in the list.
* If not, it's going to dispatch immediately anyway and we will
if (cancellable)
g_signal_connect_object (cancellable, "cancelled", G_CALLBACK (g_subprocess_wait_cancelled), task, 0);
- self->pending_waits = g_slist_prepend (self->pending_waits, task);
+ subprocess->pending_waits = g_slist_prepend (subprocess->pending_waits, task);
task = NULL;
}
- g_mutex_unlock (&self->pending_waits_lock);
+ g_mutex_unlock (&subprocess->pending_waits_lock);
/* If we still have task then it's because did_exit is already TRUE */
if (task != NULL)
}
}
+/**
+ * g_subprocess_wait_finish:
+ * @subprocess: a #GSubprocess
+ * @result: the #GAsyncResult passed to your #GAsyncReadyCallback
+ * @error: a pointer to a %NULL #GError, or %NULL
+ *
+ * Collects the result of a previous call to
+ * g_subprocess_wait_async().
+ *
+ * Returns: %TRUE if successful, or %FALSE with @error set
+ *
+ * Since: 2.40
+ */
gboolean
-g_subprocess_wait_finish (GSubprocess *self,
+g_subprocess_wait_finish (GSubprocess *subprocess,
GAsyncResult *result,
GError **error)
{
/**
* g_subprocess_wait:
- * @self: a #GSubprocess
+ * @subprocess: a #GSubprocess
* @cancellable: a #GCancellable
* @error: a #GError
*
- * Synchronously wait for the subprocess to terminate, returning the
- * status code in @out_exit_status. See the documentation of
- * g_spawn_check_exit_status() for how to interpret it. Note that if
- * @error is set, then @out_exit_status will be left uninitialized.
+ * Synchronously wait for the subprocess to terminate.
+ *
+ * After the process terminates you can query its exit status with
+ * functions such as g_subprocess_get_if_exited() and
+ * g_subprocess_get_exit_status().
+ *
+ * This function does not fail in the case of the subprocess having
+ * abnormal termination. See g_subprocess_wait_check() for that.
+ *
+ * Cancelling @cancellable doesn't kill the subprocess. Call
+ * g_subprocess_force_exit() if it is desirable.
*
* Returns: %TRUE on success, %FALSE if @cancellable was cancelled
*
- * Since: 2.36
+ * Since: 2.40
*/
gboolean
-g_subprocess_wait (GSubprocess *self,
+g_subprocess_wait (GSubprocess *subprocess,
GCancellable *cancellable,
GError **error)
{
GAsyncResult *result = NULL;
gboolean success;
- g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE);
/* Synchronous waits are actually the 'more difficult' case because we
* need to deal with the possibility of cancellation. That more or
/* We can shortcut in the case that the process already quit (but only
* after we checked the cancellable).
*/
- if (self->pid == 0)
+ if (subprocess->pid == 0)
return TRUE;
/* Otherwise, we need to do this the long way... */
g_subprocess_sync_setup ();
- g_subprocess_wait_async (self, cancellable, g_subprocess_sync_done, &result);
+ g_subprocess_wait_async (subprocess, cancellable, g_subprocess_sync_done, &result);
g_subprocess_sync_complete (&result);
- success = g_subprocess_wait_finish (self, result, error);
+ success = g_subprocess_wait_finish (subprocess, result, error);
g_object_unref (result);
return success;
}
/**
- * g_subprocess_wait_sync_check:
- * @self: a #GSubprocess
+ * g_subprocess_wait_check:
+ * @subprocess: a #GSubprocess
* @cancellable: a #GCancellable
* @error: a #GError
*
- * Combines g_subprocess_wait_sync() with g_spawn_check_exit_status().
+ * Combines g_subprocess_wait() with g_spawn_check_exit_status().
*
- * Returns: %TRUE on success, %FALSE if process exited abnormally, or @cancellable was cancelled
+ * Returns: %TRUE on success, %FALSE if process exited abnormally, or
+ * @cancellable was cancelled
*
- * Since: 2.36
+ * Since: 2.40
*/
gboolean
-g_subprocess_wait_check (GSubprocess *self,
+g_subprocess_wait_check (GSubprocess *subprocess,
GCancellable *cancellable,
GError **error)
{
- return g_subprocess_wait (self, cancellable, error) &&
- g_spawn_check_exit_status (self->status, error);
+ return g_subprocess_wait (subprocess, cancellable, error) &&
+ g_spawn_check_exit_status (subprocess->status, error);
}
+/**
+ * g_subprocess_wait_check_async:
+ * @subprocess: a #GSubprocess
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the operation is complete
+ * @user_data: user_data for @callback
+ *
+ * Combines g_subprocess_wait_async() with g_spawn_check_exit_status().
+ *
+ * This is the asynchronous version of g_subprocess_wait_check().
+ *
+ * Since: 2.40
+ */
void
-g_subprocess_wait_check_async (GSubprocess *self,
+g_subprocess_wait_check_async (GSubprocess *subprocess,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
- g_subprocess_wait_async (self, cancellable, callback, user_data);
+ g_subprocess_wait_async (subprocess, cancellable, callback, user_data);
}
+/**
+ * g_subprocess_wait_check_finish:
+ * @subprocess: a #GSubprocess
+ * @result: the #GAsyncResult passed to your #GAsyncReadyCallback
+ * @error: a pointer to a %NULL #GError, or %NULL
+ *
+ * Collects the result of a previous call to
+ * g_subprocess_wait_check_async().
+ *
+ * Returns: %TRUE if successful, or %FALSE with @error set
+ *
+ * Since: 2.40
+ */
gboolean
-g_subprocess_wait_check_finish (GSubprocess *self,
+g_subprocess_wait_check_finish (GSubprocess *subprocess,
GAsyncResult *result,
GError **error)
{
- return g_subprocess_wait_finish (self, result, error) &&
- g_spawn_check_exit_status (self->status, error);
+ return g_subprocess_wait_finish (subprocess, result, error) &&
+ g_spawn_check_exit_status (subprocess->status, error);
}
#ifdef G_OS_UNIX
}
static void
-g_subprocess_dispatch_signal (GSubprocess *self,
+g_subprocess_dispatch_signal (GSubprocess *subprocess,
gint signalnum)
{
- SignalRecord signal_record = { g_object_ref (self), signalnum };
+ SignalRecord signal_record = { g_object_ref (subprocess), signalnum };
- g_return_if_fail (G_IS_SUBPROCESS (self));
+ g_return_if_fail (G_IS_SUBPROCESS (subprocess));
/* This MUST be a lower priority than the priority that the child
* watch source uses in initable_init().
/**
* g_subprocess_send_signal:
- * @self: a #GSubprocess
+ * @subprocess: a #GSubprocess
* @signal_num: the signal number to send
*
* Sends the UNIX signal @signal_num to the subprocess, if it is still
*
* This API is not available on Windows.
*
- * Since: 2.36
+ * Since: 2.40
**/
void
-g_subprocess_send_signal (GSubprocess *self,
+g_subprocess_send_signal (GSubprocess *subprocess,
gint signal_num)
{
- g_return_if_fail (G_IS_SUBPROCESS (self));
+ g_return_if_fail (G_IS_SUBPROCESS (subprocess));
- g_subprocess_dispatch_signal (self, signal_num);
+ g_subprocess_dispatch_signal (subprocess, signal_num);
}
#endif
/**
* g_subprocess_force_exit:
- * @self: a #GSubprocess
+ * @subprocess: a #GSubprocess
*
* Use an operating-system specific method to attempt an immediate,
* forceful termination of the process. There is no mechanism to
*
* On Unix, this function sends %SIGKILL.
*
- * Since: 2.36
+ * Since: 2.40
**/
void
-g_subprocess_force_exit (GSubprocess *self)
+g_subprocess_force_exit (GSubprocess *subprocess)
{
- g_return_if_fail (G_IS_SUBPROCESS (self));
+ g_return_if_fail (G_IS_SUBPROCESS (subprocess));
#ifdef G_OS_UNIX
- g_subprocess_dispatch_signal (self, SIGKILL);
+ g_subprocess_dispatch_signal (subprocess, SIGKILL);
#else
- TerminateProcess (self->pid, 1);
+ TerminateProcess (subprocess->pid, 1);
#endif
}
/**
* g_subprocess_get_status:
- * @self: a #GSubprocess
+ * @subprocess: a #GSubprocess
*
* Gets the raw status code of the process, as from waitpid().
*
*
* Returns: the (meaningless) waitpid() exit status from the kernel
*
- * Since: 2.36
+ * Since: 2.40
**/
gint
-g_subprocess_get_status (GSubprocess *self)
+g_subprocess_get_status (GSubprocess *subprocess)
{
- g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
- g_return_val_if_fail (self->pid == 0, FALSE);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE);
+ g_return_val_if_fail (subprocess->pid == 0, FALSE);
- return self->status;
+ return subprocess->status;
}
/**
* g_subprocess_get_successful:
- * @self: a #GSubprocess
+ * @subprocess: a #GSubprocess
*
* Checks if the process was "successful". A process is considered
* successful if it exited cleanly with an exit status of 0, either by
*
* Returns: %TRUE if the process exited cleanly with a exit status of 0
*
- * Since: 2.36
+ * Since: 2.40
**/
gboolean
-g_subprocess_get_successful (GSubprocess *self)
+g_subprocess_get_successful (GSubprocess *subprocess)
{
- g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
- g_return_val_if_fail (self->pid == 0, FALSE);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE);
+ g_return_val_if_fail (subprocess->pid == 0, FALSE);
- return WIFEXITED (self->status) && WEXITSTATUS (self->status) == 0;
+#ifdef G_OS_UNIX
+ return WIFEXITED (subprocess->status) && WEXITSTATUS (subprocess->status) == 0;
+#else
+ return subprocess->status == 0;
+#endif
}
/**
* g_subprocess_get_if_exited:
- * @self: a #GSubprocess
+ * @subprocess: a #GSubprocess
*
* Check if the given subprocess exited normally (ie: by way of exit()
* or return from main()).
*
* Returns: %TRUE if the case of a normal exit
*
- * Since: 2.36
+ * Since: 2.40
**/
gboolean
-g_subprocess_get_if_exited (GSubprocess *self)
+g_subprocess_get_if_exited (GSubprocess *subprocess)
{
- g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
- g_return_val_if_fail (self->pid == 0, FALSE);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE);
+ g_return_val_if_fail (subprocess->pid == 0, FALSE);
- return WIFEXITED (self->status);
+#ifdef G_OS_UNIX
+ return WIFEXITED (subprocess->status);
+#else
+ return TRUE;
+#endif
}
/**
* g_subprocess_get_exit_status:
- * @self: a #GSubprocess
+ * @subprocess: a #GSubprocess
*
* Check the exit status of the subprocess, given that it exited
* normally. This is the value passed to the exit() system call or the
*
* Returns: the exit status
*
- * Since: 2.36
+ * Since: 2.40
**/
gint
-g_subprocess_get_exit_status (GSubprocess *self)
+g_subprocess_get_exit_status (GSubprocess *subprocess)
{
- g_return_val_if_fail (G_IS_SUBPROCESS (self), 1);
- g_return_val_if_fail (self->pid == 0, 1);
- g_return_val_if_fail (WIFEXITED (self->status), 1);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), 1);
+ g_return_val_if_fail (subprocess->pid == 0, 1);
+
+#ifdef G_OS_UNIX
+ g_return_val_if_fail (WIFEXITED (subprocess->status), 1);
- return WEXITSTATUS (self->status);
+ return WEXITSTATUS (subprocess->status);
+#else
+ return subprocess->status;
+#endif
}
/**
* g_subprocess_get_if_signaled:
- * @self: a #GSubprocess
+ * @subprocess: a #GSubprocess
*
* Check if the given subprocess terminated in response to a signal.
*
*
* Returns: %TRUE if the case of termination due to a signal
*
- * Since: 2.36
+ * Since: 2.40
**/
gboolean
-g_subprocess_get_if_signaled (GSubprocess *self)
+g_subprocess_get_if_signaled (GSubprocess *subprocess)
{
- g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
- g_return_val_if_fail (self->pid == 0, FALSE);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), FALSE);
+ g_return_val_if_fail (subprocess->pid == 0, FALSE);
- return WIFSIGNALED (self->status);
+#ifdef G_OS_UNIX
+ return WIFSIGNALED (subprocess->status);
+#else
+ return FALSE;
+#endif
}
/**
* g_subprocess_get_term_sig:
- * @self: a #GSubprocess
+ * @subprocess: a #GSubprocess
*
* Get the signal number that caused the subprocess to terminate, given
* that it terminated due to a signal.
*
* Returns: the signal causing termination
*
- * Since: 2.36
+ * Since: 2.40
**/
gint
-g_subprocess_get_term_sig (GSubprocess *self)
+g_subprocess_get_term_sig (GSubprocess *subprocess)
{
- g_return_val_if_fail (G_IS_SUBPROCESS (self), 0);
- g_return_val_if_fail (self->pid == 0, 0);
- g_return_val_if_fail (WIFSIGNALED (self->status), 0);
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), 0);
+ g_return_val_if_fail (subprocess->pid == 0, 0);
+
+#ifdef G_OS_UNIX
+ g_return_val_if_fail (WIFSIGNALED (subprocess->status), 0);
- return WTERMSIG (self->status);
+ return WTERMSIG (subprocess->status);
+#else
+ g_critical ("g_subprocess_get_term_sig() called on Windows, where "
+ "g_subprocess_get_if_signaled() always returns FALSE...");
+ return 0;
+#endif
}
/*< private >*/
gsize stdin_length;
gsize stdin_offset;
- /* Not actually GString. Just borrowing the struct. */
- GString stdout_string;
- GString stderr_string;
+ gboolean add_nul;
- 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,
gpointer user_data)
state = g_task_get_task_data (task);
source = source_object;
- if (source == subprocess->stdin_pipe)
- {
- gssize s;
+ state->outstanding_ops--;
- s = g_output_stream_write_finish (subprocess->stdin_pipe, result, &error);
- g_assert (s != 0);
+ if (source == subprocess->stdin_pipe ||
+ source == state->stdout_buf ||
+ source == state->stderr_buf)
+ {
+ if (g_output_stream_splice_finish ((GOutputStream*) source, result, &error) == -1)
+ goto out;
- if (s != -1)
+ if (source == state->stdout_buf ||
+ source == state->stderr_buf)
{
- 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)
+ /* This is a memory stream, so it can't be cancelled or return
+ * an error really.
+ */
+ if (state->add_nul)
{
- /* 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;
+ gsize bytes_written;
+ if (!g_output_stream_write_all (source, "\0", 1, &bytes_written,
+ NULL, &error))
+ goto out;
}
- }
- }
- 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;
+ if (!g_output_stream_close (source, NULL, &error))
+ goto out;
}
}
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 ();
+ out:
if (error)
{
/* Only report the first error we see.
* 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);
}
{
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->cancellable);
+ 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);
+ if (state->cancellable_source)
+ {
+ if (!g_source_is_destroyed (state->cancellable_source))
+ g_source_destroy (state->cancellable_source);
+ g_source_unref (state->cancellable_source);
+ }
g_slice_free (CommunicateState, state);
}
static CommunicateState *
g_subprocess_communicate_internal (GSubprocess *subprocess,
- GBytes *stdin_bytes,
- const gchar *stdin_data,
- gssize stdin_length,
+ gboolean add_nul,
+ GBytes *stdin_buf,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
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 ();
+ state->add_nul = add_nul;
if (cancellable)
{
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_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_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++;
+ g_object_unref (task);
return state;
}
/**
- * 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
+ * g_subprocess_communicate:
+ * @subprocess: a #GSubprocess
+ * @stdin_buf: (allow-none): 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_buf 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
*
* 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, FALSE, 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: (allow-none): Input data, or %NULL
+ * @cancellable: (allow-none): 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, FALSE, 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)
{
- CommunicateState *state;
gboolean success;
- GTask *task;
+ CommunicateState *state;
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);
}
+ g_object_unref (result);
return success;
}
+/**
+ * g_subprocess_communicate_utf8:
+ * @subprocess: a #GSubprocess
+ * @stdin_buf: (allow-none): data to send to the stdin of the subprocess, or %NULL
+ * @cancellable: a #GCancellable
+ * @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
+ *
+ * Like g_subprocess_communicate(), but validates the output of the
+ * process as UTF-8, and returns it as a regular NUL terminated string.
+ */
gboolean
-g_subprocess_communicate_bytes (GSubprocess *subprocess,
- GBytes *stdin_bytes,
- GCancellable *cancellable,
- GBytes **stdout_bytes,
- GBytes **stderr_bytes,
- GError **error)
+g_subprocess_communicate_utf8 (GSubprocess *subprocess,
+ const char *stdin_buf,
+ GCancellable *cancellable,
+ char **stdout_buf,
+ char **stderr_buf,
+ GError **error)
{
GAsyncResult *result = NULL;
gboolean success;
+ GBytes *stdin_bytes;
+ size_t stdin_buf_len = 0;
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 (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);
+ if (stdin_buf != NULL)
+ stdin_buf_len = strlen (stdin_buf);
+ stdin_bytes = g_bytes_new (stdin_buf, stdin_buf_len);
+
g_subprocess_sync_setup ();
- g_subprocess_communicate_internal (subprocess, stdin_bytes, NULL, 0, cancellable, g_subprocess_sync_done, &result);
+ g_subprocess_communicate_internal (subprocess, TRUE, stdin_bytes, cancellable,
+ g_subprocess_sync_done, &result);
g_subprocess_sync_complete (&result);
- success = g_subprocess_communicate_bytes_finish (subprocess, result, stdout_bytes, stderr_bytes, error);
+ success = g_subprocess_communicate_utf8_finish (subprocess, result, stdout_buf, stderr_buf, error);
g_object_unref (result);
+ g_bytes_unref (stdin_bytes);
return success;
}
+/**
+ * g_subprocess_communicate_utf8_async:
+ * @subprocess: Self
+ * @stdin_buf: (allow-none): Input data, or %NULL
+ * @cancellable: Cancellable
+ * @callback: Callback
+ * @user_data: User data
+ *
+ * Asynchronous version of g_subprocess_communicate_utf8(). Complete
+ * invocation with g_subprocess_communicate_utf8_finish().
+ */
void
-g_subprocess_communicate_bytes_async (GSubprocess *subprocess,
- GBytes *stdin_bytes,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
+g_subprocess_communicate_utf8_async (GSubprocess *subprocess,
+ const char *stdin_buf,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
+ GBytes *stdin_bytes;
+ size_t stdin_buf_len = 0;
+
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 (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, stdin_bytes, NULL, 0, cancellable, callback, user_data);
+ if (stdin_buf != NULL)
+ stdin_buf_len = strlen (stdin_buf);
+ stdin_bytes = g_bytes_new (stdin_buf, stdin_buf_len);
+
+ g_subprocess_communicate_internal (subprocess, TRUE, stdin_bytes, cancellable, callback, user_data);
+
+ g_bytes_unref (stdin_bytes);
+}
+
+static gboolean
+communicate_result_validate_utf8 (const char *stream_name,
+ char **return_location,
+ GMemoryOutputStream *buffer,
+ GError **error)
+{
+ if (return_location == NULL)
+ return TRUE;
+
+ if (buffer)
+ {
+ const char *end;
+ *return_location = g_memory_output_stream_steal_data (buffer);
+ if (!g_utf8_validate (*return_location, -1, &end))
+ {
+ g_free (*return_location);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Invalid UTF-8 in child %s at offset %lu",
+ stream_name,
+ (unsigned long) (end - *return_location));
+ return FALSE;
+ }
+ }
+ else
+ *return_location = NULL;
+
+ return TRUE;
}
+/**
+ * g_subprocess_communicate_utf8_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_utf8_async().
+ */
gboolean
-g_subprocess_communicate_bytes_finish (GSubprocess *subprocess,
- GAsyncResult *result,
- GBytes **stdout_bytes,
- GBytes **stderr_bytes,
- GError **error)
+g_subprocess_communicate_utf8_finish (GSubprocess *subprocess,
+ GAsyncResult *result,
+ char **stdout_buf,
+ char **stderr_buf,
+ GError **error)
{
- gboolean success;
- gchar *stdout_data;
- gsize stdout_length;
- gchar *stderr_data;
- gsize stderr_length;
+ gboolean ret = FALSE;
+ CommunicateState *state;
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);
+ g_object_ref (result);
- 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);
- }
+ state = g_task_get_task_data ((GTask*)result);
+ if (!g_task_propagate_boolean ((GTask*)result, error))
+ goto out;
- return success;
+ /* TODO - validate UTF-8 while streaming, rather than all at once.
+ */
+ if (!communicate_result_validate_utf8 ("stdout", stdout_buf,
+ state->stdout_buf,
+ error))
+ goto out;
+ if (!communicate_result_validate_utf8 ("stderr", stderr_buf,
+ state->stderr_buf,
+ error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ g_object_unref (result);
+ return ret;
}