/*
 *
 * Here's a similar snippet for Solaris
 * using the procfs interface...
 *
 * Daniel Brown, dbrown@ccdc.cam.ac.uk
 *
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/syscall.h>
#include <sys/uio.h>

#include <fcntl.h>
#include <procfs.h>
#include <stdio.h>
#include <unistd.h>

/* Faking of the time() system call via procfs.
 *
 * Daniel Brown, dbrown@ccdc.cam.ac.uk
 *
 * Compiled and tested under Solaris 2.6/sparc and 2.6/x86
 */

int
read_status(pid_t pid, pstatus_t *status)
{
  char buf[MAXPATHLEN];
  int fd;

  sprintf(buf, "/proc/%d/status", (int) pid);

  if ((fd = open(buf, O_RDONLY)) == -1)
    {
      perror("read_status(open)");
      return 1;
    }

  if (read(fd, (void *) status, sizeof(pstatus_t)) != sizeof(pstatus_t))
    {
      perror("read_status(read)");
      return 1;
    }
  close(fd);

  return 0;
}

int
write_ctl(pid_t pid, long syscall, void *arg, size_t arglen)
{
  int fd;
  char buf[MAXPATHLEN];
  struct iovec vec[2];

  sprintf(buf, "/proc/%d/ctl", (int) pid);

  if ((fd = open(buf, O_WRONLY)) == -1)
    {
      perror("write_ctl(open)");
      return 1;
    }

  vec[0].iov_base = (void *) &syscall;
  vec[0].iov_len = sizeof(long);

  if (arg != NULL)
    {
      vec[1].iov_base = arg;
      vec[1].iov_len = arglen;

      if (writev(fd, vec, 2) != (vec[0].iov_len + vec[1].iov_len))
        {
          perror("write_ctl(write2)");
          close(fd);
          return 1;
        }
    }
  else
    {
      if (writev(fd, vec, 1) != vec[0].iov_len)
        {
          perror("write_ctl(write1)");
          return 1;
        }
    }

  close(fd);

  return 0;
}

int
main(int argc, char **argv)
{
  pid_t pid, ppid;
  pstatus_t pstatus;
  sysset_t sysset;
  long val;

  setvbuf(stdout, (char *) NULL, _IONBF, (size_t) 0);

  ppid = getpid();

  if (read_status(ppid, &pstatus))
    exit(1);

  printf("Parent PID is %d\n", (int) ppid);

  printf("Setting PCSEXIT for time() : stop on exit from time().\n");

  premptyset(&sysset);
  praddset(&sysset, SYS_time);

  if (write_ctl(ppid, PCSEXIT, (void *) &sysset, sizeof(sysset_t)))
    exit(1);

  printf("Setting PR_FORK, so that the child inherits these traps.\n");

  val = PR_FORK;

  if (write_ctl(ppid, PCSET, (void *) &val, sizeof(long)))
    exit(1);

  printf("Finally, setting PCSENTRY for exit() "
         ": stop on entry to exit().\n");

  premptyset(&sysset);
  praddset(&sysset, SYS_exit);

  if (write_ctl(ppid, PCSENTRY, (void *) &sysset, sizeof(sysset_t)))
    exit(1);

  if ((pid = fork()) < 0)
    {
      perror("fork");
      exit(1);
    }
  else if (pid > 0)
    { /* Parent */

      printf("Clearing exit() trap for the parent...\n");

      premptyset(&sysset);
      if (write_ctl(ppid, PCSENTRY, (void *) &sysset,
                    sizeof(sysset_t)))
        exit(1);        /* Good luck! */

      if (read_status(pid, &pstatus))
        exit(1);

      printf("Child PID is %d and %s have a trap set on time().\n",
             (int) pstatus.pr_pid,
             (prismember(&pstatus.pr_sysexit,
                         SYS_time)) ? "does" : "doesn't");

      while (1)
        {

          printf("Waiting for child to call time() or exit().\n");

          if (write_ctl(pid, PCWSTOP, (void *) 0, 0))
            exit(1);

          printf("Write PCWSTOP returned. Reading status.\n");

          if (read_status(pid, &pstatus))
            exit(1);

          if (pstatus.pr_lwp.pr_syscall == SYS_exit)
            {
              printf("Child has called exit(). Bye!\n");
              exit(0);
            }
          else if (pstatus.pr_lwp.pr_syscall != SYS_time)
            {
              printf("We've caught syscall %d! Eeeek!\n",
                     (int) pstatus.pr_lwp.pr_syscall);
              exit(1);
            }

          printf("Child has called time().\n");

          printf("LWP register R_R0 is %d.\n",
                 (int) pstatus.pr_lwp.pr_reg[R_R0]);

          printf("Setting LWP register R_R0 to 123.\n");

          pstatus.pr_lwp.pr_reg[R_R0] = 123;
          if (write_ctl(pid, PCSREG, (void *) &pstatus.pr_lwp.pr_reg,
                        sizeof(prgregset_t)))
            exit(1);

          printf("Restarting the child.\n");

          val = 0L;

          if (write_ctl(pid, PCRUN, (void *) &val, sizeof(long)))
            exit(1);

          printf("Restarted.\n");

        }       /* while (1) */

      /* NOT REACHED */
    }

  /* Child */

  sleep(5);

  execl("/usr/bin/date", "/usr/bin/date", (char *) NULL);

  perror("execl failed");

  return 99;
}
