#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/resource.h>

int readn(int fd, char *buf, int n) {
  int orig = n;
  while(n > 0){
    int cc = read(fd, buf, n);
    if(cc <= 0) { perror("read"); return -1; }
    n -= cc;
    buf += cc;
  }
  return orig;
}

char *
getstr(unsigned char *p)
{
  unsigned int n = *(unsigned short *)p;
  char *buf = malloc(n+1);
  memcpy(buf, p+2, n);
  buf[n] = '\0';
  return buf;
}

int
main(){
  struct rlimit r;
  r.rlim_cur = r.rlim_max = 0;
  setrlimit(RLIMIT_CORE, &r);

  int s = socket(AF_INET, SOCK_STREAM, 0);
  { int yes = 1;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
  }
  struct sockaddr_in sin;
  memset(&sin, 0, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_port = htons(564);
  if(bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0){
    perror("bind"); exit(1);
  }
  listen(s, 10);
  sync(); sleep(1);

  if(fork() == 0){
    close(s);
    // -o ...,debug=0x10f
    if(system("echo -n mount: ; mount -t 9p -o nodevmap,trans=tcp,cache=none,access=any,debug=0x0 127.0.0.1 /mnt") == 0){

      system("mount | grep /mnt");
      
      printf("open /mnt/b:\n");
      int fd = creat("/mnt/b", 0777);
      if(fd < 0) perror("creat");
      write(fd, "x", 1);
      char junk[1];
      read(fd, junk, 1);
      printf("close /mnt/b:\n");
      close(fd);

      system("echo -n umount: ; umount -f /mnt");
    }
    exit(0);
  }

  int spid = fork();
  if(spid == 0){
    socklen_t sinlen = sizeof(sin);
    int s1 = accept(s, (struct sockaddr *) &sin, &sinlen);
    if(s1 < 0) { perror("accept"); exit(1); }
    close(s);
  
    int opno = 0;
    while(1){
      char ibuf[1024];
      if(readn(s1, ibuf, 4) < 0) break;
      int ilen = *(int*)(ibuf+0);
      if(readn(s1, ibuf+4, ilen - 4) < 0) break;

      printf("%d: ", opno);
      fflush(stdout);

      char obuf[sizeof(ibuf)];
      memset(obuf, 0xff, sizeof(obuf));
      *(int*)(obuf+0) = ilen; // length
      if(ibuf[4] == 100){ // Tversion
        printf("version %d %s\n", *(int*)(ibuf+7), getstr(ibuf+11));
        memcpy(obuf, ibuf, ilen);
      } else if(ibuf[4] == 24){ // Tgetattr (different from Tstat!)
        printf("getattr\n");
        // https://github.com/chaos/diod/blob/master/protocol.md
        int sz = 161;
        *(int*)(obuf+0) = sz + 7;
        *(int*)(obuf+32) = 0; // uid
        *(int*)(obuf+36) = 0; // gid
        if(opno == 7){
          //*(int*)(obuf+28) = 0100777; // S_IFREG, rwxrwxrwx
          *(int*)(obuf+28) = 010777; // S_IFIFO, rwxrwxrwx
        } else {
          *(int*)(obuf+28) = 0040777; // S_IFDIR, rwxrwxrwx
        }
      } else if(ibuf[4] == 110){ // Twalk
        int nwqid = *(short*)(ibuf+15);
        printf("walk %d %s\n", nwqid, nwqid?getstr(ibuf+17):"-");
        if(opno == 3){
          // error...
          ibuf[4] = 106; // Terror
          *(int*)(obuf+0) = 11;
          *(int*)(obuf+7) = ENOENT;
        } else {
          *(short*)(obuf+7) = nwqid;
          *(int*)(obuf+0) = 9 + nwqid*13;
          if(opno == 24){
            *(char*)(obuf+21) = 1;
          }
        }
      } else if(ibuf[4] == 104){ // Tattach
        printf("attach\n");
        *(int*)(obuf+0) = 20;
      } else if(ibuf[4] == 120){ // Tclunk
        printf("clunk\n");
        *(int*)(obuf+0) = 7;
      } else if(ibuf[4] == 30){ // Txattrwalk
        printf("xattrwalk\n");
        *(int*)(obuf+0) = 15;
        *(long*)(obuf+7) = 2; // size
      } else if(ibuf[4] == 116){ // Tread
        unsigned long offset = *(long*)(ibuf+11);
        unsigned int count = *(int*)(ibuf+19);
        printf("read %ld %d\n", offset, count); fflush(stdout);
        int n = 0;
        if(offset == 0 && count > 2){
          unsigned char *p = obuf+11;
          unsigned char *p0 = p;

          p += 2; // size;
          p += 2; // type
          p += 4; // dev
          p += 1; // qid.type
          p += 4; // qid.vers
          p += 8; // qid.path
          p += 4; // permissions
          p += 4; // atime
          p += 4; // mtime
          p += 8; // length
          *(short*)p = 1; // name length
          p++;
          *p++ = 'x';
          *(short*)p = 1; // owner name length
          *p++ = 'x';
          *(short*)p = 1; // group name length
          *p++ = 'x';
          *(short*)p = 1; // last modify user name length
          *p++ = 'x';
            
          n = p - p0;
          printf(" >>> n=%d <<< ", n); fflush(stdout);
          *(short*)(p0) = n;
        }
        *(int*)(obuf+0) = n + 11;
        *(int*)(obuf+7) = n;
      } else if(ibuf[4] == 12){ // Tlopen
        printf("lopen\n");
        *(int*)(obuf+0) = 24;
      } else if(ibuf[4] == 40){ // Treaddir
        printf("readdir\n");
        // each dirent is 25 bytes
        unsigned long offset = *(long*)(ibuf+11);
        unsigned int count = *(int*)(ibuf+19);
        int n = 0;
        if(offset == 0){
          n = 1;
          unsigned char *p0 = obuf + 11;
          unsigned char *p = p0;
          p += 13; // qid
          p += 8; // offset
          p += 1; // type
          *(short*)p = 1;
          p += 2;
          *p++ = 'x';
        }
        *(int*)(obuf+0) = 11 + n*25;
      } else if(ibuf[4] == 8){ // Tstatfs
        printf("statfs\n");
        *(int*)(obuf+0) = 67;
      } else if(ibuf[4] == 72){ // Tmkdir
        printf("mkdir %s\n", getstr(ibuf + 11));
        *(int*)(obuf+0) = 20;
      } else if(ibuf[4] == 74){ // Trenameat
        printf("renameat %s\n", getstr(ibuf + 11));
        *(int*)(obuf+0) = 7;
      } else if(ibuf[4] == 14){ // Tlcreate
        printf("lcreate %s\n", getstr(ibuf + 11));
        *(int*)(obuf+0) = 24;
      } else if(ibuf[4] == 26){ // Tsetattr
        printf("setattr %s\n", getstr(ibuf + 11));
        *(int*)(obuf+0) = 7;
      } else if(ibuf[4] == 76){ // Tunlinkat
        printf("unlinkat %s\n", getstr(ibuf + 11));
        *(int*)(obuf+0) = 7;
      } else {
        printf("%d ???\n", ibuf[4] & 0xff);
      }
      fflush(stdout);
      obuf[4] = ibuf[4] + 1; // convert Txxx to Rxxx
      *(short*)(obuf+5) = *(short*)(ibuf+5); // tag

      if(obuf[4] == 25){
        printf("Rgetattr #%d: ", opno);
        // https://github.com/chaos/diod/blob/master/protocol.md
        printf("op %d ", obuf[4]);
        printf("mode 0%o ", *(unsigned int *)(obuf+28));
        printf("\n");
      }

      if(write(s1, obuf, *(int*)(obuf+0))<=0) perror("write");

      opno += 1;
    }

    exit(0);
  }
  close(s);

  time_t t0 = time(0);
  while(1){
    int st;
    int ret = waitpid(-1, &st, WNOHANG);
    if(ret > 0)
      break;
    usleep(200000);
    time_t t1 = time(0);
    if(t1 - t0 >= 10){
      printf("9pnew: timeout\n");
      break;
    }
  }
}
