#include <stdio.h>
#include <signal.h>

#include "config.h"

#define NFSCLIENT
#include <sys/mount.h>

#ifdef linux
# include <linux/fs.h>		/* struct nfs_sever for linux/nfs_fs.h */
# include <linux/nfs_fs.h>	/* struct nfs_mount_data */
# include <arpa/inet.h>		/* inet_addr() */
#endif
#ifdef _IBMR2
# include "os-aix3.h"
# include "misc-aix3.h"
# include <sys/vmount.h>
# include <sys/stat.h>
#endif
#include "nfs_prot.h"

#ifndef DONT_UPDATE_MTAB
#include <mntent.h>
#endif

#include <sys/socket.h>
#include <netdb.h>
#include <rpc/rpc.h>

#include "mp.h"

static char *mntdir;

void
doexit()
{
  FILE *fpin, *fpout;
  struct mntent *ent;

  exiting--;

  if(debug) printf("Doing exit\n");

#ifdef _IBMR2
  {
    struct stat statb;

    if (stat(mntdir, &statb))
      perror("stat");
    if(debug) printf("Next call: uvmount(%d, 0)\n", statb.st_vfs);
    if(uvmount(statb.st_vfs, 0))
      {
	perror("uvmount");
	return;
      }
  }
#else
# ifdef sun
# define umount unmount
# endif
  if(umount(mntdir))
    {
      perror(mntdir); return;
    }
#endif

#ifndef DONT_UPDATE_MTAB
  if(debug) printf("unmount succeeded, trying to fix mtab.\n");
  if(!(fpout = setmntent(MTAB_TMP, "w")))
    {
      perror(MTAB_TMP); return;
    }
  if(!(fpin = setmntent(MTAB_PATH, "r")))
    {
      endmntent(fpout); unlink(MTAB_TMP);
      perror(MTAB_PATH); exit(0);
    }
  
  while ((ent = getmntent(fpin)))
    if(strcmp(ent->mnt_fsname, NFSHOST) || strcmp(ent->mnt_dir, mntdir))
      addmntent(fpout, ent);
  endmntent(fpin); endmntent(fpout);

  if(rename(MTAB_TMP, MTAB_PATH))
    {
      perror(MTAB_PATH); unlink(MTAB_TMP);
    }
#else
  if(debug) printf("no mtab fixing needed\n");
#endif
  fprintf(stderr, "p3nfsd: exiting.\n");
  
  exit(0);
}

void
dosystem(str)
  char *str;
{
  extern fattr root_fattr;

  if(vfork()) return;
  setuid(root_fattr.uid);
  setgid(root_fattr.gid);
  execl("/bin/sh", "sh", "-c", str, 0);
  _exit(0);
}

int
mount_and_run(dir, proc, root_fh)
  char *dir;
  nfs_fh *root_fh;
  void (*proc)();
{
  int sock, port, pid;
  struct sockaddr_in sain;
  int isalive = 0, ret, dtbsize;
  SVCXPRT *nfsxprt;
  int bufsiz = 0xc000;	                    /* room for a few biods */
#ifdef linux
  struct nfs_mount_data nfs_mount_data;
  int mount_flags;
  int ksock, kport;
  struct sockaddr_in kaddr;
#else
  struct nfs_args nfs_args;
#endif

  bzero(&sain, sizeof(struct sockaddr_in));
  bzero(&nfs_args, sizeof(struct nfs_args));

/*** First part: set up the rpc service */
/* Create udp socket */
  sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &bufsiz, sizeof(bufsiz)))
    perror("setsockopt");

/* Bind it to a reserved port */
  sain.sin_family = AF_INET;
  sain.sin_addr.s_addr = inet_addr("127.0.0.1");
  for(port = IPPORT_RESERVED-1; port > IPPORT_RESERVED/2; port--)
    {
      sain.sin_port = htons(port);
      if(bind(sock, (struct sockaddr *) &sain, sizeof(sain)) >= 0)
        break;
    }
  if(port <= IPPORT_RESERVED/2)
    {
      perror("bind to reserved port"); exit(1);
    }
  if((nfsxprt = svcudp_create(sock)) == 0)
    {
      perror("svcudp_create"); exit(1);
    }
  if(!svc_register(nfsxprt, NFS_PROGRAM, NFS_VERSION, proc, 0))
    {
      perror("svc_register"); exit(1);
    }

/*** Second part: mount the directory */
#ifdef linux
  /* 
   * Hold your hat! Another odd internet socket coming up. The linux kernel
   * likes reserved sockets to talk to its nfs daemons.
   */
  ksock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (ksock < 0)
    {
      perror("Cannot create kernel socket. Sigh.");
      exit(1);
    }
  kaddr.sin_family = AF_INET;
  kaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  for (kport = IPPORT_RESERVED - 1; kport > IPPORT_RESERVED/2; kport--)
    {
      kaddr.sin_port = htons(kport);
      if (bind(ksock, (struct sockaddr *)&kaddr, sizeof(kaddr)) >= 0)
        break;
    }
  if (kport <= IPPORT_RESERVED/2)
    {
      perror("bind to reserved port");
      exit(1);
    }

  nfs_mount_data.version = NFS_MOUNT_VERSION;
  nfs_mount_data.fd      = ksock;
  nfs_mount_data.root    = *root_fh;	/* structure copy */
  nfs_mount_data.flags   = NFS_MOUNT_INTR | NFS_MOUNT_NOCTO; 
  			/* NFS_MOUNT_SECURE | NFS_MOUNT_POSIX | NFS_MOUNT_SOFT | NFS_MOUNT_NOAC */
  nfs_mount_data.rsize    = PBUFSIZE;
  nfs_mount_data.wsize    = PBUFSIZE;
  nfs_mount_data.timeo    = 600;
  nfs_mount_data.retrans  = 0;		/* default 3 */
  nfs_mount_data.acregmin = 3;		/* default 3 seconds */
  nfs_mount_data.acregmax = 60;		/* default 60 seconds */
  nfs_mount_data.acdirmin = 30;		/* default 30 seconds */
  nfs_mount_data.acdirmax = 60;		/* default 60 seconds */
  nfs_mount_data.addr     = sain;	/* structure copy */
  strcpy(nfs_mount_data.hostname, "psion");

  if (connect(ksock, (struct sockaddr *)&nfs_mount_data.addr, sizeof(nfs_mount_data.addr)) < 0)
    {
      perror("Cannot connect to kernel");
      exit(1);
    }

  mount_flags = MS_MGC_VAL; /* | MS_SYNC | MS_RDONLY | MS_NOEXEC | MS_NODEV | MS_NOSUID */

