#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <asm/page.h>
#include <stdlib.h>
#include <assert.h>

long int random(void); /* shoud be prototyped by stdlib.h */

#define UWN__KERNEL__

#include "mmlib.h"

#include "../mergemem/mergemem.h"

#include "kern.h" /* should be part of mergemem.h */

/* end of includes */


/* #define H fprintf(stderr,"%s:%d Here\n",__FILE__,__LINE__) */

extern int m_fd;

/* ********** 
   Low level part that should be moved into kernel module mergemod.
*/

/* randomize_size_t returns value smaller or equal to n */
static size_t
randomize_size_t(size_t n)
{
  const int randomization_required = 1==2;
  if (randomization_required)
    {
      if (n > 0)
	{ /* simulate kernel limit */
	  size_t r = ( random() % n )  + 1;
	  assert(r <= n && r > 0);
	  return r;
	}
    }
  return n;
}

static int
process_alive(pid_t pid)
{
  struct stat buf;
  char path[PATH_MAX+1];
  sprintf( path, "/proc/%d", pid);
  if ( stat(path, &buf) == -1 )
    return 0;
  else
    return 1;
}






int
IOCTL_MERGEPAIRS( struct ioctl_mergepairs * argsp )
{
  size_t mergedpages, i, nel;
  MEQUALPAIR * pagepairs;
  if ( argsp == NULL ) /* invalid data area */
    {
      return -1;
    }
  if ( argsp -> pagepairs == NULL
       || argsp -> pagepairs + argsp -> nel == NULL ) /* invalid data area */
    {
      argsp -> errno = EFAULT;
      return -1;
    }
  mergedpages = 0;
  nel = argsp -> nel;
  pagepairs = argsp -> pagepairs;

  nel = randomize_size_t(nel); /* arbitrary kernel limit */
  for (i = 0; i < nel; i++)
    {
      int retval;
      struct mergemem_mmem mm_mmem;
      mm_mmem.pid2 = pagepairs[i].spid;
      mm_mmem.addr2 = (unsigned long) pagepairs[i].sv_addr;
      mm_mmem.pid1 = pagepairs[i].dpid;
      mm_mmem.addr1 = (unsigned long) pagepairs[i].dv_addr;
      retval = ioctl(m_fd, MERGEMEM_MERGE_MEM, &mm_mmem);
      pagepairs[i].status = retval;
      if (retval == 0)
	mergedpages++;
    }
  argsp->wel = i;
  argsp->mergedpages = mergedpages;
  return 0;
}

int IOCTL_GETPAGEINFOS( struct ioctl_getpageinfos * argsp)
{
  pid_t pid;
  unsigned long start, end;
  MPAGEINFO *pageinfos;
  size_t nel;
  size_t i;

  if (argsp == NULL) /* lacks more general condition */
    return -1;
  pid = argsp -> pid;
  start = ((unsigned long) argsp->start) & PAGE_MASK;
  end = ((unsigned long) argsp->end) & PAGE_MASK;
  pageinfos = argsp -> pageinfos;
  nel = argsp -> nel;

  if ( argsp-> pageinfos == NULL ) /* lacks more general condition */
    {
      argsp -> errno = EFAULT;
      return -1;
    }
  if ( !process_alive(argsp->pid) )
    {
      argsp -> errno = ESRCH;
      return -1;
    }

  nel = randomize_size_t(nel); /* arbitrary kernel limit */
  i = 0;
  while ( i < nel )
    {
      /* get physical address */
      struct mergemem_get_phys_addr mm_gpa;
      int retval;
      assert( i < argsp -> nel );
      mm_gpa.pid = pid;
      mm_gpa.addr = start;
      if ( (retval = ioctl(m_fd, MERGEMEM_GET_PHYS_ADDR, &mm_gpa)) == 0 )
	{
	  int retval;
	  /* get reference count (and some more that's not useful NOW) */
	  struct mergemem_chksum mm_chk;
	  mm_chk.pid = pid;
	  mm_chk.addr = start;
	  if ( ( retval = ioctl(m_fd, MERGEMEM_GEN_CHECKSUM, &mm_chk) ) == 0 )
	    {
	      pageinfos[i].v_addr = (void *) start;
	      pageinfos[i].p_addr = (void *) mm_gpa.phys_addr;
	      pageinfos[i].count = mm_chk.nrefs;
	      i++;
	    }
	  assert( i <= argsp -> nel && 1==1 );
	}
#ifdef MERGEMEM_PERMISSION
      else if (retval == MERGEMEM_PERMISSION)
	{
	  argsp -> errno = EPERM;
	  return -1;
	}
#endif
      if ( start >= end )
	break;
      start += PAGE_SIZE;
      assert( i <= argsp -> nel && 2==2 );
    }
  assert( i <= nel );
  assert( i <= argsp -> nel );
  if ( start == end )
    argsp -> cont = NULL;
  else
    argsp -> cont = (void*) start;
  argsp -> wel = i;
  if ( !process_alive(pid) )
    {
      argsp -> errno = ESRCH;
      return -1;
    }
  assert(argsp->wel <= argsp->nel);
  return 0;
}

