|
23 | 23 | #include <stdio.h>
|
24 | 24 | #include <stdlib.h>
|
25 | 25 | #include <string.h>
|
| 26 | +#include <sys/signalfd.h> |
26 | 27 | #include <sys/stat.h>
|
27 | 28 | #include <sys/types.h>
|
28 | 29 | #include <sys/wait.h>
|
| 30 | +#include <glib-unix.h> |
29 | 31 | #include <gio/gio.h>
|
30 | 32 | #include <gio/gunixfdlist.h>
|
31 | 33 |
|
32 | 34 | #include "backport-autoptr.h"
|
33 | 35 |
|
| 36 | +/* Change to #if 1 to check backwards-compatibility code paths */ |
| 37 | +#if 0 |
| 38 | +#undef GLIB_CHECK_VERSION |
| 39 | +#define GLIB_CHECK_VERSION(x, y, z) (0) |
| 40 | +#endif |
| 41 | + |
34 | 42 | typedef enum {
|
35 | 43 | FLATPAK_SPAWN_FLAGS_CLEAR_ENV = 1 << 0,
|
36 | 44 | FLATPAK_SPAWN_FLAGS_LATEST_VERSION = 1 << 1,
|
@@ -136,14 +144,37 @@ message_handler (G_GNUC_UNUSED const gchar *log_domain,
|
136 | 144 | g_printerr ("%s: %s\n", g_get_prgname (), message);
|
137 | 145 | }
|
138 | 146 |
|
139 |
| -static gboolean |
140 |
| -forward_signal_idle_cb (gpointer user_data) |
| 147 | +static void |
| 148 | +forward_signal (int sig) |
141 | 149 | {
|
142 |
| - int sig = GPOINTER_TO_INT(user_data); |
143 | 150 | g_autoptr(GVariant) reply = NULL;
|
144 | 151 | gboolean to_process_group = FALSE;
|
145 | 152 | g_autoptr(GError) error = NULL;
|
146 | 153 |
|
| 154 | + if (child_pid == 0) |
| 155 | + { |
| 156 | + /* We are not monitoring a child yet, so let the signal act on |
| 157 | + * this main process instead */ |
| 158 | + if (sig == SIGTSTP || sig == SIGTSTP) |
| 159 | + { |
| 160 | + raise (SIGSTOP); |
| 161 | + } |
| 162 | + else if (sig != SIGCONT) |
| 163 | + { |
| 164 | + sigset_t mask; |
| 165 | + |
| 166 | + sigemptyset (&mask); |
| 167 | + sigaddset (&mask, sig); |
| 168 | + /* Unblock it, so that it will be delivered properly this time. |
| 169 | + * Use pthread_sigmask instead of sigprocmask because the latter |
| 170 | + * has unspecified behaviour in a multi-threaded process. */ |
| 171 | + pthread_sigmask (SIG_UNBLOCK, &mask, NULL); |
| 172 | + raise (sig); |
| 173 | + } |
| 174 | + |
| 175 | + return; |
| 176 | + } |
| 177 | + |
147 | 178 | g_debug ("Forwarding signal: %d", sig);
|
148 | 179 |
|
149 | 180 | /* We forward stop requests as real stop, because the default doesn't
|
@@ -174,26 +205,97 @@ forward_signal_idle_cb (gpointer user_data)
|
174 | 205 | g_debug ("SIGSTOP:ing flatpak-spawn");
|
175 | 206 | raise (SIGSTOP);
|
176 | 207 | }
|
177 |
| - |
178 |
| - return G_SOURCE_REMOVE; |
179 | 208 | }
|
180 | 209 |
|
181 |
| -static void |
182 |
| -forward_signal_handler (int sig) |
| 210 | +static gboolean |
| 211 | +forward_signal_handler ( |
| 212 | +#if GLIB_CHECK_VERSION (2, 36, 0) |
| 213 | + int sfd, |
| 214 | +#else |
| 215 | + GIOChannel *source, |
| 216 | +#endif |
| 217 | + G_GNUC_UNUSED GIOCondition condition, |
| 218 | + G_GNUC_UNUSED gpointer data) |
183 | 219 | {
|
184 |
| - g_idle_add (forward_signal_idle_cb, GINT_TO_POINTER(sig)); |
| 220 | + struct signalfd_siginfo info; |
| 221 | + ssize_t size; |
| 222 | + |
| 223 | +#if !GLIB_CHECK_VERSION (2, 36, 0) |
| 224 | + int sfd; |
| 225 | + |
| 226 | + sfd = g_io_channel_unix_get_fd (source); |
| 227 | + g_return_val_if_fail (sfd >= 0, G_SOURCE_CONTINUE); |
| 228 | +#endif |
| 229 | + |
| 230 | + size = read (sfd, &info, sizeof (info)); |
| 231 | + |
| 232 | + if (size < 0) |
| 233 | + { |
| 234 | + if (errno != EINTR && errno != EAGAIN) |
| 235 | + g_warning ("Unable to read struct signalfd_siginfo: %s", |
| 236 | + g_strerror (errno)); |
| 237 | + } |
| 238 | + else if (size != sizeof (info)) |
| 239 | + { |
| 240 | + g_warning ("Expected struct signalfd_siginfo of size %" |
| 241 | + G_GSIZE_FORMAT ", got %" G_GSSIZE_FORMAT, |
| 242 | + sizeof (info), size); |
| 243 | + } |
| 244 | + else |
| 245 | + { |
| 246 | + forward_signal (info.ssi_signo); |
| 247 | + } |
| 248 | + |
| 249 | + return G_SOURCE_CONTINUE; |
185 | 250 | }
|
186 | 251 |
|
187 |
| -static void |
| 252 | +static guint |
188 | 253 | forward_signals (void)
|
189 | 254 | {
|
190 |
| - int forward[] = { |
| 255 | + static int forward[] = { |
191 | 256 | SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGCONT, SIGTSTP, SIGUSR1, SIGUSR2
|
192 | 257 | };
|
| 258 | + sigset_t mask; |
193 | 259 | guint i;
|
| 260 | + int sfd; |
| 261 | + |
| 262 | + sigemptyset (&mask); |
| 263 | + |
| 264 | + for (i = 0; i < G_N_ELEMENTS (forward); i++) |
| 265 | + sigaddset (&mask, forward[i]); |
| 266 | + |
| 267 | + sfd = signalfd (-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); |
194 | 268 |
|
195 |
| - for (i = 0; i < G_N_ELEMENTS(forward); i++) |
196 |
| - signal (forward[i], forward_signal_handler); |
| 269 | + if (sfd < 0) |
| 270 | + { |
| 271 | + g_warning ("Unable to watch signals: %s", g_strerror (errno)); |
| 272 | + return 0; |
| 273 | + } |
| 274 | + |
| 275 | + /* |
| 276 | + * We have to block the signals, for two reasons: |
| 277 | + * - If we didn't, most of them would kill our process. |
| 278 | + * Listening for a signal with a signalfd does not prevent the signal's |
| 279 | + * default disposition from being acted on. |
| 280 | + * - Reading from a signalfd only returns information about the signals |
| 281 | + * that are still pending for the process. If we ignored them instead |
| 282 | + * of blocking them, they would no longer be pending by the time the |
| 283 | + * main loop wakes up and reads from the signalfd. |
| 284 | + */ |
| 285 | + pthread_sigmask (SIG_BLOCK, &mask, NULL); |
| 286 | + |
| 287 | +#if GLIB_CHECK_VERSION (2, 36, 0) |
| 288 | + return g_unix_fd_add (sfd, G_IO_IN, forward_signal_handler, NULL); |
| 289 | +#else |
| 290 | + GIOChannel *channel = g_io_channel_unix_new (sfd); |
| 291 | + guint ret; |
| 292 | + |
| 293 | + /* Disable text recoding, treat it as a bytestream */ |
| 294 | + g_io_channel_set_encoding (channel, NULL, NULL); |
| 295 | + ret = g_io_add_watch (channel, G_IO_IN, forward_signal_handler, NULL); |
| 296 | + g_io_channel_unref (channel); |
| 297 | + return ret; |
| 298 | +#endif |
197 | 299 | }
|
198 | 300 |
|
199 | 301 | static void
|
@@ -473,6 +575,7 @@ main (int argc,
|
473 | 575 | { "directory", 0, 0, G_OPTION_ARG_FILENAME, &opt_directory, "Working directory in which to run the command", "DIR" },
|
474 | 576 | { NULL }
|
475 | 577 | };
|
| 578 | + guint signal_source = 0; |
476 | 579 |
|
477 | 580 | setlocale (LC_ALL, "");
|
478 | 581 |
|
@@ -517,6 +620,16 @@ main (int argc,
|
517 | 620 | if (verbose)
|
518 | 621 | g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, message_handler, NULL);
|
519 | 622 |
|
| 623 | + /* We have to block the signals we want to forward before we start any |
| 624 | + * other thread, and in particular the GDBus worker thread, because |
| 625 | + * the signal mask is per-thread. We need all threads to have the same |
| 626 | + * mask, otherwise a thread that doesn't have the mask will receive |
| 627 | + * process-directed signals, causing the whole process to exit. */ |
| 628 | + signal_source = forward_signals (); |
| 629 | + |
| 630 | + if (signal_source == 0) |
| 631 | + return 1; |
| 632 | + |
520 | 633 | session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
|
521 | 634 | if (session_bus == NULL)
|
522 | 635 | {
|
@@ -820,13 +933,14 @@ main (int argc,
|
820 | 933 |
|
821 | 934 | g_debug ("child_pid: %d", child_pid);
|
822 | 935 |
|
823 |
| - forward_signals (); |
824 |
| - |
825 | 936 | loop = g_main_loop_new (NULL, FALSE);
|
826 | 937 |
|
827 | 938 | g_signal_connect (session_bus, "closed", G_CALLBACK (session_bus_closed_cb), loop);
|
828 | 939 |
|
829 | 940 | g_main_loop_run (loop);
|
830 | 941 |
|
| 942 | + if (signal_source != 0) |
| 943 | + g_source_remove (signal_source); |
| 944 | + |
831 | 945 | return 0;
|
832 | 946 | }
|
0 commit comments