< prev index next >

src/java.base/unix/native/libjava/ProcessImpl_md.c

Print this page

        

@@ -37,10 +37,11 @@
  */
 #include <assert.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <sys/types.h>
+#include <sys/stat.h>
 #include <ctype.h>
 #include <sys/wait.h>
 #include <signal.h>
 #include <string.h>
 

@@ -322,11 +323,11 @@
 }
 
 #define IOE_FORMAT "error=%d, %s"
 
 static void
-throwIOException(JNIEnv *env, int errnum, const char *defaultDetail)
+throwIOException(JNIEnv *env, int errnum, const char *defaultDetail, const char* addinfo)
 {
     const char *detail = defaultDetail;
     char *errmsg;
     size_t fmtsize;
     char tmpbuf[1024];

@@ -336,16 +337,23 @@
         int ret = getErrorString(errnum, tmpbuf, sizeof(tmpbuf));
         if (ret != EINVAL)
             detail = tmpbuf;
     }
     /* ASCII Decimal representation uses 2.4 times as many bits as binary. */
-    fmtsize = sizeof(IOE_FORMAT) + strlen(detail) + 3 * sizeof(errnum);
+    fmtsize = sizeof(IOE_FORMAT) + strlen(detail) + 3 * sizeof(errnum) +
+              (addinfo == NULL ? 0 : 1 + strlen(addinfo) + 2); // " (<info>)"
     errmsg = NEW(char, fmtsize);
     if (errmsg == NULL)
         return;
 
     snprintf(errmsg, fmtsize, IOE_FORMAT, errnum, detail);
+    if (addinfo != NULL) {
+      strcat(errmsg, " (");
+      strcat(errmsg, addinfo);
+      strcat(errmsg, ")");
+    }
+
     s = JNU_NewStringPlatform(env, errmsg);
     if (s != NULL) {
         jobject x = JNU_NewObjectByName(env, "java/io/IOException",
                                         "(Ljava/lang/String;)V", s);
         if (x != NULL)

@@ -469,19 +477,71 @@
     }
     assert(resultPid != 0);  /* childProcess never returns */
     return resultPid;
 }
 
+/* Returns 0 if at least one executable bit is set on the spawn helper binary,
+ * -1 otherwise. */
+static int isHelperExecutable(const char* helperpath) {
+    /* Note that this check will never be sufficient; exec() may still fail
+     * for many reasons and it is impossible to predict them all. The point of
+     * this test is to catch the most common failure reason and give the caller
+     * a clear diagnostic message.
+     * But never shall this test give us false positives (make us avoid exec()ing
+     * where it would have actually worked.) So since exec() success cannot be
+     * completely predicted, we err on the side of false negatives - when in doubt,
+     * try it.
+     */
+    static int cached_rc = -2;
+    if (cached_rc == 0 || cached_rc == -1) {
+        /* The helper binary is part of the JDK and should have been set up
+         * correctly. This is very unlikely to happen intermittently. Therefore
+         * it should be okay to cache the result. */
+        return cached_rc;
+    }
+
+    struct stat s;
+    int rc = stat(helperpath, &s);
+    if (rc == 0) {
+        /* Require at least one exec bit set since this is how the spawn helper
+         * is set up in a canonical installation. */
+        if (s.st_mode & S_IXUSR ||
+            s.st_mode & S_IXGRP ||
+            s.st_mode & S_IXOTH) {
+            cached_rc = 0;
+            return 0;
+        }
+    }
+    cached_rc = -1;
+    return -1;
+}
+
 static pid_t
 spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath) {
-    pid_t resultPid;
+    pid_t resultPid = -1;
     jboolean isCopy;
     int i, offset, rval, bufsize, magic;
     char *buf, buf1[16];
     char *hlpargs[2];
     SpawnInfo sp;
 
+    /* Some posix_spawn() implementations do not report exec() errors back to the caller;
+     * POSIX leaves that up to the implementor, it only requires the child process to fail with
+     * error code 127.
+     * The problem with that is that posix_spawn() will seemingly succeed, child process will
+     * have been started but will immediately die with exit code 127. This is impossible to
+     * tell apart from cases where we successfully exec()'d the target program and it died
+     * with 127. In the former case we want to throw an IOException, the latter case is fine
+     * and should be handled by the java program.
+     * There is no perfect solution, but here we try - before starting the child - to catch
+     * obvious cases which will make the first exec() fail. */
+    if (isHelperExecutable(helperpath) != 0) {
+        errno = EACCES;
+        c->error_detail = "No execute permission for spawn helper.";
+        return -1;
+    }
+
     /* need to tell helper which fd is for receiving the childstuff
      * and which fd to send response back on
      */
     snprintf(buf1, sizeof(buf1), "%d:%d", c->childenv[0], c->fail[1]);
     /* put the fd string as argument to the helper cmd */

@@ -637,11 +697,11 @@
     if ((fds[0] == -1 && pipe(in)  < 0) ||
         (fds[1] == -1 && pipe(out) < 0) ||
         (fds[2] == -1 && pipe(err) < 0) ||
         (pipe(childenv) < 0) ||
         (pipe(fail) < 0)) {
-        throwIOException(env, errno, "Bad file descriptor");
+        throwIOException(env, errno, "Bad file descriptor", NULL);
         goto Catch;
     }
     c->fds[0] = fds[0];
     c->fds[1] = fds[1];
     c->fds[2] = fds[2];

@@ -652,38 +712,39 @@
     copyPipe(fail, c->fail);
     copyPipe(childenv, c->childenv);
 
     c->redirectErrorStream = redirectErrorStream;
     c->mode = mode;
+    c->error_detail = NULL;
 
     resultPid = startChild(env, process, c, phelperpath);
     assert(resultPid != 0);
 
     if (resultPid < 0) {
         switch (c->mode) {
           case MODE_VFORK:
-            throwIOException(env, errno, "vfork failed");
+            throwIOException(env, errno, "vfork failed", c->error_detail);
             break;
           case MODE_FORK:
-            throwIOException(env, errno, "fork failed");
+            throwIOException(env, errno, "fork failed", c->error_detail);
             break;
           case MODE_POSIX_SPAWN:
-            throwIOException(env, errno, "posix_spawn failed");
+            throwIOException(env, errno, "posix_spawn failed", c->error_detail);
             break;
         }
         goto Catch;
     }
     close(fail[1]); fail[1] = -1; /* See: WhyCantJohnnyExec  (childproc.c)  */
 
     switch (readFully(fail[0], &errnum, sizeof(errnum))) {
     case 0: break; /* Exec succeeded */
     case sizeof(errnum):
         waitpid(resultPid, NULL, 0);
-        throwIOException(env, errnum, "Exec failed");
+        throwIOException(env, errnum, "Exec failed", NULL);
         goto Catch;
     default:
-        throwIOException(env, errno, "Read failed");
+        throwIOException(env, errno, "Read failed", NULL);
         goto Catch;
     }
 
     fds[0] = (in [1] != -1) ? in [1] : -1;
     fds[1] = (out[0] != -1) ? out[0] : -1;
< prev index next >