/* Start a translator
   Copyright (C) 1994 Free Software Foundation

   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., 675 Mass Ave, Cambridge, MA 02139, USA. */

#include "trans.h"
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

/* Try and create an auth port for the specified UID and GID.  If we
   can't create one or the other, still try to do just one.  If we can't do 
   either, then get a null auth port. */
auth_t
make_auth (uid_t uid, gid_t gid)
{
  uid_t auxuids[2], auxgids[2];
  auth_t ourauth, ret;
  error_t err;

  auxuids[0] = auxuids[1] = uid;
  auxgids[0] = auxgids[1] = gid;

  ourauth = getauth ();
  if (ourauth == MACH_PORT_NULL)
    perror ("getauth");
  assert (ourauth != MACH_PORT_NULL);

  err = auth_makeauth (ourauth, 0, MACH_MSG_TYPE_COPY_SEND, 0,
		       &uid, 1, auxuids, 2, &gid, 1, auxgids, 2, &ret);
  if (!err)
    goto out;
  
  err = auth_makeauth (ourauth, 0, MACH_MSG_TYPE_COPY_SEND, 0,
		       &uid, 1, auxuids, 2, 0, 0, 0, 0, &ret);
  if (!err)
    goto out;
  
  err = auth_makeauth (ourauth, 0, MACH_MSG_TYPE_COPY_SEND, 0,
		       0, 0, 0, 0, &gid, 1, auxgids, 2, &ret);
  if (!err)
    goto out;
  
  err = auth_makeauth (ourauth, 0, MACH_MSG_TYPE_COPY_SEND, 0,
		       0, 0, 0, 0, 0, 0, 0, 0, &ret);
  if (err)
    printf ("auth_makeauth (%u): %s\n", ourauth, strerror (err)); /* XXX */
  assert_perror (err);

 out:
  mach_port_deallocate (mach_task_self (), ourauth);
  return ret;
}


/* Implement fshelp_start_translator; see <hurd/fshelp.h> for
   the description of the arguments.  */
error_t
fshelp_start_translator (struct trans_link *link, char *name, int namelen,
			 file_t dir, file_t realnode, uid_t uid, gid_t gid)
{
  error_t err;
  auth_t transauth;
  file_t our_crdir;
  file_t crdir, cwdir;
  struct transboot *tb;
  mach_port_t init_ports[INIT_PORT_MAX];
  int init_ints[INIT_INT_MAX];
  task_t newt;
  file_t execfile;
  mach_port_t right;
  process_t childproc;
  int i;
  mach_port_t foo;
  mach_port_t ref;

  mutex_lock (&link->lock);

 retry:
  if (link->control != MACH_PORT_NULL)
    {
      mutex_unlock (&link->lock);
      return 0;
    }
  
  if (link->error)
    {
      err = link->error;
      link->error = 0;
      mutex_unlock (&link->lock);
      return err;
    }

  if (link->starting)
    {
      condition_wait (&link->initwait, &link->lock);
      goto retry;
    }
  
  /* Get the translator's auth port.  */
  transauth = make_auth (uid, gid);
  
  /* Get a root port for the translator.  */
  our_crdir = getcrdir ();
  ref = mach_reply_port ();
  err = io_reauthenticate (our_crdir, ref, MACH_MSG_TYPE_MAKE_SEND);
  assert_perror (err);
  err = auth_user_authenticate (transauth, our_crdir, ref,
				MACH_MSG_TYPE_MAKE_SEND, &crdir);
  assert_perror (err);
  mach_port_deallocate (mach_task_self (), our_crdir);
  mach_port_destroy (mach_task_self (), ref);

  /* Get a cwdir port for the translator.  */
  ref = mach_reply_port ();
  io_reauthenticate (dir, ref, MACH_MSG_TYPE_MAKE_SEND);
  err = auth_user_authenticate (transauth, dir, ref, MACH_MSG_TYPE_MAKE_SEND,
				&cwdir);
  assert_perror (err);
  mach_port_destroy (mach_task_self (), ref);

  assert (!err);

  /* Find the translator itself.  */
  err = hurd_file_name_lookup (crdir, cwdir, name, O_EXEC, 0, &execfile);
  if (err)
    {
      /* Drop it all.  Sigh. */
      mutex_unlock (&link->lock);
      mach_port_deallocate (mach_task_self (), crdir);
      mach_port_deallocate (mach_task_self (), cwdir);
      mach_port_deallocate (mach_task_self (), realnode);
      mach_port_deallocate (mach_task_self (), transauth);
      return err;
    }
  
  /* Allocate the structure for the translator's bootstrap port */
  tb = ports_allocate_port (sizeof (struct transboot), 
			    fshelp_transboot_port_type);
  
  tb->node = realnode;
  tb->link = link;
  
  /* Create the task for the translator */
  task_create (mach_task_self (), 0, &newt);

  /* Designate this as a child, and then authenticate it with its own
     auth handle. */
  __USEPORT (PROC, 
	     ({
	       proc_child (port, newt);
	       proc_task2proc (port, newt, &childproc);
	     }));

  ref = mach_reply_port ();
  proc_reauthenticate (childproc, ref, MACH_MSG_TYPE_MAKE_SEND);
  auth_user_authenticate (transauth, childproc, ref, MACH_MSG_TYPE_MAKE_SEND,
			  &foo);
  mach_port_destroy (mach_task_self (), ref);
  assert (foo == MACH_PORT_NULL);

  proc_setowner (childproc, uid);
  
  /* Set up the init ports. */
  for (i = 0; i < INIT_PORT_MAX; i++)
    init_ports[i] = MACH_PORT_NULL;

  right = ports_get_right (tb);
  init_ports[INIT_PORT_BOOTSTRAP] = right;
  mach_port_insert_right (mach_task_self (), right, right,
			  MACH_MSG_TYPE_MAKE_SEND);
  init_ports[INIT_PORT_CWDIR] = cwdir;
  init_ports[INIT_PORT_CRDIR] = crdir;
  init_ports[INIT_PORT_AUTH] = transauth;
  init_ports[INIT_PORT_PROC] = childproc;

  bzero (init_ints, INIT_INT_MAX * sizeof (int));
  
  err = file_exec (execfile, newt, EXEC_DEFAULTS, name, namelen,
		   0, 0, 0, MACH_MSG_TYPE_COPY_SEND, 0,
		   init_ports, MACH_MSG_TYPE_COPY_SEND, INIT_PORT_MAX,
		   init_ints, INIT_INT_MAX, 0, 0, 0, 0);

  /* Deallocate values we aren't saving in TB. */
  mach_port_deallocate (mach_task_self (), crdir);
  mach_port_deallocate (mach_task_self (), transauth);
  mach_port_deallocate (mach_task_self (), right);
  if (err)
    {
      mutex_unlock (&link->lock);
      return err;
    }

  link->starting = 1;
  
  condition_wait (&link->initwait, &link->lock);
  goto retry;
}