#endif

#if defined(sun) || defined(hpux)

  nfs_args.wsize    = PBUFSIZE;
  nfs_args.rsize    = PBUFSIZE;
  nfs_args.addr     = &sain;
  nfs_args.fh       = (char *)root_fh;
  nfs_args.retrans = 0;
  /* 1 minute timeout - means that we receive double requests in worst case,
   if the file is longer than 100k */
  nfs_args.timeo = 600;
  nfs_args.hostname = "psion";
  nfs_args.flags    = NFSMNT_INT | NFSMNT_HOSTNAME | NFSMNT_NOCTO |
                   NFSMNT_RETRANS | NFSMNT_TIMEO | NFSMNT_WSIZE | NFSMNT_RSIZE;
#endif

#if defined(_IBMR2)
  nfs_args.addr     = sain;
  nfs_args.fh       = *(fhandle_t *)root_fh;
  nfs_args.wsize    = PBUFSIZE;
  nfs_args.rsize    = PBUFSIZE;
  nfs_args.retrans  = 0;
  nfs_args.biods    = 1;
  nfs_args.timeo    = 150;
  nfs_args.hostname = "psion";
  nfs_args.flags    = NFSMNT_INT | NFSMNT_HOSTNAME | NFSMNT_BIODS |
                   NFSMNT_RETRANS | NFSMNT_TIMEO | NFSMNT_WSIZE | NFSMNT_RSIZE;
#endif

  mntdir = dir;
  switch(pid=fork())
    {
      case 0:
        break;
      case -1:
        perror("fork");
	exit(1);
      default:
	if(debug)
	  printf("Going to mount...\n");
#ifdef sun
	if(mount("nfs", dir, M_NEWTYPE, &nfs_args))
#endif
#ifdef hpux
	if (vfsmount(MOUNT_NFS, dir, 0, &nfs_args))	
#endif
#ifdef linux
	if (mount("nfs", dir, "nfs", mount_flags, &nfs_mount_data))
#endif
#ifdef _IBMR2
	if(aix3_mount("psion:loc", dir, 0, MOUNT_TYPE_NFS, &nfs_args, "p3nfsd"))
#endif
	  {
	    extern int errno;

	    fprintf(stderr, "nfs mount %s: (errno %d) ", dir, errno);
	    perror("");
	    kill(pid, SIGTERM);
	  }
	else
	  {
#ifndef DONT_UPDATE_MTAB
	    FILE *mfp;
	    struct mntent mnt;

	    if(debug)
	      printf("Mount succeded, making mtab entry.\n");
	    mnt.mnt_fsname = NFSHOST;
	    mnt.mnt_dir = mntdir;
	    mnt.mnt_type = "nfs";
	    mnt.mnt_opts = "hard,intr";
	    mnt.mnt_freq = mnt.mnt_passno = 0;
	    if ((mfp = setmntent(MTAB_PATH, "a")))
	      addmntent(mfp, &mnt);
	    else
	      perror(MTAB_PATH);
	    endmntent(mfp);
#endif
	  }
	exit(0);
    }

/*** Third part: let's go */
  printf("p3nfsd: to stop the server do \"ls %s/exit\". (pid %d)\n",
         mntdir, getpid());

#ifdef sun
  dtbsize = _rpc_dtablesize();
#else
  dtbsize = FD_SETSIZE;
#endif
  for(;;)
    {
      fd_set readfd;
      struct timeval tv;
      struct cache *cp;

      readfd = svc_fdset;
      tv.tv_sec = 2; tv.tv_usec = 0;

      ret = select(dtbsize, &readfd, 0, 0, &tv);
      if(ret > 0)
        svc_getreqset(&readfd);
      if(ret != 0)
        continue;

/* Do some housekeeping */
      
      if(exiting)
        doexit();
      clean_cache(&readcache); clean_cache(&attrcache);
      for(cp = writecache; cp; cp = cp->next)
        if(!cp->written)
	  {
	    if(debug)
	      printf("Warning, block at offset %d in %s not written\n",
		     cp->offset, get_num(cp->inode)->name);
	    break;
	  }
      clean_cache(&writecache);

      ret = fd_is_still_alive(psionfd, 0);
      if(isalive && !ret)
        {
	  if(debug) printf("Disconnected...\n");
	  if(disconnprog && *disconnprog)
	    dosystem(disconnprog);
	  isalive = ret;
	}
      else if(!isalive && ret)
        {
	  if(debug) printf("Connected...\n");
	  if(connprog && *connprog)
	    dosystem(connprog);
	  isalive = ret;
	}

    }
}