int IOCTL_HASHPAGES( struct ioctl_hashpages *argsp )
{
  pid_t pid;
  MPAGEINFO *pageinfos;
  size_t nel, i;
  size_t hashedpages = 0;
  unsigned char * wbufferp, *endbufferp;
  KHASHMETHOD khashmethod;

  if ( argsp == NULL)
    return -1;

  pid = argsp -> pid;
  pageinfos = argsp -> pageinfos;
  nel = argsp -> nel;
  khashmethod = argsp -> khashmethod;

  switch ( argsp -> khashmethod )
    {
    case KHADDROTHALF:
    case KHCONST:
      break;
    case KHEXTERNAL:
      wbufferp = argsp -> pbufferp;

      if ( 1==2 && wbufferp != NULL ) /* pseudo code */
	{
	  /* if wbufferp points to a read only mapping, then free this
	     mapping and use a new mapping for the next pages */
	  wbufferp = NULL;
	}

      endbufferp = wbufferp + argsp -> size;
      if (endbufferp - wbufferp < PAGE_SIZE)
	{ /* not a single page can be written */
	  errno = EOVERFLOW; /* internal error */
	  /* if the kernel can itself provide/map the space its ok as well */
	  return -1;
	}
      argsp -> pagesize = PAGE_SIZE;
      break;
    default:
      argsp -> errno = ENXIO;
      return -1; /* errno missing */
    }

  nel = randomize_size_t(nel); /* arbitrary kernel limit */
  for ( i = 0; i < nel; i++ )
    {
      /* insert here some better method to avoid same hash values for
         incompatible pages */

      const unsigned long virtualcachemask = 0;
      pageinfos[i].hash = ( (unsigned long) pageinfos[i].v_addr ) &
	virtualcachemask & PAGE_MASK; /* ignore insignificant address parts */

      /* get physical address */
      {
	struct mergemem_get_phys_addr mm_gpa;
	int retval;
	mm_gpa.pid = pid;
	mm_gpa.addr = (unsigned long) pageinfos[i].v_addr;
	retval =  ioctl(m_fd, MERGEMEM_GET_PHYS_ADDR, &mm_gpa);
#ifdef MERGEMEM_PERMISSION
	if (retval == MERGEMEM_PERMISSION)
	  {
	    argsp -> errno = EPERM;
	    return -1;
	  }
#endif
	if ( retval != 0)
	  {
	    pageinfos[i].p_addr = NULL;
	    pageinfos[i].count = 0;
	    continue;
	  }
	pageinfos[i].p_addr = (void *) mm_gpa.phys_addr;
      }

      /* get reference count and built in hash */
      {
	struct mergemem_chksum mm_chk;
	mm_chk.pid = pid;
	mm_chk.addr = (unsigned long) pageinfos[i].v_addr;
	if ( ioctl(m_fd, MERGEMEM_GEN_CHECKSUM, &mm_chk) == 0 )
	  {
	    if ( khashmethod == KHADDROTHALF)
		pageinfos[i].hash ^= mm_chk.chksum;
	    pageinfos[i].count = mm_chk.nrefs;
	  }
	else
	  {
	    pageinfos[i].p_addr = NULL;
	    pageinfos[i].count = 0;
	    continue;
	  }
      }
      if ( khashmethod == KHCONST )
	pageinfos[i].hash ^= m_hash_const(NULL,0);
      else if ( khashmethod == KHEXTERNAL )
	{
	  struct mergemem_get_page mgp;
	  int retval;

	  mgp.pid = pid;
	  mgp.addr = (unsigned long) pageinfos[i].v_addr;
	  mgp.page = wbufferp;
	  if (wbufferp == NULL)
	    { /* use mapping instead of copying */
	    }
	  retval = ioctl(m_fd, MERGEMEM_GET_PAGE, &mgp);
	  if ( retval )
	    {
	      pageinfos[i].p_addr = NULL;
	      pageinfos[i].count = 0;
	      continue;
	    }
	  wbufferp += PAGE_SIZE; /* Update only in this case! */
	  if ( wbufferp+PAGE_SIZE > endbufferp )
	    break;
	}
      hashedpages++;
    }

  argsp -> wel = i;
  argsp -> succwel = hashedpages;
  return 0;
}
