text: /*
   * Copyright 2013, Michael Ellerman, IBM Corp.
   * Licensed under GPLv2.
   */
  
  #include <errno.h>
  #include <signal.h>
  #include <stdbool.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <sys/types.h>
  #include <sys/wait.h>
  #include <unistd.h>
  #include <elf.h>
  #include <fcntl.h>
  #include <link.h>
  #include <sys/stat.h>
  
  #include "subunit.h"
  #include "utils.h"
  
  #define KILL_TIMEOUT	5
  
  static uint64_t timeout = 120;
  
  int run_test(int (test_function)(void), char *name)
  {
  	bool terminated;
  	int rc, status;
  	pid_t pid;
  
  	/* Make sure output is flushed before forking */
  	fflush(stdout);
  
  	pid = fork();
  	if (pid == 0) {
  		setpgid(0, 0);
  		exit(test_function());
  	} else if (pid == -1) {
  		perror("fork");
  		return 1;
  	}
  
  	setpgid(pid, pid);
  
  	/* Wake us up in timeout seconds */
  	alarm(timeout);
  	terminated = false;
  
  wait:
  	rc = waitpid(pid, &status, 0);
  	if (rc == -1) {
  		if (errno != EINTR) {
  			printf("unknown error from waitpid\n");
  			return 1;
  		}
  
  		if (terminated) {
  			printf("!! force killing %s\n", name);
  			kill(-pid, SIGKILL);
  			return 1;
  		} else {
  			printf("!! killing %s\n", name);
  			kill(-pid, SIGTERM);
  			terminated = true;
  			alarm(KILL_TIMEOUT);
  			goto wait;
  		}
  	}
  
  	/* Kill anything else in the process group that is still running */
  	kill(-pid, SIGTERM);
  
  	if (WIFEXITED(status))
  		status = WEXITSTATUS(status);
  	else {
  		if (WIFSIGNALED(status))
  			printf("!! child died by signal %d\n", WTERMSIG(status));
  		else
  			printf("!! child died by unknown cause\n");
  
  		status = 1; /* Signal or other */
  	}
  
  	return status;
  }
  
  static void alarm_handler(int signum)
  {
  	/* Jut wake us up from waitpid */
  }
  
  static struct sigaction alarm_action = {
  	.sa_handler = alarm_handler,
  };
  
  void test_harness_set_timeout(uint64_t time)
  {
  	timeout = time;
  }
  
  int test_harness(int (test_function)(void), char *name)
  {
  	int rc;
  
  	test_start(name);
  	test_set_git_version(GIT_VERSION);
  
  	if (sigaction(SIGALRM, &alarm_action, NULL)) {
  		perror("sigaction");
  		test_error(name);
  		return 1;
  	}
  
  	rc = run_test(test_function, name);
  
  	if (rc == MAGIC_SKIP_RETURN_VALUE) {
  		test_skip(name);
  		/* so that skipped test is not marked as failed */
  		rc = 0;
  	} else
  		test_finish(name, rc);
  
  	return rc;
  }
  