Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
steam-runtime-tools
Manage
Activity
Members
Labels
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Analyze
Contributor analytics
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
steamrt
steam-runtime-tools
Commits
3d336351
Commit
3d336351
authored
4 years ago
by
Simon McVittie
Browse files
Options
Downloads
Patches
Plain Diff
utils: Add pv_terminate_all_child_processes()
Signed-off-by:
Simon McVittie
<
smcv@collabora.com
>
parent
b912cdee
Branches
Branches containing commit
Tags
Tags containing commit
No related merge requests found
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
src/adverb.c
+0
-4
0 additions, 4 deletions
src/adverb.c
src/meson.build
+1
-0
1 addition, 0 deletions
src/meson.build
src/utils.c
+401
-0
401 additions, 0 deletions
src/utils.c
src/utils.h
+12
-0
12 additions, 0 deletions
src/utils.h
with
414 additions
and
4 deletions
src/adverb.c
+
0
−
4
View file @
3d336351
...
...
@@ -43,10 +43,6 @@
#include
"launcher.h"
#include
"utils.h"
#ifndef PR_SET_CHILD_SUBREAPER
#define PR_SET_CHILD_SUBREAPER 36
#endif
static
GPtrArray
*
global_locks
=
NULL
;
static
GArray
*
global_pass_fds
=
NULL
;
static
gboolean
opt_create
=
FALSE
;
...
...
This diff is collapsed.
Click to expand it.
src/meson.build
+
1
−
0
View file @
3d336351
...
...
@@ -58,6 +58,7 @@ pressure_vessel_utils = static_library(
'utils.h'
,
],
dependencies
:
[
threads
,
gio_unix
,
libelf
,
libglnx
.
get_variable
(
'libglnx_dep'
),
...
...
This diff is collapsed.
Click to expand it.
src/utils.c
+
401
−
0
View file @
3d336351
...
...
@@ -23,10 +23,13 @@
#include
"utils.h"
#include
<ftw.h>
#include
<sys/prctl.h>
#include
<sys/signalfd.h>
#include
<sys/types.h>
#include
<sys/wait.h>
#include
<glib.h>
#include
<glib-unix.h>
#include
<glib/gstdio.h>
#include
<gio/gio.h>
...
...
@@ -662,3 +665,401 @@ pv_wait_for_child_processes (pid_t main_process,
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
(
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
,
(
GSourceFunc
)
G_CALLBACK
(
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
;
}
This diff is collapsed.
Click to expand it.
src/utils.h
+
12
−
0
View file @
3d336351
...
...
@@ -27,6 +27,14 @@
#include
<glib.h>
#ifndef PR_GET_CHILD_SUBREAPER
#define PR_GET_CHILD_SUBREAPER 37
#endif
#ifndef PR_SET_CHILD_SUBREAPER
#define PR_SET_CHILD_SUBREAPER 36
#endif
void
pv_avoid_gvfs
(
void
);
int
pv_envp_cmp
(
const
void
*
p1
,
...
...
@@ -65,3 +73,7 @@ gchar *pv_get_random_uuid (GError **error);
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
);
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment