jdk/src/solaris/native/java/lang/UNIXProcess_md.c
changeset 2946 f95752c3204a
parent 1936 66e7f98f4dde
child 2948 7f4d7737d38f
--- a/jdk/src/solaris/native/java/lang/UNIXProcess_md.c	Thu Jun 11 21:32:12 2009 -0700
+++ b/jdk/src/solaris/native/java/lang/UNIXProcess_md.c	Sun Jun 14 14:23:22 2009 -0700
@@ -49,6 +49,18 @@
 #include <fcntl.h>
 #include <limits.h>
 
+#ifndef USE_CLONE
+#ifdef __linux__
+#define USE_CLONE 1
+#else
+#define USE_CLONE 0
+#endif
+#endif
+
+#if USE_CLONE
+#include <sched.h>
+#endif
+
 #ifndef STDIN_FILENO
 #define STDIN_FILENO 0
 #endif
@@ -376,70 +388,61 @@
 }
 #endif /* DEBUG_PROCESS */
 
-/* Version of execvpe when child's PATH differs from parent's */
-static int
-execvp_usingParentPath(const char *file, const char *const argv[])
+/**
+ * Exec FILE as a traditional Bourne shell script (i.e. one without #!).
+ * If we could do it over again, we would probably not support such an ancient
+ * misfeature, but compatibility wins over sanity.  The original support for
+ * this was imported accidentally from execvp().
+ */
+static void
+execve_as_traditional_shell_script(const char *file,
+                                   const char *argv[],
+                                   const char *const envp[])
 {
-    char expanded_file[PATH_MAX];
-    int filelen = strlen(file);
-    int sticky_errno = 0;
-    const char * const * dirs;
-    /* Search parent's PATH */
-    for (dirs = parentPathv; *dirs; dirs++) {
-        const char * dir = *dirs;
-        int dirlen = strlen(dir);
-        if (filelen + dirlen + 1 >= PATH_MAX) {
-            /* Resist the urge to remove this limit;
-             * calling malloc after fork is unsafe. */
-            errno = ENAMETOOLONG;
-            continue;
-        }
-        strcpy(expanded_file, dir);
-        strcpy(expanded_file + dirlen, file);
-        execvp(expanded_file, (char **) argv);
-        /* There are 3 responses to various classes of errno:
-         * return immediately, continue (especially for ENOENT),
-         * or continue with "sticky" errno.
-         *
-         * From exec(3):
-         *
-         * If permission is denied for a file (the attempted
-         * execve returned EACCES), these functions will continue
-         * searching the rest of the search path.  If no other
-         * file is found, however, they will return with the
-         * global variable errno set to EACCES.
-         */
-        switch (errno) {
-        case EACCES:
-            sticky_errno = errno;
-            /* FALLTHRU */
-        case ENOENT:
-        case ENOTDIR:
-#ifdef ELOOP
-        case ELOOP:
-#endif
-#ifdef ESTALE
-        case ESTALE:
-#endif
-#ifdef ENODEV
-        case ENODEV:
-#endif
-#ifdef ETIMEDOUT
-        case ETIMEDOUT:
-#endif
-            break; /* Try other directories in PATH */
-        default:
-            return -1;
-        }
-    }
-    if (sticky_errno != 0)
-        errno = sticky_errno;
-    return -1;
+    /* Use the extra word of space provided for us in argv by caller. */
+    const char *argv0 = argv[0];
+    const char *const *end = argv;
+    while (*end != NULL)
+        ++end;
+    memmove(argv+2, argv+1, (end-argv) * sizeof (*end));
+    argv[0] = "/bin/sh";
+    argv[1] = file;
+    execve(argv[0], (char **) argv, (char **) envp);
+    /* Can't even exec /bin/sh?  Big trouble, but let's soldier on... */
+    memmove(argv+1, argv+2, (end-argv) * sizeof (*end));
+    argv[0] = argv0;
 }
 
