/*
 * 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;

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.
     */
    qemu_close_all_open_fd(NULL, 0);

    /* 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);
}
