|
|
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
|
|
|
|