-/* execvpe should have been included in the Unix standards. */
-static int
-execvpe(const char *file, const char *const argv[], const char *const envp[])
+/**
+ * Like execve(2), except that in case of ENOEXEC, FILE is assumed to
+ * be a shell script and the system default shell is invoked to run it.
+ */
+static void
+execve_with_shell_fallback(const char *file,
+                           const char *argv[],
+                           const char *const envp[])
+{
+#if USE_CLONE
+    execve(file, (char **) argv, (char **) envp);
+    if (errno == ENOEXEC)
+        execve_as_traditional_shell_script(file, argv, envp);
+#else
+    /* Our address space is unshared, so can mutate environ. */
+    extern char **environ;
+    environ = (char **) envp;
+    execvp(file, (char **) argv);
+#endif
+}
+
+/**
+ * execvpe should have been included in the Unix standards.
+ * execvpe is identical to execvp, except that the child environment is
+ * specified via the 3rd argument instead of being inherited from environ.
+ */
+static void
+execvpe(const char *file,
+        const char *argv[],
+        const char *const envp[])
 {
     /* This is one of the rare times it's more portable to declare an
      * external symbol explicitly, rather than via a system header.
@@ -454,28 +457,73 @@
      */
     extern char **environ;
 
-    if (envp != NULL)
-        environ = (char **) envp;
+    if (envp == NULL || (char **) envp == environ) {
+        execvp(file, (char **) argv);
+        return;
+    }
 
-    if (/* Parent and child environment the same?  Use child PATH. */
-        (envp == NULL)
+    if (*file == '\0') {
+        errno = ENOENT;
+        return;
+    }
 
-        /* http://www.opengroup.org/onlinepubs/009695399/functions/exec.html
-         * "If the file argument contains a slash character, it is used as
-         * the pathname for this file.  Otherwise, the path prefix for this
-         * file is obtained by a search of the directories passed in the
-         * PATH environment variable" */
-        || (strchr(file, '/') != NULL)
-
-        /* Parent and child PATH the same?  Use child PATH. */
-        || (strcmp(parentPath, effectivePath()) == 0)
-
-        /* We want ENOENT, not EACCES, for zero-length program names. */
-        || (*file == '\0'))
-
-        return execvp(file, (char **) argv);
-    else
-        return execvp_usingParentPath(file, argv);
+    if (strchr(file, '/') != NULL) {
+        execve_with_shell_fallback(file, argv, envp);
+    } else {
+        /* We must search PATH (parent's, not child's) */
+        char expanded_file[PATH_MAX];
+        int filelen = strlen(file);
+        int sticky_errno = 0;
+        const char * const * dirs;
+        for (dirs = parentPathv; *dirs; dirs++) {
+            const char * dir = *dirs;
+            int dirlen = strlen(dir);
+            if (filelen + dirlen + 1 >= PATH_MAX) {
+                errno = ENAMETOOLONG;
+                continue;
+            }
+            memcpy(expanded_file, dir, dirlen);
+            memcpy(expanded_file + dirlen, file, filelen);
+            expanded_file[dirlen + filelen] = '\0';
+            execve_with_shell_fallback(expanded_file, argv, envp);
+            /* There are 3 responses to various classes of errno:
+             * return immediately, continue (especially for ENOENT),
+             * or continue with "sticky" errno.
+             *
+             * From exec(3):
+             *
+             * If permission is denied for a file (the attempted
+             * execve returned EACCES), these functions will continue
+             * searching the rest of the search path.  If no other
+             * file is found, however, they will return with the
+             * global variable errno set to EACCES.
+             */
+            switch (errno) {
+            case EACCES:
+                sticky_errno = errno;
+                /* FALLTHRU */
+            case ENOENT:
+            case ENOTDIR:
+#ifdef ELOOP
+            case ELOOP:
+#endif
+#ifdef ESTALE
+            case ESTALE:
+#endif
+#ifdef ENODEV
+            case ENODEV:
+#endif
+#ifdef ETIMEDOUT
+            case ETIMEDOUT:
+#endif
+                break; /* Try other directories in PATH */
+            default:
+                return;
+            }
+        }
+        if (sticky_errno != 0)
+            errno = sticky_errno;
+    }
 }
 
 static void
