/* Test case for async-signal-safe fork (with respect to malloc). Copyright (C) 2016-2018 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; see the file COPYING.LIB. If not, see . */ /* This test will fail if the process is multi-threaded because we only have an async-signal-safe fork in the single-threaded case (where we skip acquiring the malloc heap locks). This test only checks async-signal-safety with regards to malloc; other, more rarely-used glibc subsystems could have locks which still make fork unsafe, even in single-threaded processes. */ #include #include #include #include #include #include #include #include #include #include /* How many malloc objects to keep arond. */ enum { malloc_objects = 1009 }; /* The maximum size of an object. */ enum { malloc_maximum_size = 70000 }; /* How many signals need to be delivered before the test exits. */ enum { signal_count = 1000 }; static int do_test (void); #define TIMEOUT 100 #define TEST_FUNCTION do_test () #include "../test-skeleton.c" /* Process ID of the subprocess which sends SIGUSR1 signals. */ static pid_t sigusr1_sender_pid; /* Set to 1 if SIGUSR1 is received. Used to detect a signal during malloc/free. */ static volatile sig_atomic_t sigusr1_received; /* Periodically set to 1, to indicate that the process is making progress. Checked by liveness_signal_handler. */ static volatile sig_atomic_t progress_indicator = 1; static void sigusr1_handler (int signo) { /* Let the main program make progress, by temporarily suspending signals from the subprocess. */ if (sigusr1_received) return; /* sigusr1_sender_pid might not be initialized in the parent when the first SIGUSR1 signal arrives. */ if (sigusr1_sender_pid > 0 && kill (sigusr1_sender_pid, SIGSTOP) != 0) { write_message ("error: kill (SIGSTOP)\n"); abort (); } sigusr1_received = 1; /* Perform a fork with a trivial subprocess. */ pid_t pid = fork (); if (pid == -1) { write_message ("error: fork\n"); abort (); } if (pid == 0) _exit (0); int status; int ret = TEMP_FAILURE_RETRY (waitpid (pid, &status, 0)); if (ret < 0) { write_message ("error: waitpid\n"); abort (); } if (status != 0) { write_message ("error: unexpected exit status from subprocess\n"); abort (); } } static void liveness_signal_handler (int signo) { if (progress_indicator) progress_indicator = 0; else write_message ("warning: process seems to be stuck\n"); } static void __attribute__ ((noreturn)) signal_sender (int signo, bool sleep) { pid_t target = getppid (); while (true) { if (kill (target, signo) != 0) { dprintf (STDOUT_FILENO, "error: kill: %m\n"); abort (); } if (sleep) usleep (1 * 1000 * 1000); else /* Reduce the rate at which we send signals. */ sched_yield (); } } static int do_test (void) { struct sigaction action = { .sa_handler = sigusr1_handler, }; sigemptyset (&action.sa_mask); if (sigaction (SIGUSR1, &action, NULL) != 0) { printf ("error: sigaction: %m"); return 1; } action.sa_handler = liveness_signal_handler; if (sigaction (SIGUSR2, &action, NULL) != 0) { printf ("error: sigaction: %m"); return 1; } pid_t sigusr2_sender_pid = fork (); if (sigusr2_sender_pid == 0) signal_sender (SIGUSR2, true); sigusr1_sender_pid = fork (); if (sigusr1_sender_pid == 0) signal_sender (SIGUSR1, false); void *objects[malloc_objects] = {}; unsigned signals = 0; unsigned seed = 1; time_t last_report = 0; while (signals < signal_count) { progress_indicator = 1; int slot = rand_r (&seed) % malloc_objects; size_t size = rand_r (&seed) % malloc_maximum_size; if (kill (sigusr1_sender_pid, SIGCONT) != 0) { printf ("error: kill (SIGCONT): %m\n"); signal (SIGUSR1, SIG_IGN); kill (sigusr1_sender_pid, SIGKILL); kill (sigusr2_sender_pid, SIGKILL); return 1; } sigusr1_received = false; free (objects[slot]); objects[slot] = malloc (size); if (sigusr1_received) { ++signals; time_t current = time (0); if (current != last_report) { printf ("info: SIGUSR1 signal count: %u\n", signals); last_report = current; } } if (objects[slot] == NULL) { printf ("error: malloc: %m\n"); signal (SIGUSR1, SIG_IGN); kill (sigusr1_sender_pid, SIGKILL); kill (sigusr2_sender_pid, SIGKILL); return 1; } } /* Clean up allocations. */ for (int slot = 0; slot < malloc_objects; ++slot) free (objects[slot]); /* Terminate the signal-sending subprocess. The SIGUSR1 handler should no longer run because it uses sigusr1_sender_pid. */ signal (SIGUSR1, SIG_IGN); kill (sigusr1_sender_pid, SIGKILL); kill (sigusr2_sender_pid, SIGKILL); return 0; }