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

#include "rrutil.h"

/* This test spawns a ptracer and a ptracee, where the ptracee has 10
   sub-threads.
   The pracer attaches to all the ptracee's threads, then exits.
   We check that all ptracee threads are resumed.
   Similar to ptracer_detah_multithread, except the ptracer is
   not a parent of the ptracee.
*/

static int status_pipe[2];
static int tid_pipe[2];
static int ready_pipe[2];
static int thread_wait_pipe[2];

static void write_tid(void) {
  pid_t tid = sys_gettid();
  test_assert(sizeof(tid) == write(tid_pipe[1], &tid, sizeof(tid)));
}

static pid_t read_tid(void) {
  pid_t tid;
  test_assert(sizeof(tid) == read(tid_pipe[0], &tid, sizeof(tid)));
  return tid;
}

static void* child_thread(__attribute__((unused)) void* p) {
  char ch = 0;
  write_tid();
  test_assert(1 == read(thread_wait_pipe[0], &ch, 1));
  test_assert('W' == ch);
  return NULL;
}

static int child_runner(void) {
  char ch = 0;
  pthread_t threads[10];
  int i;

  /* This fails on some kernels, so don't check its result */
  prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);

  write_tid();
  for (i = 0; i < 10; ++i) {
    pthread_create(&threads[i], NULL, child_thread, NULL);
  }

  atomic_printf("Waiting on ready_pipe\n");
  test_assert(1 == read(ready_pipe[0], &ch, 1));
  test_assert(ch == 'R');

  for (i = 0; i < 10; ++i) {
    char ch2 = 'W';
    test_assert(1 == write(thread_wait_pipe[1], &ch2, 1));
  }
  for (i = 0; i < 10; ++i) {
    atomic_printf("Joining thread %d\n", i);
    pthread_join(threads[i], NULL);
  }

  char ok = 'K';
  test_assert(1 == write(status_pipe[1], &ok, 1));

  return 77;
}

static int ptracer(void) {
  int status;
  char ready = 'R';
  int i;
  pid_t child_tids[11];

  for (i = 0; i < 11; ++i) {
    child_tids[i] = read_tid();
  }
  for (i = 0; i < 11; ++i) {
    int ret;
    test_assert(0 == ptrace(PTRACE_ATTACH, child_tids[i], NULL, NULL));
    ret = waitpid(child_tids[i], &status, __WALL);
    atomic_printf("waitpid on %d gives %d with errno=%d\n", child_tids[i], ret,
                  errno);
    test_assert(ret == child_tids[i]);
    test_assert(status == ((SIGSTOP << 8) | 0x7f));
  }

  test_assert(1 == write(ready_pipe[1], &ready, 1));

  /* Now just exit, and all child threads should resume */
  return 44;
}

int main(void) {
  char ch = 0;
  pid_t ptracer_pid, child;
  int status;

  test_assert(0 == pipe(ready_pipe));
  test_assert(0 == pipe(tid_pipe));
  test_assert(0 == pipe(status_pipe));
  test_assert(0 == pipe(thread_wait_pipe));

  if (0 == (child = fork())) {
    return child_runner();
  }

  if (0 == (ptracer_pid = fork())) {
    return ptracer();
  }

  test_assert(ptracer_pid == waitpid(ptracer_pid, &status, 0));
  test_assert(WIFEXITED(status) && WEXITSTATUS(status) == 44);

  test_assert(1 == read(status_pipe[0], &ch, 1));
  test_assert(ch == 'K');

  atomic_puts("EXIT-SUCCESS");
  return 0;
}
