mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			516 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			516 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
// natPosixProcess.cc - Native side of POSIX process code.
 | 
						|
 | 
						|
/* Copyright (C) 1998, 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007
 | 
						|
  Free Software Foundation
 | 
						|
 | 
						|
   This file is part of libgcj.
 | 
						|
 | 
						|
This software is copyrighted work licensed under the terms of the
 | 
						|
Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
 | 
						|
details.  */
 | 
						|
 | 
						|
#include <config.h>
 | 
						|
 | 
						|
#ifdef HAVE_UNISTD_H
 | 
						|
#include <unistd.h>
 | 
						|
#endif
 | 
						|
#include <errno.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <sys/types.h>
 | 
						|
#include <sys/wait.h>
 | 
						|
#ifdef HAVE_SYS_TIME_H
 | 
						|
#include <sys/time.h>
 | 
						|
#endif
 | 
						|
#ifdef HAVE_SYS_RESOURCE_H
 | 
						|
#include <sys/resource.h>
 | 
						|
#endif
 | 
						|
#include <signal.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <pthread.h>
 | 
						|
 | 
						|
#include <posix.h>
 | 
						|
#include <posix-threads.h>
 | 
						|
#include <jvm.h>
 | 
						|
 | 
						|
#include <java/lang/PosixProcess$ProcessManager.h>
 | 
						|
#include <java/lang/PosixProcess.h>
 | 
						|
#include <java/lang/IllegalThreadStateException.h>
 | 
						|
#include <java/lang/InternalError.h>
 | 
						|
#include <java/lang/InterruptedException.h>
 | 
						|
#include <java/lang/NullPointerException.h>
 | 
						|
#include <java/lang/Thread.h>
 | 
						|
#include <java/io/File.h>
 | 
						|
#include <java/io/FileDescriptor.h>
 | 
						|
#include <gnu/java/nio/channels/FileChannelImpl.h>
 | 
						|
#include <java/io/FileInputStream.h>
 | 
						|
#include <java/io/FileOutputStream.h>
 | 
						|
#include <java/io/IOException.h>
 | 
						|
#include <java/lang/OutOfMemoryError.h>
 | 
						|
#include <java/lang/PosixProcess$EOFInputStream.h>
 | 
						|
 | 
						|
using gnu::java::nio::channels::FileChannelImpl;
 | 
						|
using namespace java::lang;
 | 
						|
 | 
						|
extern char **environ;
 | 
						|
 | 
						|
static char *
 | 
						|
