/* memfs-1.c - A trivial single-memory-based-file translator, multithreaded.
   Copyright (C) 2000 Farid Hajji <farid.hajji@ob.kamp.net>
   Copyright (C) 1998, 1999 Free Software Foundation, Inc.

   This program 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.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

/* Original hurd/trans/hello-mt.c written by Free Software Foundation, Inc.
   Modified by Farid Hajji <farid.hajji@ob.kamp.net>  */

/* Modified by Ludovic Courtes <ludo@type-z.org>
 */

/* This trivfs translator provides a fixed-sized file that is stored
   entirely in memory (actually in a mmap() allocted buffer).
   The file can be read from or written to, just like any other file.
   Currently, it is not permissible to change the size of the file
   (try this with vi, you'll get EIEIO). More trivfs hooks/callbacks
   need to be implemented.
  
   The main usage for memfs-1 is to provide a backend for filesystems.
   Suggested use (note that -s option is currently mandatory!):

     settrans -ac memfile path/to/memfs-1 -s <size_in_bytes>

     # write a disklabel in memfile in some way, then:  
     mkfs.ufs memfile
     settrans -ac path/to/newdir /hurd/ufs memfile
        -- OR --
     # disklabels are not necessary for ext2fs:
     mkfs.ext2 memfile
     settrans -ac path/to/newdir /hurd/ext2fs memfile
  
   Note that it is not the most efficient way to provide memory-based
   filesystems!  */

#ifndef _GNU_SOURCE
# define _GNU_SOURCE 1
#endif

#include <hurd/trivfs.h>
#include <stdio.h>
#include <argp.h>
#include <argz.h>
#include <error.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#include <sys/types.h>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>
#include <assert.h>
#include "debug.h"

/* The max number of bytes that we will reserve via mmap().
   This is mainly to protect against user-errors while invoking
   memfs-1. Change this if desired.  */
#define MAXSIZE (30 * 1024 * 1024)

/* Eventual output file name (written when goaway()). */
static char* output_file_name = NULL;

/* The memory buffer starts at CONTENTS and is CONTENTS_LEN bytes in size. */
static char *contents = NULL;
static size_t contents_len = 0;

/* This lock protects access to CONTENTS and CONTENTS_LEN.  */
static pthread_rwlock_t contents_lock;

/* Trivfs hooks. */
int trivfs_fstype = FSTYPE_MISC;
int trivfs_fsid = 0;

int trivfs_allow_open = O_READ | O_WRITE;

int trivfs_support_read = 1;
int trivfs_support_write = 1;
int trivfs_support_exec = 0;

/* NOTE: This example is not robust: it is possible to trigger some
   assertion failures because we don't implement the following:

   $ cd /src/hurd/libtrivfs
   $ grep -l 'assert.*!trivfs_support_read' *.c |
     xargs grep '^trivfs_S_' | sed 's/^[^:]*:\([^ 	]*\).*$/\1/'
   trivfs_S_io_get_openmodes
   trivfs_S_io_clear_some_openmodes
   trivfs_S_io_set_some_openmodes
   trivfs_S_io_set_all_openmodes
   trivfs_S_io_readable
   trivfs_S_io_select
   $

   For that reason, you should run this as an active translator
   `settrans -ac testnode /path/to/mem-mt' so that you can see the
   error messages when they appear. */

/* A hook for us to keep track of the file descriptor state. */
struct open
{
  pthread_mutex_t lock;
  off_t offs;
};

void
trivfs_modify_stat (struct trivfs_protid *cred, struct stat *st)
{
  debuglog (DEBUG_TRACE, __FUNCTION__);

  /* Mark the node as a read/write plain file. */
  st->st_mode &= ~(S_IFMT | ALLPERMS);
  st->st_mode |= (S_IFREG |
		  S_IRUSR | S_IRGRP | S_IROTH |
		  S_IWUSR | S_IWGRP | S_IWOTH);
  st->st_size = contents_len;	/* No need to lock for reading one word.  */
}

