#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
#if defined(sun) && defined(__svr4__)
#include <sys/mnttab.h>
#else
#include <mntent.h>
#endif
#endif

#if defined(sun) && defined(__svr4__) /*gec*/
#include <nfs/mount.h>
#include <sys/fstyp.h>
#include <sys/fsid.h>
#include <sys/types.h>
#include <sys/stat.h>
#define PORTMAP
#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
# if defined(sun) && !defined(__svr4__) /*gec*/
# 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 defined(sun) && defined(__svr4__)

/* it is nasty to have /etc/mnttab with permission 0600 ... gec*/
  { 
    struct mnttab entr;
    mode_t msk=umask(S_IWGRP|S_IWOTH);

    if(!(fpout = fopen(MTAB_TMP, "w"))) {
        perror(MTAB_TMP); return;
    }

    if(!(fpin = fopen(MTAB_PATH, "r"))) {
        fclose(fpout); unlink(MTAB_TMP);
        perror(MTAB_PATH); exit(0);
    }
    (void)umask(msk); 
  
    while (!getmntent(fpin,&entr))
    if(strcmp(entr.mnt_special, NFSHOST) || strcmp(entr.mnt_mountp, mntdir))
        putmntent(fpout, &entr);

    fclose(fpin); fclose(fpout);
  }

#else

  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);

#endif /* not solaris */

  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));
#ifdef linux
  bzero(&nfs_mount_data, sizeof(struct nfs_mount_data));
#else
  bzero(&nfs_args, sizeof(struct nfs_args));
#endif

/*** 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");
  /*sain.sin_addr.s_addr = htonl(INADDR_ANY);gec*/
  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)
#if defined(sun) && defined (__svr4__)
  { 								/*gec*/
    struct netbuf myaddr;
    struct stat stb;
    struct knetconfig knc;
    struct netconfig *nc=getnetconfigent("udp");
    if (!nc) {
	extern int errno;
	fprintf(stderr, "getnetconfigent \"udp\": (errno %d) ", errno);
	perror("");
	exit(1);
    }
    knc.knc_semantics=nc->nc_semantics;
    knc.knc_protofmly=strdup(nc->nc_protofmly);
    knc.knc_proto=strdup(nc->nc_proto);

    if (stat(nc->nc_device,&stb)) {
	extern int errno;
	fprintf(stderr, "stat \"%s\": (errno %d) ",nc->nc_device, errno);
	perror("");
	exit(1);
    }
    knc.knc_rdev=stb.st_rdev;

    /*freenetconfigent(nc) has the struct been allocated, or is it static ???*/

    printf("%d,%s,%s,%lx (1,inet,udp,0x002c0029)\n",knc.knc_semantics,
    knc.knc_protofmly,knc.knc_proto,knc.knc_rdev);

    myaddr.maxlen=myaddr.len=sizeof(sain);
    myaddr.buf=&sain;

    nfs_args.knconf   = &knc;
    nfs_args.wsize    = PBUFSIZE;
    nfs_args.rsize    = PBUFSIZE;
    nfs_args.addr     = &myaddr;
    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 | NFSMNT_KNCONF; 
  }
#else

  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 /* not solaris */
#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");
#if defined(sun) && !defined(__svr4__)
	if(mount("nfs", dir, M_NEWTYPE, &nfs_args))
#endif
#if defined(sun) && defined(__svr4__) /*gec*/
        if(mount("", dir, MS_DATA, "nfs", &nfs_args,sizeof(nfs_args)))
	/* eventually drop knc-strings now (memory-leak) */
#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
#if defined(sun) && defined(__svr4__) /*gec*/
	    FILE *mfp;
	    struct mnttab mnt;
	    char tim[32];

	    if(debug)
  	        printf("Mount succeded, making mtab entry.\n");
	    mnt.mnt_special = NFSHOST;
	    mnt.mnt_mountp = mntdir;
	    mnt.mnt_fstype = "nfs";
	    mnt.mnt_mntopts = "hard,intr"; /* dev=??? */
	    sprintf(tim,"%ld",time(NULL)); /* umount crashes! without this*/
	    mnt.mnt_time = tim;

	    if ((mfp = fopen(MTAB_PATH, "a"))) putmntent(mfp, &mnt);
	    else perror(MTAB_PATH);

	    fclose(mfp);
#else
	    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 /* not solaris */
#endif
	  }
	exit(0);
    }

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

#if defined(sun) && !defined(__svr4__)
  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;
	}

    }
}
