| /* |
| * Asynchronous teardown |
| * |
| * Copyright IBM, Corp. 2022 |
| * |
| * Authors: |
| * Claudio Imbrenda <imbrenda@linux.ibm.com> |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2 or (at your |
| * option) any later version. See the COPYING file in the top-level directory. |
| * |
| */ |
| |
| #include "qemu/osdep.h" |
| #include <dirent.h> |
| #include <sys/prctl.h> |
| #include <sched.h> |
| |
| #include "qemu/async-teardown.h" |
| |
| #ifdef _SC_THREAD_STACK_MIN |
| #define CLONE_STACK_SIZE sysconf(_SC_THREAD_STACK_MIN) |
| #else |
| #define CLONE_STACK_SIZE 16384 |
| #endif |
| |
| static pid_t the_ppid; |
| |
| /* |
| * Close all open file descriptors. |
| */ |
| static void close_all_open_fd(void) |
| { |
| struct dirent *de; |
| int fd, dfd; |
| DIR *dir; |
| |
| #ifdef CONFIG_CLOSE_RANGE |
| int r = close_range(0, ~0U, 0); |
| if (!r) { |
| /* Success, no need to try other ways. */ |
| return; |
| } |
| #endif |
| |
| dir = opendir("/proc/self/fd"); |
| if (!dir) { |
| /* If /proc is not mounted, there is nothing that can be done. */ |
| return; |
| } |
| /* Avoid closing the directory. */ |
| dfd = dirfd(dir); |
| |
| for (de = readdir(dir); de; de = readdir(dir)) { |
| fd = atoi(de->d_name); |
| if (fd != dfd) { |
| close(fd); |
| } |
| } |
| closedir(dir); |
| } |
| |
| static void hup_handler(int signal) |
| { |
| /* Check every second if this process has been reparented. */ |
| while (the_ppid == getppid()) { |
| /* sleep() is safe to use in a signal handler. */ |
| sleep(1); |
| } |
| |
| /* At this point the parent process has terminated completely. */ |
| _exit(0); |
| } |
| |
| static int async_teardown_fn(void *arg) |
| { |
| struct sigaction sa = { .sa_handler = hup_handler }; |
| sigset_t hup_signal; |
| char name[16]; |
| |
| /* Set a meaningful name for this process. */ |
| snprintf(name, 16, "cleanup/%d", the_ppid); |
| prctl(PR_SET_NAME, (unsigned long)name); |
| |
| /* |
| * Close all file descriptors that might have been inherited from the |
| * main qemu process when doing clone, needed to make libvirt happy. |
| * Not using close_range for increased compatibility with older kernels. |
| */ |
| close_all_open_fd(); |
| |
| /* Set up a handler for SIGHUP and unblock SIGHUP. */ |
| sigaction(SIGHUP, &sa, NULL); |
| sigemptyset(&hup_signal); |
| sigaddset(&hup_signal, SIGHUP); |
| sigprocmask(SIG_UNBLOCK, &hup_signal, NULL); |
| |
| /* Ask to receive SIGHUP when the parent dies. */ |
| prctl(PR_SET_PDEATHSIG, SIGHUP); |
| |
| /* |
| * Sleep forever, unless the parent process has already terminated. The |
| * only interruption can come from the SIGHUP signal, which in normal |
| * operation is received when the parent process dies. |
| */ |
| if (the_ppid == getppid()) { |
| pause(); |
| } |
| |
| /* At this point the parent process has terminated completely. */ |
| _exit(0); |
| } |
| |
| /* |
| * Allocate a new stack of a reasonable size, and return a pointer to its top. |
| */ |
| static void *new_stack_for_clone(void) |
| { |
| size_t stack_size = CLONE_STACK_SIZE; |
| char *stack_ptr; |
| |
| /* Allocate a new stack and get a pointer to its top. */ |
| stack_ptr = qemu_alloc_stack(&stack_size); |
| stack_ptr += stack_size; |
| |
| return stack_ptr; |
| } |
| |
| /* |
| * Block all signals, start (clone) a new process sharing the address space |
| * with qemu (CLONE_VM), then restore signals. |
| */ |
| void init_async_teardown(void) |
| { |
| sigset_t all_signals, old_signals; |
| |
| the_ppid = getpid(); |
| |
| sigfillset(&all_signals); |
| sigprocmask(SIG_BLOCK, &all_signals, &old_signals); |
| clone(async_teardown_fn, new_stack_for_clone(), CLONE_VM, NULL); |
| sigprocmask(SIG_SETMASK, &old_signals, NULL); |
| } |