mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-07-22 09:57:52 +02:00
subprocess WIP
This commit is contained in:
@@ -424,9 +424,9 @@ libgio_2_0_la_SOURCES = \
|
||||
gsocketlistener.c \
|
||||
gsocketoutputstream.c \
|
||||
gsocketoutputstream.h \
|
||||
gsubprocesslauncher.c \
|
||||
gsubprocess.c \
|
||||
gsubprocesscontext.c \
|
||||
gsubprocesscontext-private.h \
|
||||
gsubprocesslauncher-private.h \
|
||||
gproxy.c \
|
||||
gproxyaddress.c \
|
||||
gproxyaddressenumerator.c \
|
||||
@@ -592,7 +592,7 @@ gio_headers = \
|
||||
gsrvtarget.h \
|
||||
gtask.h \
|
||||
gsubprocess.h \
|
||||
gsubprocesscontext.h \
|
||||
gsubprocesslauncher.h \
|
||||
gtcpconnection.h \
|
||||
gtcpwrapperconnection.h \
|
||||
gthreadedsocketservice.h\
|
||||
|
@@ -124,7 +124,7 @@
|
||||
#include <gio/gsrvtarget.h>
|
||||
#include <gio/gtask.h>
|
||||
#include <gio/gsubprocess.h>
|
||||
#include <gio/gsubprocesscontext.h>
|
||||
#include <gio/gsubprocesslauncher.h>
|
||||
#include <gio/gtcpconnection.h>
|
||||
#include <gio/gtcpwrapperconnection.h>
|
||||
#include <gio/gtestdbus.h>
|
||||
|
@@ -1659,23 +1659,25 @@ typedef enum /*< flags >*/ {
|
||||
} GTestDBusFlags;
|
||||
|
||||
/**
|
||||
* GSubprocessStreamDisposition:
|
||||
* @G_SUBPROCESS_STREAM_DISPOSITION_NULL: Redirect to operating system's null output stream
|
||||
* @G_SUBPROCESS_STREAM_DISPOSITION_INHERIT: Keep the stream from the parent process
|
||||
* @G_SUBPROCESS_STREAM_DISPOSITION_PIPE: Open a private unidirectional channel between the processes
|
||||
* @G_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE: Only applicable to standard error; causes it to be merged with standard output
|
||||
* GSubprocessFlags:
|
||||
* @G_SUBPROCESS_FLAGS_NONE: No flags.
|
||||
*
|
||||
* Flags to define the behaviour of the standard input/output/error of
|
||||
* a #GSubprocess.
|
||||
* Flags to define the behaviour of a #GSubprocess.
|
||||
*
|
||||
* Since: 2.36
|
||||
**/
|
||||
typedef enum {
|
||||
G_SUBPROCESS_STREAM_DISPOSITION_NULL,
|
||||
G_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
|
||||
G_SUBPROCESS_STREAM_DISPOSITION_PIPE,
|
||||
G_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE
|
||||
} GSubprocessStreamDisposition;
|
||||
G_SUBPROCESS_FLAGS_NONE = 0,
|
||||
G_SUBPROCESS_FLAGS_SEARCH_PATH = (1u << 0),
|
||||
G_SUBPROCESS_FLAGS_SEARCH_PATH_FROM_ENVP = (1u << 1),
|
||||
G_SUBPROCESS_FLAGS_STDIN_PIPE = (1u << 2),
|
||||
G_SUBPROCESS_FLAGS_STDIN_INHERIT = (1u << 3),
|
||||
G_SUBPROCESS_FLAGS_STDOUT_PIPE = (1u << 4),
|
||||
G_SUBPROCESS_FLAGS_STDOUT_SILENCE = (1u << 5),
|
||||
G_SUBPROCESS_FLAGS_STDERR_PIPE = (1u << 6),
|
||||
G_SUBPROCESS_FLAGS_STDERR_SILENCE = (1u << 7),
|
||||
G_SUBPROCESS_FLAGS_STDERR_MERGE = (1u << 8)
|
||||
} GSubprocessFlags;
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@@ -478,13 +478,13 @@ typedef struct _GTestDBus GTestDBus;
|
||||
*/
|
||||
typedef struct _GSubprocess GSubprocess;
|
||||
/**
|
||||
* GSubprocessContext:
|
||||
* GSubprocessLauncher:
|
||||
*
|
||||
* Options for launching a child process.
|
||||
*
|
||||
* Since: 2.36
|
||||
*/
|
||||
typedef struct _GSubprocessContext GSubprocessContext;
|
||||
typedef struct _GSubprocessLauncher GSubprocessLauncher;
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@@ -38,7 +38,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "gsubprocess.h"
|
||||
#include "gsubprocesscontext-private.h"
|
||||
#include "gsubprocesslauncher-private.h"
|
||||
#include "gasyncresult.h"
|
||||
#include "giostream.h"
|
||||
#include "gmemoryinputstream.h"
|
||||
@@ -77,7 +77,11 @@ struct _GSubprocess
|
||||
{
|
||||
GObject parent;
|
||||
|
||||
GSubprocessContext *context;
|
||||
/* only used during construction */
|
||||
GSubprocessFlags flags;
|
||||
gchar **argv;
|
||||
|
||||
GSubprocessLauncher *launcher;
|
||||
GPid pid;
|
||||
|
||||
guint pid_valid : 1;
|
||||
@@ -96,7 +100,8 @@ G_DEFINE_TYPE_WITH_CODE (GSubprocess, g_subprocess, G_TYPE_OBJECT,
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_CONTEXT,
|
||||
PROP_FLAGS,
|
||||
PROP_ARGV,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
@@ -143,27 +148,12 @@ g_subprocess_set_property (GObject *object,
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_CONTEXT:
|
||||
self->context = g_value_dup_object (value);
|
||||
case PROP_FLAGS:
|
||||
self->flags = g_value_get_flags (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
g_subprocess_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GSubprocess *self = G_SUBPROCESS (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_CONTEXT:
|
||||
g_value_set_object (value, self->context);
|
||||
case PROP_ARGV:
|
||||
self->argv = g_value_dup_boxed (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -177,18 +167,14 @@ g_subprocess_class_init (GSubprocessClass *class)
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
|
||||
|
||||
gobject_class->finalize = g_subprocess_finalize;
|
||||
gobject_class->get_property = g_subprocess_get_property;
|
||||
gobject_class->set_property = g_subprocess_set_property;
|
||||
|
||||
/**
|
||||
* GSubprocess:context:
|
||||
*
|
||||
*
|
||||
* Since: 2.36
|
||||
*/
|
||||
g_subprocess_pspecs[PROP_CONTEXT] = g_param_spec_object ("context", P_("Context"), P_("Subprocess options"), G_TYPE_SUBPROCESS_CONTEXT,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_STATIC_STRINGS);
|
||||
g_subprocess_pspecs[PROP_FLAGS] = g_param_spec_flags ("flags", P_("Flags"), P_("Subprocess flags"),
|
||||
G_TYPE_SUBPROCESS_FLAGS, 0, G_PARAM_WRITABLE |
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
||||
g_subprocess_pspecs[PROP_ARGV] = g_param_spec_boxed ("argv", P_("Arguments"), P_("Argument vector"),
|
||||
G_TYPE_STRV, G_PARAM_WRITABLE |
|
||||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, N_PROPS, g_subprocess_pspecs);
|
||||
}
|
||||
@@ -326,92 +312,79 @@ initable_init (GInitable *initable,
|
||||
*
|
||||
* First, stdin.
|
||||
*/
|
||||
#ifdef G_OS_UNIX
|
||||
if (self->context->stdin_fd != -1)
|
||||
child_data.fds[0] = self->context->stdin_fd;
|
||||
else if (self->context->stdin_path != NULL)
|
||||
{
|
||||
child_data.fds[0] = close_fds[0] = unix_open_file (self->context->stdin_path,
|
||||
O_RDONLY, error);
|
||||
if (child_data.fds[0] == -1)
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (self->context->stdin_disposition == G_SUBPROCESS_STREAM_DISPOSITION_NULL)
|
||||
; /* nothing */
|
||||
else if (self->context->stdin_disposition == G_SUBPROCESS_STREAM_DISPOSITION_INHERIT)
|
||||
if (self->flags & G_SUBPROCESS_FLAGS_STDIN_INHERIT)
|
||||
spawn_flags |= G_SPAWN_CHILD_INHERITS_STDIN;
|
||||
else if (self->context->stdin_disposition == G_SUBPROCESS_STREAM_DISPOSITION_PIPE)
|
||||
else if (self->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE)
|
||||
pipe_ptrs[0] = &pipe_fds[0];
|
||||
else
|
||||
g_assert_not_reached ();
|
||||
#ifdef G_OS_UNIX
|
||||
else if (self->launcher)
|
||||
{
|
||||
if (self->launcher->stdin_fd != -1)
|
||||
child_data.fds[0] = self->launcher->stdin_fd;
|
||||
else if (self->launcher->stdin_path != NULL)
|
||||
{
|
||||
child_data.fds[0] = close_fds[0] = unix_open_file (self->launcher->stdin_path, O_RDONLY, error);
|
||||
if (child_data.fds[0] == -1)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Next, stdout. */
|
||||
#ifdef G_OS_UNIX
|
||||
if (self->context->stdout_fd != -1)
|
||||
child_data.fds[1] = self->context->stdout_fd;
|
||||
else if (self->context->stdout_path != NULL)
|
||||
{
|
||||
child_data.fds[1] = close_fds[1] = unix_open_file (self->context->stdout_path,
|
||||
O_CREAT | O_WRONLY, error);
|
||||
if (child_data.fds[1] == -1)
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (self->context->stdout_disposition == G_SUBPROCESS_STREAM_DISPOSITION_NULL)
|
||||
if (self->flags & G_SUBPROCESS_FLAGS_STDOUT_SILENCE)
|
||||
spawn_flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
|
||||
else if (self->context->stdout_disposition == G_SUBPROCESS_STREAM_DISPOSITION_INHERIT)
|
||||
; /* Nothing */
|
||||
else if (self->context->stdout_disposition == G_SUBPROCESS_STREAM_DISPOSITION_PIPE)
|
||||
else if (self->flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE)
|
||||
pipe_ptrs[1] = &pipe_fds[1];
|
||||
else
|
||||
g_assert_not_reached ();
|
||||
#ifdef G_OS_UNIX
|
||||
else if (self->launcher)
|
||||
{
|
||||
if (self->launcher->stdout_fd != -1)
|
||||
child_data.fds[1] = self->launcher->stdout_fd;
|
||||
else if (self->launcher->stdout_path != NULL)
|
||||
{
|
||||
child_data.fds[1] = close_fds[1] = unix_open_file (self->launcher->stdout_path,
|
||||
O_CREAT | O_WRONLY, error);
|
||||
if (child_data.fds[1] == -1)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Finally, stderr. */
|
||||
#ifdef G_OS_UNIX
|
||||
if (self->context->stderr_fd != -1)
|
||||
child_data.fds[2] = self->context->stderr_fd;
|
||||
else if (self->context->stderr_path != NULL)
|
||||
{
|
||||
child_data.fds[2] = close_fds[2] = unix_open_file (self->context->stderr_path,
|
||||
O_CREAT | O_WRONLY, error);
|
||||
if (child_data.fds[2] == -1)
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (self->context->stderr_disposition == G_SUBPROCESS_STREAM_DISPOSITION_NULL)
|
||||
if (self->flags & G_SUBPROCESS_FLAGS_STDERR_SILENCE)
|
||||
spawn_flags |= G_SPAWN_STDERR_TO_DEV_NULL;
|
||||
else if (self->context->stderr_disposition == G_SUBPROCESS_STREAM_DISPOSITION_INHERIT)
|
||||
; /* Nothing */
|
||||
else if (self->context->stderr_disposition == G_SUBPROCESS_STREAM_DISPOSITION_PIPE)
|
||||
else if (self->flags & G_SUBPROCESS_FLAGS_STDERR_PIPE)
|
||||
pipe_ptrs[2] = &pipe_fds[2];
|
||||
else if (self->context->stderr_disposition == G_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE)
|
||||
/* This will work because stderr gets setup after stdout. */
|
||||
child_data.fds[2] = 1;
|
||||
else
|
||||
g_assert_not_reached ();
|
||||
#ifdef G_OS_UNIX
|
||||
if (self->launcher)
|
||||
{
|
||||
if (self->launcher->stderr_fd != -1)
|
||||
child_data.fds[2] = self->launcher->stderr_fd;
|
||||
else if (self->launcher->stderr_path != NULL)
|
||||
{
|
||||
child_data.fds[2] = close_fds[2] = unix_open_file (self->launcher->stderr_path,
|
||||
O_CREAT | O_WRONLY, error);
|
||||
if (child_data.fds[2] == -1)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (self->context->keep_descriptors)
|
||||
spawn_flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
|
||||
|
||||
if (self->context->search_path)
|
||||
if (self->flags & G_SUBPROCESS_FLAGS_SEARCH_PATH)
|
||||
spawn_flags |= G_SPAWN_SEARCH_PATH;
|
||||
else if (self->context->search_path_from_envp)
|
||||
|
||||
else if (self->flags & G_SUBPROCESS_FLAGS_SEARCH_PATH_FROM_ENVP)
|
||||
spawn_flags |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
|
||||
else if (!g_path_is_absolute (((gchar**)self->context->argv->pdata)[0]))
|
||||
spawn_flags |= G_SPAWN_SEARCH_PATH;
|
||||
|
||||
spawn_flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
|
||||
spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
|
||||
spawn_flags |= G_SPAWN_CLOEXEC_PIPES;
|
||||
|
||||
child_data.child_setup_func = self->context->child_setup_func;
|
||||
child_data.child_setup_data = self->context->child_setup_data;
|
||||
success = g_spawn_async_with_pipes (self->context->cwd,
|
||||
(char**)self->context->argv->pdata,
|
||||
self->context->envp,
|
||||
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;
|
||||
success = g_spawn_async_with_pipes (self->launcher ? self->launcher->cwd : NULL,
|
||||
self->argv,
|
||||
self->launcher ? self->launcher->envp : NULL,
|
||||
spawn_flags,
|
||||
child_setup, &child_data,
|
||||
&self->pid,
|
||||
@@ -439,23 +412,66 @@ initable_iface_init (GInitableIface *initable_iface)
|
||||
}
|
||||
|
||||
/**
|
||||
* g_subprocess_new:
|
||||
* g_subprocess_new: (skip)
|
||||
*
|
||||
* Create a new process, using the parameters specified by
|
||||
* GSubprocessContext.
|
||||
* Create a new process with the given flags and varargs argument list.
|
||||
*
|
||||
* Returns: (transfer full): A newly created %GSubprocess, or %NULL on error (and @error will be set)
|
||||
* 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
|
||||
*/
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GSubprocess *
|
||||
g_subprocess_new (GSubprocessContext *context,
|
||||
GError **error)
|
||||
g_subprocess_new (GSubprocessFlags flags,
|
||||
GError **error,
|
||||
const gchar *argv0,
|
||||
...)
|
||||
{
|
||||
return g_initable_new (G_TYPE_SUBPROCESS,
|
||||
NULL, error,
|
||||
"context", context,
|
||||
GSubprocess *result;
|
||||
GPtrArray *args;
|
||||
const gchar *arg;
|
||||
va_list ap;
|
||||
|
||||
g_return_val_if_fail (argv0 != NULL, 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_newv ((const gchar * const *) args->pdata, flags, error);
|
||||
|
||||
g_ptr_array_free (args, TRUE);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_subprocess_newv:
|
||||
*
|
||||
* Create a new process with the given flags and argument list.
|
||||
*
|
||||
* The argument list is expected to be %NULL-terminated.
|
||||
*
|
||||
* Returns: A newly created #GSubprocess, or %NULL on error (and @error
|
||||
* will be set)
|
||||
*
|
||||
* Since: 2.36
|
||||
* Rename to: g_subprocess_new
|
||||
*/
|
||||
GSubprocess *
|
||||
g_subprocess_newv (const gchar * const *argv,
|
||||
GSubprocessFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
return g_initable_new (G_TYPE_SUBPROCESS, NULL, error,
|
||||
"argv", argv,
|
||||
"flags", flags,
|
||||
NULL);
|
||||
}
|
||||
|
||||
@@ -566,10 +582,10 @@ g_subprocess_on_child_exited (GPid pid,
|
||||
* Since: 2.36
|
||||
*/
|
||||
void
|
||||
g_subprocess_wait (GSubprocess *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
g_subprocess_wait_async (GSubprocess *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GSource *source;
|
||||
GSubprocessWatchData *data;
|
||||
|
@@ -36,68 +36,68 @@ G_BEGIN_DECLS
|
||||
#define G_IS_SUBPROCESS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_SUBPROCESS))
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GType g_subprocess_get_type (void) G_GNUC_CONST;
|
||||
GType g_subprocess_get_type (void) G_GNUC_CONST;
|
||||
|
||||
/**** Core API ****/
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GSubprocess * g_subprocess_new (GSubprocessContext *context,
|
||||
GError **error);
|
||||
GSubprocess * g_subprocess_new (GSubprocessFlags flags,
|
||||
GError **error,
|
||||
const gchar *argv0,
|
||||
...) G_GNUC_NULL_TERMINATED;
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GSubprocess * g_subprocess_newv (const gchar * const *argv,
|
||||
GSubprocessFlags flags,
|
||||
GError **error);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GOutputStream * g_subprocess_get_stdin_pipe (GSubprocess *self);
|
||||
GOutputStream * g_subprocess_get_stdin_pipe (GSubprocess *self);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GInputStream * g_subprocess_get_stdout_pipe (GSubprocess *self);
|
||||
GInputStream * g_subprocess_get_stdout_pipe (GSubprocess *self);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GInputStream * g_subprocess_get_stderr_pipe (GSubprocess *self);
|
||||
GInputStream * g_subprocess_get_stderr_pipe (GSubprocess *self);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_wait (GSubprocess *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
GPid g_subprocess_get_pid (GSubprocess *self);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
gboolean g_subprocess_wait_finish (GSubprocess *self,
|
||||
GAsyncResult *result,
|
||||
int *out_exit_status,
|
||||
GError **error);
|
||||
void g_subprocess_request_exit (GSubprocess *self);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
gboolean g_subprocess_wait_sync (GSubprocess *self,
|
||||
int *out_exit_status,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
void g_subprocess_force_exit (GSubprocess *self);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
gboolean g_subprocess_wait_sync_check (GSubprocess *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
gboolean g_subprocess_wait (GSubprocess *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GPid g_subprocess_get_pid (GSubprocess *self);
|
||||
void g_subprocess_wait_async (GSubprocess *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
gboolean g_subprocess_request_exit (GSubprocess *self);
|
||||
gboolean g_subprocess_wait_finish (GSubprocess *self,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_force_exit (GSubprocess *self);
|
||||
|
||||
/** High level helpers **/
|
||||
gboolean g_subprocess_get_successful (GSubprocess *self);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GSubprocess * g_subprocess_new_simple_argl (GSubprocessStreamDisposition stdout_disposition,
|
||||
GSubprocessStreamDisposition stderr_disposition,
|
||||
GError **error,
|
||||
const char *first_arg,
|
||||
...) G_GNUC_NULL_TERMINATED;
|
||||
gboolean g_subprocess_get_if_exited (GSubprocess *self);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GSubprocess * g_subprocess_new_simple_argv (char **argv,
|
||||
GSubprocessStreamDisposition stdout_disposition,
|
||||
GSubprocessStreamDisposition stderr_disposition,
|
||||
GError **error);
|
||||
gint g_subprocess_get_exit_status (GSubprocess *self);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
gboolean g_subprocess_get_if_signaled (GSubprocess *self);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
gint g_subprocess_get_term_signal (GSubprocess *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@@ -1,307 +0,0 @@
|
||||
/* GIO - GLib Input, Output and Streaming Library
|
||||
*
|
||||
* Copyright © 2012 Red Hat, Inc.
|
||||
* Copyright © 2012 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 Context
|
||||
* @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
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "gsubprocesscontext-private.h"
|
||||
#include "gsubprocess.h"
|
||||
#include "gasyncresult.h"
|
||||
#include "glibintl.h"
|
||||
#include "glib-private.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef GObjectClass GSubprocessContextClass;
|
||||
|
||||
G_DEFINE_TYPE (GSubprocessContext, g_subprocess_context, G_TYPE_OBJECT);
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_ARGV,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *g_subprocess_context_pspecs[N_PROPS];
|
||||
|
||||
GSubprocessContext *
|
||||
g_subprocess_context_new (gchar **argv)
|
||||
{
|
||||
return g_object_new (G_TYPE_SUBPROCESS_CONTEXT,
|
||||
"argv", argv,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
g_subprocess_context_init (GSubprocessContext *self)
|
||||
{
|
||||
self->argv = g_ptr_array_new_with_free_func (g_free);
|
||||
self->stdin_fd = -1;
|
||||
self->stdout_fd = -1;
|
||||
self->stderr_fd = -1;
|
||||
}
|
||||
|
||||
static void
|
||||
g_subprocess_context_finalize (GObject *object)
|
||||
{
|
||||
GSubprocessContext *self = G_SUBPROCESS_CONTEXT (object);
|
||||
|
||||
g_ptr_array_unref (self->argv);
|
||||
g_strfreev (self->envp);
|
||||
g_free (self->cwd);
|
||||
|
||||
g_free (self->stdin_path);
|
||||
g_free (self->stdout_path);
|
||||
g_free (self->stderr_path);
|
||||
|
||||
if (G_OBJECT_CLASS (g_subprocess_context_parent_class)->finalize != NULL)
|
||||
G_OBJECT_CLASS (g_subprocess_context_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
g_subprocess_context_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GSubprocessContext *self = G_SUBPROCESS_CONTEXT (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_ARGV:
|
||||
g_subprocess_context_set_args (self, (char**)g_value_get_boxed (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
g_subprocess_context_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GSubprocessContext *self = G_SUBPROCESS_CONTEXT (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_ARGV:
|
||||
g_value_set_boxed (value, self->argv->pdata);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
g_subprocess_context_class_init (GSubprocessContextClass *class)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
|
||||
|
||||
gobject_class->finalize = g_subprocess_context_finalize;
|
||||
gobject_class->get_property = g_subprocess_context_get_property;
|
||||
gobject_class->set_property = g_subprocess_context_set_property;
|
||||
|
||||
/**
|
||||
* GSubprocessContext:argv:
|
||||
*
|
||||
* Array of arguments passed to child process; must have at least
|
||||
* one element. The first element has special handling - if it is
|
||||
* an not absolute path ( as determined by g_path_is_absolute() ),
|
||||
* then the system search path will be used. See
|
||||
* %G_SPAWN_SEARCH_PATH.
|
||||
*
|
||||
* Note that in order to use the Unix-specific argv0 functionality,
|
||||
* you must use the setter function
|
||||
* g_subprocess_context_set_args_and_argv0(). For more information
|
||||
* about this, see %G_SPAWN_FILE_AND_ARGV_ZERO.
|
||||
*
|
||||
* Since: 2.36
|
||||
*/
|
||||
g_subprocess_context_pspecs[PROP_ARGV] = g_param_spec_boxed ("argv", P_("Arguments"), P_("Arguments for child process"), G_TYPE_STRV,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, N_PROPS, g_subprocess_context_pspecs);
|
||||
}
|
||||
|
||||
/* Only exported on Unix */
|
||||
#ifndef G_OS_UNIX
|
||||
static
|
||||
#endif
|
||||
void
|
||||
g_subprocess_context_set_args_and_argv0 (GSubprocessContext *self,
|
||||
const gchar *argv0,
|
||||
gchar **args)
|
||||
{
|
||||
gchar **iter;
|
||||
|
||||
g_ptr_array_set_size (self->argv, 0);
|
||||
|
||||
if (argv0)
|
||||
g_ptr_array_add (self->argv, g_strdup (argv0));
|
||||
|
||||
for (iter = args; *iter; iter++)
|
||||
g_ptr_array_add (self->argv, g_strdup (*iter));
|
||||
g_ptr_array_add (self->argv, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_args (GSubprocessContext *self,
|
||||
gchar **args)
|
||||
{
|
||||
g_subprocess_context_set_args_and_argv0 (self, NULL, args);
|
||||
}
|
||||
|
||||
/* Environment */
|
||||
|
||||
void
|
||||
g_subprocess_context_set_environment (GSubprocessContext *self,
|
||||
gchar **environ)
|
||||
{
|
||||
g_strfreev (self->envp);
|
||||
self->envp = g_strdupv (environ);
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_cwd (GSubprocessContext *self,
|
||||
const gchar *cwd)
|
||||
{
|
||||
g_free (self->cwd);
|
||||
self->cwd = g_strdup (cwd);
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_keep_descriptors (GSubprocessContext *self,
|
||||
gboolean keep_descriptors)
|
||||
|
||||
{
|
||||
self->keep_descriptors = keep_descriptors ? 1 : 0;
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_search_path (GSubprocessContext *self,
|
||||
gboolean search_path,
|
||||
gboolean search_path_from_envp)
|
||||
{
|
||||
self->search_path = search_path ? 1 : 0;
|
||||
self->search_path_from_envp = search_path_from_envp ? 1 : 0;
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_stdin_disposition (GSubprocessContext *self,
|
||||
GSubprocessStreamDisposition disposition)
|
||||
{
|
||||
g_return_if_fail (disposition != G_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE);
|
||||
self->stdin_disposition = disposition;
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_stdout_disposition (GSubprocessContext *self,
|
||||
GSubprocessStreamDisposition disposition)
|
||||
{
|
||||
g_return_if_fail (disposition != G_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE);
|
||||
self->stdout_disposition = disposition;
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_stderr_disposition (GSubprocessContext *self,
|
||||
GSubprocessStreamDisposition disposition)
|
||||
{
|
||||
self->stderr_disposition = disposition;
|
||||
}
|
||||
|
||||
#ifdef G_OS_UNIX
|
||||
void
|
||||
g_subprocess_context_set_stdin_file_path (GSubprocessContext *self,
|
||||
const gchar *path)
|
||||
{
|
||||
self->stdin_disposition = G_SUBPROCESS_STREAM_DISPOSITION_NULL;
|
||||
g_free (self->stdin_path);
|
||||
self->stdin_path = g_strdup (path);
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_stdin_fd (GSubprocessContext *self,
|
||||
gint fd)
|
||||
{
|
||||
self->stdin_disposition = G_SUBPROCESS_STREAM_DISPOSITION_NULL;
|
||||
self->stdin_fd = fd;
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_stdout_file_path (GSubprocessContext *self,
|
||||
const gchar *path)
|
||||
{
|
||||
self->stdout_disposition = G_SUBPROCESS_STREAM_DISPOSITION_NULL;
|
||||
g_free (self->stdout_path);
|
||||
self->stdout_path = g_strdup (path);
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_stdout_fd (GSubprocessContext *self,
|
||||
gint fd)
|
||||
{
|
||||
self->stdout_disposition = G_SUBPROCESS_STREAM_DISPOSITION_NULL;
|
||||
self->stdout_fd = fd;
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_stderr_file_path (GSubprocessContext *self,
|
||||
const gchar *path)
|
||||
{
|
||||
self->stderr_disposition = G_SUBPROCESS_STREAM_DISPOSITION_NULL;
|
||||
g_free (self->stderr_path);
|
||||
self->stderr_path = g_strdup (path);
|
||||
}
|
||||
|
||||
void
|
||||
g_subprocess_context_set_stderr_fd (GSubprocessContext *self,
|
||||
gint fd)
|
||||
{
|
||||
self->stderr_disposition = G_SUBPROCESS_STREAM_DISPOSITION_NULL;
|
||||
self->stderr_fd = fd;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef G_OS_UNIX
|
||||
void
|
||||
g_subprocess_context_set_child_setup (GSubprocessContext *self,
|
||||
GSpawnChildSetupFunc child_setup,
|
||||
gpointer user_data)
|
||||
{
|
||||
self->child_setup_func = child_setup;
|
||||
self->child_setup_data = user_data;
|
||||
}
|
||||
#endif
|
@@ -1,118 +0,0 @@
|
||||
/* GIO - GLib Input, Output and Streaming Library
|
||||
*
|
||||
* Copyright © 2012 Colin Walters <walters@verbum.org>
|
||||
* Copyright © 2012 Canonical Limited
|
||||
*
|
||||
* This library 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 License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General
|
||||
* Public License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
* Author: Colin Walters <walters@verbum.org>
|
||||
*/
|
||||
|
||||
#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
|
||||
#error "Only <gio/gio.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#ifndef __G_SUBPROCESS_CONTEXT_H__
|
||||
#define __G_SUBPROCESS_CONTEXT_H__
|
||||
|
||||
#include <gio/giotypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define G_TYPE_SUBPROCESS_CONTEXT (g_subprocess_context_get_type ())
|
||||
#define G_SUBPROCESS_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_SUBPROCESS_CONTEXT, GSubprocessContext))
|
||||
#define G_IS_SUBPROCESS_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_SUBPROCESS_CONTEXT))
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GType g_subprocess_context_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GSubprocessContext * g_subprocess_context_new (gchar **argv);
|
||||
|
||||
/* Argument control */
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_args (GSubprocessContext *self,
|
||||
gchar **args);
|
||||
#ifdef G_OS_UNIX
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_args_and_argv0 (GSubprocessContext *self,
|
||||
const gchar *argv0,
|
||||
gchar **args);
|
||||
#endif
|
||||
|
||||
/* Environment */
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_environment (GSubprocessContext *self,
|
||||
gchar **environ);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_cwd (GSubprocessContext *self,
|
||||
const gchar *cwd);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_keep_descriptors (GSubprocessContext *self,
|
||||
gboolean keep_descriptors);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_search_path (GSubprocessContext *self,
|
||||
gboolean search_path,
|
||||
gboolean search_path_from_envp);
|
||||
|
||||
/* Basic I/O control */
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_stdin_disposition (GSubprocessContext *self,
|
||||
GSubprocessStreamDisposition disposition);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_stdout_disposition (GSubprocessContext *self,
|
||||
GSubprocessStreamDisposition disposition);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_stderr_disposition (GSubprocessContext *self,
|
||||
GSubprocessStreamDisposition disposition);
|
||||
|
||||
/* Extended I/O control, only available on UNIX */
|
||||
|
||||
#ifdef G_OS_UNIX
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_stdin_file_path (GSubprocessContext *self,
|
||||
const gchar *path);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_stdin_fd (GSubprocessContext *self,
|
||||
gint fd);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_stdout_file_path (GSubprocessContext *self,
|
||||
const gchar *path);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_stdout_fd (GSubprocessContext *self,
|
||||
gint fd);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_stderr_file_path (GSubprocessContext *self,
|
||||
const gchar *path);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_stderr_fd (GSubprocessContext *self,
|
||||
gint fd);
|
||||
#endif
|
||||
|
||||
/* Child setup, only available on UNIX */
|
||||
#ifdef G_OS_UNIX
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_context_set_child_setup (GSubprocessContext *self,
|
||||
GSpawnChildSetupFunc child_setup,
|
||||
gpointer user_data);
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __G_SUBPROCESS_H__ */
|
@@ -21,29 +21,19 @@
|
||||
#ifndef __G_SUBPROCESS_CONTEXT_PRIVATE_H__
|
||||
#define __G_SUBPROCESS_CONTEXT_PRIVATE_H__
|
||||
|
||||
#include "gsubprocesscontext.h"
|
||||
#include "gsubprocesslauncher.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _GSubprocessContext
|
||||
struct _GSubprocessLauncher
|
||||
{
|
||||
GObject parent;
|
||||
|
||||
GSpawnFlags flags;
|
||||
GPtrArray *argv;
|
||||
gboolean has_argv0;
|
||||
GSubprocessFlags flags;
|
||||
char **envp;
|
||||
char *cwd;
|
||||
|
||||
GSubprocessStreamDisposition stdin_disposition;
|
||||
GSubprocessStreamDisposition stdout_disposition;
|
||||
GSubprocessStreamDisposition stderr_disposition;
|
||||
|
||||
guint keep_descriptors : 1;
|
||||
guint search_path : 1;
|
||||
guint search_path_from_envp : 1;
|
||||
guint unused_flags : 29;
|
||||
|
||||
#ifdef G_OS_UNIX
|
||||
gint stdin_fd;
|
||||
gchar *stdin_path;
|
||||
|
||||
@@ -54,7 +44,9 @@ struct _GSubprocessContext
|
||||
gchar *stderr_path;
|
||||
|
||||
GSpawnChildSetupFunc child_setup_func;
|
||||
gpointer child_setup_data;
|
||||
gpointer child_setup_user_data;
|
||||
GDestroyNotify child_setup_destroy_notify;
|
||||
#endif
|
||||
};
|
||||
|
||||
G_END_DECLS
|
536
gio/gsubprocesslauncher.c
Normal file
536
gio/gsubprocesslauncher.c
Normal file
@@ -0,0 +1,536 @@
|
||||
/* GIO - GLib Input, Output and Streaming Library
|
||||
*
|
||||
* Copyright © 2012 Red Hat, Inc.
|
||||
* Copyright © 2012 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"
|
||||
|
||||
typedef GObjectClass GSubprocessLauncherClass;
|
||||
|
||||
G_DEFINE_TYPE (GSubprocessLauncher, g_subprocess_launcher, G_TYPE_OBJECT);
|
||||
|
||||
static void
|
||||
g_subprocess_launcher_finalize (GObject *object)
|
||||
{
|
||||
GSubprocessLauncher *self = G_SUBPROCESS_LAUNCHER (object);
|
||||
|
||||
g_strfreev (self->envp);
|
||||
g_free (self->cwd);
|
||||
|
||||
g_free (self->stdin_path);
|
||||
g_free (self->stdout_path);
|
||||
g_free (self->stderr_path);
|
||||
|
||||
if (self->child_setup_destroy_notify)
|
||||
(* self->child_setup_destroy_notify) (self->child_setup_user_data);
|
||||
|
||||
if (G_OBJECT_CLASS (g_subprocess_launcher_parent_class)->finalize != NULL)
|
||||
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->finalize = g_subprocess_launcher_finalize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (void)
|
||||
{
|
||||
return g_object_new (G_TYPE_SUBPROCESS_LAUNCHER, 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);
|
||||
}
|
||||
|
||||
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_set_%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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_set_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
|
||||
/**
|
||||
* 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_set_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_set_stdin_fd (GSubprocessLauncher *self,
|
||||
gint fd)
|
||||
{
|
||||
if (verify_disposition ("stdin", self->flags & ALL_STDIN_FLAGS, fd, self->stdin_path))
|
||||
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_STDIN_FLAGS, self->stdout_fd, path))
|
||||
{
|
||||
g_free (self->stdout_path);
|
||||
self->stdout_path = g_strdup (path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* g_subprocess_launcher_set_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_set_stdout_fd (GSubprocessLauncher *self,
|
||||
gint fd)
|
||||
{
|
||||
if (verify_disposition ("stdin", self->flags & ALL_STDIN_FLAGS, fd, self->stdin_path))
|
||||
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_STDIN_FLAGS, self->stderr_fd, path))
|
||||
{
|
||||
g_free (self->stderr_path);
|
||||
self->stderr_path = g_strdup (path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* g_subprocess_launcher_set_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 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 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_set_stderr_fd (GSubprocessLauncher *self,
|
||||
gint fd)
|
||||
{
|
||||
if (verify_disposition ("stdin", self->flags & ALL_STDIN_FLAGS, fd, self->stdin_path))
|
||||
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
|
102
gio/gsubprocesslauncher.h
Normal file
102
gio/gsubprocesslauncher.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/* GIO - GLib Input, Output and Streaming Library
|
||||
*
|
||||
* Copyright © 2012 Colin Walters <walters@verbum.org>
|
||||
* Copyright © 2012 Canonical Limited
|
||||
*
|
||||
* This library 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 License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General
|
||||
* Public License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Author: Ryan Lortie <desrt@desrt.ca>
|
||||
* Author: Colin Walters <walters@verbum.org>
|
||||
*/
|
||||
|
||||
#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
|
||||
#error "Only <gio/gio.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#ifndef __G_SUBPROCESS_LAUNCHER_H__
|
||||
#define __G_SUBPROCESS_LAUNCHER_H__
|
||||
|
||||
#include <gio/giotypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define G_TYPE_SUBPROCESS_LAUNCHER (g_subprocess_launcher_get_type ())
|
||||
#define G_SUBPROCESS_LAUNCHER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_SUBPROCESS_LAUNCHER, GSubprocessLauncher))
|
||||
#define G_IS_SUBPROCESS_LAUNCHER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_SUBPROCESS_LAUNCHER))
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GType g_subprocess_launcher_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
GSubprocessLauncher * g_subprocess_launcher_new (void);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_set_environ (GSubprocessLauncher *self,
|
||||
gchar **environ);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_setenv (GSubprocessLauncher *self,
|
||||
const gchar *variable,
|
||||
const gchar *value,
|
||||
gboolean overwrite);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_unsetenv (GSubprocessLauncher *self,
|
||||
const gchar *variable);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
const gchar * g_subprocess_launcher_getenv (GSubprocessLauncher *self,
|
||||
const gchar *variable);
|
||||
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_set_cwd (GSubprocessLauncher *self,
|
||||
const gchar *cwd);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_set_flags (GSubprocessLauncher *self,
|
||||
GSubprocessFlags flags);
|
||||
|
||||
/* Extended I/O control, only available on UNIX */
|
||||
#ifdef G_OS_UNIX
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_set_stdin_file_path (GSubprocessLauncher *self,
|
||||
const gchar *path);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_set_stdin_fd (GSubprocessLauncher *self,
|
||||
gint fd);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_set_stdout_file_path (GSubprocessLauncher *self,
|
||||
const gchar *path);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_set_stdout_fd (GSubprocessLauncher *self,
|
||||
gint fd);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_set_stderr_file_path (GSubprocessLauncher *self,
|
||||
const gchar *path);
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_set_stderr_fd (GSubprocessLauncher *self,
|
||||
gint fd);
|
||||
|
||||
/* Child setup, only available on UNIX */
|
||||
GLIB_AVAILABLE_IN_2_36
|
||||
void g_subprocess_launcher_set_child_setup (GSubprocessLauncher *self,
|
||||
GSpawnChildSetupFunc child_setup,
|
||||
gpointer user_data,
|
||||
GDestroyNotify destroy_notify);
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __G_SUBPROCESS_H__ */
|
Reference in New Issue
Block a user