[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

run (another sample translator)



Hi,

here is another sample translator for you to play with, if you are
curious how this translator thing works.

It has probably some security issues if not used carefully,
and it is not more robus than the hello example, so don't
expect production quality.

It accepts any command plus options as arguments, and provides a
stream of the commands output. For example:

$ settrans -c fortune /hurd/run /games/fortune -s
$ cat fortune
Logic doesn't apply to the real world.
		-- Marvin Minsky

It is possible to specify commands with a lot of output: The
output is read on demand (try for example "/hurd/run find /").

It is possible to specify only the command without full path,
it will search in the standard locations (as execvp does), however,
I don't know which PATH settings a translator sees, so be careful.

The command runs as the user who owns the underlying node, and
not as the user who reads the file!

You can use it for example to create random email signatures. Write a script
which generates a sig for you, and put it in ~/bin/gen_sig (or similar).
Then do:

$ settrans -c ~/.signature /hurd/run ~/bin/gen_sig

and it will work with every mail user agent that reads .signature, without
any special setup.
 
Compilation and Installation:
gcc -g -o run run.c -lshouldbeinlibc -lfshelp -lports -ltrivfs
cp run /hurd/run

Of course, you don't need to put it in /hurd/run as root. You can just
as well compile it as any user and put it in ~/hurd/run or whereever.
Just use the new path in the settrans command as well.

I did some last minute cleanup, so hopefully I didn't manage to add a buglet,
too. :)

No other options are accepted, btw. If you have ideas how this translator
could be extended, let me know. For example, I could provide an option
to merge stderr with stdout, so one sees error messages in the file.

Another idea will lead to a wholly different translator: Mapping the
commands with the options into the filesystem hierarchy like this:
$ cat /run/fortune
$ cat /run/fortune/-s

A prefix could be used to allow empty arguments as well:
$ cat /run/echo/EMPTY
$ cat /run/echo/EMPTYEMPTY
EMPTY

Well, that requires a bit more work, though, as this translator
would provide a directory. Enough crazy ideas for today.
Another related idea btw is a cgi-bin translator, so that cgi
is implemented outside of the browser. Pretty trivial variation
of the above, actually.

Thanks,
Marcus

/* 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 <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.  */
error_t
trivfs_S_io_read (struct trivfs_protid *cred,
		  mach_port_t reply, mach_msg_type_name_t reply_type,
		  vm_address_t *data, mach_msg_type_number_t *data_len,
		  off_t offs, mach_msg_type_number_t amount)
{
  struct open *op;
  int leftover = amount;

  /* 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 = (vm_address_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 */
error_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;
  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;
}

Reply to: