//
// run random system calls in parallel forever.
//

#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/fcntl.h"
#include "kernel/syscall.h"
#include "kernel/memlayout.h"
#include "kernel/riscv.h"

// from FreeBSD.
int
do_rand(unsigned long *ctx)
{
  /*
   * Compute x = (7^5 * x) mod (2^31 - 1)
   * without overflowing 31 bits:
   *      (2^31 - 1) = 127773 * (7^5) + 2836
   * From "Random number generators: good ones are hard to find",
   * Park and Miller, Communications of the ACM, vol. 31, no. 10,
   * October 1988, p. 1195.
   */
  long hi, lo, x;

  /* Transform to [1, 0x7ffffffe] range. */
  x = (*ctx % 0x7ffffffe) + 1;
  hi = x / 127773;
  lo = x % 127773;
  x = 16807 * lo - 2836 * hi;
  if(x < 0)
    x += 0x7fffffff;
  /* Transform to [0, 0x7ffffffd] range. */
  x--;
  *ctx = x;
  return (x);
}

unsigned long rand_next = 1;

int
rand(void)
{
  return (do_rand(&rand_next));
}

void
go(int which_child)
{
  int         fd = -1;
  static char buf[999];
  char       *break0 = sbrk(0);
  u64         iters = 0;

  mkdir("grindir");
  if(chdir("grindir") != 0) {
    printf("grind: chdir grindir failed\n");
    exit(1);
  }
  chdir("/");

  while(1) {
    iters++;
    if((iters % 500) == 0)
      write(1, which_child ? "B" : "A", 1);
    int what = rand() % 23;
    if(what == 1) {
      close(open("grindir/../a", O_CREATE | O_RDWR));
    } else if(what == 2) {
      close(open("grindir/../grindir/../b", O_CREATE | O_RDWR));
    } else if(what == 3) {
      unlink("grindir/../a");
    } else if(what == 4) {
      if(chdir("grindir") != 0) {
        printf("grind: chdir grindir failed\n");
        exit(1);
      }
      unlink("../b");
      chdir("/");
    } else if(what == 5) {
      close(fd);
      fd = open("/grindir/../a", O_CREATE | O_RDWR);
    } else if(what == 6) {
      close(fd);
      fd = open("/./grindir/./../b", O_CREATE | O_RDWR);
    } else if(what == 7) {
      write(fd, buf, sizeof(buf));
    } else if(what == 8) {
      read(fd, buf, sizeof(buf));
    } else if(what == 9) {
      mkdir("grindir/../a");
      close(open("a/../a/./a", O_CREATE | O_RDWR));
      unlink("a/a");
    } else if(what == 10) {
      mkdir("/../b");
      close(open("grindir/../b/b", O_CREATE | O_RDWR));
      unlink("b/b");
    } else if(what == 11) {
      unlink("b");
      link("../grindir/./../a", "../b");
    } else if(what == 12) {
      unlink("../grindir/../a");
      link(".././b", "/grindir/../a");
    } else if(what == 13) {
      int pid = fork();
      if(pid == 0) {
        exit(0);
      } else if(pid < 0) {
        printf("grind: fork failed\n");
        exit(1);
      }
      wait(0);
    } else if(what == 14) {
      int pid = fork();
      if(pid == 0) {
        fork();
        fork();
        exit(0);
      } else if(pid < 0) {
        printf("grind: fork failed\n");
        exit(1);
      }
      wait(0);
    } else if(what == 15) {
      sbrk(6011);
    } else if(what == 16) {
      if(sbrk(0) > break0)
        sbrk(-(sbrk(0) - break0));
    } else if(what == 17) {
      int pid = fork();
      if(pid == 0) {
        close(open("a", O_CREATE | O_RDWR));
        exit(0);
      } else if(pid < 0) {
        printf("grind: fork failed\n");
        exit(1);
      }
      if(chdir("../grindir/..") != 0) {
        printf("grind: chdir failed\n");
        exit(1);
      }
      kill(pid);
      wait(0);
    } else if(what == 18) {
      int pid = fork();
      if(pid == 0) {
        kill(getpid());
        exit(0);
      } else if(pid < 0) {
        printf("grind: fork failed\n");
        exit(1);
      }
      wait(0);
    } else if(what == 19) {
      int fds[2];
      if(pipe(fds) < 0) {
        printf("grind: pipe failed\n");
        exit(1);
      }
      int pid = fork();
      if(pid == 0) {
        fork();
        fork();
        if(write(fds[1], "x", 1) != 1)
          printf("grind: pipe write failed\n");
        char c;
        if(read(fds[0], &c, 1) != 1)
          printf("grind: pipe read failed\n");
        exit(0);
      } else if(pid < 0) {
        printf("grind: fork failed\n");
        exit(1);
      }
      close(fds[0]);
      close(fds[1]);
      wait(0);
    } else if(what == 20) {
      int pid = fork();
      if(pid == 0) {
        unlink("a");
        mkdir("a");
        chdir("a");
        unlink("../a");
        fd = open("x", O_CREATE | O_RDWR);
        unlink("x");
        exit(0);
      } else if(pid < 0) {
        printf("grind: fork failed\n");
        exit(1);
      }
      wait(0);
    } else if(what == 21) {
      unlink("c");
      // should always succeed. check that there are free i-nodes,
      // file descriptors, blocks.
      int fd1 = open("c", O_CREATE | O_RDWR);
      if(fd1 < 0) {
        printf("grind: create c failed\n");
        exit(1);
      }
      if(write(fd1, "x", 1) != 1) {
        printf("grind: write c failed\n");
        exit(1);
      }
      struct stat st;
      if(fstat(fd1, &st) != 0) {
        printf("grind: fstat failed\n");
        exit(1);
      }
      if(st.size != 1) {
        printf("grind: fstat reports wrong size %d\n", (int)st.size);
        exit(1);
      }
      if(st.ino > 200) {
        printf("grind: fstat reports crazy i-number %d\n", st.ino);
        exit(1);
      }
      close(fd1);
      unlink("c");
    } else if(what == 22) {
      // echo hi | cat
      int aa[2], bb[2];
      if(pipe(aa) < 0) {
        fprintf(2, "grind: pipe failed\n");
        exit(1);
      }
      if(pipe(bb) < 0) {
        fprintf(2, "grind: pipe failed\n");
        exit(1);
      }
      int pid1 = fork();
      if(pid1 == 0) {
        close(bb[0]);
        close(bb[1]);
        close(aa[0]);
        close(1);
        if(dup(aa[1]) != 1) {
          fprintf(2, "grind: dup failed\n");
          exit(1);
        }
        close(aa[1]);
        char *args[3] = { "echo", "hi", 0 };
        exec("grindir/../echo", args);
        fprintf(2, "grind: echo: not found\n");
        exit(2);
      } else if(pid1 < 0) {
        fprintf(2, "grind: fork failed\n");
        exit(3);
      }
      int pid2 = fork();
      if(pid2 == 0) {
        close(aa[1]);
        close(bb[0]);
        close(0);
        if(dup(aa[0]) != 0) {
          fprintf(2, "grind: dup failed\n");
          exit(4);
        }
        close(aa[0]);
        close(1);
        if(dup(bb[1]) != 1) {
          fprintf(2, "grind: dup failed\n");
          exit(5);
        }
        close(bb[1]);
        char *args[2] = { "cat", 0 };
        exec("/cat", args);
        fprintf(2, "grind: cat: not found\n");
        exit(6);
      } else if(pid2 < 0) {
        fprintf(2, "grind: fork failed\n");
        exit(7);
      }
      close(aa[0]);
      close(aa[1]);
      close(bb[1]);
      char buf[4] = { 0, 0, 0, 0 };
      read(bb[0], buf + 0, 1);
      read(bb[0], buf + 1, 1);
      read(bb[0], buf + 2, 1);
      close(bb[0]);
      int st1, st2;
      wait(&st1);
      wait(&st2);
      if(st1 != 0 || st2 != 0 || strcmp(buf, "hi\n") != 0) {
        printf("grind: exec pipeline failed %d %d \"%s\"\n", st1, st2, buf);
        exit(1);
      }
    }
  }
}

void
iter()
{
  unlink("a");
  unlink("b");

  int pid1 = fork();
  if(pid1 < 0) {
    printf("grind: fork failed\n");
    exit(1);
  }
  if(pid1 == 0) {
    rand_next ^= 31;
    go(0);
    exit(0);
  }

  int pid2 = fork();
  if(pid2 < 0) {
    printf("grind: fork failed\n");
    exit(1);
  }
  if(pid2 == 0) {
    rand_next ^= 7177;
    go(1);
    exit(0);
  }

  int st1 = -1;
  wait(&st1);
  if(st1 != 0) {
    kill(pid1);
    kill(pid2);
  }
  int st2 = -1;
  wait(&st2);

  exit(0);
}

int
main()
{
  while(1) {
    int pid = fork();
    if(pid == 0) {
      iter();
      exit(0);
    }
    if(pid > 0) {
      wait(0);
    }
    sleep(20);
    rand_next += 1;
  }
}