This is the mail archive of the
java-patches@gcc.gnu.org
mailing list for the Java project.
[RFA] Patch: Fix PosixProcess / PR libgcj 11801
- From: David Daney <ddaney at avtrex dot com>
- To: java-patches at gcc dot gnu dot org
- Date: Tue, 03 Aug 2004 09:43:16 -0700
- Subject: [RFA] Patch: Fix PosixProcess / PR libgcj 11801
This is the second version of the patch posted here:
http://gcc.gnu.org/ml/java-patches/2004-q3/msg00378.html
The main aim of the patch is two fold:
1) Don't leave zombie processes around.
2) Allow waitFor() from a different thread than that which started the
Process.
I made all of the changes suggested by Bryce McKinlay except for
converting all Process implementations (Posix, Win32, Ecos...) to use
their real names instead of ConcreteProcess and modifying the
configure/build process to match.
Tested on both i686-pc-linux-gnu (Fedora Core 1, NPTL) and
mipsel-linux-gnu (glibc 2.2.5) with no regressions, with additional
tests from here:
http://gcc.gnu.org/ml/java-patches/2004-q3/msg00451.html
OK to commit?
David Daney.
2004-08-03 David Daney <ddaney@avtrex.com>
PR libgcj/11801
* java/lang/PosixProcess.java: Rewrote.
* java/lang/natPosixProcess.cc: Rewrote.
* java/lang/Runtime.java (execInternal): Declare throws IOException.
* gcj/javaprims.h (ConcreteProcess$ProcessManager): Declare.
* posix-threads.cc (_Jv_ThreadRegister) Block SIGCHLD.
(_Jv_ThreadStart) Likewise.
* configure.in (PLATFORM_INNER_NAT_HDRS): New AC_SUBST() used in...
* Makefile.am: ... to specify extra native headers.
* configure: Regenerated.
* Makefile.in: Regenerated.
* gcj/Makefile.in: Regenerated.
* include/Makefile.in: Regenerated.
* testsuite/Makefile.in: Regenerated.
Index: Makefile.am
===================================================================
RCS file: /cvs/gcc/gcc/libjava/Makefile.am,v
retrieving revision 1.396
diff -c -p -r1.396 Makefile.am
*** Makefile.am 24 Jul 2004 16:43:44 -0000 1.396
--- Makefile.am 3 Aug 2004 15:53:17 -0000
*************** inner_nat_headers = java/io/ObjectOutput
*** 531,537 ****
gnu/java/net/PlainSocketImpl$$SocketInputStream.h \
gnu/java/net/PlainSocketImpl$$SocketOutputStream.h \
gnu/java/nio/PipeImpl$$SinkChannelImpl.h \
! gnu/java/nio/PipeImpl$$SourceChannelImpl.h
nat_headers = $(ordinary_nat_headers) $(inner_nat_headers)
nat_headers_install = $(ordinary_nat_headers)
--- 531,538 ----
gnu/java/net/PlainSocketImpl$$SocketInputStream.h \
gnu/java/net/PlainSocketImpl$$SocketOutputStream.h \
gnu/java/nio/PipeImpl$$SinkChannelImpl.h \
! gnu/java/nio/PipeImpl$$SourceChannelImpl.h \
! $(PLATFORM_INNER_NAT_HDRS)
nat_headers = $(ordinary_nat_headers) $(inner_nat_headers)
nat_headers_install = $(ordinary_nat_headers)
*************** gnu/java/nio/PipeImpl$$SourceChannelImpl
*** 641,646 ****
--- 642,652 ----
$(GCJH) -classpath '' -bootclasspath $(top_builddir) \
'gnu/java/nio/PipeImpl$$SourceChannelImpl'
+ ## Only used by PosixProcess.java
+ java/lang/ConcreteProcess$$ProcessManager.h: java/lang/ConcreteProcess.class
+ $(GCJH) -classpath '' -bootclasspath $(top_builddir) \
+ 'java/lang/ConcreteProcess$$ProcessManager'
+
## Headers we maintain by hand and which we want to install.
extra_headers = java/lang/Object.h java/lang/Class.h
Index: configure.in
===================================================================
RCS file: /cvs/gcc/gcc/libjava/configure.in,v
retrieving revision 1.197
diff -c -p -r1.197 configure.in
*** configure.in 28 Jul 2004 19:08:44 -0000 1.197
--- configure.in 3 Aug 2004 15:53:21 -0000
*************** TARGET_ECOS="$with_ecos"
*** 403,408 ****
--- 403,411 ----
EXTRA_CC_FILES=
AC_SUBST(EXTRA_CC_FILES)
+ PLATFORM_INNER_NAT_HDRS=
+ AC_SUBST(PLATFORM_INNER_NAT_HDRS)
+
PLATFORMOBJS=
case "$TARGET_ECOS" in
no) case "$host" in
*************** case "$TARGET_ECOS" in
*** 418,423 ****
--- 421,427 ----
PLATFORMNET=Posix
PLATFORMOBJS=posix.lo
PLATFORMH=posix.h
+ PLATFORM_INNER_NAT_HDRS='java/lang/ConcreteProcess$$ProcessManager.h'
;;
esac
;;
Index: posix-threads.cc
===================================================================
RCS file: /cvs/gcc/gcc/libjava/posix-threads.cc,v
retrieving revision 1.34
diff -c -p -r1.34 posix-threads.cc
*** posix-threads.cc 21 Oct 2003 04:46:19 -0000 1.34
--- posix-threads.cc 3 Aug 2004 15:53:21 -0000
***************
*** 1,6 ****
// posix-threads.cc - interface between libjava and POSIX threads.
! /* Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation
This file is part of libgcj.
--- 1,6 ----
// posix-threads.cc - interface between libjava and POSIX threads.
! /* Copyright (C) 1998, 1999, 2000, 2001, 2004 Free Software Foundation
This file is part of libgcj.
*************** details. */
*** 34,39 ****
--- 34,40 ----
#include <java/lang/System.h>
#include <java/lang/Long.h>
#include <java/lang/OutOfMemoryError.h>
+ #include <java/lang/InternalError.h>
// This is used to implement thread startup.
struct starter
*************** _Jv_ThreadRegister (_Jv_Thread_t *data)
*** 358,363 ****
--- 359,371 ----
_Jv_self_cache[current_index].high_sp_bits = BAD_HIGH_SP_VALUE;
}
# endif
+ // Block SIGCHLD which is used in natPosixProcess.cc.
+ sigset_t mask;
+ sigemptyset (&mask);
+ sigaddset (&mask, SIGCHLD);
+ int c = pthread_sigmask (SIG_BLOCK, &mask, NULL);
+ if (c != 0)
+ throw new java::lang::InternalError (JvNewStringLatin1 (strerror (c)));
}
void
*************** _Jv_ThreadStart (java::lang::Thread *thr
*** 403,408 ****
--- 411,425 ----
return;
data->flags |= FLAG_START;
+ // Block SIGCHLD which is used in natPosixProcess.cc.
+ // The current mask is inherited by the child thread.
+ sigset_t mask;
+ sigemptyset (&mask);
+ sigaddset (&mask, SIGCHLD);
+ int c = pthread_sigmask (SIG_BLOCK, &mask, NULL);
+ if (c != 0)
+ throw new java::lang::InternalError (JvNewStringLatin1 (strerror (c)));
+
param.sched_priority = thread->getPriority();
pthread_attr_init (&attr);
Index: gcj/javaprims.h
===================================================================
RCS file: /cvs/gcc/gcc/libjava/gcj/javaprims.h,v
retrieving revision 1.50
diff -c -p -r1.50 javaprims.h
*** gcj/javaprims.h 10 Jul 2004 14:50:15 -0000 1.50
--- gcj/javaprims.h 3 Aug 2004 15:53:21 -0000
***************
*** 1,6 ****
// javaprims.h - Main external header file for libgcj. -*- c++ -*-
! /* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 Free Software Foundation
This file is part of libgcj.
--- 1,7 ----
// javaprims.h - Main external header file for libgcj. -*- c++ -*-
! /* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004
! Free Software Foundation
This file is part of libgcj.
*************** extern "Java"
*** 150,155 ****
--- 151,157 ----
class Comparable;
class Compiler;
class ConcreteProcess;
+ class ConcreteProcess$ProcessManager;
class Double;
class Error;
class Exception;
Index: java/lang/PosixProcess.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/lang/PosixProcess.java,v
retrieving revision 1.5
diff -c -p -r1.5 PosixProcess.java
*** java/lang/PosixProcess.java 14 Aug 2002 01:07:59 -0000 1.5
--- java/lang/PosixProcess.java 3 Aug 2004 15:53:22 -0000
***************
*** 1,6 ****
// PosixProcess.java - Subclass of Process for POSIX systems.
!
! /* Copyright (C) 1998, 1999 Free Software Foundation
This file is part of libgcj.
--- 1,5 ----
// PosixProcess.java - Subclass of Process for POSIX systems.
! /* Copyright (C) 1998, 1999, 2004 Free Software Foundation
This file is part of libgcj.
*************** details. */
*** 11,86 ****
package java.lang;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
! import java.io.IOException;
/**
* @author Tom Tromey <tromey@cygnus.com>
* @date May 3, 1999
*/
// This is entirely internal to our implementation.
-
// This file is copied to `ConcreteProcess.java' before compilation.
// Hence the class name apparently does not match the file name.
final class ConcreteProcess extends Process
{
! public native void destroy ();
! public int exitValue ()
{
! if (! hasExited)
! throw new IllegalThreadStateException("Process has not exited");
return status;
}
! public InputStream getErrorStream ()
{
return errorStream;
}
! public InputStream getInputStream ()
{
return inputStream;
}
! public OutputStream getOutputStream ()
{
return outputStream;
}
! public native int waitFor () throws InterruptedException;
! // This is used for actual initialization, as we can't write a
! // native constructor.
! public native void startProcess (String[] progarray,
! String[] envp,
! File dir)
! throws IOException;
// This file is copied to `ConcreteProcess.java' before
// compilation. Hence the constructor name apparently does not
// match the file name.
! public ConcreteProcess (String[] progarray,
! String[] envp,
! File dir)
! throws IOException
{
! startProcess (progarray, envp, dir);
}
! // The process id. This is cast to a pid_t on the native side.
private long pid;
! // True when child has exited.
! private boolean hasExited;
! // The exit status, if the child has exited.
! private int status;
// The streams.
private InputStream errorStream;
private InputStream inputStream;
private OutputStream outputStream;
}
--- 10,393 ----
package java.lang;
import java.io.File;
+ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
! import java.util.HashMap;
! import java.util.LinkedList;
! import java.util.List;
! import java.util.Map;
!
/**
* @author Tom Tromey <tromey@cygnus.com>
* @date May 3, 1999
+ * @author David Daney <ddaney@avtrex.com> Rewrote using
+ * ProcessManager
*/
// This is entirely internal to our implementation.
// This file is copied to `ConcreteProcess.java' before compilation.
// Hence the class name apparently does not match the file name.
final class ConcreteProcess extends Process
{
! static class ProcessManager extends Thread
! {
! /**
! * A list of {@link ConcreteProcess ConcreteProcesses} to be
! * started. The queueLock object is used as the lock Object
! * for all process related operations. To avoid dead lock
! * ensure queueLock is obtained before ConcreteProcess.
! */
! List queue = new LinkedList();
! private Map pidToProcess = new HashMap();
! private boolean ready = false;
! private long reaperPID;
!
! ProcessManager()
! {
! super("ProcessManager");
! // Don't keep the (main) process from exiting on our account.
! this.setDaemon(true);
! }
!
! /**
! * Get the ConcreteProcess object with the given pid and
! * remove it from the map. This method is called from the
! * native code for {@link #reap()). The mapping is removed so
! * the ConcreteProcesses can be GCed after they terminate.
! *
! * @param p The pid of the process.
! */
! private ConcreteProcess removeProcessFromMap(long p)
! {
! return (ConcreteProcess) pidToProcess.remove(new Long(p));
! }
!
! /**
! * Put the given ConcreteProcess in the map using the Long
! * value of its pid as the key.
! *
! * @param p The ConcreteProcess.
! */
! void addProcessToMap(ConcreteProcess p)
! {
! pidToProcess.put(new Long(p.pid), p);
! }
!
! /**
! * Queue up the ConcreteProcess and awake the ProcessManager.
! * The ProcessManager will start the ConcreteProcess from its
! * thread so it can be reaped when it terminates.
! *
! * @param p The ConcreteProcess.
! */
! void startExecuting(ConcreteProcess p)
! {
! synchronized (queueLock)
! {
! queue.add(p);
! signalReaper(); // If blocked in waitForSignal().
! queueLock.notifyAll(); // If blocked in wait();
! }
! }
!
! /**
! * Block until the ProcessManager thread is ready to accept
! * commands.
! */
! void waitUntilReady()
! {
! synchronized (this)
! {
! try
! {
! while (! ready)
! wait();
! }
! catch (InterruptedException ie)
! {
! // Ignore.
! }
! }
! }
!
! /**
! * Main Process starting/reaping loop.
! */
! public void run()
! {
! init();
! // Now ready to accept requests.
! synchronized (this)
! {
! ready = true;
! this.notifyAll();
! }
!
! for (;;)
! {
! try
! {
! synchronized (queueLock)
! {
! boolean haveMoreChildren = reap();
! if (! haveMoreChildren && queue.size() == 0)
! {
! // This reaper thread could exit, but we
! // keep it alive for a while in case
! // someone wants to start more Processes.
! try
! {
! queueLock.wait(1000L);
! if (queue.size() == 0)
! {
! processManager = null;
! return; // Timed out.
! }
! }
! catch (InterruptedException ie)
! {
! // Ignore and exit the thread.
! return;
! }
! }
! while (queue.size() > 0)
! {
! ConcreteProcess p = (ConcreteProcess) queue.remove(0);
! p.spawn(this);
! }
! }
!
! // Wait for a SIGCHLD from either an exiting
! // process or the startExecuting() method. This
! // is done outside of the synchronized block to
! // allow other threads to enter and submit more
! // jobs.
! waitForSignal();
! }
! catch (Exception ex)
! {
! ex.printStackTrace(System.err);
! }
! }
! }
!
! /**
! * Setup native signal handlers and other housekeeping things.
! *
! */
! private native void init();
!
! /**
! * Block waiting for SIGCHLD.
! *
! */
! private native void waitForSignal();
!
! /**
! * Try to reap as many children as possible without blocking.
! *
! * @return true if more live children exist.
! *
! */
! private native boolean reap();
!
! /**
! * Send SIGCHLD to the reaper thread.
! */
! private native void signalReaper();
! }
! public void destroy()
{
! // Synchronized on the queueLock. This ensures that the reaper
! // thread cannot be doing a wait() on the child.
! // Otherwise there would be a race where the OS could
! // create a process with the same pid between the wait()
! // and the update of the state which would cause a kill to
! // the wrong process.
! synchronized (queueLock)
! {
! synchronized (this)
! {
! // If there is no ProcessManager we cannot kill.
! if (state != STATE_TERMINATED)
! {
! if (processManager == null)
! throw new InternalError();
! nativeDestroy();
! }
! }
! }
! }
!
! private native void nativeDestroy();
!
! public int exitValue()
! {
! synchronized (this)
! {
! if (state != STATE_TERMINATED)
! throw new IllegalThreadStateException("Process has not exited");
! }
return status;
}
! public InputStream getErrorStream()
{
return errorStream;
}
! public InputStream getInputStream()
{
return inputStream;
}
! public OutputStream getOutputStream()
{
return outputStream;
}
! public int waitFor() throws InterruptedException
! {
! synchronized (this)
! {
! while (state != STATE_TERMINATED)
! wait();
! }
! return status;
! }
!
! /**
! * Start this process running. This should only be called by the
! * ProcessManager.
! *
! * @param pm The ProcessManager that made the call.
! */
! void spawn(ProcessManager pm)
! {
! synchronized (this)
! {
! // Do the fork/exec magic.
! nativeSpawn();
! // There is no race with reap() in the pidToProcess map
! // because this is always called from the same thread
! // doing the reaping.
! pm.addProcessToMap(this);
! state = STATE_RUNNING;
! // Notify anybody waiting on state change.
! this.notifyAll();
! }
! }
! /**
! * Do the fork and exec.
! */
! private native void nativeSpawn();
// This file is copied to `ConcreteProcess.java' before
// compilation. Hence the constructor name apparently does not
// match the file name.
! ConcreteProcess(String[] progarray, String[] envp, File dir)
! throws IOException
{
! // Check to ensure there is something to run, and avoid
! // dereferencing null pointers in native code.
! if (progarray[0] == null)
! throw new NullPointerException();
!
! this.progarray = progarray;
! this.envp = envp;
! this.dir = dir;
!
! // Start a ProcessManager if there is not one already running.
! synchronized (queueLock)
! {
! if (processManager == null)
! {
! processManager = new ProcessManager();
! processManager.start();
! processManager.waitUntilReady();
! }
!
! // Queue this ConcreteProcess for starting by the ProcessManager.
! processManager.startExecuting(this);
! }
!
! // Wait until ProcessManager has started us.
! synchronized (this)
! {
! while (state == STATE_WAITING_TO_START)
! {
! try
! {
! wait();
! }
! catch (InterruptedException ie)
! {
! // FIXME: What to do when interrupted while blocking in a constructor?
! // Ignore.
! }
! }
! }
!
! // If there was a problem, re-throw it.
! if (exception != null)
! {
! if (exception instanceof IOException)
! {
! IOException ioe = new IOException(exception.toString());
! ioe.initCause(exception);
! throw ioe;
! }
!
! // Not an IOException. Something bad happened.
! InternalError ie = new InternalError(exception.toString());
! ie.initCause(exception);
! throw ie;
! }
!
! // If we get here, all is well, the Process has started.
}
! private String[] progarray;
! private String[] envp;
! private File dir;
!
! /** Set by the ProcessManager on problems starting. */
! private Throwable exception;
!
! /** The process id. This is cast to a pid_t on the native side. */
private long pid;
! // FIXME: Why doesn't the friend declaration in ConcreteProcess.h
! // allow ConcreteProcess$ProcessManager native code access these
! // when they are private?
!
! /** Before the process is forked. */
! static final int STATE_WAITING_TO_START = 0;
!
! /** After the fork. */
! static final int STATE_RUNNING = 1;
! /** After exit code has been collected. */
! static final int STATE_TERMINATED = 2;
!
! /** One of STATE_WAITING_TO_START, STATE_RUNNING, STATE_TERMINATED. */
! int state;
!
! /** The exit status, if the child has exited. */
! int status;
// The streams.
private InputStream errorStream;
private InputStream inputStream;
private OutputStream outputStream;
+
+ /**
+ * Lock Object for all processManager related locking.
+ */
+ private static Object queueLock = new Object();
+ private static ProcessManager processManager;
}
Index: java/lang/Runtime.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/lang/Runtime.java,v
retrieving revision 1.15
diff -c -p -r1.15 Runtime.java
*** java/lang/Runtime.java 27 Jul 2004 18:42:37 -0000 1.15
--- java/lang/Runtime.java 3 Aug 2004 15:53:22 -0000
***************
*** 1,5 ****
/* Runtime.java -- access to the VM process
! Copyright (C) 1998, 2002, 2003 Free Software Foundation
This file is part of GNU Classpath.
--- 1,5 ----
/* Runtime.java -- access to the VM process
! Copyright (C) 1998, 2002, 2003, 2004 Free Software Foundation
This file is part of GNU Classpath.
*************** public class Runtime
*** 734,741 ****
* @param dir the directory to use, may be null
* @return the newly created process
* @throws NullPointerException if cmd or env have null elements
*/
! native Process execInternal(String[] cmd, String[] env, File dir);
/**
* Get the system properties. This is done here, instead of in System,
--- 734,744 ----
* @param dir the directory to use, may be null
* @return the newly created process
* @throws NullPointerException if cmd or env have null elements
+ * @throws IOException if the exec fails
*/
! native Process execInternal(String[] cmd, String[] env, File dir)
! throws IOException;
!
/**
* Get the system properties. This is done here, instead of in System,
Index: java/lang/natPosixProcess.cc
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/lang/natPosixProcess.cc,v
retrieving revision 1.18
diff -c -p -r1.18 natPosixProcess.cc
*** java/lang/natPosixProcess.cc 1 Mar 2004 21:33:27 -0000 1.18
--- java/lang/natPosixProcess.cc 3 Aug 2004 15:53:22 -0000
*************** details. */
*** 21,32 ****
--- 21,36 ----
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
+ #include <unistd.h>
+ #include <pthread.h>
#include <gcj/cni.h>
#include <jvm.h>
+ #include <java/lang/ConcreteProcess$ProcessManager.h>
#include <java/lang/ConcreteProcess.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>
*************** details. */
*** 40,91 ****
using gnu::java::nio::channels::FileChannelImpl;
! extern char **environ;
!
! void
! java::lang::ConcreteProcess::destroy (void)
! {
! if (! hasExited)
! {
! // Really kill it.
! kill ((pid_t) pid, SIGKILL);
! }
! }
!
! jint
! java::lang::ConcreteProcess::waitFor (void)
! {
! if (! hasExited)
! {
! int wstat;
! int r = waitpid ((pid_t) pid, &wstat, 0);
!
! if (r == -1)
! {
! if (java::lang::Thread::interrupted())
! throw new InterruptedException (JvNewStringLatin1 (strerror
! (errno)));
! }
! else
! {
! hasExited = true;
!
! if (WIFEXITED (wstat))
! status = WEXITSTATUS (wstat);
! else
! status = -1;
! }
! }
!
! return status;
! }
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;
}
--- 44,57 ----
using gnu::java::nio::channels::FileChannelImpl;
! 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;
}
*************** myclose (int &fd)
*** 120,138 ****
fd = -1;
}
void
! java::lang::ConcreteProcess::startProcess (jstringArray progarray,
! jstringArray envp,
! java::io::File *dir)
{
! using namespace java::io;
! hasExited = false;
// 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;
--- 86,223 ----
fd = -1;
}
+ // 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
+ sigchld_handler (int)
+ {
+ // Ignore.
+ }
+
+
+ // Get ready to enter the main reaper thread loop.
void
! java::lang::ConcreteProcess$ProcessManager::init ()
{
! using namespace java::lang;
! // Remenber our PID so other threads can kill us.
! reaperPID = (jlong) pthread_self ();
!
! // SIGCHLD is blocked in all threads in posix-threads.cc.
! // Setup the SIGCHLD handler.
! struct sigaction sa;
! memset (&sa, 0, sizeof (sa));
!
! sa.sa_handler = sigchld_handler;
! // We only want signals when the things exit.
! sa.sa_flags = SA_NOCLDSTOP;
!
! if (-1 == sigaction (SIGCHLD, &sa, NULL))
! goto error;
!
! // All OK.
! return;
!
! error:
! throw new InternalError (JvNewStringLatin1 (strerror (errno)));
! }
!
! void
! java::lang::ConcreteProcess$ProcessManager::waitForSignal ()
! {
! using namespace java::lang;
!
! sigset_t mask;
! // Wait for SIGCHLD
! sigemptyset (&mask);
! sigaddset (&mask, SIGCHLD);
!
! int sig;
! int c = sigwait (&mask, &sig);
!
! if (c != 0)
! goto error;
!
! // All OK.
! return;
!
! error:
! throw new InternalError (JvNewStringLatin1 (strerror (c)));
! }
!
! jboolean java::lang::ConcreteProcess$ProcessManager::reap ()
! {
! using namespace java::lang;
!
! pid_t pid;
!
! for (;;)
! {
! // Get the return code from a dead child process.
! int status;
! pid = waitpid ((pid_t) - 1, &status, WNOHANG);
! if (pid == -1)
! {
! if (errno == ECHILD)
! return false;
! else
! goto error;
! }
!
! if (pid == 0)
! return true; // No children to wait for.
!
! // Look up the process in our pid map.
! ConcreteProcess * process = removeProcessFromMap ((jlong) pid);
!
! if (process)
! {
! JvSynchronize sync (process);
! process->status = WIFEXITED (status) ? WEXITSTATUS (status) : -1;
! process->state = ConcreteProcess::STATE_TERMINATED;
! process->notifyAll ();
! }
! else
! {
! // Unknown child. How did this happen?
! fprintf (stderr, "Reaped unknown child pid = %ld\n", (long) pid);
! }
! }
!
! error:
! throw new InternalError (JvNewStringLatin1 (strerror (errno)));
! }
!
! void
! java::lang::ConcreteProcess$ProcessManager::signalReaper ()
! {
! int c = pthread_kill ((pthread_t) reaperPID, SIGCHLD);
! if (c == 0)
! return;
! // pthread_kill() failed.
! throw new InternalError (JvNewStringLatin1 (strerror (c)));
! }
! void
! java::lang::ConcreteProcess::nativeDestroy ()
! {
! int c = kill ((pid_t) pid, SIGKILL);
! if (c == 0)
! return;
! // kill() failed.
! throw new InternalError (JvNewStringLatin1 (strerror (errno)));
! }
!
! void
! java::lang::ConcreteProcess::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;
*************** java::lang::ConcreteProcess::startProces
*** 142,338 ****
errp[1] = -1;
msgp[0] = -1;
msgp[1] = -1;
- java::lang::Throwable *exc = NULL;
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)
! {
! env = (char **) _Jv_Malloc ((envp->length + 1) * sizeof (char *));
! elts = elements (envp);
!
! // Initialize so we can gracefully recover.
! for (int i = 0; i <= envp->length; ++i)
! env[i] = NULL;
!
! for (int i = 0; i < envp->length; ++i)
! env[i] = new_string (elts[i]);
! env[envp->length] = 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 (pipe (inp) || pipe (outp) || pipe (errp) || pipe (msgp)
! || fcntl (msgp[1], F_SETFD, FD_CLOEXEC))
! throw new IOException (JvNewStringLatin1 (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.
! 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.
! if ((pid = (jlong) fork ()) == -1)
! throw new IOException (JvNewStringLatin1 (strerror (errno)));
!
! if (pid == 0)
! {
! // Child process, so remap descriptors, chdir and exec.
!
! if (envp)
! {
! // Preserve PATH and LD_LIBRARY_PATH unless specified
! // explicitly.
! char *path_val = getenv ("PATH");
! char *ld_path_val = getenv ("LD_LIBRARY_PATH");
! environ = env;
! if (path_val && getenv ("PATH") == NULL)
! {
! char *path_env = (char *) _Jv_Malloc (strlen (path_val)
! + 5 + 1);
! strcpy (path_env, "PATH=");
! strcat (path_env, path_val);
! putenv (path_env);
! }
! if (ld_path_val && getenv ("LD_LIBRARY_PATH") == NULL)
! {
! char *ld_path_env
! = (char *) _Jv_Malloc (strlen (ld_path_val) + 16 + 1);
! strcpy (ld_path_env, "LD_LIBRARY_PATH=");
! strcat (ld_path_env, ld_path_val);
! putenv (ld_path_env);
! }
! }
!
! // We ignore errors from dup2 because they should never occur.
! dup2 (outp[0], 0);
! dup2 (inp[1], 1);
! dup2 (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]);
! 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);
! }
! }
!
! 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.
! myclose (outp[0]);
! myclose (inp[1]);
! myclose (errp[1]);
! myclose (msgp[1]);
!
! char c;
! int r = read (msgp[0], &c, 1);
! if (r == -1)
! throw new IOException (JvNewStringLatin1 (strerror (errno)));
! else if (r != 0)
! throw new IOException (JvNewStringLatin1 (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
! 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]);
! myclose (errp[1]);
! myclose (msgp[1]);
! exc = thrown;
! }
myclose (msgp[0]);
cleanup (args, env, path);
! if (exc != NULL)
! throw exc;
! else
{
fcntl (outp[1], F_SETFD, FD_CLOEXEC);
fcntl (inp[0], F_SETFD, FD_CLOEXEC);
--- 227,429 ----
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)
! {
! env = (char **) _Jv_Malloc ((envp->length + 1) * sizeof (char *));
! elts = elements (envp);
!
! // Initialize so we can gracefully recover.
! for (int i = 0; i <= envp->length; ++i)
! env[i] = NULL;
!
! for (int i = 0; i < envp->length; ++i)
! env[i] = new_string (elts[i]);
! env[envp->length] = 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 (pipe (inp) || pipe (outp) || pipe (errp) || pipe (msgp)
! || fcntl (msgp[1], F_SETFD, FD_CLOEXEC))
! throw new IOException (JvNewStringLatin1 (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.
! 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 (JvNewStringLatin1 (strerror (errno)));
!
! if (pid_tmp == 0)
! {
! // Child process, so remap descriptors, chdir and exec.
! if (envp)
! {
! // Preserve PATH and LD_LIBRARY_PATH unless specified
! // explicitly.
! char * path_val = getenv ("PATH");
! char * ld_path_val = getenv ("LD_LIBRARY_PATH");
! environ = env;
! if (path_val && getenv ("PATH") == NULL)
! {
! char * path_env =
! (char *) _Jv_Malloc (strlen (path_val) + 5 + 1);
! strcpy (path_env, "PATH=");
! strcat (path_env, path_val);
! putenv (path_env);
! }
! if (ld_path_val && getenv ("LD_LIBRARY_PATH") == NULL)
! {
! char * ld_path_env =
! (char *) _Jv_Malloc (strlen (ld_path_val) + 16 + 1);
! strcpy (ld_path_env, "LD_LIBRARY_PATH=");
! strcat (ld_path_env, ld_path_val);
! putenv (ld_path_env);
! }
! }
!
! // We ignore errors from dup2 because they should never occur.
! dup2 (outp[0], 0);
! dup2 (inp[1], 1);
! dup2 (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]);
! 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);
! }
! }
!
! 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]);
! myclose (errp[1]);
! myclose (msgp[1]);
!
! char c;
! int r = read (msgp[0], &c, 1);
! if (r == -1)
! throw new IOException (JvNewStringLatin1 (strerror (errno)));
! else if (r != 0)
! throw new IOException (JvNewStringLatin1 (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
! 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]);
! myclose (errp[1]);
! myclose (msgp[1]);
! exception = thrown;
! }
myclose (msgp[0]);
cleanup (args, env, path);
! if (exception == NULL)
{
fcntl (outp[1], F_SETFD, FD_CLOEXEC);
fcntl (inp[0], F_SETFD, FD_CLOEXEC);