| // 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); |
| } |