@@ -516,10 +564,95 @@
     }
 }
 
-#ifndef __solaris__
-#undef fork1
-#define fork1() fork()
-#endif
+typedef struct _ChildStuff
+{
+    int in[2];
+    int out[2];
+    int err[2];
+    int fail[2];
+    int fds[3];
+    const char **argv;
+    const char **envv;
+    const char *pdir;
+    jboolean redirectErrorStream;
+} ChildStuff;
+
+static void
+copyPipe(int from[2], int to[2])
+{
+    to[0] = from[0];
+    to[1] = from[1];
+}
+
+/**
+ * Child process after a successful fork() or clone().
+ * This function must not return, and must be prepared for either all
+ * of its address space to be shared with its parent, or to be a copy.
+ * It must not modify global variables such as "environ".
+ */
+static int
+childProcess(void *arg)
+{
+    const ChildStuff* p = (const ChildStuff*) arg;
+
+    /* Close the parent sides of the pipes.
+       Closing pipe fds here is redundant, since closeDescriptors()
+       would do it anyways, but a little paranoia is a good thing. */
+    closeSafely(p->in[1]);
+    closeSafely(p->out[0]);
+    closeSafely(p->err[0]);
+    closeSafely(p->fail[0]);
+
+    /* Give the child sides of the pipes the right fileno's. */
+    /* Note: it is possible for in[0] == 0 */
+    moveDescriptor(p->in[0] != -1 ?  p->in[0] : p->fds[0], STDIN_FILENO);
+    moveDescriptor(p->out[1]!= -1 ? p->out[1] : p->fds[1], STDOUT_FILENO);
+
+    if (p->redirectErrorStream) {
+      closeSafely(p->err[1]);
+      dup2(STDOUT_FILENO, STDERR_FILENO);
+    } else {
+      moveDescriptor(p->err[1] != -1 ? p->err[1] : p->fds[2], STDERR_FILENO);
+    }
+
+    moveDescriptor(p->fail[1], FAIL_FILENO);
+
+    /* close everything */
+    if (closeDescriptors() == 0) { /* failed,  close the old way */
+        int max_fd = (int)sysconf(_SC_OPEN_MAX);
+        int i;
+        for (i = FAIL_FILENO + 1; i < max_fd; i++)
+            close(i);
+    }
+
+    /* change to the new working directory */
+    if (p->pdir != NULL && chdir(p->pdir) < 0)
+        goto WhyCantJohnnyExec;
+
+    if (fcntl(FAIL_FILENO, F_SETFD, FD_CLOEXEC) == -1)
+        goto WhyCantJohnnyExec;
+
+    execvpe(p->argv[0], p->argv, p->envv);
+
+ WhyCantJohnnyExec:
+    /* We used to go to an awful lot of trouble to predict whether the
+     * child would fail, but there is no reliable way to predict the
+     * success of an operation without *trying* it, and there's no way
+     * to try a chdir or exec in the parent.  Instead, all we need is a
+     * way to communicate any failure back to the parent.  Easy; we just
+     * send the errno back to the parent over a pipe in case of failure.
+     * The tricky thing is, how do we communicate the *success* of exec?
+     * We use FD_CLOEXEC together with the fact that a read() on a pipe
+     * yields EOF when the write ends (we have two of them!) are closed.
+     */
+    {
+        int errnum = errno;
+        write(FAIL_FILENO, &errnum, sizeof(errnum));
+    }
+    close(FAIL_FILENO);
+    _exit(-1);
+    return 0;  /* Suppress warning "no return value from function" */
+}
 
 JNIEXPORT jint JNICALL
 Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