error_t
trivfs_goaway (struct trivfs_control *cntl, int flags)
{
  debuglog (DEBUG_TRACE, __FUNCTION__);

  /* Eventually backs up the file */
  if (output_file_name)
  {
    file_t f;

    debuglog (DEBUG_TRACE, "%s: writing back to %s.",
              __FUNCTION__, output_file_name);
    f = file_name_lookup(output_file_name, O_WRITE|O_CREAT, 0640);
    assert (f != MACH_PORT_NULL); // XXX: remove this
    io_write (f, contents, contents_len, -1, NULL);
  }
  debugend ();
  exit (0);
}

static error_t
open_hook (struct trivfs_peropen *peropen)
{
  struct open *op;

  debuglog (DEBUG_TRACE, __FUNCTION__);

  op = malloc (sizeof (struct open));
  if (op == NULL)
    return ENOMEM;

  /* Initialize the offset. */
  if (peropen->openmodes & O_APPEND)
    op->offs=contents_len;
  else
    op->offs = 0;

  pthread_mutex_init (&op->lock, NULL);
  peropen->hook = op;
  return 0;
}


static void
close_hook (struct trivfs_peropen *peropen)
{
  struct open *op;

  debuglog (DEBUG_TRACE, __FUNCTION__);

  op = peropen->hook;

  pthread_mutex_destroy (&op->lock);
  free (op);
}

kern_return_t
trivfs_S_file_set_size (struct trivfs_protid *cred,
			mach_port_t reply, mach_msg_type_name_t reply_type,
			off_t size)
{
  if (size > MAXSIZE)
    return ENOSPC;

  contents_len = size;
  return 0;
}

kern_return_t
trivfs_S_io_readable (struct trivfs_protid* cred,
                      mach_port_t reply, mach_msg_type_name_t replytype,
		      mach_msg_type_number_t* amount)
{
  if (!cred) return EOPNOTSUPP;
  *amount = (contents_len>10240) ? 10240 : contents_len; /* arbitrary value of 10k */
  return 0;
}

/* Read data from an IO object.  If OFFSet is -1, read from the object
   maintained file pointer.  If the object is not seekable, OFFSet is
   ignored.  The amount desired to be read is in AMOUNT.  */
kern_return_t
trivfs_S_io_read (struct trivfs_protid *cred,
		  mach_port_t reply, mach_msg_type_name_t reply_type,
		  data_t *data, mach_msg_type_number_t *data_len,
		  off_t offs, vm_size_t amount)
{
  struct open *op;

  debuglog (DEBUG_TRACE,
	    "%s(offs=%ld, amount=%d)",
	    __FUNCTION__, offs, amount);

  /* Deny access if they have bad credentials. */
  if (! cred)
    return EOPNOTSUPP;
  else if (! (cred->po->openmodes & O_READ))
    return EBADF;

  op = cred->po->hook;

  pthread_mutex_lock (&op->lock);

  /* Get the offset. */
  if (offs == -1)
    offs = op->offs;

  pthread_rwlock_rdlock (&contents_lock);

  /* Prune the amount they want to read. */
  if (offs > contents_len)
    offs = contents_len;
  if (offs + amount > contents_len)
    amount = contents_len - offs;

  if (amount > 0)
    {
      assert (data != NULL);

      /* Possibly allocate a new buffer for the reply. */
      if (*data_len < amount)
	*data = (data_t) mmap (0, amount, PROT_READ|PROT_WRITE,
		               MAP_ANON, 0, 0);
//	*data = (data_t) realloc ((void*)*data, amount);

      /* Copy AMOUNT bytes of data, starting at OFFSet, into the buffer. */
      memcpy ((char *) *data, contents + offs, amount);

      /* Update the saved offset.  */
      op->offs += amount;
    }

  pthread_mutex_unlock (&op->lock);

  pthread_rwlock_unlock (&contents_lock);

  *data_len = amount;

  debuglog (DEBUG_TRACE, "%i bytes read.", amount);
  return 0;
}

/* Write data to an IO object.  If OFFSet is -1, write at the object
   maintained file pointer.  If the object is not seekable, OFFSet is
   ignored.  The amount successfully written is returned in *AMOUNT. A
   given user should not have more than one outstanding io_write on an
   object at a time; servers implement congestion control by delaying
   responses to io_write.  Servers may drop data (returning ENOBUFS)
   if they receive more than one write when not prepared for it.  */
