/* Execution server for a.out format files
   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.  */

void
release_it (struct shared_io *cntl)
{
  spin_lock (&cntl->lock);
  if (cntl->it_status == USER_RELEASE_IT)
    {
      spin_unlock (&cntl->lock);
      io_release_it (file, cntlmap);
    }
  else
    {
      cntl->it_status = USER_POTENTIALLY_IT;
      spin_unlock (&cntl->lock);
    }
}

error_t
exec_loadtask (mach_port_t execserver,
	       task_t usertask,
	       file_t file,
	       vm_address_t deallocregion,
	       vm_size_t deallocsize,
	       vm_address_t *entry)
{
  void *firstpage;
  struct shared_io *cntl;
  memory_object_t filemap, cntlmap;
  struct loader_info lp;

  io_map (file, &filemap, 0, 0);
  io_map_cntl (file, &cntlmap, 0, 0);
  
  /* Check error?  */

  vm_map (mach_task_self (), &cntl, vm_page_size, vm_page_size - 1, 
	  0, cntlmap, 0, 0, VM_PROT_READ|VM_PROT_WRITE, 
	  VM_PROT_READ|VM_PROT_WRITE, VM_INHERIT_NONE);

  /* Become "it" for the file */
  spin_lock (&cntl->lock);
  for (;;)
    {
      switch (cntl->it_status)
	{
	case USER_POTENTIALLY_IT:
	  cntl->it_status = USER_IT;
	case USER_IT:
	  spin_unlock (&cntl->lock);
	  goto gotit;

	case USER_RELEASE_IT:
	  spin_unlock (&cntl->lock);
	  io_release_it (file, cntlmap);
	  io_get_it (file, cntlmap);
	  spin_lock (&cntl->lock);
	  break;
	  
	default:
	  /* Oh shit. */
	case USER_NOT_IT:
	  spin_unlock (&cntl->lock);
	  io_get_it (file, cntlmap);
	  spin_lock (&cntl->lock);
	  break;
	}
    }
 gotit:

  /* It might be nice to allow execs for !use_file_size && use_read_size. */

  if (cntl->use_file_size == 0 || cntl->file_size < vm_page_size)
    error = POSIX_ENOEXEC;
  else
    error = 0;

  if (!error)
    {
      vm_map (mach_task_self (), &firstpage, vm_page_size, vm_page_size - 1, 
	      0, filemap, 0, 0, VM_PROT_READ, VM_PROT_READ, VM_INHERIT_NONE);

      exec_read_header (firstpage, &lp);
  
      if (lp.text_size + lp.text_offset > cntl->file_size
	  || lp.data_size + lp.data_offset > cntl->file_size)
	{
	  error = POSIX_ENOEXEC;
	  vm_deallocate (mach_task_self (), firstpage, vm_page_size);
	}
    }
  if (error)
    {
      release_it (cntl);
      mach_port_deallocate (mach_task_self (), cntlmap);
      mach_port_deallocate (mach_task_self (), filemap);
      mach_port_deallocate (mach_task_self (), file);
      vm_deallocate (mach_task_self (), cntl, vm_page_size);
      return POSIX_ENOEXEC;
    }
  
  /* The exec can now proceed. */

  if (deallocsize)
    vm_deallocate (task, deallocregion, deallocsize);
  
  if (lp.text_size)
    vm_map (task, &lp.text_start, lp.text_size, 0, 0, filemap, lp.text_offset,
	    1, VM_PROT_READ|VM_PROT_EXECUTE, 
	    VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE, VM_INHERIT_COPY);
  if (lp.data_size)
    vm_map (task, &lp.data_start, lp.data_size, 0, 0, filemap, lp.data_offset,
	    1, VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE,
	    VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE, VM_INHERIT_COPY);

  /* Now we're done with the file, and we can free the lock. */
  release_it (cntl);
  mach_port_deallocate (mach_task_self (), cntlmap);
  mach_port_deallocate (mach_task_self (), filemap);
  mach_port_deallocate (mach_task_self (), file);
  vm_deallocate (mach_task_self (), cntl, vm_page_size);
 
  if (lp.bss_size)
    {
      vm_address_t bss_page_start;
      vm_size_t bss_page_len;
      vm_address_t bss_start;
      vm_address_t zero_page;
      vm_address_t ourpage;
      vm_size_t size;

      bss_start = lp.data_size + lp.data_start;
      bss_page_start = round_page (bss_start);
      bss_page_len = trunc_page (lp.bss_size);
      if (bss_page_len)
	vm_allocate (task, &bss_page_start, bss_page_len, 0);
      
      /* The overlapping page with the data segment needs to be zeroed. */
      if (bss_page_start > bss_start)
	{
	  zero_page = trunc_page (bss_start);
	  size = vm_page_size;
	  vm_read (task, zero_page, vm_page_size, &ourpage, &size);
	  bzero (ourpage + (bss_start - zero_page),
		 size - (bss_start - zero_page));
	  vm_write (task, zero_page, ourpage, size);
	  vm_deallocate (mach_task_self (), ourpage, size);
	}
    }
  mach_port_deallocate (mach_task_self (), task);
  *entry = lp.entry;
}