@@ -533,34 +666,43 @@
 {
     int errnum;
     int resultPid = -1;
+#if USE_CLONE
+    void *clone_stack = NULL;
+#endif
     int in[2], out[2], err[2], fail[2];
-    const char **argv = NULL;
-    const char **envv = NULL;
-    const char *pprog     = getBytes(env, prog);
-    const char *pargBlock = getBytes(env, argBlock);
-    const char *penvBlock = getBytes(env, envBlock);
-    const char *pdir      = getBytes(env, dir);
     jint *fds = NULL;
+    const char *pprog = NULL;
+    const char *pargBlock = NULL;
+    const char *penvBlock = NULL;
+    ChildStuff *c;
 
     in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1;
 
-    assert(prog != NULL && argBlock != NULL);
-    if (pprog     == NULL) goto Catch;
-    if (pargBlock == NULL) goto Catch;
-    if (envBlock  != NULL && penvBlock == NULL) goto Catch;
-    if (dir       != NULL && pdir      == NULL) goto Catch;
+    if ((c = NEW(ChildStuff, 1)) == NULL) return -1;
+    c->argv = NULL;
+    c->envv = NULL;
+    c->pdir = NULL;
 
-    /* Convert pprog + pargBlock into a char ** argv */
-    if ((argv = NEW(const char *, argc + 2)) == NULL)
-        goto Catch;
-    argv[0] = pprog;
-    initVectorFromBlock(argv+1, pargBlock, argc);
+    /* Convert prog + argBlock into a char ** argv.
+     * Add one word room for expansion of argv for use by
+     * execve_as_traditional_shell_script.
+     */
+    assert(prog != NULL && argBlock != NULL);
+    if ((pprog     = getBytes(env, prog))       == NULL) goto Catch;
+    if ((pargBlock = getBytes(env, argBlock))   == NULL) goto Catch;
+    if ((c->argv = NEW(const char *, argc + 3)) == NULL) goto Catch;
+    c->argv[0] = pprog;
+    initVectorFromBlock(c->argv+1, pargBlock, argc);
 
     if (envBlock != NULL) {
-        /* Convert penvBlock into a char ** envv */
-        if ((envv = NEW(const char *, envc + 1)) == NULL)
-            goto Catch;
-        initVectorFromBlock(envv, penvBlock, envc);
+        /* Convert envBlock into a char ** envv */
+        if ((penvBlock = getBytes(env, envBlock))   == NULL) goto Catch;
+        if ((c->envv = NEW(const char *, envc + 1)) == NULL) goto Catch;
+        initVectorFromBlock(c->envv, penvBlock, envc);
+    }
+
+    if (dir != NULL) {
+        if ((c->pdir = getBytes(env, dir)) == NULL) goto Catch;
     }
 
     assert(std_fds != NULL);
@@ -574,72 +716,45 @@
         throwIOException(env, errno, "Bad file descriptor");
         goto Catch;
     }
+    c->fds[0] = fds[0];
+    c->fds[1] = fds[1];
+    c->fds[2] = fds[2];
 
-    resultPid = fork1();
+    copyPipe(in,   c->in);
+    copyPipe(out,  c->out);
+    copyPipe(err,  c->err);
+    copyPipe(fail, c->fail);
+
+    c->redirectErrorStream = redirectErrorStream;
+
+    {
+#if USE_CLONE
+        /* See clone(2).
+         * Instead of worrying about which direction the stack grows, just
+         * allocate twice as much and start the stack in the middle. */
+        const int stack_size = 64 * 1024;
+        if ((clone_stack = NEW(char, 2 * stack_size)) == NULL) goto Catch;
+        resultPid = clone(childProcess, clone_stack + stack_size,
+                          /* CLONE_VFORK | // works, but unnecessary */
+                          CLONE_VM | SIGCHLD, c);
+#else
+        /* From fork(2): In Solaris 10, a call to fork() is identical
+         * to a call to fork1(); only the calling thread is replicated
+         * in the child process. This is the POSIX-specified behavior
+         * for fork(). */
+        resultPid = fork();
+        if (resultPid == 0) {
+          childProcess(c);
+          assert(0);  /* childProcess must not return */
+        }
+#endif
+    }
+
     if (resultPid < 0) {
         throwIOException(env, errno, "Fork failed");
         goto Catch;
     }
 
