gcc/libjava/java/lang/natPosixProcess.cc
David Daney 8c0dbf3490 re PR libgcj/29324 (add wait handling hook)
PR libgcj/29324
	* include/posix-threads.h (_Jv_BlockSigchld): Declare.
	(_Jv_UnBlockSigchld): Same.
	* posix-threads.cc: Include posix-threads.h.
	(block_sigchld) Rename to...
	(_Jv_BlockSigchld) ... this.
	(_Jv_UnBlockSigchld): New function.
	(_Jv_InitThreads): Call _Jv_BlockSigchld in place of block_sigchld.
	(_Jv_ThreadStart): Same.
	* java/lang/PosixProcess$ProcessManager.h: Regenerate.
	* java/lang/PosixProcess.java: Clean up imports.
	(ProcessManager): Make final.
	(ProcessManager.queue): Genericise and make private.
	(ProcessManager.pidToProcess): Remove.
	(ProcessManager.liveProcesses): New field.
	(ProcessManager.reaperPID): Remove.
	(ProcessManager.nativeData): New field.
	(ProcessManager.removeProcessFromMap): Remove.
	(ProcessManager.addProcessToMap):Remove.
	(ProcessManager.addToLiveProcesses): New method.
	(ProcessManager.run): Rewritten.
	(ProcessManager.reap): Change method signature,
	(getErrorStream): Correct formatting.
	(getInputStream): Same.
	(spawn): Add process to liveProcesses list.
	(pid): Make package private.
	* java/lang/PosixProcess.h: Regenerate.
	* java/lang/natPosixProcess.cc: Include posix.h and posix-threads.h.
	Add useing namespace java::lang.
	(ProcessManagerInternal): New struct.
	(sigchld_handler): Rewritten.
	(init): Rewritten.
	(waitForSignal): Same.
	(reap): Same.
	(signalReaper): Same.
	(nativeDestroy): Call kill as ::kill.
	(nativeSpawn): Correct formatting.
	* classpath/lib/java/lang/PosixProcess$EOFInputStream.class: Regenerate.
	* classpath/lib/java/lang/PosixProcess.class: Same.
	* classpath/lib/java/lang/PosixProcess$ProcessManager.class: Same.

From-SVN: r124638
2007-05-12 17:37:55 +00:00

502 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_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 <gcj/cni.h>
#include <jvm.h>
#include <posix.h>
#include <posix-threads.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
sigchld_handler (int sig, siginfo_t *si, void *third)
{
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)
{
if ((pmi->old_sigaction.sa_flags & SA_SIGINFO) != 0)
pmi->old_sigaction.sa_sigaction(sig, si, third);
else
(*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));
sa.sa_sigaction = sigchld_handler;
// We only want signals when the things exit.
sa.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
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);
}