/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */

#include "nsutils.h"
#include "util.h"

#define NUM_THREADS 10

static int trigger_last_thread_pipe[2];
static int trigger_pid_ns_init_exit_pipe[2];
static int toplevel_exit_pipe[2];

static void* do_thread(void* p) {
  int index = (intptr_t)p;
  /* Reduce thread priority so rr doesn't schedule these threads unless we want it to */
  setpriority(PRIO_PROCESS, 0, 5);
  if (index == NUM_THREADS - 1) {
    char ch;
    read(trigger_last_thread_pipe[0], &ch, 1);
    /* Kick off the init process exit ...
       after our exit_group has started. */
    write(trigger_pid_ns_init_exit_pipe[1], "x", 1);
    exit(1);
  }
  pause();
  return NULL;
}

/**
 * 1) make the last thread of 'child' do an 'exit_group', initiating kill of all threads
 * in particular 'child's main thread, which advances to PTRACE_EVENT_EXIT
 * 2) then have the pid_ns_init process exit, triggering kernel's zap_pid_ns_processes
 * for 'child' and 'inner_child'
 * 3) this kicks 'child' out of PTRACE_EVENT_EXIT (without rr having seen it)
 * 4) 'child' then enters its own zap_pid_ns_processes and waits on the exit of
 * 'inner_child'
 * 5) rr sees that 'child' has been reaped, but it's not in STOPPED state nor
 * is it in EXITED state.
 */
static int do_pid_ns_init(void) {
  pid_t child;
  char ch;
  pipe(trigger_last_thread_pipe);

  unshare(CLONE_NEWPID);
  child = fork();
  if (!child) {
    pid_t inner_child;
    for (int i = 0; i < NUM_THREADS; ++i) {
      pthread_t thread;
      pthread_create(&thread, NULL, do_thread, (void*)(intptr_t)i);
    }

    test_assert(0 == unshare(CLONE_NEWPID));
    inner_child = fork();
    if (!inner_child) {
      pause();
      return 0;
    }

    write(trigger_last_thread_pipe[1], "y", 1);
    pause();
    return 0;
  }
  read(trigger_pid_ns_init_exit_pipe[0], &ch, 1);
  write(toplevel_exit_pipe[1], "q", 1);
  return 77;
}

static void do_detect_glibc_bug(void) {
  pid_t child;
  int status;
  unshare(CLONE_NEWPID);
  child = fork();
  if (!child) {
    exit(0);
  }
  wait(&status);
  exit(WIFSIGNALED(status));
}

static int detect_glibc_bug_inner(void) {
  int status;
  pid_t pid = fork();
  test_assert(pid >= 0);
  if (!pid) {
    do_detect_glibc_bug();
  }
  wait(&status);
  return WEXITSTATUS(status) != 0;
}

/* Detect https://sourceware.org/legacy-ml/libc-alpha/2017-05/msg00378.html */
static int detect_glibc_bug(void) {
  int status;
  pid_t pid = fork();
  if (!pid) {
    close(STDERR_FILENO);
    if (-1 == try_setup_ns(CLONE_NEWPID)) {
      exit(0);
    }
    exit(detect_glibc_bug_inner());
  }
  wait(&status);
  return WEXITSTATUS(status);
}

int main(void) {
  pid_t pid;
  char ch;

  if (detect_glibc_bug()) {
    atomic_puts("EXIT-SUCCESS");
    return 0;
  }

  if (-1 == try_setup_ns(CLONE_NEWPID)) {
    /* We may not have permission to set up namespaces, so bail. */
    atomic_puts("Insufficient permissions, skipping test");
    atomic_puts("EXIT-SUCCESS");
    return 0;
  }

  atomic_puts("EXIT-SUCCESS");

  pipe(trigger_pid_ns_init_exit_pipe);
  pipe(toplevel_exit_pipe);
  pid = fork();
  test_assert(pid >= 0);
  if (!pid) {
    return do_pid_ns_init();
  }

  read(toplevel_exit_pipe[0], &ch, 1);
  return 0;
}
