/* Support for filesystems to do exec
   Copyright (C) 1991 Free Software Foundation

This file is part of the GNU Hurd.

The GNU Hurd 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 1, or (at your option)
any later version.

The GNU Hurd 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 the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Michael I. Bushnell.  */

#include <mach.h>
#include <gnu/errno.h>
#include <mach/error.h>
#include <gnu/posix_errors.h>
#include <hurd/hurd_types.h>
#include <mach/machine/vm_param.h>
#include <mach/mig_errors.h>
#include <lib/libhurd.h>

/* The routines in this file are used by filesystems to do execs.

   When a file_exec call happens, check appropriate permissions, and
   then call handle_exec with all the file_exec args, except that the
   first arg is a void pointer used the callback routines below.  The
   file should be locked against modification around the call to
   handle_exec.

   The filesystem needs to provide the following routines to this file:

error_t handle_exec_read (void *file, int offset, void *buffer, int len);
   Try to read the file at the specified offset into the buffer,
   reading len bytes.

error_t handle_exec_map (void *file, memory_object_t *obj)
   Return a memory object for the file.  If the filesystem does not
   support memory objects, return POSIX_EOPNOTSUPP.

error_t handle_exec_emulator (memory_object_t *obj)
   Return a memory object for the emulator.  This must be supported!
*/

#if 0
static int groupmember (gid_t test, struct idblock *cred)
{
  int i;
  for (i = 0; i < cred->ngroups; i++)
    if (test == cred->gidset[i])
      return 1;
  return 0;
}
#endif

error_t
handle_exec (void *file,
	     struct idblock *cred,
	     mode_t mode,
	     uid_t owner,
	     uid_t group,
	     unsigned size,
	     task_t task,
	     char *argv,
	     u_int argvlen,
	     char *envp,
	     u_int envplen,
	     int argc,
	     int envc,
	     mach_port_t *fds,
	     u_int fdslen,
	     mach_port_t cwdir,
	     mach_port_t crdir,
	     mach_port_t procserver,
	     mach_port_t authserver,
	     mach_port_t nameserver,
	     vm_address_t saveregion,
	     vm_size_t savesize,
	     mach_port_t *deallocnames,
	     u_int deallocnameslen,
	     int flags)
{
  error_t err;
  struct loader_info lp;
  memory_object_t fileobj;
  vm_address_t bss_start;
  vm_address_t bss_page_start;
  thread_t *threads;
  u_int nthreads;
  thread_t newthread;
  int outsave;

  err = exec_read_header (file, &lp);
  if (err)
    return err;
  
  if (lp.text_offset + lp.text_size > size
      || lp.data_offset + lp.data_size > size)
    return POSIX_ENOEXEC;
  
#if 0
  if (((mode & S_ISUID) && owner != cred->euid
       && owner != cred->ruid && owner != cred->svuid)
      || ((mode & S_ISGID) && group != cred->egid && group != cred->svgid
	  && !groupmember (group, cred)))
    {
      flags |= FS_EXEC_NEWTASK;
      /* Guarantee ports! XXX */
    }
#endif

  if (flags & (FS_EXEC_NEWTASK|FS_EXEC_NEWEMUL))
    return POSIX_EOPNOTSUPP;
  
  /* At this point, we know the exec can succeed.  Toss the old image. */
  task_suspend (task);

  if (flags & FS_EXEC_SAVE)
    {
      vm_deallocate (task, VM_MIN_ADDRESS, saveregion);
      vm_deallocate (task, saveregion + savesize,
		     VM_MAX_ADDRESS - saveregion - savesize);
    }
  else
    vm_deallocate (task, VM_MIN_ADDRESS, VM_MAX_ADDRESS);
  
  handle_exec_map (file, &fileobj);
  
  /* Map text region */
  if (lp.text_size)
    vm_map (task, &lp.text_start, lp.text_size, 0, 0, fileobj, lp.text_offset, 
	    1, VM_PROT_READ|VM_PROT_EXECUTE,
	    VM_PROT_READ|VM_PROT_EXECUTE|VM_PROT_WRITE, VM_INHERIT_COPY);

  /* Map data region */
  if (lp.data_size)
    vm_map (task, &lp.data_start, lp.data_size, 0, 0, fileobj, lp.data_offset,
	    1, VM_PROT_READ|VM_PROT_EXECUTE|VM_PROT_WRITE, 
	    VM_PROT_READ|VM_PROT_EXECUTE|VM_PROT_WRITE, VM_INHERIT_COPY);
  bss_start = lp.data_size + lp.data_start;
  bss_page_start = round_page (bss_start);
  if (bss_page_start > bss_start)
    {
      vm_offset_t page_start;
      vm_offset_t zero_start;
      vm_size_t size;
      char *local_page;

      page_start = trunc_page (bss_start);
      zero_start = bss_start - page_start;
      size = vm_page_size;
      
      vm_read (task, page_start, vm_page_size, &local_page, &size);
      bzero (local_page + zero_start, size - zero_start);
      vm_write (task, page_start, local_page, size);
      vm_deallocate (mach_task_self (), local_page, size);
    }
  if (trunc_page (lp.bss_size))
    vm_allocate (task, &bss_page_start, trunc_page (lp.bss_size), 0);
  
  /* Set args and stack */
  exec_setargs (task, argv, argvlen, envp, envplen, argc, envc, &outsave);
  
  /* Dunk all the old threads */
  task_threads (task, &threads, &nthreads);
  while (--nthreads)
    {
      thread_terminate (threads[nthreads]);
      mach_port_deallocate (threads[nthreads]);
    }
  vm_deallocate (mach_task_self (), threads, nthreads * sizeof (thread_t));
  
  /* Create the new one. */
  thread_create (task, &newthread);
  exec_setregs (newthread, lp.entry_1, lp.entry_2, outsave);
  
  thread_resume (newthread);
  task_resume (task);
  return MIG_NO_REPLY;
}