kern_return_t
trivfs_S_io_write (struct trivfs_protid *cred,
		   mach_port_t reply, mach_msg_type_name_t reply_type,
		   const_data_t data, mach_msg_type_number_t data_len,
		   off_t offs, vm_size_t *amount)
{
  struct open *op;

  debuglog (DEBUG_TRACE,
	    "%s(offs=%ld, data_len=%d)",
	    __FUNCTION__, offs, data_len);

  /* Deny access if they have bad credentials. */
  if (! cred)
    return EOPNOTSUPP;
  else if (! (cred->po->openmodes & O_WRITE))
    return EBADF;

  op = cred->po->hook;

  pthread_mutex_lock (&op->lock);

  /* Get the offset. */
  if (offs == -1)
    offs = op->offs;

  pthread_rwlock_rdlock (&contents_lock);

  /* Are we going to go beyond MAXSIZE? */
  if (offs + data_len <= MAXSIZE)
  {
    *amount = data_len;
    if (offs + data_len > contents_len)
    {
      /* writing beyond current file boundary -> allocate some more mem */
      contents_len = offs+data_len;
      contents = (char*) realloc(contents, contents_len);
    }
    else
    /* XXX otherwise, we might wanna deallocate something! */
    {
      if ((!offs) && (!data_len))
      { free(contents);
        contents = NULL;
        contents_len = 0;
      }
    }

    /* Copy *AMOUNT bytes of DATA into the CONTENTS buffer at OFFSet. */
    if (data_len)
      memcpy (contents + offs, (char *)data, *amount);

    /* Update the saved offset.  */
    op->offs += *amount;
    debuglog (DEBUG_TRACE, "%s: %i bytes written.", __FUNCTION__, *amount);

    pthread_mutex_unlock (&op->lock);
    pthread_rwlock_unlock (&contents_lock);
  }
  else
  {
    *amount = 0;
    pthread_mutex_unlock (&op->lock);
    pthread_rwlock_unlock (&contents_lock);
    return ENOSPC;
  }

  return 0;
}

/* Change current read/write offset to OFFSet, according to WHENCE.
   The new offset is returned in *NEW_OFFS.  It is not possible to
   seek at locations < 0 or >= CONTENTS_LEN.  Doing so will result
   in OFFset being silently adjusted to 0 or CONTENTS_LEN.  */
kern_return_t
trivfs_S_io_seek (struct trivfs_protid *cred,
		  mach_port_t reply, mach_msg_type_name_t reply_type,
		  off_t offs, int whence, off_t *new_offs)
{
  struct open *op;
  error_t err = 0;

  debuglog (DEBUG_TRACE,
	    "%s(offs=%ld, whence=%d)",
	    __FUNCTION__, offs, whence);

  if (! cred)
    return EOPNOTSUPP;

  op = cred->po->hook;

  pthread_mutex_lock (&op->lock);

  switch (whence)
    {
    case SEEK_SET:
      op->offs = offs; break;
    case SEEK_CUR:
      op->offs += offs; break;
    case SEEK_END:
      op->offs = contents_len - 1; break;  /* XXX: contents_len-1 ? */
    default:
      err = EINVAL;
    }

  /* XXX we must still check that 0 <= op->offs < contents_len */
  op->offs = (op->offs > contents_len) ? contents_len : op->offs;
  op->offs = (op->offs < 0) ? 0 : op->offs;

  if (! err)
    *new_offs = op->offs;

  pthread_mutex_unlock (&op->lock);

  return err;
}


/* If this variable is set, it is called every time a new peropen
   structure is created and initialized. */
error_t (*trivfs_peropen_create_hook)(struct trivfs_peropen *) = open_hook;

/* If this variable is set, it is called every time a peropen structure
   is about to be destroyed. */
void (*trivfs_peropen_destroy_hook) (struct trivfs_peropen *) = close_hook;


/* Options processing.  We accept the same options on the command line
   and from fsys_set_options.  */