-    if (resultPid == 0) {
-        /* Child process */
-
-        /* Close the parent sides of the pipes.
-           Closing pipe fds here is redundant, since closeDescriptors()
-           would do it anyways, but a little paranoia is a good thing. */
-        closeSafely(in[1]);
-        closeSafely(out[0]);
-        closeSafely(err[0]);
-        closeSafely(fail[0]);
-
-        /* Give the child sides of the pipes the right fileno's. */
-        /* Note: it is possible for in[0] == 0 */
-        moveDescriptor(in[0] != -1 ?  in[0] : fds[0], STDIN_FILENO);
-        moveDescriptor(out[1]!= -1 ? out[1] : fds[1], STDOUT_FILENO);
-
-        if (redirectErrorStream) {
-            closeSafely(err[1]);
-            dup2(STDOUT_FILENO, STDERR_FILENO);
-        } else {
-            moveDescriptor(err[1] != -1 ? err[1] : fds[2], STDERR_FILENO);
-        }
-
-        moveDescriptor(fail[1], FAIL_FILENO);
-
-        /* close everything */
-        if (closeDescriptors() == 0) { /* failed,  close the old way */
-            int max_fd = (int)sysconf(_SC_OPEN_MAX);
-            int i;
-            for (i = FAIL_FILENO + 1; i < max_fd; i++)
-                close(i);
-        }
-
-        /* change to the new working directory */
-        if (pdir != NULL && chdir(pdir) < 0)
-            goto WhyCantJohnnyExec;
-
-        if (fcntl(FAIL_FILENO, F_SETFD, FD_CLOEXEC) == -1)
-            goto WhyCantJohnnyExec;
-
-        execvpe(argv[0], argv, envv);
-
-    WhyCantJohnnyExec:
-        /* We used to go to an awful lot of trouble to predict whether the
-         * child would fail, but there is no reliable way to predict the
-         * success of an operation without *trying* it, and there's no way
-         * to try a chdir or exec in the parent.  Instead, all we need is a
-         * way to communicate any failure back to the parent.  Easy; we just
-         * send the errno back to the parent over a pipe in case of failure.
-         * The tricky thing is, how do we communicate the *success* of exec?
-         * We use FD_CLOEXEC together with the fact that a read() on a pipe
-         * yields EOF when the write ends (we have two of them!) are closed.
-         */
-        errnum = errno;
-        write(FAIL_FILENO, &errnum, sizeof(errnum));
-        close(FAIL_FILENO);
-        _exit(-1);
-    }
-
     /* parent process */
 
     close(fail[1]); fail[1] = -1; /* See: WhyCantJohnnyExec */
@@ -660,6 +775,10 @@
     fds[2] = (err[0] != -1) ? err[0] : -1;
 
  Finally:
+#if USE_CLONE
+    free(clone_stack);
+#endif
+
     /* Always clean up the child's side of the pipes */
     closeSafely(in [0]);
     closeSafely(out[1]);
@@ -669,13 +788,14 @@
     closeSafely(fail[0]);
     closeSafely(fail[1]);
 
-    free(argv);
-    free(envv);
-
     releaseBytes(env, prog,     pprog);
     releaseBytes(env, argBlock, pargBlock);
     releaseBytes(env, envBlock, penvBlock);
-    releaseBytes(env, dir,      pdir);
+    releaseBytes(env, dir,      c->pdir);
+
+    free(c->argv);
+    free(c->envv);
+    free(c);
 
     if (fds != NULL)
         (*env)->ReleaseIntArrayElements(env, std_fds, fds, 0);