/* Spin off a separate task for a program inserted into this one with makeboot.
   Copyright (C) 1992 Free Software Foundation, Inc.
   Written by Roland McGrath.

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 2, 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.  */

#include <mach.h>

extern char etext, edata, end;

/* Machine-dependent function which makes THREAD start running at ENTRY_POINT.
   This function is also responsible for allocating the stack.
   Defined in exec_machdep.c.  */
extern error_t start_thread (task_t task, thread_t thread,
			     vm_address_t entry_point);

/* The calling program was built with makeboot.  After its data segment is
   the load image of another program.  This function creates a new task and
   starts that program running in it.  The load image is then deallocated
   from the calling task.  The contained program's symbol table is lost.
   BOOTPORT is a send right to be the new task's bootstrap port.  */

error_t
boot_load_task (mach_port_t bootport)
{
  const struct boot_info *const bi = (void *) &edata;
  char *const boot_image = (char *) round_page (&end) + bi->sym_size;
  const struct loader_info *const li = (void *) (boot_image + bi->boot_size);

  task_t task;
  thread_t thread;
  vm_address_t text, data;

  error_t err;

  if (err = task_create (mach_task_self (), 0, &task))
    return err;

  /* XXX Lose if the text or data is not page-aligned,
         or if the text size is not a multiple of page size.  */

  text = trunc_page (li->text_start);
  if ((err = vm_allocate (task, &text, li->text_size, 0)) ||
      (err = vm_write (task, text, li->text_offset, li->text_size)) ||
      (err = vm_protect (task, text, li->text_size,
			 0, VM_PROT_READ|VM_PROT_EXECUTE)))
    return err;

  data = trunc_page (li->data_start);
  if ((err = vm_allocate (task, &data, li->data_size + li->bss_size, 0)) ||
      (err = vm_write (task, data,
		       li->data_offset, round_page (li->data_size))))
    return err;
  if (li->data_size % vm_page_size != 0)
    {
      /* Allocate a page, and copy the last partial page of data into it.
	 The rest is already filled with zeros.
	 Then write this page into the new task.  */

      vm_size_t extra = li->data_size % vm_page_size;
      vm_address_t page;
      vm_allocate (mach_task_self (), &page, vm_page_size, 1);
      memcpy (page, li->data_offset + li->data_size - extra, extra);
      if (err = vm_write (task, data + li->data_size - extra,
			  page, vm_page_size))
	return err;
      vm_deallocate (mach_task_self (), page, vm_page_size);
    }

  if (err = task_set_special_port (task, TASK_BOOTSTRAP_PORT, bootport))
    return err;

  if (err = thread_create (task, &thread))
    return err;

  if (err = start_thread (task, thread, li->entry_1))
    return err;

  vm_deallocate (mach_task_self (),
		 boot_image, bi->boot_size + bi->load_info_size);

  return KERN_SUCCESS;
}
