| /* 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 <http://www.gnu.org/licenses/>. */ |
| |
| /* 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 <errno.h> |
| #include <sched.h> |
| #include <signal.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/wait.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| /* 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; |
| } |