Skip to content
Snippets Groups Projects
Commit 0c9e8244 authored by Ludovico de Nittis's avatar Ludovico de Nittis
Browse files

Merge branch 'wip/smcv/into-library2' into 'main'

Move pressure-vessel-adverb subprocess supervision into common code

See merge request !665
parents 323375c6 680280c5
No related branches found
No related tags found
1 merge request!665Move pressure-vessel-adverb subprocess supervision into common code
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include "steam-runtime-tools/file-lock-internal.h" #include "steam-runtime-tools/file-lock-internal.h"
#include "steam-runtime-tools/launcher-internal.h" #include "steam-runtime-tools/launcher-internal.h"
#include "steam-runtime-tools/log-internal.h" #include "steam-runtime-tools/log-internal.h"
#include "steam-runtime-tools/process-manager-internal.h"
#include "steam-runtime-tools/profiling-internal.h" #include "steam-runtime-tools/profiling-internal.h"
#include "steam-runtime-tools/steam-runtime-tools.h" #include "steam-runtime-tools/steam-runtime-tools.h"
#include "steam-runtime-tools/utils-internal.h" #include "steam-runtime-tools/utils-internal.h"
...@@ -51,10 +52,8 @@ ...@@ -51,10 +52,8 @@
#include "wrap-interactive.h" #include "wrap-interactive.h"
static const char * const *global_original_environ = NULL; static const char * const *global_original_environ = NULL;
static GPtrArray *global_locks = NULL;
static GPtrArray *global_ld_so_conf_entries = NULL; static GPtrArray *global_ld_so_conf_entries = NULL;
static GArray *global_assign_fds = NULL; static SrtProcessManagerOptions *global_options = NULL;
static GArray *global_pass_fds = NULL;
static gboolean opt_batch = FALSE; static gboolean opt_batch = FALSE;
static gboolean opt_create = FALSE; static gboolean opt_create = FALSE;
static gboolean opt_exit_with_parent = FALSE; static gboolean opt_exit_with_parent = FALSE;
...@@ -71,27 +70,12 @@ static gboolean opt_verbose = FALSE; ...@@ -71,27 +70,12 @@ static gboolean opt_verbose = FALSE;
static gboolean opt_version = FALSE; static gboolean opt_version = FALSE;
static gboolean opt_wait = FALSE; static gboolean opt_wait = FALSE;
static gboolean opt_write = FALSE; static gboolean opt_write = FALSE;
static GPid child_pid;
typedef struct
{
int target;
int source;
} AssignFd;
typedef struct
{
size_t n_assign_fds;
const AssignFd *assign_fds;
size_t n_pass_fds;
const int *pass_fds;
} ChildSetupData;
/* (element-type PvAdverbPreloadModule) */ /* (element-type PvAdverbPreloadModule) */
static GArray *opt_preload_modules = NULL; static GArray *opt_preload_modules = NULL;
static void static void
base_child_setup_cb (gpointer nil) helper_child_setup_cb (gpointer nil)
{ {
/* The adverb should wait for its child before it exits, but if it /* The adverb should wait for its child before it exits, but if it
* gets terminated prematurely, we want the child to terminate too. * gets terminated prematurely, we want the child to terminate too.
...@@ -110,93 +94,14 @@ base_child_setup_cb (gpointer nil) ...@@ -110,93 +94,14 @@ base_child_setup_cb (gpointer nil)
_srt_child_setup_unblock_signals (NULL); _srt_child_setup_unblock_signals (NULL);
} }
static void
child_setup_cb (gpointer user_data)
{
ChildSetupData *data = user_data;
size_t j;
base_child_setup_cb (NULL);
/* Make the fds we pass through *not* be close-on-exec */
if (data != NULL)
{
/* Make all other file descriptors close-on-exec */
g_fdwalk_set_cloexec (3);
for (j = 0; j < data->n_pass_fds; j++)
{
int fd = data->pass_fds[j];
int fd_flags;
fd_flags = fcntl (fd, F_GETFD);
if (fd_flags < 0)
_srt_async_signal_safe_error ("pressure-vessel-adverb",
"Invalid fd?",
LAUNCH_EX_FAILED);
if ((fd_flags & FD_CLOEXEC) != 0
&& fcntl (fd, F_SETFD, fd_flags & ~FD_CLOEXEC) != 0)
_srt_async_signal_safe_error ("pressure-vessel-adverb",
"Unable to clear close-on-exec",
LAUNCH_EX_FAILED);
}
for (j = 0; j < data->n_assign_fds; j++)
{
int target = data->assign_fds[j].target;
int source = data->assign_fds[j].source;
if (dup2 (source, target) != target)
_srt_async_signal_safe_error ("pressure-vessel-adverb",
"Unable to assign file descriptors",
LAUNCH_EX_FAILED);
}
}
}
static gboolean static gboolean
opt_fd_cb (const char *name, opt_fd_cb (const char *name,
const char *value, const char *value,
gpointer data, gpointer data,
GError **error) GError **error)
{ {
char *endptr; return _srt_process_manager_options_lock_fd_cli (global_options,
gint64 i64 = g_ascii_strtoll (value, &endptr, 10); name, value, error);
int fd;
int fd_flags;
g_return_val_if_fail (global_locks != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
if (i64 < 0 || i64 > G_MAXINT || endptr == value || *endptr != '\0')
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Integer out of range or invalid: %s", value);
return FALSE;
}
fd = (int) i64;
fd_flags = fcntl (fd, F_GETFD);
if (fd_flags < 0)
return glnx_throw_errno_prefix (error, "Unable to receive --fd %d", fd);
if ((fd_flags & FD_CLOEXEC) == 0
&& fcntl (fd, F_SETFD, fd_flags | FD_CLOEXEC) != 0)
return glnx_throw_errno_prefix (error,
"Unable to configure --fd %d for "
"close-on-exec",
fd);
/* We don't know whether this is an OFD lock or not. Assume it is:
* it won't change our behaviour either way, and if it was passed
* to us across a fork(), it had better be an OFD. */
g_ptr_array_add (global_locks, srt_file_lock_new_take (fd, TRUE));
return TRUE;
} }
static gboolean static gboolean
...@@ -305,45 +210,8 @@ opt_assign_fd_cb (const char *name, ...@@ -305,45 +210,8 @@ opt_assign_fd_cb (const char *name,
gpointer data, gpointer data,
GError **error) GError **error)
{ {
char *endptr; return _srt_process_manager_options_assign_fd_cli (global_options,
gint64 i64 = g_ascii_strtoll (value, &endptr, 10); name, value, error);
AssignFd pair;
int fd_flags;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
g_return_val_if_fail (global_assign_fds != NULL, FALSE);
if (i64 < 0 || i64 > G_MAXINT || endptr == value || *endptr != '=')
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Target fd out of range or invalid: %s", value);
return FALSE;
}
/* Note that the target does not need to be a valid fd yet -
* we can use something like --assign-fd=9=1 to make fd 9
* a copy of existing fd 1 */
pair.target = (int) i64;
value = endptr + 1;
i64 = g_ascii_strtoll (value, &endptr, 10);
if (i64 < 0 || i64 > G_MAXINT || endptr == value || *endptr != '\0')
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Source fd out of range or invalid: %s", value);
return FALSE;
}
pair.source = (int) i64;
fd_flags = fcntl (pair.source, F_GETFD);
if (fd_flags < 0)
return glnx_throw_errno_prefix (error, "Unable to receive --assign-fd source %d", pair.source);
g_array_append_val (global_assign_fds, pair);
return TRUE;
} }
static gboolean static gboolean
...@@ -352,31 +220,8 @@ opt_pass_fd_cb (const char *name, ...@@ -352,31 +220,8 @@ opt_pass_fd_cb (const char *name,
gpointer data, gpointer data,
GError **error) GError **error)
{ {
char *endptr; return _srt_process_manager_options_pass_fd_cli (global_options,
gint64 i64 = g_ascii_strtoll (value, &endptr, 10); name, value, error);
int fd;
int fd_flags;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
g_return_val_if_fail (global_pass_fds != NULL, FALSE);
if (i64 < 0 || i64 > G_MAXINT || endptr == value || *endptr != '\0')
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Integer out of range or invalid: %s", value);
return FALSE;
}
fd = (int) i64;
fd_flags = fcntl (fd, F_GETFD);
if (fd_flags < 0)
return glnx_throw_errno_prefix (error, "Unable to receive --fd %d", fd);
g_array_append_val (global_pass_fds, fd);
return TRUE;
} }
static gboolean static gboolean
...@@ -500,7 +345,6 @@ opt_lock_file_cb (const char *name, ...@@ -500,7 +345,6 @@ opt_lock_file_cb (const char *name,
SrtFileLock *lock; SrtFileLock *lock;
SrtFileLockFlags flags = SRT_FILE_LOCK_FLAGS_NONE; SrtFileLockFlags flags = SRT_FILE_LOCK_FLAGS_NONE;
g_return_val_if_fail (global_locks != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE); g_return_val_if_fail (value != NULL, FALSE);
...@@ -518,7 +362,8 @@ opt_lock_file_cb (const char *name, ...@@ -518,7 +362,8 @@ opt_lock_file_cb (const char *name,
if (lock == NULL) if (lock == NULL)
return FALSE; return FALSE;
g_ptr_array_add (global_locks, lock); _srt_process_manager_options_take_lock (global_options,
g_steal_pointer (&lock));
return TRUE; return TRUE;
} }
...@@ -559,7 +404,7 @@ run_helper_sync (const char *cwd, ...@@ -559,7 +404,7 @@ run_helper_sync (const char *cwd,
(gchar **) argv, (gchar **) argv,
(gchar **) envp, (gchar **) envp,
G_SPAWN_LEAVE_DESCRIPTORS_OPEN, G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
base_child_setup_cb, NULL, helper_child_setup_cb, NULL,
child_stdout, child_stdout,
child_stderr, child_stderr,
wait_status, wait_status,
...@@ -825,23 +670,6 @@ out: ...@@ -825,23 +670,6 @@ out:
return ret; return ret;
} }
/* Only do async-signal-safe things here: see signal-safety(7) */
static void
terminate_child_cb (int signum)
{
if (child_pid != 0)
{
/* pass it on to the child we're going to wait for */
kill (child_pid, signum);
}
else
{
/* guess I'll just die, then */
signal (signum, SIG_DFL);
raise (signum);
}
}
static GOptionEntry options[] = static GOptionEntry options[] =
{ {
{ "assign-fd", '\0', { "assign-fd", '\0',
...@@ -1001,44 +829,30 @@ int ...@@ -1001,44 +829,30 @@ int
main (int argc, main (int argc,
char *argv[]) char *argv[])
{ {
g_auto(SrtProcessManagerOptions) process_manager_options = SRT_PROCESS_MANAGER_OPTIONS_INIT;
g_auto(GStrv) original_environ = NULL; g_auto(GStrv) original_environ = NULL;
g_autoptr(GPtrArray) ld_so_conf_entries = NULL; g_autoptr(GPtrArray) ld_so_conf_entries = NULL;
g_autoptr(GPtrArray) locks = NULL;
g_autoptr(GArray) assign_fds = NULL;
g_autoptr(GArray) pass_fds = NULL;
g_autoptr(GOptionContext) context = NULL; g_autoptr(GOptionContext) context = NULL;
g_autoptr(GError) local_error = NULL; g_autoptr(GError) local_error = NULL;
g_autoptr(SrtProcessManager) process_manager = NULL;
GError **error = &local_error; GError **error = &local_error;
int ret = EX_USAGE; int ret = EX_USAGE;
int wait_status = -1;
g_autofree gchar *locales_temp_dir = NULL; g_autofree gchar *locales_temp_dir = NULL;
glnx_autofd int original_stdout = -1; glnx_autofd int original_stdout = -1;
glnx_autofd int original_stderr = -1; glnx_autofd int original_stderr = -1;
ChildSetupData child_setup_data = { 0, NULL, 0, NULL };
sigset_t mask;
struct sigaction terminate_child_action = {};
g_autoptr(FlatpakBwrap) wrapped_command = NULL; g_autoptr(FlatpakBwrap) wrapped_command = NULL;
g_autoptr(PvPerArchDirs) lib_temp_dirs = NULL; g_autoptr(PvPerArchDirs) lib_temp_dirs = NULL;
AssignFd pair;
gsize i;
setlocale (LC_ALL, ""); setlocale (LC_ALL, "");
global_options = &process_manager_options;
original_environ = g_get_environ (); original_environ = g_get_environ ();
global_original_environ = (const char * const *) original_environ; global_original_environ = (const char * const *) original_environ;
ld_so_conf_entries = g_ptr_array_new_with_free_func (g_free); ld_so_conf_entries = g_ptr_array_new_with_free_func (g_free);
global_ld_so_conf_entries = ld_so_conf_entries; global_ld_so_conf_entries = ld_so_conf_entries;
locks = g_ptr_array_new_with_free_func ((GDestroyNotify) srt_file_lock_free);
global_locks = locks;
pass_fds = g_array_new (FALSE, FALSE, sizeof (int));
global_pass_fds = pass_fds;
assign_fds = g_array_new (FALSE, FALSE, sizeof (AssignFd));
global_assign_fds = assign_fds;
/* Set up the initial base logging */ /* Set up the initial base logging */
if (!_srt_util_set_glib_log_handler ("pressure-vessel-adverb", if (!_srt_util_set_glib_log_handler ("pressure-vessel-adverb",
G_LOG_DOMAIN, SRT_LOG_FLAGS_NONE, G_LOG_DOMAIN, SRT_LOG_FLAGS_NONE,
...@@ -1049,15 +863,6 @@ main (int argc, ...@@ -1049,15 +863,6 @@ main (int argc,
goto out; goto out;
} }
/* Before parsing arguments, the default is like shell redirection
* 1>&original_stdout 2>&original_stderr */
pair.target = STDOUT_FILENO;
pair.source = glnx_steal_fd (&original_stdout);
g_array_append_val (assign_fds, pair);
pair.target = STDERR_FILENO;
pair.source = glnx_steal_fd (&original_stderr);
g_array_append_val (assign_fds, pair);
context = g_option_context_new ( context = g_option_context_new (
"COMMAND [ARG...]\n" "COMMAND [ARG...]\n"
"Run COMMAND [ARG...] with a lock held, a subreaper, or similar.\n"); "Run COMMAND [ARG...] with a lock held, a subreaper, or similar.\n");
...@@ -1099,18 +904,11 @@ main (int argc, ...@@ -1099,18 +904,11 @@ main (int argc,
goto out; goto out;
} }
_srt_unblock_signals (); /* Must be called before we start any threads, but after we set up
sigemptyset (&mask);
sigaddset (&mask, SIGCHLD);
/* Must be called before we start any threads, but after
* _srt_unblock_signals(), which in turn should be after we set up
* logging */ * logging */
if ((errno = pthread_sigmask (SIG_BLOCK, &mask, NULL)) != 0) if (!_srt_process_manager_init_single_threaded (error))
{ {
ret = EX_UNAVAILABLE; ret = EX_UNAVAILABLE;
glnx_throw_errno_prefix (error, "pthread_sigmask");
goto out; goto out;
} }
...@@ -1132,21 +930,29 @@ main (int argc, ...@@ -1132,21 +930,29 @@ main (int argc,
ret = EX_UNAVAILABLE; ret = EX_UNAVAILABLE;
if (opt_exit_with_parent) process_manager_options.close_fds = TRUE;
{ process_manager_options.dump_parameters = TRUE;
g_debug ("Setting up to exit when parent does"); process_manager_options.exit_with_parent = !!opt_exit_with_parent;
process_manager_options.forward_signals = TRUE;
process_manager_options.subreaper = (opt_subreaper || opt_terminate_timeout >= 0.0);
if (!_srt_raise_on_parent_death (SIGTERM, error)) if (opt_terminate_idle_timeout > 0.0)
goto out; process_manager_options.terminate_wait_usec = opt_terminate_idle_timeout * G_TIME_SPAN_SECOND;
}
if ((opt_subreaper || opt_terminate_timeout >= 0) if (opt_terminate_timeout >= 0.0)
&& prctl (PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) != 0) process_manager_options.terminate_grace_usec = opt_terminate_timeout * G_TIME_SPAN_SECOND;
{
glnx_throw_errno_prefix (error, /* In the absence of --assign-fd arguments, the default is like shell
"Unable to manage background processes"); * redirection 1>&original_stdout 2>&original_stderr */
goto out; _srt_process_manager_options_take_original_stdout_stderr (&process_manager_options,
} glnx_steal_fd (&original_stdout),
glnx_steal_fd (&original_stderr));
global_options = NULL;
process_manager = _srt_process_manager_new (&process_manager_options, error);
if (process_manager == NULL)
goto out;
wrapped_command = flatpak_bwrap_new (original_environ); wrapped_command = flatpak_bwrap_new (original_environ);
...@@ -1295,165 +1101,16 @@ main (int argc, ...@@ -1295,165 +1101,16 @@ main (int argc,
} }
} }
/* Respond to common termination signals by killing the child instead of /* We take the same action whether this succeeds or fails */
* ourselves */ _srt_process_manager_run (process_manager,
terminate_child_action.sa_handler = terminate_child_cb; (const char * const *) wrapped_command->argv->pdata,
sigaction (SIGHUP, &terminate_child_action, NULL); (const char * const *) wrapped_command->envp,
sigaction (SIGINT, &terminate_child_action, NULL); error);
sigaction (SIGQUIT, &terminate_child_action, NULL); ret = _srt_process_manager_get_exit_status (process_manager);
sigaction (SIGTERM, &terminate_child_action, NULL);
sigaction (SIGUSR1, &terminate_child_action, NULL);
sigaction (SIGUSR2, &terminate_child_action, NULL);
g_debug ("Launching child process...");
child_setup_data.assign_fds = (const AssignFd *) assign_fds->data;
child_setup_data.n_assign_fds = assign_fds->len;
child_setup_data.pass_fds = (const int *) pass_fds->data;
child_setup_data.n_pass_fds = pass_fds->len;
if (_srt_util_is_debugging ())
{
g_debug ("Command-line:");
for (i = 0; i < wrapped_command->argv->len - 1; i++)
{
g_autofree gchar *quoted = NULL;
quoted = g_shell_quote (g_ptr_array_index (wrapped_command->argv, i));
g_debug ("\t%s", quoted);
}
g_assert (wrapped_command->argv->pdata[i] == NULL);
g_debug ("Environment:");
for (i = 0; wrapped_command->envp != NULL && wrapped_command->envp[i] != NULL; i++)
{
g_autofree gchar *quoted = NULL;
quoted = g_shell_quote (wrapped_command->envp[i]);
g_debug ("\t%s", quoted);
}
g_debug ("Inherited file descriptors:");
for (i = 0; i < pass_fds->len; i++)
g_debug ("\t%d", g_array_index (pass_fds, int, i));
g_debug ("Redirections:");
for (i = 0; i < assign_fds->len; i++)
{
const AssignFd *item = &g_array_index (assign_fds, AssignFd, i);
g_autofree gchar *description = NULL;
description = _srt_describe_fd (item->source);
if (description != NULL)
g_debug ("\t%d>&%d (%s)", item->target, item->source, description);
else
g_debug ("\t%d>&%d", item->target, item->source);
}
}
fflush (stdout);
fflush (stderr);
/* We use LEAVE_DESCRIPTORS_OPEN and set CLOEXEC in the child_setup,
* to work around a deadlock in GLib < 2.60 */
if (!g_spawn_async (NULL, /* working directory */
(char **) wrapped_command->argv->pdata,
wrapped_command->envp,
(G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD |
G_SPAWN_LEAVE_DESCRIPTORS_OPEN |
G_SPAWN_CHILD_INHERITS_STDIN),
child_setup_cb, &child_setup_data,
&child_pid,
&local_error))
{
if (local_error->domain == G_SPAWN_ERROR
&& local_error->code == G_SPAWN_ERROR_NOENT)
ret = LAUNCH_EX_NOT_FOUND;
else
ret = LAUNCH_EX_CANNOT_INVOKE;
goto out;
}
/* If the parent or child writes to a passed fd and closes it,
* don't stand in the way of that. Skip fds 0 (stdin),
* 1 (stdout) and 2 (stderr); we have moved our original stdout/stderr
* to another fd, which will be dealt with as one of the assign_fds,
* and we want to keep our current stdin, stdout and stderr open. */
for (i = 0; i < pass_fds->len; i++)
{
int fd = g_array_index (pass_fds, int, i);
if (fd > 2)
close (fd);
}
/* Same for reassigned fds, notably our original stdout and stderr */
for (i = 0; i < assign_fds->len; i++)
{
const AssignFd *item = &g_array_index (assign_fds, AssignFd, i);
int fd = item->source;
if (fd > 2)
close (fd);
}
/* Reap child processes until child_pid exits */
if (!pv_wait_for_child_processes (child_pid, &wait_status, error))
{
ret = LAUNCH_EX_CANNOT_REPORT;
goto out;
}
child_pid = 0;
if (WIFEXITED (wait_status))
{
ret = WEXITSTATUS (wait_status);
if (ret == 0)
g_debug ("Command exited with status %d", ret);
else
g_info ("Command exited with status %d", ret);
}
else if (WIFSIGNALED (wait_status))
{
ret = 128 + WTERMSIG (wait_status);
g_info ("Command killed by signal %d", ret - 128);
}
else
{
ret = 255;
g_info ("Command terminated in an unknown way (wait status %d)",
wait_status);
}
if (opt_terminate_idle_timeout < 0.0)
opt_terminate_idle_timeout = 0.0;
/* Wait for the other child processes, if any, possibly killing them */
if (opt_terminate_timeout >= 0.0)
{
if (!pv_terminate_all_child_processes (opt_terminate_idle_timeout * G_TIME_SPAN_SECOND,
opt_terminate_timeout * G_TIME_SPAN_SECOND,
error))
goto out;
}
else
{
if (!pv_wait_for_child_processes (0, NULL, error))
goto out;
}
out: out:
global_assign_fds = NULL;
global_ld_so_conf_entries = NULL; global_ld_so_conf_entries = NULL;
global_locks = NULL; global_options = NULL;
global_pass_fds = NULL;
g_clear_pointer (&opt_overrides, g_free); g_clear_pointer (&opt_overrides, g_free);
g_clear_pointer (&opt_regenerate_ld_so_cache, g_free); g_clear_pointer (&opt_regenerate_ld_so_cache, g_free);
......
...@@ -25,10 +25,7 @@ ...@@ -25,10 +25,7 @@
#include <ftw.h> #include <ftw.h>
#include <grp.h> #include <grp.h>
#include <pwd.h> #include <pwd.h>
#include <sys/prctl.h>
#include <sys/signalfd.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h>
#include <glib.h> #include <glib.h>
#include <glib-unix.h> #include <glib-unix.h>
...@@ -164,489 +161,6 @@ pv_hash_table_get_first_key (GHashTable *table, ...@@ -164,489 +161,6 @@ pv_hash_table_get_first_key (GHashTable *table,
return NULL; return NULL;
} }
/**
* pv_wait_for_child_processes:
* @main_process: process for which we will report wait-status;
* zero or negative if there is no main process
* @wait_status_out: (out): Used to store the wait status of `@main_process`,
* as if from `wait()`, on success
* @error: Used to raise an error on failure
*
* Wait for child processes of this process to exit, until the @main_process
* has exited. If there is no main process, wait until there are no child
* processes at all.
*
* If the process is a subreaper (`PR_SET_CHILD_SUBREAPER`),
* indirect child processes whose parents have exited will be reparented
* to it, so this will have the effect of waiting for all descendants.
*
* If @main_process is positive, return when @main_process has exited.
* Child processes that exited before @main_process will also have been
* "reaped", but child processes that exit after @main_process will not
* (use `pv_wait_for_child_processes (0, &error)` to resume waiting).
*
* If @main_process is zero or negative, wait for all child processes
* to exit.
*
* This function cannot be called in a process that is using
* g_child_watch_source_new() or similar functions, because it waits
* for all child processes regardless of their process IDs, and that is
* incompatible with waiting for individual child processes.
*
* Returns: %TRUE when @main_process has exited, or if @main_process
* is zero or negative and all child processes have exited
*/
gboolean
pv_wait_for_child_processes (pid_t main_process,
int *wait_status_out,
GError **error)
{
if (wait_status_out != NULL)
*wait_status_out = -1;
while (1)
{
int wait_status = -1;
pid_t died = wait (&wait_status);
if (died < 0)
{
if (errno == EINTR)
{
continue;
}
else if (errno == ECHILD)
{
g_debug ("No more child processes");
break;
}
else
{
return glnx_throw_errno_prefix (error, "wait");
}
}
g_debug ("Child %lld exited with wait status %d",
(long long) died, wait_status);
if (died == main_process)
{
if (wait_status_out != NULL)
*wait_status_out = wait_status;
return TRUE;
}
}
if (main_process > 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
"Process %lld was not seen to exit",
(long long) main_process);
return FALSE;
}
return TRUE;
}
typedef struct
{
GError *error;
GMainContext *context;
GSource *wait_source;
GSource *kill_source;
GSource *sigchld_source;
gchar *children_file;
/* GINT_TO_POINTER (pid) => arbitrary */
GHashTable *sent_sigterm;
/* GINT_TO_POINTER (pid) => arbitrary */
GHashTable *sent_sigkill;
/* Scratch space to build temporary strings */
GString *buffer;
/* 0, SIGTERM or SIGKILL */
int sending_signal;
/* Nonzero if wait_source has been attached to context */
guint wait_source_id;
guint kill_source_id;
guint sigchld_source_id;
/* TRUE if we reach a point where we have no more child processes. */
gboolean finished;
} TerminationData;
/*
* Free everything in @data.
*/
static void
termination_data_clear (TerminationData *data)
{
if (data->wait_source_id != 0)
{
g_source_destroy (data->wait_source);
data->wait_source_id = 0;
}
if (data->kill_source_id != 0)
{
g_source_destroy (data->kill_source);
data->kill_source_id = 0;
}
if (data->sigchld_source_id != 0)
{
g_source_destroy (data->sigchld_source);
data->sigchld_source_id = 0;
}
if (data->wait_source != NULL)
g_source_unref (g_steal_pointer (&data->wait_source));
if (data->kill_source != NULL)
g_source_unref (g_steal_pointer (&data->kill_source));
if (data->sigchld_source != NULL)
g_source_unref (g_steal_pointer (&data->sigchld_source));
g_clear_pointer (&data->children_file, g_free);
g_clear_pointer (&data->context, g_main_context_unref);
g_clear_error (&data->error);
g_clear_pointer (&data->sent_sigterm, g_hash_table_unref);
g_clear_pointer (&data->sent_sigkill, g_hash_table_unref);
if (data->buffer != NULL)
g_string_free (g_steal_pointer (&data->buffer), TRUE);
}
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (TerminationData, termination_data_clear)
/*
* Do whatever the next step for pv_terminate_all_child_processes() is.
*
* First, reap child processes that already exited, without blocking.
*
* Then, act according to the phase we are in:
* - before wait_period: do nothing
* - after wait_period but before grace_period: send SIGTERM
* - after wait_period and grace_period: send SIGKILL
*/
static void
termination_data_refresh (TerminationData *data)
{
g_autofree gchar *contents = NULL;
gboolean has_child = FALSE;
const char *p;
char *endptr;
if (data->error != NULL)
return;
g_debug ("Checking for child processes");
while (1)
{
int wait_status = -1;
pid_t died = waitpid (-1, &wait_status, WNOHANG);
if (died < 0)
{
if (errno == EINTR)
{
continue;
}
else if (errno == ECHILD)
{
/* No child processes at all. We'll double-check this
* a bit later. */
break;
}
else
{
glnx_throw_errno_prefix (&data->error, "wait");
return;
}
}
else if (died == 0)
{
/* No more child processes have exited, but at least one is
* still running. */
break;
}
/* This process has gone away, so remove any record that we have
* sent it signals. If the pid is reused, we'll want to send
* the same signals again. */
g_debug ("Process %d exited", died);
g_hash_table_remove (data->sent_sigkill, GINT_TO_POINTER (died));
g_hash_table_remove (data->sent_sigterm, GINT_TO_POINTER (died));
}
/* See whether we have any remaining children. These could be direct
* child processes, or they could be children we adopted because
* their parent was one of our descendants and has exited, leaving the
* child to be reparented to us (their (great)*grandparent) because we
* are a subreaper. */
if (!g_file_get_contents (data->children_file, &contents, NULL, &data->error))
return;
g_debug ("Child tasks: %s", contents);
for (p = contents;
p != NULL && *p != '\0';
p = endptr)
{
guint64 maybe_child;
int child;
GHashTable *already;
while (*p != '\0' && g_ascii_isspace (*p))
p++;
if (*p == '\0')
break;
maybe_child = g_ascii_strtoull (p, &endptr, 10);
if (*endptr != '\0' && !g_ascii_isspace (*endptr))
{
glnx_throw (&data->error, "Non-numeric string found in %s: %s",
data->children_file, p);
return;
}
if (maybe_child > G_MAXINT)
{
glnx_throw (&data->error, "Out-of-range number found in %s: %s",
data->children_file, p);
return;
}
child = (int) maybe_child;
g_string_printf (data->buffer, "/proc/%d", child);
/* If the task is just a thread, it won't have a /proc/%d directory
* in its own right. We don't kill threads, only processes. */
if (!g_file_test (data->buffer->str, G_FILE_TEST_IS_DIR))
{
g_debug ("Task %d is a thread, not a process", child);
continue;
}
has_child = TRUE;
if (data->sending_signal == 0)
break;
else if (data->sending_signal == SIGKILL)
already = data->sent_sigkill;
else
already = data->sent_sigterm;
if (!g_hash_table_contains (already, GINT_TO_POINTER (child)))
{
g_debug ("Sending signal %d to process %d",
data->sending_signal, child);
g_hash_table_add (already, GINT_TO_POINTER (child));
if (kill (child, data->sending_signal) < 0)
g_warning ("Unable to send signal %d to process %d: %s",
data->sending_signal, child, g_strerror (errno));
/* In case the child is stopped, wake it up to receive the signal */
if (kill (child, SIGCONT) < 0)
g_warning ("Unable to send SIGCONT to process %d: %s",
child, g_strerror (errno));
/* When the child terminates, we will get SIGCHLD and come
* back to here. */
}
}
if (!has_child)
data->finished = TRUE;
}
/*
* Move from wait period to grace period: start sending SIGTERM to
* child processes.
*/
static gboolean
start_sending_sigterm (gpointer user_data)
{
TerminationData *data = user_data;
g_debug ("Wait period finished, starting to send SIGTERM...");
if (data->sending_signal == 0)
data->sending_signal = SIGTERM;
termination_data_refresh (data);
data->wait_source_id = 0;
return G_SOURCE_REMOVE;
}
/*
* End of grace period: start sending SIGKILL to child processes.
*/
static gboolean
start_sending_sigkill (gpointer user_data)
{
TerminationData *data = user_data;
g_debug ("Grace period finished, starting to send SIGKILL...");
data->sending_signal = SIGKILL;
termination_data_refresh (data);
data->kill_source_id = 0;
return G_SOURCE_REMOVE;
}
/*
* Called when at least one child process has exited, resulting in
* SIGCHLD to this process.
*/
static gboolean
sigchld_cb (int sfd,
G_GNUC_UNUSED GIOCondition condition,
gpointer user_data)
{
TerminationData *data = user_data;
struct signalfd_siginfo info;
ssize_t size;
size = read (sfd, &info, sizeof (info));
if (size < 0)
{
if (errno != EINTR && errno != EAGAIN)
g_warning ("Unable to read struct signalfd_siginfo: %s",
g_strerror (errno));
}
else if (size != sizeof (info))
{
g_warning ("Expected struct signalfd_siginfo of size %"
G_GSIZE_FORMAT ", got %" G_GSSIZE_FORMAT,
sizeof (info), size);
}
g_debug ("One or more child processes exited");
termination_data_refresh (data);
return G_SOURCE_CONTINUE;
}
/**
* pv_terminate_all_child_processes:
* @wait_period: If greater than 0, wait this many microseconds before
* sending `SIGTERM`
* @grace_period: If greater than 0, after @wait_period plus this many
* microseconds, use `SIGKILL` instead of `SIGTERM`. If 0, proceed
* directly to sending `SIGKILL`.
* @error: Used to raise an error on failure
*
* Make sure all child processes are terminated.
*
* If a child process catches `SIGTERM` but does not exit promptly and
* does not pass the signal on to its descendants, note that its
* descendant processes are not guaranteed to be terminated gracefully
* with `SIGTERM`; they might only receive `SIGKILL`.
*
* Return when all child processes have exited or when an error has
* occurred.
*
* This function cannot be called in a process that is using
* g_child_watch_source_new() or similar functions.
*
* The process must be a subreaper, and must have `SIGCHLD` blocked.
*
* Returns: %TRUE on success.
*/
gboolean
pv_terminate_all_child_processes (GTimeSpan wait_period,
GTimeSpan grace_period,
GError **error)
{
g_auto(TerminationData) data = {};
sigset_t mask;
int is_subreaper = -1;
glnx_autofd int sfd = -1;
if (prctl (PR_GET_CHILD_SUBREAPER, (unsigned long) &is_subreaper, 0, 0, 0) != 0)
return glnx_throw_errno_prefix (error, "prctl PR_GET_CHILD_SUBREAPER");
if (is_subreaper != 1)
return glnx_throw (error, "Process is not a subreaper");
sigemptyset (&mask);
if ((errno = pthread_sigmask (SIG_BLOCK, NULL, &mask)) != 0)
return glnx_throw_errno_prefix (error, "pthread_sigmask");
if (!sigismember (&mask, SIGCHLD))
return glnx_throw (error, "Process has not blocked SIGCHLD");
data.children_file = g_strdup_printf ("/proc/%d/task/%d/children",
getpid (), getpid ());
data.context = g_main_context_new ();
data.sent_sigterm = g_hash_table_new (NULL, NULL);
data.sent_sigkill = g_hash_table_new (NULL, NULL);
data.buffer = g_string_new ("/proc/2345678901");
if (wait_period > 0 && grace_period > 0)
{
data.wait_source = g_timeout_source_new (wait_period / G_TIME_SPAN_MILLISECOND);
g_source_set_callback (data.wait_source, start_sending_sigterm,
&data, NULL);
data.wait_source_id = g_source_attach (data.wait_source, data.context);
}
else if (grace_period > 0)
{
start_sending_sigterm (&data);
}
if (wait_period + grace_period > 0)
{
data.kill_source = g_timeout_source_new ((wait_period + grace_period) / G_TIME_SPAN_MILLISECOND);
g_source_set_callback (data.kill_source, start_sending_sigkill,
&data, NULL);
data.kill_source_id = g_source_attach (data.kill_source, data.context);
}
else
{
start_sending_sigkill (&data);
}
sigemptyset (&mask);
sigaddset (&mask, SIGCHLD);
sfd = signalfd (-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
if (sfd < 0)
return glnx_throw_errno_prefix (error, "signalfd");
data.sigchld_source = g_unix_fd_source_new (sfd, G_IO_IN);
g_source_set_callback (data.sigchld_source,
G_SOURCE_FUNC (sigchld_cb), &data, NULL);
data.sigchld_source_id = g_source_attach (data.sigchld_source, data.context);
termination_data_refresh (&data);
while (data.error == NULL
&& !data.finished
&& (data.wait_source_id != 0
|| data.kill_source_id != 0
|| data.sigchld_source_id != 0))
g_main_context_iteration (data.context, TRUE);
if (data.error != NULL)
{
g_propagate_error (error, g_steal_pointer (&data.error));
return FALSE;
}
return TRUE;
}
/** /**
* pv_current_namespace_path_to_host_path: * pv_current_namespace_path_to_host_path:
* @current_env_path: a path in the current environment * @current_env_path: a path in the current environment
......
...@@ -42,14 +42,6 @@ gboolean pv_run_sync (const char * const * argv, ...@@ -42,14 +42,6 @@ gboolean pv_run_sync (const char * const * argv,
gpointer pv_hash_table_get_first_key (GHashTable *table, gpointer pv_hash_table_get_first_key (GHashTable *table,
GCompareFunc cmp); GCompareFunc cmp);
gboolean pv_wait_for_child_processes (pid_t main_process,
int *wait_status_out,
GError **error);
gboolean pv_terminate_all_child_processes (GTimeSpan wait_period,
GTimeSpan grace_period,
GError **error);
gchar *pv_current_namespace_path_to_host_path (const gchar *current_env_path); gchar *pv_current_namespace_path_to_host_path (const gchar *current_env_path);
void pv_delete_dangling_symlink (int dirfd, void pv_delete_dangling_symlink (int dirfd,
......
...@@ -129,6 +129,8 @@ libsteamrt_static_extras = [ ...@@ -129,6 +129,8 @@ libsteamrt_static_extras = [
'file-lock-internal.h', 'file-lock-internal.h',
'portal-listener.c', 'portal-listener.c',
'portal-listener-internal.h', 'portal-listener-internal.h',
'process-manager.c',
'process-manager-internal.h',
'pty-bridge-internal.h', 'pty-bridge-internal.h',
'pty-bridge.c', 'pty-bridge.c',
] ]
......
/*
* Copyright © 2019-2024 Collabora Ltd.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include <sys/wait.h>
#include <glib.h>
#include "libglnx.h"
#include "steam-runtime-tools/file-lock-internal.h"
int _srt_wait_status_to_exit_status (int wait_status);
gboolean _srt_wait_for_child_processes (pid_t main_process,
int *wait_status_out,
GError **error);
gboolean _srt_subreaper_terminate_all_child_processes (GTimeSpan wait_period,
GTimeSpan grace_period,
GError **error);
gboolean _srt_process_manager_init_single_threaded (GError **error);
typedef struct
{
/* File descriptors to assign to other file descriptors, for example
* { .target = 2, .source = 1 } is equivalent to 2>&1 in the shell */
GArray *assign_fds;
/* Exceptions to close_all_fds */
GArray *pass_fds;
/* Array of SrtFileLock */
GPtrArray *locks;
/* If greater than 0, wait this many microseconds after the main child
* process has exited before terminating remaining child processes.
* Must be non-negative. */
GTimeSpan terminate_wait_usec;
/* If greater than 0, after @terminate_wait_usec plus this many
* microseconds, use `SIGKILL` instead of `SIGTERM`. If 0, proceed
* directly to sending `SIGKILL`. */
GTimeSpan terminate_grace_usec;
/* If true, all fds not mentioned in @pass_fds or @assign_fds will be
* closed, except for stdin, stdout and stderr. */
unsigned close_fds : 1;
/* If true, log the arguments and environment before launching the
* child process. */
unsigned dump_parameters : 1;
/* If true, send SIGTERM to the process manager when its parent exits.
* This will be forwarded to the child if @forward_signals is also true. */
unsigned exit_with_parent : 1;
/* If true, forward SIGTERM and similar signals from the process manager
* to the main child process. */
unsigned forward_signals : 1;
/* If true, wait for all descendant processes to exit.
* Must be true if using @terminate_wait_usec or @terminate_grace_usec. */
unsigned subreaper : 1;
} SrtProcessManagerOptions;
#define SRT_PROCESS_MANAGER_OPTIONS_INIT \
{ \
.terminate_grace_usec = -1, \
}
void _srt_process_manager_options_take_fd_assignment (SrtProcessManagerOptions *self,
int target,
int source);
void _srt_process_manager_options_take_original_stdout_stderr (SrtProcessManagerOptions *self,
int original_stdout,
int original_stderr);
gboolean _srt_process_manager_options_assign_fd_cli (SrtProcessManagerOptions *self,
const char *name,
const char *value,
GError **error);
gboolean _srt_process_manager_options_lock_fd_cli (SrtProcessManagerOptions *self,
const char *name,
const char *value,
GError **error);
gboolean _srt_process_manager_options_pass_fd_cli (SrtProcessManagerOptions *self,
const char *name,
const char *value,
GError **error);
void _srt_process_manager_options_take_lock (SrtProcessManagerOptions *self,
SrtFileLock *lock);
GOptionGroup *_srt_process_manager_options_get_option_group (SrtProcessManagerOptions *self,
GError **error);
void _srt_process_manager_options_clear (SrtProcessManagerOptions *self);
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (SrtProcessManagerOptions, _srt_process_manager_options_clear)
typedef struct _SrtProcessManager SrtProcessManager;
typedef struct _SrtProcessManagerClass SrtProcessManagerClass;
#define SRT_TYPE_PROCESS_MANAGER \
(_srt_process_manager_get_type ())
#define SRT_PROCESS_MANAGER(o) \
(G_TYPE_CHECK_INSTANCE_CAST ((o), SRT_TYPE_PROCESS_MANAGER, SrtProcessManager))
#define SRT_IS_PROCESS_MANAGER(o) \
(G_TYPE_CHECK_INSTANCE_TYPE ((o), SRT_TYPE_PROCESS_MANAGER))
#define SRT_PROCESS_MANAGER_GET_CLASS(o) \
(G_TYPE_INSTANCE_GET_CLASS ((o), SRT_TYPE_PROCESS_MANAGER, SrtProcessManagerClass))
#define SRT_PROCESS_MANAGER_CLASS(c) \
(G_TYPE_CHECK_CLASS_CAST ((c), SRT_TYPE_PROCESS_MANAGER, SrtProcessManagerClass))
#define SRT_IS_PROCESS_MANAGER_CLASS(c) \
(G_TYPE_CHECK_CLASS_TYPE ((c), SRT_TYPE_PROCESS_MANAGER))
GType _srt_process_manager_get_type (void);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SrtProcessManager, g_object_unref)
SrtProcessManager *_srt_process_manager_new (SrtProcessManagerOptions *options,
GError **error);
gboolean _srt_process_manager_run (SrtProcessManager *self,
const char * const *argv,
const char * const *envp,
GError **error);
int _srt_process_manager_get_exit_status (SrtProcessManager *self);
This diff is collapsed.
...@@ -51,6 +51,7 @@ tests = [ ...@@ -51,6 +51,7 @@ tests = [
{'name': 'library', 'static': true}, {'name': 'library', 'static': true},
{'name': 'locale'}, {'name': 'locale'},
{'name': 'os-release', 'static': true}, {'name': 'os-release', 'static': true},
{'name': 'process-manager', 'static': true},
{'name': 'resolve-in-sysroot', 'static': true}, {'name': 'resolve-in-sysroot', 'static': true},
{'name': 'system-info', 'static': true}, {'name': 'system-info', 'static': true},
{'name': 'utils', 'static': true}, {'name': 'utils', 'static': true},
......
...@@ -70,7 +70,6 @@ compiled_tests = [ ...@@ -70,7 +70,6 @@ compiled_tests = [
{'name': 'adverb-preload', 'adverb': true}, {'name': 'adverb-preload', 'adverb': true},
{'name': 'bwrap', 'wrap': true}, {'name': 'bwrap', 'wrap': true},
{'name': 'graphics-provider', 'wrap': true}, {'name': 'graphics-provider', 'wrap': true},
{'name': 'wait-for-child-processes'},
{'name': 'wrap-setup', 'wrap': true}, {'name': 'wrap-setup', 'wrap': true},
{'name': 'utils'}, {'name': 'utils'},
] ]
......
/* /*
* Copyright © 2019-2020 Collabora Ltd. * Copyright © 2019-2024 Collabora Ltd.
* *
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
* *
...@@ -31,13 +31,14 @@ ...@@ -31,13 +31,14 @@
#include <glib.h> #include <glib.h>
#include <glib/gstdio.h> #include <glib/gstdio.h>
#include "libglnx.h"
#include "steam-runtime-tools/glib-backports-internal.h" #include "steam-runtime-tools/glib-backports-internal.h"
#include "libglnx.h" #include "steam-runtime-tools/missing-internal.h"
#include "steam-runtime-tools/process-manager-internal.h"
#include "steam-runtime-tools/utils-internal.h" #include "steam-runtime-tools/utils-internal.h"
#include "tests/test-utils.h" #include "tests/test-utils.h"
#include "utils.h"
typedef struct typedef struct
{ {
...@@ -81,20 +82,25 @@ test_terminate_nothing (Fixture *f, ...@@ -81,20 +82,25 @@ test_terminate_nothing (Fixture *f,
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
gboolean ret; gboolean ret;
ret = pv_terminate_all_child_processes (0, 0, &error); ret = _srt_subreaper_terminate_all_child_processes (0, 0, &error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
ret = pv_terminate_all_child_processes (0, 100 * G_TIME_SPAN_MILLISECOND, &error); ret = _srt_subreaper_terminate_all_child_processes (0,
100 * G_TIME_SPAN_MILLISECOND,
&error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
ret = pv_terminate_all_child_processes (100 * G_TIME_SPAN_MILLISECOND, 0, &error); ret = _srt_subreaper_terminate_all_child_processes (100 * G_TIME_SPAN_MILLISECOND,
0,
&error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
ret = pv_terminate_all_child_processes (100 * G_TIME_SPAN_MILLISECOND, ret = _srt_subreaper_terminate_all_child_processes (100 * G_TIME_SPAN_MILLISECOND,
100 * G_TIME_SPAN_MILLISECOND, &error); 100 * G_TIME_SPAN_MILLISECOND,
&error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
} }
...@@ -117,7 +123,9 @@ test_terminate_sigterm (Fixture *f, ...@@ -117,7 +123,9 @@ test_terminate_sigterm (Fixture *f,
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
ret = pv_terminate_all_child_processes (0, 60 * G_TIME_SPAN_SECOND, &error); ret = _srt_subreaper_terminate_all_child_processes (0,
60 * G_TIME_SPAN_SECOND,
&error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
} }
...@@ -144,8 +152,9 @@ test_terminate_sigkill (Fixture *f, ...@@ -144,8 +152,9 @@ test_terminate_sigkill (Fixture *f,
g_assert_true (ret); g_assert_true (ret);
/* We give it 100ms before SIGTERM to let it put the trap in place */ /* We give it 100ms before SIGTERM to let it put the trap in place */
ret = pv_terminate_all_child_processes (100 * G_TIME_SPAN_MILLISECOND, ret = _srt_subreaper_terminate_all_child_processes (100 * G_TIME_SPAN_MILLISECOND,
100 * G_TIME_SPAN_MILLISECOND, &error); 100 * G_TIME_SPAN_MILLISECOND,
&error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
} }
...@@ -171,7 +180,7 @@ test_terminate_sigkill_immediately (Fixture *f, ...@@ -171,7 +180,7 @@ test_terminate_sigkill_immediately (Fixture *f,
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
ret = pv_terminate_all_child_processes (0, 0, &error); ret = _srt_subreaper_terminate_all_child_processes (0, 0, &error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
} }
...@@ -195,7 +204,7 @@ test_wait_for_all (Fixture *f, ...@@ -195,7 +204,7 @@ test_wait_for_all (Fixture *f,
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
ret = pv_wait_for_child_processes (0, &wstat, &error); ret = _srt_wait_for_child_processes (0, &wstat, &error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
g_assert_cmpint (wstat, ==, -1); g_assert_cmpint (wstat, ==, -1);
...@@ -221,7 +230,7 @@ test_wait_for_main (Fixture *f, ...@@ -221,7 +230,7 @@ test_wait_for_main (Fixture *f,
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
ret = pv_wait_for_child_processes (main_pid, &wstat, &error); ret = _srt_wait_for_child_processes (main_pid, &wstat, &error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
g_assert_true (WIFEXITED (wstat)); g_assert_true (WIFEXITED (wstat));
...@@ -270,14 +279,14 @@ test_wait_for_main_plus (Fixture *f, ...@@ -270,14 +279,14 @@ test_wait_for_main_plus (Fixture *f,
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
ret = pv_wait_for_child_processes (main_pid, &wstat, &error); ret = _srt_wait_for_child_processes (main_pid, &wstat, &error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
g_assert_true (WIFSIGNALED (wstat)); g_assert_true (WIFSIGNALED (wstat));
g_assert_cmpint (WTERMSIG (wstat), ==, SIGTERM); g_assert_cmpint (WTERMSIG (wstat), ==, SIGTERM);
/* Don't leak the other processes, if any (probably after_argv) */ /* Don't leak the other processes, if any (probably after_argv) */
ret = pv_wait_for_child_processes (0, &wstat, &error); ret = _srt_wait_for_child_processes (0, &wstat, &error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
} }
...@@ -290,7 +299,7 @@ test_wait_for_nothing (Fixture *f, ...@@ -290,7 +299,7 @@ test_wait_for_nothing (Fixture *f,
int wstat; int wstat;
gboolean ret; gboolean ret;
ret = pv_wait_for_child_processes (0, &wstat, &error); ret = _srt_wait_for_child_processes (0, &wstat, &error);
g_assert_no_error (error); g_assert_no_error (error);
g_assert_true (ret); g_assert_true (ret);
g_assert_cmpint (wstat, ==, -1); g_assert_cmpint (wstat, ==, -1);
...@@ -304,7 +313,7 @@ test_wait_for_wrong_main (Fixture *f, ...@@ -304,7 +313,7 @@ test_wait_for_wrong_main (Fixture *f,
int wstat; int wstat;
gboolean ret; gboolean ret;
ret = pv_wait_for_child_processes (getpid (), &wstat, &error); ret = _srt_wait_for_child_processes (getpid (), &wstat, &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
g_assert_false (ret); g_assert_false (ret);
g_assert_cmpint (wstat, ==, -1); g_assert_cmpint (wstat, ==, -1);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment