/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include "util.h"

#ifndef PTRACE_EVENT_STOP
#define PTRACE_EVENT_STOP 128
#endif

#include <math.h>
#include <signal.h>

static int EXECUTION_FENCE_PIPES[2];
static char force_clone;
static char stack[65536];

static void seize(const pid_t pid) {
  const int opts =
      PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE | PTRACE_O_TRACESYSGOOD;
  test_assert(0 == ptrace(PTRACE_SEIZE, pid, 0, opts));
}

static int vfork_child(__attribute__((unused)) void* p) {
  long res = 0;
  for (int i = 1; i < 1000000; i++) {
    for (int j = 1; j < 100; j++) {
      res = (res / 3) + 14*i;
      if (res /= res > 2) {
        res = 3;
      }
    }
  }
  exit(99);
  return 0;
}

static int tracees(void) {
  pid_t child;
  if (force_clone) {
    child = clone(vfork_child, stack + sizeof(stack),
                  CLONE_VFORK | CLONE_VM | SIGCHLD, NULL);
  } else {
    child = vfork();
    if (!child) {
      vfork_child(NULL);
      return 0;
    }
  }
  if (child < 0) {
    exit(EXIT_FAILURE);
  } else {
    exit(66);
  }
}

int main(int argc, char** argv) {
  char notify;
  assert(0 == pipe(EXECUTION_FENCE_PIPES));

  if (argc > 1 && !strcmp(argv[1], "clone")) {
    force_clone = 1;
  }

  pid_t fork_child = fork();

  switch (fork_child) {
    case 0: {
      assert(1 == read(EXECUTION_FENCE_PIPES[0], &notify, 1) &&
             "Failed to read notify event");
      tracees();
    } break;
    default:
      seize(fork_child);
      test_assert(1 == write(EXECUTION_FENCE_PIPES[1], "1", 1));
      int ws;
      long grand_child = 0;

      test_assert(fork_child == waitpid(fork_child, &ws, 0));
      // Wait for fork-child to stop at VFORK event
      test_assert(WIFSTOPPED(ws) && WSTOPSIG(ws) == SIGTRAP &&
                  ws >> 8 == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)));
      // Get vfork-child's pid
      test_assert(0 == ptrace(PTRACE_GETEVENTMSG, fork_child, NULL, &grand_child));
      // Wait for vfork-child to stop
      test_assert(grand_child == waitpid(0, &ws, 0));
      // PTRACE_O_TRACEVFORK generates PTRACE_EVENT_STOP (for vfork child) if
      // SEIZE is used for ATTACH, SIGSTOP will be generated instead (man
      // ptrace)
      test_assert(WIFSTOPPED(ws) && WSTOPSIG(ws) == SIGTRAP &&
                  ws >> 8 == (SIGTRAP | (PTRACE_EVENT_STOP << 8)));
      // Just to be sure; let's send SIGINT *before* grand child is done
      // and make sure it arrives *after* VFORK_DONE
      kill(fork_child, SIGINT);
      test_assert(0 == ptrace(PTRACE_CONT, grand_child, NULL, NULL));
      // Continue fork-child at vfork event
      test_assert(0 == ptrace(PTRACE_CONT, fork_child, NULL, NULL));
      test_assert(fork_child == waitpid(fork_child, &ws, 0));
      // Verify that fork-child (vfork-parent), received VFORK_DONE
      test_assert(ws >> 8 == (SIGTRAP | (PTRACE_EVENT_VFORK_DONE << 8)));

      // Check that vfork-child exited with correct exit code
      test_assert(grand_child == waitpid(grand_child, &ws, 0));
      test_assert(WIFEXITED(ws) && WEXITSTATUS(ws) == 99);

      // Check that fork-child (vfork-parent) stopped due to SIGCHILD
      test_assert(0 == ptrace(PTRACE_CONT, fork_child, NULL, NULL));
      test_assert(fork_child == waitpid(fork_child, &ws, 0));
      test_assert(WIFSTOPPED(ws) && WSTOPSIG(ws) == SIGINT);

      test_assert(0 == ptrace(PTRACE_CONT, fork_child, NULL, NULL));
      test_assert(fork_child == waitpid(fork_child, &ws, 0));
      test_assert(WIFSTOPPED(ws) && WSTOPSIG(ws) == SIGCHLD);

      // And finally verify fork-childs exit code
      test_assert(0 == ptrace(PTRACE_CONT, fork_child, NULL, NULL));
      test_assert(fork_child == waitpid(fork_child, &ws, 0));
      test_assert(WIFEXITED(ws) && WEXITSTATUS(ws) == 66);
  }
  atomic_puts("EXIT-SUCCESS");
}