static const struct argp_option options[] =
{
  {"size",	's', "STRING",	0, "Specify the max size of the virtual file"},
  {"debug",     'd', "STRING",  0, "Specify the debug level, outfile = .out"},
  {"write-back", 'w', "FILE",   0, "Writes back to FILE when going away"},
  {0}
};

static error_t
parse_opt (int opt, char *arg, struct argp_state *state)
{
  debuglog (DEBUG_TRACE, __FUNCTION__);

  switch (opt)
    {
    default:
      return ARGP_ERR_UNKNOWN;
    case ARGP_KEY_INIT:
    case ARGP_KEY_SUCCESS:
    case ARGP_KEY_ERROR:
      break;

    case 's':
      {
	size_t size = (size_t)strtoul (arg, NULL, 10);
	size = (size < 0) ? 0 : size;
	size = (size >= MAXSIZE) ? MAXSIZE : size;

	pthread_rwlock_wrlock (&contents_lock);

//	contents = (char *) mmap (0, size,
//				  PROT_READ | PROT_WRITE,
//				  MAP_ANON, 0, 0);

	contents = (char*) malloc (size);

	if ((contents == NULL) && size)
	  {
	    debuglog(DEBUG_TRACE, "can't allocate  %ul bytes.\n", size);
	    return ENOMEM;
	  }
	contents_len = size;
	bzero (contents, contents_len);

	pthread_rwlock_unlock (&contents_lock);
	break;
      }
    case 'd':
      {
	debugsetlevel ((int)strtoul (arg, NULL, 10));
	break;
      }
    case 'w':
      {
        output_file_name = (char*)malloc(sizeof(char)*strlen(arg)+1);
	memcpy (output_file_name, arg, strlen(arg)+1);
	break;
      }
    }
  return 0;
}

/* This will be called from libtrivfs to help construct the answer
   to an fsys_get_options RPC.  */
error_t
trivfs_append_args (struct trivfs_control *fsys,
		    char **argz, size_t *argz_len)
{
  error_t err;
  char *opt;

  debuglog (DEBUG_TRACE, __FUNCTION__);

  pthread_rwlock_rdlock (&contents_lock);
  err = asprintf (&opt, "--size=%zud", contents_len) < 0 ? ENOMEM : 0;
  pthread_rwlock_unlock (&contents_lock);

  if (!err)
    {
      err = argz_add (argz, argz_len, opt);
      free (opt);
    }

/*  err = asprintf (&opt, "--debug=%ud", debuglevel) < 0 ? ENOMEM : 0;
  if (!err)
    {
      err = argz_add (argz, argz_len, opt);
      free (opt);
    } */

  return err;
}

static struct argp mem_argp = { options, parse_opt, 0, 0 };

/* Setting this variable makes libtrivfs use our argp to
   parse options passed in an fsys_set_options RPC.  */
struct argp *trivfs_runtime_argp = &mem_argp;


int
main (int argc, char **argv)
{
  error_t err;
  mach_port_t bootstrap;
  struct trivfs_control *fsys;

  debuginit (argc, argv);
  debuglog (DEBUG_TRACE, "main(): memfs started");

  /* Initialize the lock that will protect CONTENTS and CONTENTS_LEN.
     We must do this before argp_parse, because parse_opt (above) will
     use the lock.  */
  pthread_rwlock_init (&contents_lock, NULL);

  /* We use the same argp for options available at startup
     as for options we'll accept in an fsys_set_options RPC.  */
  argp_parse (&mem_argp, argc, argv, 0, 0, 0);

  task_get_bootstrap_port (mach_task_self (), &bootstrap);
  if (bootstrap == MACH_PORT_NULL)
    error (1, 0, "Must be started as a translator");

  /* Reply to our parent */
  err = trivfs_startup (bootstrap, 0, 0, 0, 0, 0, &fsys);
  mach_port_deallocate (mach_task_self (), bootstrap);
  if (err)
    error (3, err, "trivfs_startup");

  /* Launch. */
  ports_manage_port_operations_multithread (fsys->pi.bucket, trivfs_demuxer,
					    10 * 1000, /* idle thread */
					    10 * 60 * 1000, /* idle server */
					    0);

  return 0;
}
