summaryrefslogtreecommitdiff
path: root/gio/gsubprocesslauncher.c
diff options
context:
space:
mode:
Diffstat (limited to 'gio/gsubprocesslauncher.c')
-rw-r--r--gio/gsubprocesslauncher.c654
1 files changed, 654 insertions, 0 deletions
diff --git a/gio/gsubprocesslauncher.c b/gio/gsubprocesslauncher.c
new file mode 100644
index 000000000..d0ecd93f8
--- /dev/null
+++ b/gio/gsubprocesslauncher.c
@@ -0,0 +1,654 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2012 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
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Authors: Colin Walters <walters@verbum.org>
+ * Ryan Lortie <desrt@desrt.ca>
+ */
+
+/**
+ * SECTION:gsubprocess
+ * @title: GSubprocess Launcher
+ * @short_description: Environment options for launching a child process
+ *
+ * This class contains a set of options for launching child processes,
+ * such as where its standard input and output will be directed, the
+ * argument list, the environment, and more.
+ *
+ * While the #GSubprocess class has high level functions covering
+ * popular cases, use of this class allows access to more advanced
+ * options. It can also be used to launch multiple subprocesses with
+ * a similar configuration.
+ *
+ * Since: 2.36
+ */
+
+#define ALL_STDIN_FLAGS (G_SUBPROCESS_FLAGS_STDIN_PIPE | \
+ G_SUBPROCESS_FLAGS_STDIN_INHERIT)
+#define ALL_STDOUT_FLAGS (G_SUBPROCESS_FLAGS_STDOUT_PIPE | \
+ G_SUBPROCESS_FLAGS_STDOUT_SILENCE)
+#define ALL_STDERR_FLAGS (G_SUBPROCESS_FLAGS_STDERR_PIPE | \
+ G_SUBPROCESS_FLAGS_STDERR_SILENCE | \
+ G_SUBPROCESS_FLAGS_STDERR_MERGE)
+
+#include "config.h"
+
+#include "gsubprocesslauncher-private.h"
+#include "gioenumtypes.h"
+#include "gsubprocess.h"
+#include "ginitable.h"
+
+#ifdef G_OS_UNIX
+#include <unistd.h>
+#include <fcntl.h>
+#endif
+
+typedef GObjectClass GSubprocessLauncherClass;
+
+G_DEFINE_TYPE (GSubprocessLauncher, g_subprocess_launcher, G_TYPE_OBJECT);
+
+static gboolean
+verify_disposition (const gchar *stream_name,
+ GSubprocessFlags filtered_flags,
+ gint fd,
+ const gchar *filename)
+{
+ guint n_bits;
+
+ if (!filtered_flags)
+ n_bits = 0;
+ else if (((filtered_flags - 1) & filtered_flags) == 0)
+ n_bits = 1;
+ else
+ n_bits = 2; /* ...or more */
+
+ if (n_bits + (fd >= 0) + (filename != NULL) > 1)
+ {
+ GString *err;
+
+ err = g_string_new (NULL);
+ if (n_bits)
+ {
+ GFlagsClass *class;
+ GFlagsValue *value;
+
+ class = g_type_class_peek (G_TYPE_SUBPROCESS_FLAGS);
+ while ((value = g_flags_get_first_value (class, filtered_flags)))
+ {
+ g_string_append_printf (err, " %s", value->value_name);
+ filtered_flags &= value->value;
+ }
+
+ g_type_class_unref (class);
+ }
+
+ if (fd >= 0)
+ g_string_append_printf (err, " g_subprocess_launcher_take_%s_fd()", stream_name);
+
+ if (filename)
+ g_string_append_printf (err, " g_subprocess_launcher_set_%s_file_path()", stream_name);
+
+ g_critical ("You may specify at most one disposition for the %s stream, but you specified:%s.",
+ stream_name, err->str);
+ g_string_free (err, TRUE);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+verify_flags (GSubprocessFlags flags)
+{
+ return verify_disposition ("stdin", flags & ALL_STDIN_FLAGS, -1, NULL) &&
+ verify_disposition ("stdout", flags & ALL_STDOUT_FLAGS, -1, NULL) &&
+ verify_disposition ("stderr", flags & ALL_STDERR_FLAGS, -1, NULL);
+}
+
+static void
+g_subprocess_launcher_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ GSubprocessLauncher *launcher = G_SUBPROCESS_LAUNCHER (object);
+
+ g_assert (prop_id == 1);
+
+ if (verify_flags (g_value_get_flags (value)))
+ launcher->flags = g_value_get_flags (value);
+}
+
+static void
+g_subprocess_launcher_finalize (GObject *object)
+{
+ GSubprocessLauncher *self = G_SUBPROCESS_LAUNCHER (object);
+
+ g_strfreev (self->envp);
+ g_free (self->cwd);
+
+#ifdef G_OS_UNIX
+ g_free (self->stdin_path);
+ g_free (self->stdout_path);
+ g_free (self->stderr_path);
+
+ if (self->stdin_fd != -1)
+ close (self->stdin_fd);
+
+ if (self->stdout_fd != -1)
+ close (self->stdout_fd);
+
+ if (self->stderr_fd != -1)
+ close (self->stderr_fd);
+#endif
+
+ if (self->child_setup_destroy_notify)
+ (* self->child_setup_destroy_notify) (self->child_setup_user_data);
+
+ G_OBJECT_CLASS (g_subprocess_launcher_parent_class)->finalize (object);
+}
+
+static void
+g_subprocess_launcher_init (GSubprocessLauncher *self)
+{
+ self->envp = g_listenv ();
+
+ self->stdin_fd = -1;
+ self->stdout_fd = -1;
+ self->stderr_fd = -1;
+}
+
+static void
+g_subprocess_launcher_class_init (GSubprocessLauncherClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->set_property = g_subprocess_launcher_set_property;
+ gobject_class->finalize = g_subprocess_launcher_finalize;
+
+ g_object_class_install_property (gobject_class, 1,
+ g_param_spec_flags ("flags", "Flags", "GSubprocessFlags for launched processes",
+ G_TYPE_SUBPROCESS_FLAGS, 0, G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
+}
+
+/**
+ * g_subprocess_launcher_new:
+ *
+ * Creates a new #GSubprocessLauncher.
+ *
+ * The launcher is created with the default options. A copy of the
+ * environment of the calling process is made at the time of this call
+ * and will be used as the environment that the process is launched in.
+ *
+ * Since: 2.36
+ **/
+GSubprocessLauncher *
+g_subprocess_launcher_new (GSubprocessFlags flags)
+{
+ if (!verify_flags (flags))
+ return NULL;
+
+ return g_object_new (G_TYPE_SUBPROCESS_LAUNCHER,
+ "flags", flags,
+ NULL);
+}
+
+/**
+ * g_subprocess_launcher_set_environ:
+ * @self: a #GSubprocess
+ * @environ: the replacement environment
+ *
+ * Replace the entire environment of processes launched from this
+ * launcher with the given 'environ' variable.
+ *
+ * Typically you will build this variable by using g_listenv() to copy
+ * the process 'environ' and using the functions g_environ_setenv(),
+ * g_environ_unsetenv(), etc.
+ *
+ * As an alternative, you can use g_subprocess_launcher_setenv(),
+ * g_subprocess_launcher_unsetenv(), etc.
+ *
+ * All strings in this array are expected to be in the GLib file name
+ * encoding. On UNIX, this means that they can be arbitrary byte
+ * strings. On Windows, they should be in UTF-8.
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_set_environ (GSubprocessLauncher *self,
+ gchar **environ)
+{
+ g_strfreev (self->envp);
+ self->envp = g_strdupv (environ);
+}
+
+/**
+ * g_subprocess_launcher_setenv:
+ * @self: a #GSubprocess
+ * @variable: the environment variable to set, must not contain '='
+ * @value: the new value for the variable
+ * @overwrite: whether to change the variable if it already exists
+ *
+ * Sets the environment variable @variable in the environment of
+ * processes launched from this launcher.
+ *
+ * Both the variable's name and value should be in the GLib file name
+ * encoding. On UNIX, this means that they can be arbitrary byte
+ * strings. On Windows, they should be in UTF-8.
+ *
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_setenv (GSubprocessLauncher *self,
+ const gchar *variable,
+ const gchar *value,
+ gboolean overwrite)
+{
+ self->envp = g_environ_setenv (self->envp, variable, value, overwrite);
+}
+
+/**
+ * g_subprocess_launcher_unsetsenv:
+ * @self: a #GSubprocess
+ * @variable: the environment variable to unset, must not contain '='
+ *
+ * Removes the environment variable @variable from the environment of
+ * processes launched from this launcher.
+ *
+ * The variable name should be in the GLib file name encoding. On UNIX,
+ * this means that they can be arbitrary byte strings. On Windows, they
+ * should be in UTF-8.
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_unsetenv (GSubprocessLauncher *self,
+ const gchar *variable)
+{
+ self->envp = g_environ_unsetenv (self->envp, variable);
+}
+
+/**
+ * g_subprocess_launcher_getenv:
+ * @self: a #GSubprocess
+ * @variable: the environment variable to get
+ *
+ * Returns the value of the environment variable @variable in the
+ * environment of processes launched from this launcher.
+ *
+ * The returned string is in the GLib file name encoding. On UNIX, this
+ * means that it can be an arbitrary byte string. On Windows, it will
+ * be UTF-8.
+ *
+ * Returns: the value of the environment variable, %NULL if unset
+ *
+ * Since: 2.36
+ **/
+const gchar *
+g_subprocess_launcher_getenv (GSubprocessLauncher *self,
+ const gchar *variable)
+{
+ return g_environ_getenv (self->envp, variable);
+}
+
+/**
+ * g_subprocess_launcher_set_cwd:
+ * @self: a #GSubprocess
+ * @cwd: the cwd for launched processes
+ *
+ * Sets the current working directory that processes will be launched
+ * with.
+ *
+ * By default processes are launched with the current working directory
+ * of the launching process at the time of launch.
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_set_cwd (GSubprocessLauncher *self,
+ const gchar *cwd)
+{
+ g_free (self->cwd);
+ self->cwd = g_strdup (cwd);
+}
+
+/**
+ * g_subprocess_launcher_set_flags:
+ * @self: a #GSubprocessLauncher
+ * @flags: #GSubprocessFlags
+ *
+ * Sets the flags on the launcher.
+ *
+ * The default flags are %G_SUBPROCESS_FLAGS_NONE.
+ *
+ * You may not set flags that specify conflicting options for how to
+ * handle a particular stdio stream (eg: specifying both
+ * %G_SUBPROCESS_FLAGS_STDIN_PIPE and
+ * %G_SUBPROCESS_FLAGS_STDIN_INHERIT).
+ *
+ * You may also not set a flag that conflicts with a previous call to a
+ * function like g_subprocess_launcher_set_stdin_file_path() or
+ * g_subprocess_launcher_take_stdout_fd().
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_set_flags (GSubprocessLauncher *self,
+ GSubprocessFlags flags)
+{
+ if (verify_disposition ("stdin", flags & ALL_STDIN_FLAGS, self->stdin_fd, self->stdin_path) &&
+ verify_disposition ("stdout", flags & ALL_STDOUT_FLAGS, self->stdout_fd, self->stdout_path) &&
+ verify_disposition ("stderr", flags & ALL_STDERR_FLAGS, self->stderr_fd, self->stderr_path))
+ self->flags = flags;
+}
+
+#ifdef G_OS_UNIX
+static void
+assign_fd (gint *fd_ptr, gint fd)
+{
+ gint flags;
+
+ if (*fd_ptr != -1)
+ close (*fd_ptr);
+
+ *fd_ptr = fd;
+
+ if (fd != -1)
+ {
+ /* best effort */
+ flags = fcntl (fd, F_GETFD);
+ if (~flags & FD_CLOEXEC)
+ fcntl (fd, F_SETFD, flags | FD_CLOEXEC);
+ }
+}
+
+/**
+ * g_subprocess_launcher_set_stdin_file_path:
+ * @self: a #GSubprocessLauncher
+ * @path: a filename or %NULL
+ *
+ * Sets the file path to use as the stdin for spawned processes.
+ *
+ * If @path is %NULL then any previously given path is unset.
+ *
+ * The file must exist or spawning the process will fail.
+ *
+ * You may not set a stdin file path if a stdin fd is already set or if
+ * the launcher flags contain any flags directing stdin elsewhere.
+ *
+ * This feature is only available on UNIX.
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_set_stdin_file_path (GSubprocessLauncher *self,
+ const gchar *path)
+{
+ if (verify_disposition ("stdin", self->flags & ALL_STDIN_FLAGS, self->stdin_fd, path))
+ {
+ g_free (self->stdin_path);
+ self->stdin_path = g_strdup (path);
+ }
+}
+
+/**
+ * g_subprocess_launcher_take_stdin_fd:
+ * @self: a #GSubprocessLauncher
+ * @fd: a file descriptor, or -1
+ *
+ * Sets the file descriptor to use as the stdin for spawned processes.
+ *
+ * If @fd is -1 then any previously given fd is unset.
+ *
+ * Note that if your intention is to have the stdin of the calling
+ * process inherited by the child then %G_SUBPROCESS_FLAGS_STDIN_INHERIT
+ * is a better way to go about doing that.
+ *
+ * The passed @fd is noted but will not be touched in the current
+ * process. It is therefore necessary that it be kept open by the
+ * caller until the subprocess is spawned. The file descriptor will
+ * also not be explicitly closed on the child side, so it must be marked
+ * O_CLOEXEC if that's what you want.
+ *
+ * You may not set a stdin fd if a stdin file path is already set or if
+ * the launcher flags contain any flags directing stdin elsewhere.
+ *
+ * This feature is only available on UNIX.
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_take_stdin_fd (GSubprocessLauncher *self,
+ gint fd)
+{
+ if (verify_disposition ("stdin", self->flags & ALL_STDIN_FLAGS, fd, self->stdin_path))
+ assign_fd (&self->stdin_fd, fd);
+}
+
+/**
+ * g_subprocess_launcher_set_stdout_file_path:
+ * @self: a #GSubprocessLauncher
+ * @path: a filename or %NULL
+ *
+ * Sets the file path to use as the stdout for spawned processes.
+ *
+ * If @path is %NULL then any previously given path is unset.
+ *
+ * The file will be created or truncated when the process is spawned, as
+ * would be the case if using '>' at the shell.
+ *
+ * You may not set a stdout file path if a stdout fd is already set or
+ * if the launcher flags contain any flags directing stdout elsewhere.
+ *
+ * This feature is only available on UNIX.
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_set_stdout_file_path (GSubprocessLauncher *self,
+ const gchar *path)
+{
+ if (verify_disposition ("stdout", self->flags & ALL_STDOUT_FLAGS, self->stdout_fd, path))
+ {
+ g_free (self->stdout_path);
+ self->stdout_path = g_strdup (path);
+ }
+}
+
+/**
+ * g_subprocess_launcher_take_stdout_fd:
+ * @self: a #GSubprocessLauncher
+ * @fd: a file descriptor, or -1
+ *
+ * Sets the file descriptor to use as the stdout for spawned processes.
+ *
+ * If @fd is -1 then any previously given fd is unset.
+ *
+ * Note that the default behaviour is to pass stdout through to the
+ * stdout of the parent process.
+ *
+ * The passed @fd is noted but will not be touched in the current
+ * process. It is therefore necessary that it be kept open by the
+ * caller until the subprocess is spawned. The file descriptor will
+ * also not be explicitly closed on the child side, so it must be marked
+ * O_CLOEXEC if that's what you want.
+ *
+ * You may not set a stdout fd if a stdout file path is already set or
+ * if the launcher flags contain any flags directing stdout elsewhere.
+ *
+ * This feature is only available on UNIX.
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_take_stdout_fd (GSubprocessLauncher *self,
+ gint fd)
+{
+ if (verify_disposition ("stdout", self->flags & ALL_STDOUT_FLAGS, fd, self->stdout_path))
+ assign_fd (&self->stdout_fd, fd);
+}
+
+/**
+ * g_subprocess_launcher_set_stderr_file_path:
+ * @self: a #GSubprocessLauncher
+ * @path: a filename or %NULL
+ *
+ * Sets the file path to use as the stderr for spawned processes.
+ *
+ * If @path is %NULL then any previously given path is unset.
+ *
+ * The file will be created or truncated when the process is spawned, as
+ * would be the case if using '2>' at the shell.
+ *
+ * If you want to send both stdout and stderr to the same file then use
+ * %G_SUBPROCESS_FLAGS_STDERR_MERGE.
+ *
+ * You may not set a stderr file path if a stderr fd is already set or
+ * if the launcher flags contain any flags directing stderr elsewhere.
+ *
+ * This feature is only available on UNIX.
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_set_stderr_file_path (GSubprocessLauncher *self,
+ const gchar *path)
+{
+ if (verify_disposition ("stderr", self->flags & ALL_STDERR_FLAGS, self->stderr_fd, path))
+ {
+ g_free (self->stderr_path);
+ self->stderr_path = g_strdup (path);
+ }
+}
+
+/**
+ * g_subprocess_launcher_take_stderr_fd:
+ * @self: a #GSubprocessLauncher
+ * @fd: a file descriptor, or -1
+ *
+ * Sets the file descriptor to use as the stderr for spawned processes.
+ *
+ * If @fd is -1 then any previously given fd is unset.
+ *
+ * Note that the default behaviour is to pass stderr through to the
+ * stderr of the parent process.
+ *
+ * The passed @fd belongs to the #GSubprocessLauncher. It will be
+ * automatically closed when the launcher is finalized. The file
+ * descriptor will also be closed on the child side when executing the
+ * spawned process.
+ *
+ * You may not set a stderr fd if a stderr file path is already set or
+ * if the launcher flags contain any flags directing stderr elsewhere.
+ *
+ * This feature is only available on UNIX.
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_take_stderr_fd (GSubprocessLauncher *self,
+ gint fd)
+{
+ if (verify_disposition ("stderr", self->flags & ALL_STDERR_FLAGS, fd, self->stderr_path))
+ assign_fd (&self->stderr_fd, fd);
+}
+
+/**
+ * g_subprocess_launcher_set_child_setup:
+ * @self: a #GSubprocessLauncher
+ * @child_setup: a #GSpawnChildSetupFunc to use as the child setup function
+ * @user_data: user data for @child_setup
+ * @destroy_notify: a #GDestroyNotify for @user_data
+ *
+ * Sets up a child setup function.
+ *
+ * The child setup function will be called after fork() but before
+ * exec() on the child's side.
+ *
+ * @destroy_notify will not be automatically called on the child's side
+ * of the fork(). It will only be called when the last reference on the
+ * #GSubprocessLauncher is dropped or when a new child setup function is
+ * given.
+ *
+ * %NULL can be given as @child_setup to disable the functionality.
+ *
+ * Child setup functions are only available on UNIX.
+ *
+ * Since: 2.36
+ **/
+void
+g_subprocess_launcher_set_child_setup (GSubprocessLauncher *self,
+ GSpawnChildSetupFunc child_setup,
+ gpointer user_data,
+ GDestroyNotify destroy_notify)
+{
+ if (self->child_setup_destroy_notify)
+ (* self->child_setup_destroy_notify) (self->child_setup_user_data);
+
+ self->child_setup_func = child_setup;
+ self->child_setup_user_data = user_data;
+ self->child_setup_destroy_notify = destroy_notify;
+}
+#endif
+
+GSubprocess *
+g_subprocess_launcher_spawn (GSubprocessLauncher *launcher,
+ GError **error,
+ const gchar *argv0,
+ ...)
+{
+ GSubprocess *result;
+ GPtrArray *args;
+ const gchar *arg;
+ va_list ap;
+
+ g_return_val_if_fail (argv0 != NULL && argv0[0] != '\0', NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ args = g_ptr_array_new ();
+
+ va_start (ap, argv0);
+ g_ptr_array_add (args, (gchar *) argv0);
+ while ((arg = va_arg (ap, const gchar *)))
+ g_ptr_array_add (args, (gchar *) arg);
+
+ result = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error);
+
+ g_ptr_array_free (args, TRUE);
+
+ return result;
+
+}
+
+GSubprocess *
+g_subprocess_launcher_spawnv (GSubprocessLauncher *launcher,
+ const gchar * const *argv,
+ GError **error)
+{
+ GSubprocess *subprocess;
+
+ g_return_val_if_fail (argv != NULL && argv[0] != NULL && argv[0][0] != '\0', NULL);
+
+ subprocess = g_object_new (G_TYPE_SUBPROCESS,
+ "argv", argv,
+ "flags", launcher->flags,
+ NULL);
+ g_subprocess_set_launcher (subprocess, launcher);
+
+ if (!g_initable_init (G_INITABLE (subprocess), NULL, error))
+ {
+ g_object_unref (subprocess);
+ return NULL;
+ }
+
+ return subprocess;
+}