new_string (jstring string)
 | 
						|
{
 | 
						|
  jsize s = _Jv_GetStringUTFLength (string);
 | 
						|
  char *buf = (char *) _Jv_Malloc (s + 1);
 | 
						|
  _Jv_GetStringUTFRegion (string, 0, string->length(), buf);
 | 
						|
  buf[s] = '\0';
 | 
						|
  return buf;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
cleanup (char **args, char **env, char *path)
 | 
						|
{
 | 
						|
  if (args != NULL)
 | 
						|
    {
 | 
						|
      for (int i = 0; args[i] != NULL; ++i)
 | 
						|
	_Jv_Free (args[i]);
 | 
						|
      _Jv_Free (args);
 | 
						|
    }
 | 
						|
  if (env != NULL)
 | 
						|
    {
 | 
						|
      for (int i = 0; env[i] != NULL; ++i)
 | 
						|
	_Jv_Free (env[i]);
 | 
						|
      _Jv_Free (env);
 | 
						|
    }
 | 
						|
  if (path != NULL)
 | 
						|
    _Jv_Free (path);
 | 
						|
}
 | 
						|
 | 
						|
// This makes our error handling a bit simpler and it lets us avoid
 | 
						|
// thread bugs where we close a possibly-reopened file descriptor for
 | 
						|
// a second time.
 | 
						|
static void
 | 
						|
myclose (int &fd)
 | 
						|
{
 | 
						|
  if (fd != -1)
 | 
						|
    close (fd);
 | 
						|
  fd = -1;
 | 
						|
}
 | 
						|
 | 
						|
namespace
 | 
						|
{
 | 
						|
  struct ProcessManagerInternal
 | 
						|
  {
 | 
						|
    int pipe_ends[2];
 | 
						|
    struct sigaction old_sigaction;
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// There has to be a signal handler in order to be able to
 | 
						|
// sigwait() on SIGCHLD.  The information passed is ignored as it
 | 
						|
// will be recovered by the waitpid() call.
 | 
						|
static void
 | 
						|
#ifdef SA_SIGINFO
 | 
						|
sigchld_handler (int sig, siginfo_t *si, void *third)
 | 
						|
#else
 | 
						|
sigchld_handler (int sig)
 | 
						|
#endif
 | 
						|
{
 | 
						|
  if (PosixProcess$ProcessManager::nativeData != NULL)
 | 
						|
    {
 | 
						|
      ProcessManagerInternal *pmi =
 | 
						|
        (ProcessManagerInternal *)PosixProcess$ProcessManager::nativeData;
 | 
						|
      char c = 0;
 | 
						|
      ::write(pmi->pipe_ends[1], &c, 1);
 | 
						|
      if (pmi->old_sigaction.sa_handler != SIG_DFL
 | 
						|
          && pmi->old_sigaction.sa_handler != SIG_IGN)
 | 
						|
        {
 | 
						|
#ifdef SA_SIGINFO
 | 
						|
          if ((pmi->old_sigaction.sa_flags & SA_SIGINFO) != 0)
 | 
						|
            pmi->old_sigaction.sa_sigaction(sig, si, third);
 | 
						|
          else
 | 
						|
#endif
 | 
						|
            (*pmi->old_sigaction.sa_handler)(sig);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// Get ready to enter the main reaper thread loop.
 | 
						|
void
 | 
						|
java::lang::PosixProcess$ProcessManager::init ()
 | 
						|
{
 | 
						|
  // The nativeData is static to avoid races installing the signal
 | 
						|
  // handler in the case that it is chained.
 | 
						|
  if (nativeData == NULL )
 | 
						|
    {
 | 
						|
      ProcessManagerInternal *pmi =
 | 
						|
        (ProcessManagerInternal *)JvAllocBytes(sizeof(ProcessManagerInternal));
 | 
						|
 | 
						|
      if (0 != ::pipe(pmi->pipe_ends))
 | 
						|
        goto error;
 | 
						|
 | 
						|
      // Make writing non-blocking so that the signal handler will
 | 
						|
      // never block.
 | 
						|
      int fl = ::fcntl(pmi->pipe_ends[1], F_GETFL);
 | 
						|
      ::fcntl(pmi->pipe_ends[1], F_SETFL, fl | O_NONBLOCK);
 | 
						|
 | 
						|
      nativeData = (::gnu::gcj::RawDataManaged *)pmi;
 | 
						|
 | 
						|
      // SIGCHLD is blocked in all threads in posix-threads.cc.
 | 
						|
      // Setup the SIGCHLD handler.
 | 
						|
      struct sigaction sa;
 | 
						|
      memset (&sa, 0, sizeof (sa));
 | 
						|
 | 
						|
#ifdef SA_SIGINFO
 | 
						|
      sa.sa_sigaction = sigchld_handler;
 | 
						|
      // We only want signals when the things exit.
 | 
						|
      sa.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
 | 
						|
#else
 | 
						|
      sa.sa_handler = sigchld_handler;
 | 
						|
      // We only want signals when the things exit.
 | 
						|
      sa.sa_flags = SA_NOCLDSTOP;
 | 
						|
#endif
 | 
						|
 | 
						|
      if (-1 == sigaction (SIGCHLD, &sa, &pmi->old_sigaction))
 | 
						|
        goto error;
 | 
						|
    }
 | 
						|
  // All OK.
 | 
						|
  return;
 | 
						|
 | 
						|
error:
 | 
						|
  throw new InternalError (JvNewStringUTF (strerror (errno)));
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
java::lang::PosixProcess$ProcessManager::waitForSignal ()
 | 
						|
{
 | 
						|
  // Wait for SIGCHLD
 | 
						|
  _Jv_UnBlockSigchld();
 | 
						|
  ProcessManagerInternal *pmi = (ProcessManagerInternal *)nativeData;
 | 
						|
 | 
						|
  // Try to read multiple (64) notifications in one go.
 | 
						|
  char c[64];
 | 
						|
 ::read(pmi->pipe_ends[0], c, sizeof (c));
 | 
						|
 | 
						|
  _Jv_BlockSigchld();
 | 
						|
 | 
						|
  return;
 | 
						|
}
 | 
						|
 | 
						|
jboolean java::lang::PosixProcess$ProcessManager::reap (PosixProcess *p)
 | 
						|
{
 | 
						|
  pid_t rv;
 | 
						|
 | 
						|
  // Try to get the return code from the child process.
 | 
						|
  int status;
 | 
						|
  rv = ::waitpid ((pid_t)p->pid, &status, WNOHANG);
 | 
						|
  if (rv == -1)
 | 
						|
    throw new InternalError (JvNewStringUTF (strerror (errno)));
 | 
						|
 | 
						|
  if (rv == 0)
 | 
						|
    return false;   // No children to wait for.
 | 
						|
 | 
						|
  JvSynchronize sync (p);
 | 
						|
  p->status = WIFEXITED (status) ? WEXITSTATUS (status) : -1;
 | 
						|
  p->state = PosixProcess::STATE_TERMINATED;
 | 
						|
  p->processTerminationCleanup();
 | 
						|
  p->notifyAll ();
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
java::lang::PosixProcess$ProcessManager::signalReaper ()
 | 
						|
{
 | 
						|
  ProcessManagerInternal *pmi = (ProcessManagerInternal *)nativeData;
 | 
						|
  char c = 0;
 | 
						|
  ::write(pmi->pipe_ends[1], &c, 1);
 | 
						|
  // Ignore errors.  If EPIPE the reaper has already exited.
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
java::lang::PosixProcess::nativeDestroy ()
 | 
						|
{
 | 
						|
  int c = ::kill ((pid_t) pid, SIGKILL);
 | 
						|
  if (c == 0)
 | 
						|
    return;
 | 
						|
  // kill() failed.
 | 
						|
  throw new InternalError (JvNewStringUTF (strerror (errno)));
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
java::lang::PosixProcess::nativeSpawn ()
 | 
						|
{
 | 
						|
  using namespace java::io;
 | 
						|
 | 
						|
  // Initialize all locals here to make cleanup simpler.
 | 
						|
  char **args = NULL;
 | 
						|
  char **env = NULL;
 | 
						|
  char *path = NULL;
 | 
						|
  int inp[2], outp[2], errp[2], msgp[2];
 | 
						|
  inp[0] = -1;
 | 
						|
  inp[1] = -1;
 | 
						|
  outp[0] = -1;
 | 
						|
  outp[1] = -1;
 | 
						|
  errp[0] = -1;
 | 
						|
  errp[1] = -1;
 | 
						|
  msgp[0] = -1;
 | 
						|
  msgp[1] = -1;
 | 
						|
  errorStream = NULL;
 | 
						|
  inputStream = NULL;
 | 
						|
  outputStream = NULL;
 | 
						|
 | 
						|
  try
 | 
						|
    {
 | 
						|
      // Transform arrays to native form.
 | 
						|
      args = (char **) _Jv_Malloc ((progarray->length + 1) * sizeof (char *));
 | 
						|
 | 
						|
      // Initialize so we can gracefully recover.
 | 
						|
      jstring *elts = elements (progarray);
 | 
						|
      for (int i = 0; i <= progarray->length; ++i)
 | 
						|
	args[i] = NULL;
 | 
						|
 | 
						|
      for (int i = 0; i < progarray->length; ++i)
 | 
						|
	args[i] = new_string (elts[i]);
 | 
						|
      args[progarray->length] = NULL;
 | 
						|
 | 
						|
      if (envp)
 | 
						|
	{
 | 
						|
          bool need_path = true;
 | 
						|
          bool need_ld_library_path = true;
 | 
						|
          int i;
 | 
						|
 | 
						|
          // Preserve PATH and LD_LIBRARY_PATH unless specified
 | 
						|
          // explicitly.  We need three extra slots.  Potentially PATH
 | 
						|
          // and LD_LIBRARY_PATH will be added plus the NULL
 | 
						|
          // termination.
 | 
						|
	  env = (char **) _Jv_Malloc ((envp->length + 3) * sizeof (char *));
 | 
						|
	  elts = elements (envp);
 | 
						|
 | 
						|
	  // Initialize so we can gracefully recover.
 | 
						|
	  for (i = 0; i < envp->length + 3; ++i)
 | 
						|
	    env[i] = NULL;
 | 
						|
 | 
						|
	  for (i = 0; i < envp->length; ++i)
 | 
						|
            {
 | 
						|
              env[i] = new_string (elts[i]);
 | 
						|
              if (!strncmp (env[i], "PATH=", sizeof("PATH=")))
 | 
						|
                need_path = false;
 | 
						|
              if (!strncmp (env[i], "LD_LIBRARY_PATH=",
 | 
						|
                            sizeof("LD_LIBRARY_PATH=")))
 | 
						|
                need_ld_library_path = false;
 | 
						|
            }
 | 
						|
 | 
						|
          if (need_path)
 | 
						|
            {
 | 
						|
	      char *path_val = getenv ("PATH");
 | 
						|
              if (path_val)
 | 
						|
                {
 | 
						|
                  env[i] = (char *) _Jv_Malloc (strlen (path_val) +
 | 
						|
                                                sizeof("PATH=") + 1);
 | 
						|
                  strcpy (env[i], "PATH=");
 | 
						|
                  strcat (env[i], path_val);
 | 
						|
                  i++;
 | 
						|
                }
 | 
						|
            }
 | 
						|
          if (need_ld_library_path)
 | 
						|
            {
 | 
						|
	      char *path_val = getenv ("LD_LIBRARY_PATH");
 | 
						|
              if (path_val)
 | 
						|
                {
 | 
						|
                  env[i] =
 | 
						|
                    (char *) _Jv_Malloc (strlen (path_val) +
 | 
						|
                                         sizeof("LD_LIBRARY_PATH=") + 1);
 | 
						|
                  strcpy (env[i], "LD_LIBRARY_PATH=");
 | 
						|
                  strcat (env[i], path_val);
 | 
						|
                  i++;
 | 
						|
                }
 | 
						|
            }
 | 
						|
	  env[i] = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
      // We allocate this here because we can't call malloc() after
 | 
						|
      // the fork.
 | 
						|
      if (dir != NULL)
 | 
						|
	path = new_string (dir->getPath ());
 | 
						|
 | 
						|
      // Create pipes for I/O.  MSGP is for communicating exec()
 | 
						|
      // status.  If redirecting stderr to stdout, we don't need to
 | 
						|
      // create the ERRP pipe.
 | 
						|
      if (pipe (inp) || pipe (outp) || pipe (msgp)
 | 
						|
	  || fcntl (msgp[1], F_SETFD, FD_CLOEXEC))
 | 
						|
	throw new IOException (JvNewStringUTF (strerror (errno)));
 | 
						|
      if (! redirect && pipe (errp))
 | 
						|
	throw new IOException (JvNewStringUTF (strerror (errno)));
 | 
						|
 | 
						|
      // We create the streams before forking.  Otherwise if we had an
 | 
						|
      // error while creating the streams we would have run the child
 | 
						|
      // with no way to communicate with it.
 | 
						|
      if (redirect)
 | 
						|
	errorStream = PosixProcess$EOFInputStream::instance;
 | 
						|
      else
 | 
						|
	errorStream =
 | 
						|
	  new FileInputStream (new
 | 
						|
			       FileChannelImpl (errp[0],
 | 
						|
						FileChannelImpl::READ));
 | 
						|
      inputStream =
 | 
						|
	new FileInputStream (new
 | 
						|
			     FileChannelImpl (inp[0], FileChannelImpl::READ));
 | 
						|
      outputStream =
 | 
						|
	new FileOutputStream (new FileChannelImpl (outp[1],
 | 
						|
						   FileChannelImpl::WRITE));
 | 
						|
 | 
						|
      // We don't use vfork() because that would cause the local
 | 
						|
      // environment to be set by the child.
 | 
						|
 | 
						|
    // Use temporary for fork result to avoid dirtying an extra page.
 | 
						|
    pid_t pid_tmp;
 | 
						|
    if ((pid_tmp = fork ()) == -1)
 | 
						|
      throw new IOException (JvNewStringUTF (strerror (errno)));
 | 
						|
 | 
						|
    if (pid_tmp == 0)
 | 
						|
	{
 | 
						|
	  // Child process, so remap descriptors, chdir and exec.
 | 
						|
	  if (envp)
 | 
						|
            environ = env;
 | 
						|
 | 
						|
	  // We ignore errors from dup2 because they should never occur.
 | 
						|
	  dup2 (outp[0], 0);
 | 
						|
	  dup2 (inp[1], 1);
 | 
						|
	  dup2 (redirect ? inp[1] : errp[1], 2);
 | 
						|
 | 
						|
	  // Use close and not myclose -- we're in the child, and we
 | 
						|
	  // aren't worried about the possible race condition.
 | 
						|
	  close (inp[0]);
 | 
						|
	  close (inp[1]);
 | 
						|
	  if (! redirect)
 | 
						|
	    {
 | 
						|
	      close (errp[0]);
 | 
						|
	      close (errp[1]);
 | 
						|
	    }
 | 
						|
	  close (outp[0]);
 | 
						|
	  close (outp[1]);
 | 
						|
	  close (msgp[0]);
 | 
						|
 | 
						|
	  // Change directory.
 | 
						|
	  if (path != NULL)
 | 
						|
	    {
 | 
						|
	      if (chdir (path) != 0)
 | 
						|
		{
 | 
						|
		  char c = errno;
 | 
						|
		  write (msgp[1], &c, 1);
 | 
						|
		  _exit (127);
 | 
						|
		}
 | 
						|
	    }
 | 
						|
          // Make sure all file descriptors are closed.  In
 | 
						|
          // multi-threaded programs, there is a race between when a
 | 
						|
          // descriptor is obtained, when we can set FD_CLOEXEC, and
 | 
						|
          // fork().  If the fork occurs before FD_CLOEXEC is set, the
 | 
						|
          // descriptor would leak to the execed process if we did not
 | 
						|
          // manually close it.  So that is what we do.  Since we
 | 
						|
          // close all the descriptors, it is redundant to set
 | 
						|
          // FD_CLOEXEC on them elsewhere.
 | 
						|
          int max_fd;
 | 
						|
#ifdef HAVE_GETRLIMIT
 | 
						|
          rlimit rl;
 | 
						|
          int rv = getrlimit(RLIMIT_NOFILE, &rl);
 | 
						|
          if (rv == 0)
 | 
						|
            max_fd = rl.rlim_max - 1;
 | 
						|
          else
 | 
						|
            max_fd = 1024 - 1;
 | 
						|
#else
 | 
						|
          max_fd = 1024 - 1;
 | 
						|
#endif
 | 
						|
          while(max_fd > 2)
 | 
						|
            {
 | 
						|
              if (max_fd != msgp[1])
 | 
						|
                close (max_fd);
 | 
						|
              max_fd--;
 | 
						|
            }
 | 
						|
	  // Make sure that SIGCHLD is unblocked for the new process.
 | 
						|
	  sigset_t mask;
 | 
						|
	  sigemptyset (&mask);
 | 
						|
	  sigaddset (&mask, SIGCHLD);
 | 
						|
	  sigprocmask (SIG_UNBLOCK, &mask, NULL);
 | 
						|
 | 
						|
	  execvp (args[0], args);
 | 
						|
 | 
						|
	  // Send the parent notification that the exec failed.
 | 
						|
	  char c = errno;
 | 
						|
	  write (msgp[1], &c, 1);
 | 
						|
	  _exit (127);
 | 
						|
	}
 | 
						|
 | 
						|
      // Parent.  Close extra file descriptors and mark ours as
 | 
						|
      // close-on-exec.
 | 
						|
      pid = (jlong) pid_tmp;
 | 
						|
 | 
						|
      myclose (outp[0]);
 | 
						|
      myclose (inp[1]);
 | 
						|
      if (! redirect)
 | 
						|
	myclose (errp[1]);
 | 
						|
      myclose (msgp[1]);
 | 
						|
 | 
						|
      char c;
 | 
						|
      int r = read (msgp[0], &c, 1);
 | 
						|
      if (r == -1)
 | 
						|
        throw new IOException (JvNewStringUTF (strerror (errno)));
 | 
						|
      else if (r != 0)
 | 
						|
        throw new IOException (JvNewStringUTF (strerror (c)));
 | 
						|
    }
 | 
						|
  catch (java::lang::Throwable *thrown)
 | 
						|
    {
 | 
						|
      // Do some cleanup we only do on failure.  If a stream object
 | 
						|
      // has been created, we must close the stream itself (to avoid
 | 
						|
      // duplicate closes when the stream object is collected).
 | 
						|
      // Otherwise we simply close the underlying file descriptor.
 | 
						|
      // We ignore errors here as they are uninteresting.
 | 
						|
 | 
						|
      try
 | 
						|
	{
 | 
						|
	  if (inputStream != NULL)
 | 
						|
	    inputStream->close ();
 | 
						|
	  else
 | 
						|
	    myclose (inp[0]);
 | 
						|
	}
 | 
						|
      catch (java::lang::Throwable *ignore)
 | 
						|
	{
 | 
						|
	}
 | 
						|
 | 
						|
      try
 | 
						|
	{
 | 
						|
	  if (outputStream != NULL)
 | 
						|
	    outputStream->close ();
 | 
						|
	  else
 | 
						|
	    myclose (outp[1]);
 | 
						|
	}
 | 
						|
      catch (java::lang::Throwable *ignore)
 | 
						|
	{
 | 
						|
	}
 | 
						|
 | 
						|
      try
 | 
						|
	{
 | 
						|
	  if (errorStream != NULL)
 | 
						|
	    errorStream->close ();
 | 
						|
	  else if (! redirect)
 | 
						|
	    myclose (errp[0]);
 | 
						|
	}
 | 
						|
      catch (java::lang::Throwable *ignore)
 | 
						|
	{
 | 
						|
	}
 | 
						|
 | 
						|
      // These are potentially duplicate, but it doesn't matter due to
 | 
						|
      // the use of myclose.
 | 
						|
      myclose (outp[0]);
 | 
						|
      myclose (inp[1]);
 | 
						|
      if (! redirect)
 | 
						|
	myclose (errp[1]);
 | 
						|
      myclose (msgp[1]);
 | 
						|
 | 
						|
      exception = thrown;
 | 
						|
    }
 | 
						|
 | 
						|
  myclose (msgp[0]);
 | 
						|
  cleanup (args, env, path);
 | 
						|
}
 |