/*
 *  plex86: run multiple x86 operating systems concurrently
 *  Copyright (C) 2000  Kevin P. Lawton
 *
 *  write-cache.cc: A write-cache implementation.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 */


/*
 * Initial quick C++ implementation, Tom Vijlbrief, 20001230
 *
 * TODO:
 * - implement commit
 * - add persistance option (stores cache in a file)
 * - add support for more disks 
 */

#define DEBUG 1
#define LARGEFILES 0

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

// The C++ STL map container makes the cache real easy to implement
#include <map>

#include "plex86.h"
#include "user.h"
#include "plugin.h"

extern "C" {
static Bit64u   wcLSeek(int fd, Bit64u off64, int whence);
static unsigned wcRead(int fd, void *buffer, unsigned len);
static unsigned wcWrite(int fd, void *buffer, unsigned len);
static void     wcCommit(int fd);
}


  static void
CachePanic(char *s)
{
  fprintf(stderr, "Write cache problem: %s\n", s);
  vm_abort();
  exit(1);
}


  extern "C"
  int
plugin_init(plugin_t *plugin, int argc, char *argv[])
{

  /* You can pass options to this plugin, from the conf file,
   * if you require any.  Maybe something like sector size hints,
   * maximum cache size before a commit is forced (we should
   * interrupt the user in the future for that), etc.
   */

  pluginRegisterWriteCache(wcLSeek, wcRead, wcWrite, wcCommit);
  return 0; /* OK */
}


  extern "C"
  void
plugin_fini(void)
{
  return;
}


/* Fill in the functions below.  These provide the functions
 * analogous to the libc lseek(), read() and write() functions,
 * but should be implemented with the write cache in mind.
 * Currently, these pass the functionality through to libc,
 * for a starting point.
 *
 * The commit function is called by the associated GUI button.
 * The idea is that the user wants to commit all of the previous write
 * operations in the cache to disk.  So commit the writes and
 * flush the cache.
 *
 * Note that their are builtin default handlers which simply pass-through
 * these events, when this plugin is not loaded.  If you need to fix
 * things in the plugin interface, you might need to fix them too.
 * They are in 'user/plugin.c'.  The header file is 'user/plugin.h'.
 *
 * The calls in user/plugins/bochs/iodev/harddrv.cc where changed
 * to call these functions.  We may need to modify some code to make
 * them use 64-bit offset to get past the 2-GB limit.
 *
 * Look in conf/freedos for a commented line, which is how you might
 * call the plugin.
 */

struct sector {
  Bit8u data[512];
};


typedef map<Bit64u,sector> sectormap;

class WriteCache {
public:
  WriteCache(): fd(-1) { };

  void SetFd(int fd);
  int GetFd() { return fd; };

  Bit64u WriteCache::seek(Bit64u off64, int whence);
  unsigned read(void* buffer, unsigned len);
  unsigned write(void* buffer, unsigned len);
private:
  void doseek();

  int		fd;
  Bit64u	pos;
  sectormap	map;
};

void WriteCache::SetFd(int fd) {
  if (this->fd != -1 && this->fd != fd) {
    fprintf(stderr, "new fd: %d old: %d\n", fd, this->fd);
    CachePanic("already in use");
  }
  this->fd= fd;
}

Bit64u WriteCache::seek(Bit64u off64, int whence)
{
  if (whence != 0)
    CachePanic("whence != 0 not implemented");
  pos= off64;
  return off64;
}

void WriteCache::doseek()
{
  loff_t result;
#if LARGEFILES
  lseek64(fd,
	(unsigned long) (pos>>32), (unsigned long) (pos&0xFFFFFFFF),
	&result, 0);
#else
  result= lseek(fd, (unsigned long) (pos&0xFFFFFFFF), 0);
#endif
  if (result < 0)
    CachePanic("seek failed");
}


unsigned WriteCache::read(void* buffer, unsigned len)
{
  if (len != 512)
    CachePanic("len != 512");
  
  sectormap::const_iterator i;
  if ((i= map.find(pos)) != map.end()) {
    memcpy(buffer, i->second.data, len); 
    pos+= len;
    return len;
  } else {
    doseek();
    pos+= len;
    return( ::read(fd, buffer, len) );
  }
}

unsigned WriteCache::write(void* buffer, unsigned len)
{
  if (len != 512)
    CachePanic("len != 512");
  sector s;
  memcpy(s.data, buffer, len);
  map[pos]= s; 
#if DEBUG
  fprintf(stderr, "Write cache: offset %u:%u, size %d\n",
	(unsigned)(pos>>32), (unsigned)(pos&0xFFFFFFFF), map.size());
#endif
  pos+= len;
  return len;
}


// Just one cache for now...
WriteCache thecache;


  Bit64u
wcLSeek(int fd, Bit64u off64, int whence)
{
  thecache.SetFd(fd);
  return thecache.seek(off64, whence);
}

  unsigned
wcRead(int fd, void* buffer, unsigned len)
{
  return( thecache.read(buffer, len) );
}

  unsigned
wcWrite(int fd, void* buffer, unsigned len)
{
  return( thecache.write(buffer, len) );
}

  void
wcCommit(int fd)
{
}
