1 /*
2 * Asynchronous teardown
3 *
4 * Copyright IBM, Corp. 2022
5 *
6 * Authors:
7 * Claudio Imbrenda <imbrenda@linux.ibm.com>
8 *
9 * This work is licensed under the terms of the GNU GPL, version 2 or (at your
10 * option) any later version. See the COPYING file in the top-level directory.
11 *
12 */
13
14 #include "qemu/osdep.h"
15 #include <dirent.h>
16 #include <sys/prctl.h>
17 #include <sched.h>
18
19 #include "qemu/async-teardown.h"
20
21 #ifdef _SC_THREAD_STACK_MIN
22 #define CLONE_STACK_SIZE sysconf(_SC_THREAD_STACK_MIN)
23 #else
24 #define CLONE_STACK_SIZE 16384
25 #endif
26
27 static pid_t the_ppid;
28
hup_handler(int signal)29 static void hup_handler(int signal)
30 {
31 /* Check every second if this process has been reparented. */
32 while (the_ppid == getppid()) {
33 /* sleep() is safe to use in a signal handler. */
34 sleep(1);
35 }
36
37 /* At this point the parent process has terminated completely. */
38 _exit(0);
39 }
40
async_teardown_fn(void * arg)41 static int async_teardown_fn(void *arg)
42 {
43 struct sigaction sa = { .sa_handler = hup_handler };
44 sigset_t hup_signal;
45 char name[16];
46
47 /* Set a meaningful name for this process. */
48 snprintf(name, 16, "cleanup/%d", the_ppid);
49 prctl(PR_SET_NAME, (unsigned long)name);
50
51 /*
52 * Close all file descriptors that might have been inherited from the
53 * main qemu process when doing clone, needed to make libvirt happy.
54 */
55 qemu_close_all_open_fd(NULL, 0);
56
57 /* Set up a handler for SIGHUP and unblock SIGHUP. */
58 sigaction(SIGHUP, &sa, NULL);
59 sigemptyset(&hup_signal);
60 sigaddset(&hup_signal, SIGHUP);
61 sigprocmask(SIG_UNBLOCK, &hup_signal, NULL);
62
63 /* Ask to receive SIGHUP when the parent dies. */
64 prctl(PR_SET_PDEATHSIG, SIGHUP);
65
66 /*
67 * Sleep forever, unless the parent process has already terminated. The
68 * only interruption can come from the SIGHUP signal, which in normal
69 * operation is received when the parent process dies.
70 */
71 if (the_ppid == getppid()) {
72 pause();
73 }
74
75 /* At this point the parent process has terminated completely. */
76 _exit(0);
77 }
78
79 /*
80 * Allocate a new stack of a reasonable size, and return a pointer to its top.
81 */
new_stack_for_clone(void)82 static void *new_stack_for_clone(void)
83 {
84 size_t stack_size = CLONE_STACK_SIZE;
85 char *stack_ptr;
86
87 /* Allocate a new stack and get a pointer to its top. */
88 stack_ptr = qemu_alloc_stack(&stack_size);
89 stack_ptr += stack_size;
90
91 return stack_ptr;
92 }
93
94 /*
95 * Block all signals, start (clone) a new process sharing the address space
96 * with qemu (CLONE_VM), then restore signals.
97 */
init_async_teardown(void)98 void init_async_teardown(void)
99 {
100 sigset_t all_signals, old_signals;
101
102 the_ppid = getpid();
103
104 sigfillset(&all_signals);
105 sigprocmask(SIG_BLOCK, &all_signals, &old_signals);
106 clone(async_teardown_fn, new_stack_for_clone(), CLONE_VM, NULL);
107 sigprocmask(SIG_SETMASK, &old_signals, NULL);
108 }
109