summary refs log tree commit diff
path: root/sysdeps/mach/hurd/fork.c
diff options
context:
space:
mode:
authorRoland McGrath <roland@gnu.org>1995-02-18 01:27:10 +0000
committerRoland McGrath <roland@gnu.org>1995-02-18 01:27:10 +0000
commit28f540f45bbacd939bfd07f213bcad2bf730b1bf (patch)
tree15f07c4c43d635959c6afee96bde71fb1b3614ee /sysdeps/mach/hurd/fork.c
downloadglibc-28f540f45bbacd939bfd07f213bcad2bf730b1bf.tar.gz
glibc-28f540f45bbacd939bfd07f213bcad2bf730b1bf.tar.xz
glibc-28f540f45bbacd939bfd07f213bcad2bf730b1bf.zip
initial import
Diffstat (limited to 'sysdeps/mach/hurd/fork.c')
-rw-r--r--sysdeps/mach/hurd/fork.c574
1 files changed, 574 insertions, 0 deletions
diff --git a/sysdeps/mach/hurd/fork.c b/sysdeps/mach/hurd/fork.c
new file mode 100644
index 0000000000..b9170f155e
--- /dev/null
+++ b/sysdeps/mach/hurd/fork.c
@@ -0,0 +1,574 @@
+/* Copyright (C) 1994, 1995 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB.  If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA.  */
+
+#include <errno.h>
+#include <unistd.h>
+#include <hurd.h>
+#include <hurd/signal.h>
+#include <setjmp.h>
+#include "thread_state.h"
+#include <sysdep.h>		/* For stack growth direction.  */
+#include "set-hooks.h"
+#include <assert.h>
+#include "hurdmalloc.h"		/* XXX */
+
+extern void _hurd_longjmp_thread_state (struct machine_thread_state *,
+					jmp_buf env, int value);
+
+
+/* Things that want to be locked while forking.  */
+struct
+  {
+    size_t n;
+    struct mutex *locks[0];
+  } _hurd_fork_locks;
+
+
+/* Things that want to be called before we fork, to prepare the parent for
+   task_create, when the new child task will inherit our address space.  */
+DEFINE_HOOK (_hurd_fork_prepare_hook, (void));
+
+/* Things that want to be called when we are forking, with the above all
+   locked.  They are passed the task port of the child.  The child process
+   is all set up except for doing proc_child, and has no threads yet.  */
+DEFINE_HOOK (_hurd_fork_setup_hook, (void));
+
+/* Things to be run in the child fork.  */
+DEFINE_HOOK (_hurd_fork_child_hook, (void));
+
+/* Things to be run in the parent fork.  */
+DEFINE_HOOK (_hurd_fork_parent_hook, (void));
+
+
+/* Clone the calling process, creating an exact copy.
+   Return -1 for errors, 0 to the new process,
+   and the process ID of the new process to the old process.  */
+pid_t
+__fork (void)
+{
+  jmp_buf env;
+  pid_t pid;
+  size_t i;
+  error_t err;
+  thread_t thread_self = __mach_thread_self ();
+  struct hurd_sigstate *volatile ss;
+  sigset_t pending;
+
+  void unlockss (void)
+    {
+      __spin_lock (&ss->lock);
+      ss->critical_section = 0;
+      pending = ss->pending & ~ss->blocked;
+      __spin_unlock (&ss->lock);
+      /* XXX Copying mutex into child and calling mutex_unlock lossy.  */
+      __mutex_unlock (&_hurd_siglock);
+      ss = NULL;		/* Make sure we crash if we use it again.  */
+    }
+
+  ss = _hurd_self_sigstate ();
+  __spin_lock (&ss->lock);
+  ss->critical_section = 1;
+  __spin_unlock (&ss->lock);
+  __mutex_lock (&_hurd_siglock);
+
+  if (! setjmp (env))
+    {
+      process_t newproc;
+      task_t newtask;
+      thread_t thread, sigthread;
+      mach_port_urefs_t thread_refs, sigthread_refs;
+      struct machine_thread_state state;
+      mach_msg_type_number_t statecount;
+      mach_port_t *portnames = NULL;
+      mach_msg_type_number_t nportnames = 0;
+      mach_port_type_t *porttypes = NULL;
+      mach_msg_type_number_t nporttypes = 0;
+      thread_t *threads = NULL;
+      mach_msg_type_number_t nthreads = 0;
+      int ports_locked = 0;
+
+      /* Run things that prepare for forking before we create the task.  */
+      RUN_HOOK (_hurd_fork_prepare_hook, ());
+
+      /* Lock things that want to be locked before we fork.  */
+      for (i = 0; i < _hurd_fork_locks.n; ++i)
+	__mutex_lock (_hurd_fork_locks.locks[i]);
+      
+      newtask = MACH_PORT_NULL;
+      thread = sigthread = MACH_PORT_NULL;
+      newproc = MACH_PORT_NULL;
+
+      /* Lock all the port cells for the standard ports while we copy the
+	 address space.  We want to insert all the send rights into the
+	 child with the same names.  */
+      for (i = 0; i < _hurd_nports; ++i)
+	__spin_lock (&_hurd_ports[i].lock);
+      ports_locked = 1;
+
+      /* Create the child task.  It will inherit a copy of our memory.  */
+      if (err = __task_create (__mach_task_self (), 1, &newtask))
+	goto lose;
+
+      /* Fetch the names of all ports used in this task.  */
+      if (err = __mach_port_names (__mach_task_self (),
+				   &portnames, &nportnames,
+				   &porttypes, &nporttypes))
+	goto lose;
+      if (nportnames != nporttypes)
+	{
+	  err = EGRATUITOUS;
+	  goto lose;
+	}
+
+      /* Get send rights for all the threads in this task.
+	 We want to avoid giving these rights to the child.  */
+      if (err = __task_threads (__mach_task_self (), &threads, &nthreads))
+	goto lose;
+
+      /* Get the child process's proc server port.  We will insert it into
+	 the child with the same name as we use for our own proc server
+	 port; and we will need it to set the child's message port.  */
+      if (err = __proc_task2proc (_hurd_ports[INIT_PORT_PROC].port,
+				  newtask, &newproc))
+	goto lose;
+
+      /* Insert all our port rights into the child task.  */
+      thread_refs = sigthread_refs = 0;
+      for (i = 0; i < nportnames; ++i)
+	{
+	  if (porttypes[i] & MACH_PORT_TYPE_RECEIVE)
+	    {
+	      /* This is a receive right.  We want to give the child task
+		 its own new receive right under the same name.  */
+	      err = __mach_port_allocate_name (newtask,
+					       MACH_PORT_RIGHT_RECEIVE,
+					       portnames[i]);
+	      if (err == KERN_NAME_EXISTS)
+		{
+		  /* It already has a right under this name (?!).  Well,
+		     there is this bizarre old Mach IPC feature (in #ifdef
+		     MACH_IPC_COMPAT in the ukernel) which results in new
+		     tasks getting a new receive right for task special
+		     port number 2.  What else might be going on I'm not
+		     sure.  So let's check.  */
+#if !MACH_IPC_COMPAT
+#define TASK_NOTIFY_PORT 2
+#endif
+		  assert (({ mach_port_t thisport, notify_port;
+			     mach_msg_type_name_t poly;
+			     (__task_get_special_port (newtask,
+						       TASK_NOTIFY_PORT,
+						       &notify_port) == 0 &&
+			      __mach_port_extract_right 
+			      (newtask,
+			       portnames[i],
+			       MACH_MSG_TYPE_MAKE_SEND,
+			       &thisport, &poly) == 0 &&
+			      (thisport == notify_port) &&
+			      __mach_port_deallocate (__mach_task_self (),
+						      thisport) == 0 &&
+			      __mach_port_deallocate (__mach_task_self (),
+						      notify_port) == 0);
+			   }));
+		}
+	      else if (err)
+		goto lose;
+	      if (porttypes[i] & MACH_PORT_TYPE_SEND)
+		{
+		  /* Give the child as many send rights for its receive
+		     right as we have for ours.  */
+		  mach_port_urefs_t refs;
+		  mach_port_t port;
+		  mach_msg_type_name_t poly;
+		  if (err = __mach_port_get_refs (__mach_task_self (),
+						  portnames[i],
+						  MACH_PORT_RIGHT_SEND,
+						  &refs))
+		    goto lose;
+		  if (err = __mach_port_extract_right (newtask,
+						       portnames[i],
+						       MACH_MSG_TYPE_MAKE_SEND,
+						       &port, &poly))
+		    goto lose;
+		  if (portnames[i] == _hurd_msgport)
+		    {
+		      /* We just created a receive right for the child's
+			 message port and are about to insert send rights
+			 for it.  Now, while we happen to have a send right
+			 for it, give it to the proc server.  */
+		      mach_port_t old;
+		      if (err = __proc_setmsgport (newproc, port, &old))
+			goto lose;
+		      if (old != MACH_PORT_NULL)
+			/* XXX what to do here? */
+			__mach_port_deallocate (__mach_task_self (), old);
+		    }
+		  if (err = __mach_port_insert_right (newtask,
+						      portnames[i],
+						      port,
+						      MACH_MSG_TYPE_MOVE_SEND))
+		    goto lose;
+		  if (refs > 1 &&
+		      (err = __mach_port_mod_refs (newtask,
+						   portnames[i],
+						   MACH_PORT_RIGHT_SEND,
+						   refs - 1)))
+		    goto lose;
+		}
+	      if (porttypes[i] & MACH_PORT_TYPE_SEND_ONCE)
+		{
+		  /* Give the child a send-once right for its receive right,
+		     since we have one for ours.  */
+		  mach_port_t port;
+		  mach_msg_type_name_t poly;
+		  if (err = __mach_port_extract_right
+		      (newtask,
+		       portnames[i],
+		       MACH_MSG_TYPE_MAKE_SEND_ONCE,
+		       &port, &poly))
+		    goto lose;
+		  if (err = __mach_port_insert_right
+		      (newtask,
+		       portnames[i], port,
+		       MACH_MSG_TYPE_MOVE_SEND_ONCE))
+		    goto lose;
+		}
+	    }
+	  else if (porttypes[i] & MACH_PORT_TYPE_SEND)
+	    {
+	      /* This is a send right or a dead name.
+		 Give the child as many references for it as we have.  */
+	      mach_port_urefs_t refs, *record_refs = NULL;
+	      mach_port_t insert;
+	      if (portnames[i] == newtask)
+		/* Skip the name we use for the child's task port.  */
+		continue;
+	      if (portnames[i] == __mach_task_self ())
+		/* For the name we use for our own task port,
+		   insert the child's task port instead.  */
+		insert = newtask;
+	      else if (portnames[i] == _hurd_ports[INIT_PORT_PROC].port)
+		{
+		  /* Get the proc server port for the new task.  */
+		  if (err = __proc_task2proc (portnames[i], newtask, &insert))
+		    goto lose;
+		}
+	      else if (portnames[i] == thread_self)
+		{
+		  /* For the name we use for our own thread port, we will
+		     insert the thread port for the child main user thread
+		     after we create it.  */
+		  insert = MACH_PORT_NULL;
+		  record_refs = &thread_refs;
+		  /* Allocate a dead name right for this name as a
+                     placeholder, so the kernel will not chose this name
+                     for any other new port (it might use it for one of the
+                     rights created when a thread is created).  */
+		  if (err = __mach_port_allocate_name
+		      (newtask, MACH_PORT_RIGHT_DEAD_NAME, portnames[i]))
+		    goto lose;
+		}
+	      else if (portnames[i] == _hurd_msgport_thread)
+		/* For the name we use for our signal thread's thread port,
+		   we will insert the thread port for the child's signal
+		   thread after we create it.  */
+		{
+		  insert = MACH_PORT_NULL;
+		  record_refs = &sigthread_refs;
+		  /* Allocate a dead name right as a placeholder.  */
+		  if (err = __mach_port_allocate_name
+		      (newtask, MACH_PORT_RIGHT_DEAD_NAME, portnames[i]))
+		    goto lose;
+		}
+	      else
+		{
+		  /* Skip the name we use for any of our own thread ports.  */
+		  mach_msg_type_number_t j;
+		  for (j = 0; j < nthreads; ++j)
+		    if (portnames[i] == threads[j])
+		      break;
+		  if (j < nthreads)
+		    continue;
+
+		  insert = portnames[i];
+		}
+	      /* Find out how many user references we have for
+		 the send right with this name.  */
+	      if (err = __mach_port_get_refs (__mach_task_self (),
+					      portnames[i],
+					      MACH_PORT_RIGHT_SEND,
+					      record_refs ?: &refs))
+		goto lose;
+	      if (insert == MACH_PORT_NULL)
+		continue;
+	      /* Insert the chosen send right into the child.  */
+	      err = __mach_port_insert_right (newtask,
+					      portnames[i],
+					      insert,
+					      MACH_MSG_TYPE_COPY_SEND);
+	      if (err == KERN_NAME_EXISTS)
+		{
+		  /* It already has a send right under this name (?!).
+		     Well, it starts out with a send right for its task
+		     port, and inherits the bootstrap and exception ports
+		     from us.  */
+		  mach_port_t childport;
+		  mach_msg_type_name_t poly;
+		  assert (__mach_port_extract_right (newtask, portnames[i],
+						     MACH_MSG_TYPE_COPY_SEND,
+						     &childport, &poly) == 0 &&
+			  childport == insert &&
+			  __mach_port_deallocate (__mach_task_self (),
+						  childport) == 0);
+		}
+	      else if (err)
+		goto lose;
+	      /* Give the child as many user references as we have.  */
+	      if (refs > 1 &&
+		  (err = __mach_port_mod_refs (newtask,
+					       portnames[i],
+					       MACH_PORT_RIGHT_SEND,
+					       refs - 1)))
+		goto lose;
+	    }
+	}
+
+      /* Unlock the standard port cells.  The child must unlock its own
+	 copies too.  */
+      for (i = 0; i < _hurd_nports; ++i)
+	__spin_unlock (&_hurd_ports[i].lock);
+      ports_locked = 0;
+
+      /* Unlock the signal state.  The child must unlock its own copy too.  */
+      unlockss ();
+
+      /* Create the child main user thread and signal thread.  */
+      if ((err = __thread_create (newtask, &thread)) ||
+	  (err = __thread_create (newtask, &sigthread)))
+	goto lose;
+
+      /* Insert send rights for those threads.  We previously allocated
+         dead name rights with the names we want to give the thread ports
+         in the child as placeholders.  Now deallocate them so we can use
+         the names.  */
+      if ((err = __mach_port_deallocate (newtask, thread_self)) ||
+	  (err = __mach_port_insert_right (newtask, thread_self,
+					   thread, MACH_MSG_TYPE_COPY_SEND)))
+	goto lose;
+      /* We have one extra user reference created at the beginning of this
+	 function, accounted for by mach_port_names (and which will thus be
+	 accounted for in the child below).  This extra right gets consumed
+	 in the child by the store into _hurd_sigthread in the child fork.  */
+      if (thread_refs > 1 &&
+	  (err = __mach_port_mod_refs (newtask, thread_self,
+				       MACH_PORT_RIGHT_SEND,
+				       thread_refs - 1)))
+	goto lose;
+      if ((_hurd_msgport_thread != MACH_PORT_NULL) /* Let user have none.  */
+	  && ((err = __mach_port_deallocate (newtask, _hurd_msgport_thread)) ||
+	      (err = __mach_port_insert_right (newtask, _hurd_msgport_thread,
+					       sigthread,
+					       MACH_MSG_TYPE_COPY_SEND))))
+	goto lose;
+      if (sigthread_refs > 1 &&
+	  (err = __mach_port_mod_refs (newtask, _hurd_msgport_thread,
+				       MACH_PORT_RIGHT_SEND,
+				       sigthread_refs - 1)))
+	goto lose;
+
+      /* This seems like a convenient juncture to copy the proc server's
+	 idea of what addresses our argv and envp are found at from the
+	 parent into the child.  Since we happen to know that the child
+	 shares our memory image, it is we who should do this copying.  */
+      {
+	vm_address_t argv, envp;
+	err = (__USEPORT (PROC, __proc_get_arg_locations (port, &argv, &envp))
+	       ?: __proc_set_arg_locations (newproc, argv, envp));
+	if (err)
+	  goto lose;
+      }
+	    
+      /* Set the child signal thread up to run the msgport server function
+	 using the same signal thread stack copied from our address space.
+	 We fetch the state before longjmp'ing it so that miscellaneous
+	 registers not affected by longjmp (such as i386 segment registers)
+	 are in their normal default state.  */
+      statecount = MACHINE_THREAD_STATE_COUNT;
+      if (err = __thread_get_state (_hurd_msgport_thread,
+				    MACHINE_THREAD_STATE_FLAVOR,
+				    (natural_t *) &state, &statecount))
+	goto lose;
+#if STACK_GROWTH_UP
+      state.SP = __hurd_sigthread_stack_base;
+#else
+      state.SP = __hurd_sigthread_stack_end;
+#endif      
+      MACHINE_THREAD_STATE_SET_PC (&state,
+				   (unsigned long int) _hurd_msgport_receive);
+      if (err = __thread_set_state (sigthread, MACHINE_THREAD_STATE_FLAVOR,
+				    (natural_t *) &state, statecount))
+	goto lose;
+      /* We do not thread_resume SIGTHREAD here because the child
+	 fork needs to do more setup before it can take signals.  */
+
+      /* Set the child user thread up to return 1 from the setjmp above.  */
+      _hurd_longjmp_thread_state (&state, env, 1);
+      if (err = __thread_set_state (thread, MACHINE_THREAD_STATE_FLAVOR,
+				    (natural_t *) &state, statecount))
+	goto lose;
+
+      /* Get the PID of the child from the proc server.  We must do this
+	 before calling proc_child below, because at that point any
+	 authorized POSIX.1 process may kill the child task with SIGKILL.  */
+      if (err = __USEPORT (PROC, __proc_task2pid (port, newtask, &pid)))
+	goto lose;
+
+      /* Register the child with the proc server.  It is important that
+	 this be that last thing we do before starting the child thread
+	 running.  Once proc_child has been done for the task, it appears
+	 as a POSIX.1 process.  Any errors we get must be detected before
+	 this point, and the child must have a message port so it responds
+	 to POSIX.1 signals.  */
+      if (err = __USEPORT (PROC, __proc_child (port, newtask)))
+	goto lose;
+
+      /* This must be the absolutely last thing we do; we can't assume that
+	 the child will remain alive for even a moment once we do this.  We
+	 ignore errors because we have committed to the fork and are not
+	 allowed to return them after the process becomes visible to
+	 POSIX.1 (which happened right above when we called proc_child).  */
+      (void) __thread_resume (thread);
+
+    lose:
+      if (ports_locked)
+	for (i = 0; i < _hurd_nports; ++i)
+	  __spin_unlock (&_hurd_ports[i].lock);
+
+      if (newtask != MACH_PORT_NULL)
+	{
+	  if (err)
+	    __task_terminate (newtask);
+	  __mach_port_deallocate (__mach_task_self (), newtask);
+	}
+      if (thread != MACH_PORT_NULL)
+	__mach_port_deallocate (__mach_task_self (), thread);
+      if (sigthread != MACH_PORT_NULL)
+	__mach_port_deallocate (__mach_task_self (), sigthread);
+      if (newproc != MACH_PORT_NULL)
+	__mach_port_deallocate (__mach_task_self (), newproc);
+      if (thread_self != MACH_PORT_NULL)
+	__mach_port_deallocate (__mach_task_self (), thread_self);
+
+      if (portnames)
+	__vm_deallocate (__mach_task_self (),
+			 (vm_address_t) portnames,
+			 nportnames * sizeof (*portnames));
+      if (porttypes)
+	__vm_deallocate (__mach_task_self (),
+			 (vm_address_t) porttypes,
+			 nporttypes * sizeof (*porttypes));
+      if (threads)
+	{
+	  for (i = 0; i < nthreads; ++i)
+	    __mach_port_deallocate (__mach_task_self (), threads[i]);
+	  __vm_deallocate (__mach_task_self (),
+			   (vm_address_t) threads,
+			   nthreads * sizeof (*threads));
+	}
+
+      /* Run things that want to run in the parent to restore it to
+	 normality.  Usually prepare hooks and parent hooks are
+	 symmetrical: the prepare hook arrests state in some way for the
+	 fork, and the parent hook restores the state for the parent to
+	 continue executing normally.  */
+      RUN_HOOK (_hurd_fork_parent_hook, ());
+    }
+  else
+    {
+      struct hurd_sigstate *oldstates;
+
+      /* We are the child task.  Unlock the standard port cells, which were
+         locked in the parent when we copied its memory.  The parent has
+         inserted send rights with the names that were in the cells then.  */
+      for (i = 0; i < _hurd_nports; ++i)
+	__spin_unlock (&_hurd_ports[i].lock);
+
+      /* We are the only thread in this new task, so we will
+	 take the task-global signals.  */
+      _hurd_sigthread = thread_self;
+
+      /* Unchain the sigstate structures for threads that existed in the
+	 parent task but don't exist in this task (the child process).
+	 Delay freeing them until later because some of the further setup
+	 and unlocking might be required for free to work.  */
+      oldstates = _hurd_sigstates;
+      if (oldstates == ss)
+	oldstates = ss->next;
+      else
+	{
+	  while (_hurd_sigstates->next != ss)
+	    _hurd_sigstates = _hurd_sigstates->next;
+	  _hurd_sigstates->next = ss->next;
+	}
+      ss->next = NULL;
+      _hurd_sigstates = ss;
+
+      /* Unlock our copies of the signal state locks.  */
+      unlockss ();
+
+      /* Fetch our new process IDs from the proc server.  No need to
+	 refetch our pgrp; it is always inherited from the parent (so
+	 _hurd_pgrp is already correct), and the proc server will send us a
+	 proc_newids notification when it changes.  */
+      err = __USEPORT (PROC, __proc_getpids (port, &_hurd_pid, &_hurd_ppid,
+					     &_hurd_orphaned));
+
+      /* Run things that want to run in the child task to set up.  */
+      RUN_HOOK (_hurd_fork_child_hook, ());
+
+      /* Set up proc server-assisted fault recovery for the signal thread.  */
+      _hurdsig_fault_init ();
+
+      /* Start the signal thread listening on the message port.  */
+      if (!err)
+	err = __thread_resume (_hurd_msgport_thread);
+
+      /* Free the old sigstate structures.  */
+      while (oldstates != NULL)
+	{
+	  struct hurd_sigstate *next = oldstates->next;
+	  free (oldstates);
+	  oldstates = next;
+	}
+      /* XXX what to do if we have any errors here? */
+
+      pid = 0;
+    }
+
+  /* Unlock things we locked before creating the child task.
+     They are locked in both the parent and child tasks.  */
+  for (i = 0; i < _hurd_fork_locks.n; ++i)
+    __mutex_unlock (_hurd_fork_locks.locks[i]);
+
+  if (pending)
+    __msg_sig_post (_hurd_msgport, 0, __mach_task_self ());
+
+  return err ? __hurd_fail (err) : pid;
+}
+
+weak_alias (__fork, fork)