/* run.c - A simple single-file translator
   Copyright (C) 2000  Marcus Brinkmann <brinkmd@debian.org>.

   Most of this program is directly taken from the Hurd
   hello example. Some tweaks for the argument parsing are taken
   from the Hurd hostmux translator. Those parts are
   Copyright (C) 1998, 1999 Free Software Foundation, Inc.

   and written by:
   Thomas Bushnell BSG, Roland McGrath, Miles Bader and
   Gordon Matzigkeit <gord@fig.org>, 1999
   
   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 */

#define _GNU_SOURCE 1

#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 <unistd.h>

/*
 #include <sys/stat.h>
 #include <sys/wait.h>
*/

/* Global Variables */

/* ARGZ */
static char *cmd = 0;
static size_t cmd_len;
/* ARGV */
static char **cmdp = 0;

/* A hook for us to keep track of the file descriptor state. */
struct open
{
  int fd;      /* The file descriptor of the pipe to the child,
                  or -1 if not yet opened or -2 if open failed. */
  pid_t child; /* The pid of the child process */
};

/* start_child: Return -1 on error, else return 0 
   and fill the arguments with a filedescriptor and a child pid. */

int start_child (int *fd, pid_t *child)
{
  int p[2];

  if (pipe(p))
    return -1;

  if (!(*child = fork()))
    {
      /* Translators are started without a terminal, so the first
         invocation of pipe() returns 0 and 1 as file descriptors. */
      if (p[1] != 1)
        {
          dup2 (p[1],1);
          close (p[1]);
        }
      close (p[0]);
      execvp (cmdp[0], cmdp);
      exit (-1); /* Only reached if execvp fails. */
    }
  if (*child == -1)
    {
      close (p[0]);
      close (p[1]);
      return -1;
    }
  close (p[1]);
  *fd = p[0];
  return 0;
}

int check_child(struct open *op)
{
  if (op->fd == -1)
    {
      /* Initialize the file descriptor and pid. */
      if (start_child(&op->fd, &op->child)) {
        op->fd = -2;
      }
    }
  if (op->fd == -2) 
    return -1;
  return 0;

  /* What should we do if  child dies abnormally?
     Have an option so we can flush an error message. */
  /*  int status;
  pid_t r;
  
  while ((r = waitpid (child, &status, WNOHANG)) == -1 && errno == EINTR);
  if (r == 0)
    return 0;
  if (r != child) {
    close (fd);
    return -1;
  }
  if (! ( (WIFEXITED(status) && !WEXITSTATUS(status))
          || (WIFSIGNALED(status) && !WTERMSIG(status))))
    {
      close (fd);
      return -1;
    }
  */
}
    

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

int trivfs_allow_open = O_READ;

int trivfs_support_read = 1;
int trivfs_support_write = 0;
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/run' so that you can see the
   error messages when they appear. */

void
trivfs_modify_stat (struct trivfs_protid *cred, struct stat *st)
{
  /*  struct open *op = cred->po->hook;
      struct stat buf;
      fstat (op->fd, &buf); */

  /* Mark the node as a read-only pipe. */
  st->st_mode &= ~(S_IFMT | ALLPERMS);
  st->st_mode |= (S_IFIFO | S_IRUSR | S_IRGRP | S_IROTH);
  st->st_size = 0 /* buf.st_size */;
}

error_t
trivfs_goaway (struct trivfs_control *cntl, int flags)
{
  exit (0);
}


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

  if (op == NULL)
    return ENOMEM;

  op->fd = -1;    /* -1: We have not tried to open it yet. */
  op->child = -1; /* Doesn't really matter. */

  peropen->hook = op;
  return 0;
}


static void
close_hook (struct trivfs_peropen *peropen)
{
  struct open *op = peropen->hook;
  if (op->fd >= 0)
    close(op->fd);
  free (peropen->hook);
}


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

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

  /* Get the open hook. */
  op = cred->po->hook;
  check_child (op);

  /* Check if the pipe is really open. */
  if (op->fd < 0)
    {
      *data_len = 0;
      return 0;
    }

  /* Offset is not supported and ignored. */

  if (amount > 0)
    {
      /* Possibly allocate a new buffer. */
      if (*data_len < amount)
        *data = (data_t) mmap (0, amount, PROT_READ|PROT_WRITE,
                               MAP_ANON, 0, 0);

      /* Copy the constant data into the buffer. */
      amount = read (op->fd, *data, amount);
      if (amount == -1)
        return errno;
    }
  
  *data_len = amount;
  return 0;
}


/* Change current read/write offset */
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)
{
  if (! cred)
    return EOPNOTSUPP;

  return ESPIPE;
}


/* 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[] =
{
  {0}
};

const char *argp_program_version = "run 0.1";
static const char args_doc[] = "COMMAND [ARG...]";

static const char doc[] =
  "A translator for invoking a command"
  "\vThis translator appears like a file which content is the output"
  " of a program.";

static error_t
parse_opt (int opt, char *arg, struct argp_state *state)
{
  switch (opt)
    {
    default:
      return ARGP_ERR_UNKNOWN;
    case ARGP_KEY_INIT:
    case ARGP_KEY_SUCCESS:
    case ARGP_KEY_ERROR:
      break;

    case ARGP_KEY_NO_ARGS:
      argp_usage(state);
      return EINVAL;

    case ARGP_KEY_ARGS:
      /* Steal the entire tail of arg vector for our own use.  */
      if (cmd)
        free(cmd);
      if (cmdp)
        free(cmdp);
      cmd = 0;
      cmdp = 0;
      if (argz_create (state->argv + state->next,
                       &cmd, &cmd_len))
        return ENOMEM;
      if (! (cmdp = (char **) malloc (sizeof(char *) * argz_count(cmd, cmd_len))))
        return ENOMEM;
      argz_extract(cmd, cmd_len, cmdp);
      return 0;

    }
  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)
{
  return argz_append (argz, argz_len, cmd, cmd_len);
}

static struct argp run_argp = { options, parse_opt, args_doc, doc };

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


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

  /* We use the same argp for options available at startup
     as for options we'll accept in an fsys_set_options RPC.  */
  argp_parse (&run_argp, argc, argv, ARGP_IN_ORDER, 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_one_thread (fsys->pi.bucket, trivfs_demuxer, 0);

  return 0;
}

