026c2e2b3d
Resolves: RHEL-14761
392 lines
12 KiB
Diff
392 lines
12 KiB
Diff
From 0bbd63bf1945c6f3e1c88232521e1618c21d44f2 Mon Sep 17 00:00:00 2001
|
||
From: Philip Withnall <pwithnall@endlessos.org>
|
||
Date: Thu, 23 Dec 2021 17:45:51 +0000
|
||
Subject: [PATCH 1/4] gmain: Use waitid() on pidfds rather than a global
|
||
SIGCHLD handler
|
||
MIME-Version: 1.0
|
||
Content-Type: text/plain; charset=UTF-8
|
||
Content-Transfer-Encoding: 8bit
|
||
|
||
When the system supports it (as all Linux kernels ≥ 5.3 should), it’s
|
||
preferable to use `pidfd_open()` and `waitid()` to be notified of
|
||
child processes exiting or being signalled, rather than installing a
|
||
default `SIGCHLD` handler.
|
||
|
||
A default `SIGCHLD` handler is global, and can never interact well with
|
||
other code (from the application or other libraries) which also wants to
|
||
install a `SIGCHLD` handler.
|
||
|
||
This use of `pidfd_open()` is racy (the PID may be reused between
|
||
`g_child_watch_source_new()` being called and `pidfd_open()` being
|
||
called), so it doesn’t improve behaviour there. For that, we’d need
|
||
continuous use of pidfds throughout GLib, from fork/spawn time until
|
||
here. See #1866 for that.
|
||
|
||
The use of `waitid()` to get the process exit status could be expanded
|
||
in future to also work for stopped or continued processes (as per #175)
|
||
by adding `WSTOPPED | WCONTINUED` into the flags. That’s a behaviour
|
||
change which is outside the strict scope of adding pidfd support,
|
||
though.
|
||
|
||
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
|
||
|
||
Helps: #1866
|
||
Fixes: #2216
|
||
---
|
||
glib/gmain.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++---
|
||
meson.build | 14 ++++++
|
||
2 files changed, 125 insertions(+), 6 deletions(-)
|
||
|
||
diff --git a/glib/gmain.c b/glib/gmain.c
|
||
index 15581ee7a..e9965f7f6 100644
|
||
--- a/glib/gmain.c
|
||
+++ b/glib/gmain.c
|
||
@@ -67,6 +67,12 @@
|
||
#include <errno.h>
|
||
#include <string.h>
|
||
|
||
+#ifdef HAVE_PIDFD
|
||
+#include <sys/syscall.h>
|
||
+#include <sys/wait.h>
|
||
+#include <linux/wait.h> /* P_PIDFD */
|
||
+#endif /* HAVE_PIDFD */
|
||
+
|
||
#ifdef G_OS_WIN32
|
||
#define STRICT
|
||
#include <windows.h>
|
||
@@ -329,10 +335,11 @@ struct _GChildWatchSource
|
||
GSource source;
|
||
GPid pid;
|
||
gint child_status;
|
||
-#ifdef G_OS_WIN32
|
||
+ /* @poll is always used on Windows, and used on Unix iff @using_pidfd is set: */
|
||
GPollFD poll;
|
||
-#else /* G_OS_WIN32 */
|
||
- gboolean child_exited; /* (atomic) */
|
||
+#ifndef G_OS_WIN32
|
||
+ gboolean child_exited; /* (atomic); not used iff @using_pidfd is set */
|
||
+ gboolean using_pidfd;
|
||
#endif /* G_OS_WIN32 */
|
||
};
|
||
|
||
@@ -5325,7 +5332,8 @@ dispatch_unix_signals_unlocked (void)
|
||
{
|
||
GChildWatchSource *source = node->data;
|
||
|
||
- if (!g_atomic_int_get (&source->child_exited))
|
||
+ if (!source->using_pidfd &&
|
||
+ !g_atomic_int_get (&source->child_exited))
|
||
{
|
||
pid_t pid;
|
||
do
|
||
@@ -5384,6 +5392,38 @@ g_child_watch_prepare (GSource *source,
|
||
return g_atomic_int_get (&child_watch_source->child_exited);
|
||
}
|
||
|
||
+#ifdef HAVE_PIDFD
|
||
+static int
|
||
+siginfo_t_to_wait_status (const siginfo_t *info)
|
||
+{
|
||
+ /* Each of these returns is essentially the inverse of WIFEXITED(),
|
||
+ * WIFSIGNALED(), etc. */
|
||
+ switch (info->si_code)
|
||
+ {
|
||
+ case CLD_EXITED:
|
||
+ return W_EXITCODE (info->si_status, 0);
|
||
+ case CLD_KILLED:
|
||
+ return W_EXITCODE (0, info->si_status);
|
||
+ case CLD_DUMPED:
|
||
+#ifdef WCOREFLAG
|
||
+ return W_EXITCODE (0, info->si_status | WCOREFLAG);
|
||
+#else
|
||
+ g_assert_not_reached ();
|
||
+#endif
|
||
+ case CLD_CONTINUED:
|
||
+#ifdef __W_CONTINUED
|
||
+ return __W_CONTINUED;
|
||
+#else
|
||
+ g_assert_not_reached ();
|
||
+#endif
|
||
+ case CLD_STOPPED:
|
||
+ case CLD_TRAPPED:
|
||
+ default:
|
||
+ return W_STOPCODE (info->si_status);
|
||
+ }
|
||
+}
|
||
+#endif /* HAVE_PIDFD */
|
||
+
|
||
static gboolean
|
||
g_child_watch_check (GSource *source)
|
||
{
|
||
@@ -5391,6 +5431,34 @@ g_child_watch_check (GSource *source)
|
||
|
||
child_watch_source = (GChildWatchSource *) source;
|
||
|
||
+#ifdef HAVE_PIDFD
|
||
+ if (child_watch_source->using_pidfd)
|
||
+ {
|
||
+ gboolean child_exited = child_watch_source->poll.revents & G_IO_IN;
|
||
+
|
||
+ if (child_exited)
|
||
+ {
|
||
+ siginfo_t child_info = { 0, };
|
||
+
|
||
+ /* Get the exit status */
|
||
+ if (waitid (P_PIDFD, child_watch_source->poll.fd, &child_info, WEXITED | WNOHANG) >= 0 &&
|
||
+ child_info.si_pid != 0)
|
||
+ {
|
||
+ /* waitid() helpfully provides the wait status in a decomposed
|
||
+ * form which is quite useful. Unfortunately we have to report it
|
||
+ * to the #GChildWatchFunc as a waitpid()-style platform-specific
|
||
+ * wait status, so that the user code in #GChildWatchFunc can then
|
||
+ * call WIFEXITED() (etc.) on it. That means re-composing the
|
||
+ * status information. */
|
||
+ child_watch_source->child_status = siginfo_t_to_wait_status (&child_info);
|
||
+ child_watch_source->child_exited = TRUE;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return child_exited;
|
||
+ }
|
||
+#endif /* HAVE_PIDFD */
|
||
+
|
||
return g_atomic_int_get (&child_watch_source->child_exited);
|
||
}
|
||
|
||
@@ -5575,6 +5643,11 @@ g_unix_signal_watch_finalize (GSource *source)
|
||
static void
|
||
g_child_watch_finalize (GSource *source)
|
||
{
|
||
+ GChildWatchSource *child_watch_source = (GChildWatchSource *) source;
|
||
+
|
||
+ if (child_watch_source->using_pidfd)
|
||
+ return;
|
||
+
|
||
G_LOCK (unix_signal_lock);
|
||
unix_child_watches = g_slist_remove (unix_child_watches, source);
|
||
unref_unix_signal_handler_unlocked (SIGCHLD);
|
||
@@ -5676,6 +5749,9 @@ g_child_watch_source_new (GPid pid)
|
||
{
|
||
GSource *source;
|
||
GChildWatchSource *child_watch_source;
|
||
+#ifdef HAVE_PIDFD
|
||
+ int errsv;
|
||
+#endif
|
||
|
||
#ifndef G_OS_WIN32
|
||
g_return_val_if_fail (pid > 0, NULL);
|
||
@@ -5694,14 +5770,43 @@ g_child_watch_source_new (GPid pid)
|
||
child_watch_source->poll.events = G_IO_IN;
|
||
|
||
g_source_add_poll (source, &child_watch_source->poll);
|
||
-#else /* G_OS_WIN32 */
|
||
+#else /* !G_OS_WIN32 */
|
||
+
|
||
+#ifdef HAVE_PIDFD
|
||
+ /* Use a pidfd, if possible, to avoid having to install a global SIGCHLD
|
||
+ * handler and potentially competing with any other library/code which wants
|
||
+ * to install one.
|
||
+ *
|
||
+ * Unfortunately this use of pidfd isn’t race-free (the PID could be recycled
|
||
+ * between the caller calling g_child_watch_source_new() and here), but it’s
|
||
+ * better than SIGCHLD.
|
||
+ */
|
||
+ child_watch_source->poll.fd = (int) syscall (SYS_pidfd_open, pid, 0);
|
||
+ errsv = errno;
|
||
+
|
||
+ if (child_watch_source->poll.fd >= 0)
|
||
+ {
|
||
+ child_watch_source->using_pidfd = TRUE;
|
||
+ child_watch_source->poll.events = G_IO_IN;
|
||
+ g_source_add_poll (source, &child_watch_source->poll);
|
||
+
|
||
+ return source;
|
||
+ }
|
||
+ else
|
||
+ {
|
||
+ g_debug ("pidfd_open(%" G_PID_FORMAT ") failed with error: %s",
|
||
+ pid, g_strerror (errsv));
|
||
+ /* Fall through; likely the kernel isn’t new enough to support pidfd_open() */
|
||
+ }
|
||
+#endif /* HAVE_PIDFD */
|
||
+
|
||
G_LOCK (unix_signal_lock);
|
||
ref_unix_signal_handler_unlocked (SIGCHLD);
|
||
unix_child_watches = g_slist_prepend (unix_child_watches, child_watch_source);
|
||
if (waitpid (pid, &child_watch_source->child_status, WNOHANG) > 0)
|
||
child_watch_source->child_exited = TRUE;
|
||
G_UNLOCK (unix_signal_lock);
|
||
-#endif /* G_OS_WIN32 */
|
||
+#endif /* !G_OS_WIN32 */
|
||
|
||
return source;
|
||
}
|
||
diff --git a/meson.build b/meson.build
|
||
index a0223ce5b..1e1bd602c 100644
|
||
--- a/meson.build
|
||
+++ b/meson.build
|
||
@@ -810,6 +810,20 @@ if cc.links('''#include <sys/eventfd.h>
|
||
glib_conf.set('HAVE_EVENTFD', 1)
|
||
endif
|
||
|
||
+# Check for pidfd_open(2)
|
||
+if cc.links('''#include <sys/syscall.h>
|
||
+ #include <sys/wait.h>
|
||
+ #include <linux/wait.h>
|
||
+ #include <unistd.h>
|
||
+ int main (int argc, char ** argv) {
|
||
+ siginfo_t child_info = { 0, };
|
||
+ syscall (SYS_pidfd_open, 0, 0);
|
||
+ waitid (P_PIDFD, 0, &child_info, WEXITED | WNOHANG);
|
||
+ return 0;
|
||
+ }''', name : 'pidfd_open(2) system call')
|
||
+ glib_conf.set('HAVE_PIDFD', 1)
|
||
+endif
|
||
+
|
||
# Check for __uint128_t (gcc) by checking for 128-bit division
|
||
uint128_t_src = '''int main() {
|
||
static __uint128_t v1 = 100;
|
||
--
|
||
2.41.0
|
||
|
||
|
||
From 13c62bc181c6da9f287b737f7a3238e0269b40b3 Mon Sep 17 00:00:00 2001
|
||
From: Christian Hergert <chergert@redhat.com>
|
||
Date: Tue, 2 Aug 2022 12:35:40 -0700
|
||
Subject: [PATCH 2/4] gmain: close pidfd when finalizing GChildWatchSource
|
||
|
||
A file-descriptor was created with the introduction of pidfd_getfd() but
|
||
nothing is closing it when the source finalizes. The GChildWatchSource is
|
||
the creator and consumer of this FD and therefore responsible for closing
|
||
it on finalization.
|
||
|
||
The pidfd leak was introduced in !2408.
|
||
|
||
This fixes issues with Builder where anon_inode:[pidfd] exhaust the
|
||
available FD limit for the process.
|
||
|
||
Fixes #2708
|
||
---
|
||
glib/gmain.c | 6 +++++-
|
||
1 file changed, 5 insertions(+), 1 deletion(-)
|
||
|
||
diff --git a/glib/gmain.c b/glib/gmain.c
|
||
index e9965f7f6..3ceec61ee 100644
|
||
--- a/glib/gmain.c
|
||
+++ b/glib/gmain.c
|
||
@@ -5646,7 +5646,11 @@ g_child_watch_finalize (GSource *source)
|
||
GChildWatchSource *child_watch_source = (GChildWatchSource *) source;
|
||
|
||
if (child_watch_source->using_pidfd)
|
||
- return;
|
||
+ {
|
||
+ if (child_watch_source->poll.fd >= 0)
|
||
+ close (child_watch_source->poll.fd);
|
||
+ return;
|
||
+ }
|
||
|
||
G_LOCK (unix_signal_lock);
|
||
unix_child_watches = g_slist_remove (unix_child_watches, source);
|
||
--
|
||
2.41.0
|
||
|
||
|
||
From 378c72cbe12767b8f6aedc19c7ca46c07aa1ca73 Mon Sep 17 00:00:00 2001
|
||
From: Owen Rafferty <owen@owenrafferty.com>
|
||
Date: Tue, 12 Jul 2022 20:03:56 -0500
|
||
Subject: [PATCH 3/4] gmain: define non-posix symbols
|
||
|
||
---
|
||
glib/gmain.c | 6 ++++++
|
||
1 file changed, 6 insertions(+)
|
||
|
||
diff --git a/glib/gmain.c b/glib/gmain.c
|
||
index 3ceec61ee..a2d7bb3ba 100644
|
||
--- a/glib/gmain.c
|
||
+++ b/glib/gmain.c
|
||
@@ -71,6 +71,12 @@
|
||
#include <sys/syscall.h>
|
||
#include <sys/wait.h>
|
||
#include <linux/wait.h> /* P_PIDFD */
|
||
+#ifndef W_EXITCODE
|
||
+#define W_EXITCODE(ret, sig) ((ret) << 8 | (sig))
|
||
+#endif
|
||
+#ifndef W_STOPCODE
|
||
+#define W_STOPCODE(sig) ((sig) << 8 | 0x7f)
|
||
+#endif
|
||
#endif /* HAVE_PIDFD */
|
||
|
||
#ifdef G_OS_WIN32
|
||
--
|
||
2.41.0
|
||
|
||
|
||
From aac37188ce26366bd86626700d49cee0cb121472 Mon Sep 17 00:00:00 2001
|
||
From: Philip Withnall <pwithnall@endlessos.org>
|
||
Date: Wed, 21 Dec 2022 12:11:46 +0000
|
||
Subject: [PATCH 4/4] gmain: Define fallback values for siginfo_t constants for
|
||
musl
|
||
MIME-Version: 1.0
|
||
Content-Type: text/plain; charset=UTF-8
|
||
Content-Transfer-Encoding: 8bit
|
||
|
||
musl doesn’t define them itself, presumably because they’re not defined
|
||
in POSIX. glibc does define them. Thankfully, the values used in glibc
|
||
match the values used internally in other musl macros.
|
||
|
||
Define the values as a fallback. As a result of this, we can get rid of
|
||
the `g_assert_if_reached()` checks in `siginfo_t_to_wait_status()`.
|
||
|
||
This should fix catching signals from a subprocess when built against
|
||
musl.
|
||
|
||
Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
|
||
|
||
Fixes: #2852
|
||
---
|
||
glib/gmain.c | 18 ++++++++++--------
|
||
1 file changed, 10 insertions(+), 8 deletions(-)
|
||
|
||
diff --git a/glib/gmain.c b/glib/gmain.c
|
||
index a2d7bb3ba..f0cf700c0 100644
|
||
--- a/glib/gmain.c
|
||
+++ b/glib/gmain.c
|
||
@@ -77,6 +77,16 @@
|
||
#ifndef W_STOPCODE
|
||
#define W_STOPCODE(sig) ((sig) << 8 | 0x7f)
|
||
#endif
|
||
+#ifndef WCOREFLAG
|
||
+/* musl doesn’t define WCOREFLAG while glibc does. Unfortunately, there’s no way
|
||
+ * to detect we’re building against musl, so just define it and hope.
|
||
+ * See https://git.musl-libc.org/cgit/musl/tree/include/sys/wait.h#n51 */
|
||
+#define WCOREFLAG 0x80
|
||
+#endif
|
||
+#ifndef __W_CONTINUED
|
||
+/* Same as above, for musl */
|
||
+#define __W_CONTINUED 0xffff
|
||
+#endif
|
||
#endif /* HAVE_PIDFD */
|
||
|
||
#ifdef G_OS_WIN32
|
||
@@ -5411,17 +5421,9 @@ siginfo_t_to_wait_status (const siginfo_t *info)
|
||
case CLD_KILLED:
|
||
return W_EXITCODE (0, info->si_status);
|
||
case CLD_DUMPED:
|
||
-#ifdef WCOREFLAG
|
||
return W_EXITCODE (0, info->si_status | WCOREFLAG);
|
||
-#else
|
||
- g_assert_not_reached ();
|
||
-#endif
|
||
case CLD_CONTINUED:
|
||
-#ifdef __W_CONTINUED
|
||
return __W_CONTINUED;
|
||
-#else
|
||
- g_assert_not_reached ();
|
||
-#endif
|
||
case CLD_STOPPED:
|
||
case CLD_TRAPPED:
|
||
default:
|
||
--
|
||
2.41.0
|
||
|