< 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 >