/*
  mmlib.c
  Interface library for mergemod & mergemem
  
  This file is part of mergemem by Philipp Richter & Philipp Reisner
  Created 1999-01-15 by Ulrich Neumerkel

  mergemem is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.
  
  mergemem is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with mergemem; see the file COPYING.  If not, write to
  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
  This is a rather inefficient reference implementation based on
  mergemod version 0.12.  To become more efficient, more flexible
  ioctl calls are needed.
*/

#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 */

#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__) */


/* test function */
static void
randomizearea(void *p, size_t len)
{
  const int randomization_required = 1 == 2;
  char * sp = (char *) p;
  size_t i;
  if (randomization_required)
    for( i = 0 ; i < len; i++ )
      sp[i] = random();
}

int /* should be static, after kern.c has been removed */
m_fd = -1;

static unsigned long
(*m_hash_default)(const void* addr, size_t size) = NULL;


/* semi exported */
void
dosimpleioctl(void)
{
  int modver;
  ioctl(m_fd, MERGEMEM_CHECK_VER, &modver);
}


static ssize_t
do_initialization(void)
{
  int modver;
  if ( (m_fd = open("/dev/mergemem", O_RDONLY)) == -1 )
    {
      struct stat buf;
      if ( stat("/dev/mergemem", &buf) == -1 )
	errno = ENOPKG; /* package not installed */
      else
	errno = EPERM; /* exists, but cannot read */
      return -1;
    }
  if ( ioctl(m_fd, MERGEMEM_CHECK_VER, &modver) != 0 || modver != MOD_VERSION )
    {
      m_fd = -1;
      errno = EPROTO; /* version mismatch or similar */
      return -1;
    }
  m_hash_default = m_hash_addrothalf;
  return 0;
}

static ssize_t
ensure_initialization(void)
{
  if (m_fd != -1)
    return 0;
  return do_initialization();
}

ssize_t
m_getpageinfos(  
	       const pid_t pid,          
	       const void * const start,
	       const void * const end,
	       MPAGEINFO * const pageinfos,
	       const size_t nel,
	       const void * * const contp )
{
  size_t i = 0;
  struct ioctl_getpageinfos args;
  int retval;

  if ( ensure_initialization() < 0)
    return -1;
  if ( start > end || nel == 0 )
    {
      errno = EINVAL;
      return -1;
    }
  args.pid = pid;
  args.start = start;
  args.end = end;

  while ( i < nel  )
    {
      args.pageinfos = pageinfos+i;
      args.nel = nel-i;
      retval = IOCTL_GETPAGEINFOS( & args );
      if ( retval != 0 )
	{
	  errno = args.errno;
	  return -1;
	}
      i += args.wel;
      if ( args.cont == NULL )
	break;
      args.start = args.cont;
    }
  *contp = args.cont;
  return i;
}


ssize_t
m_hashpages(
	    const pid_t pid,
	    MPAGEINFO * const pageinfos,
	    const size_t nel,
	    unsigned long (*hashfunction)(const void* addr, size_t size) )
{
  ssize_t hashedpages, i;
  struct ioctl_hashpages args;


#define MAXPAGES 11

  unsigned char pagearray [PAGE_SIZE*MAXPAGES];
  randomizearea(pagearray, sizeof(pagearray));

  if ( ensure_initialization() < 0)
    return -1;
  if ( nel == 0 )
    {
      errno = EINVAL;
      return -1;
    }
  if ( hashfunction == NULL )
    hashfunction = m_hash_default; /* current default */

  hashedpages = 0;
  
  args.pid = pid;
  if ( hashfunction == m_hash_addrothalf )
    args.khashmethod = KHADDROTHALF;
  else if ( hashfunction == m_hash_const )
    args.khashmethod = KHCONST;
  else
    {
      args.khashmethod = KHEXTERNAL;
      args.pbufferp = pagearray;
      args.size = sizeof(pagearray);
    }
  i = 0;
  while (i < nel)
    {
      int retval;
      args.pageinfos = pageinfos+i;
      if ( args.khashmethod == KHEXTERNAL
	   && nel - i >= MAXPAGES )
	args.nel = MAXPAGES;
      else
	args.nel = nel - i;
      retval = IOCTL_HASHPAGES( & args);
      if (retval != 0)
	{
	  errno = args.errno;
	  return -1;
	}
      if (args.khashmethod == KHEXTERNAL)
	{
	  size_t j;
	  const unsigned char * pagestart = args.pbufferp;
	  /* the kernel might have chosen a better location */
	  size_t pagesize = args.pagesize;
	  for (j = 0; j < args.wel; j++)
	    {
	      if ( pageinfos[j+i].count != 0)
		{
		  pageinfos[j+i].hash ^=
		    (*hashfunction)(pagestart,pagesize);
		  pagestart += pagesize;
		}
	    }
	}
      i += args.wel;
      hashedpages += args.succwel;
    }
  if ( args.khashmethod == KHEXTERNAL
       && args.pbufferp != pagearray )
    {
      int retval;
      /* the kernel changed pbufferp to make it point to its own
         area, now remove this map from our process */
      args.nel = 0;
      retval = IOCTL_HASHPAGES( & args);
      if (retval != 0)
	{
	  errno = args.errno;
	  return -1;
	}
    }
  randomizearea(pagearray, sizeof(pagearray));      
  return hashedpages;
}


ssize_t
m_merge( MEQUALPAIR * const pagepairs, const size_t nel )
{
  ssize_t mergedpages, i;

  if ( ensure_initialization() < 0)
    return -1;
  mergedpages = 0;
  i = 0;
  while ( i < nel )
    {
      struct ioctl_mergepairs args;
      int retval;

      args.pagepairs = pagepairs+i;
      args.nel = nel - i;
      retval = IOCTL_MERGEPAIRS( &args );
      if (retval != 0)
	{
	  errno = args.errno;
	  return -1;
	}
      i += args.wel;
      mergedpages += args.mergedpages;
    }
  return mergedpages;
}


ssize_t
m_set_hash_default( unsigned long (* const hashfunction)(const void* addr, size_t size) )
{
  if ( ensure_initialization() < 0 )
    return -1;
  if ( hashfunction == NULL )
    {
      errno = EINVAL;
      return -1;
    }
  m_hash_default = hashfunction;
  return 0;
}

/* Predefined hashfunctions */

#define BITS_OF_UNSIGNEDLONG (sizeof(unsigned long)*8)

unsigned long
m_hash_addrothalf(const void* addr, const size_t size)
{
  unsigned long * page = (unsigned long *) addr;
  unsigned long * end = (unsigned long *) ( (char *) addr + size/2 );
  unsigned long chksum = 0;
  while (page < end)
    {
      chksum = (chksum << 3) + (chksum >> (BITS_OF_UNSIGNEDLONG-3)) + *page;
      page++;
    }
  return chksum;
}

unsigned long
m_libhash_addrothalf(const void* addr, size_t size)
{
  return m_hash_addrothalf(addr, size);
}

unsigned long
m_hash_const(const void* addr, size_t size)
{
  return 27;
}

unsigned long
m_libhash_const(const void* addr, size_t size)
{
  return m_hash_const(addr, size);
}
