mirror of
https://github.com/openjdk/jdk.git
synced 2026-05-11 05:59:52 +00:00
8379967: (process) Improve ProcessBuilder error reporting
Reviewed-by: mbaesken, rriggs
This commit is contained in:
parent
318646a6b4
commit
2240ff4179
@ -95,7 +95,8 @@ ifeq ($(call isTargetOsType, unix), true)
|
||||
CFLAGS := $(VERSION_CFLAGS), \
|
||||
EXTRA_HEADER_DIRS := libjava, \
|
||||
EXTRA_OBJECT_FILES := \
|
||||
$(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libjava/childproc$(OBJ_SUFFIX), \
|
||||
$(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libjava/childproc$(OBJ_SUFFIX) \
|
||||
$(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libjava/childproc_errorcodes$(OBJ_SUFFIX), \
|
||||
LD_SET_ORIGIN := false, \
|
||||
OUTPUT_DIR := $(SUPPORT_OUTPUTDIR)/modules_libs/$(MODULE), \
|
||||
))
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "childproc.h"
|
||||
#include "childproc_errorcodes.h"
|
||||
|
||||
extern int errno;
|
||||
|
||||
@ -41,36 +42,34 @@ extern int errno;
|
||||
void *mptr; \
|
||||
mptr = malloc (Y); \
|
||||
if (mptr == 0) { \
|
||||
error (fdout, ERR_MALLOC); \
|
||||
sendErrorCodeAndExit (fdout, ESTEP_JSPAWN_ALLOC_FAILED, (int)Y, errno); \
|
||||
} \
|
||||
X = mptr; \
|
||||
}
|
||||
|
||||
#define ERR_MALLOC 1
|
||||
#define ERR_PIPE 2
|
||||
#define ERR_ARGS 3
|
||||
|
||||
#ifndef VERSION_STRING
|
||||
#error VERSION_STRING must be defined
|
||||
#endif
|
||||
|
||||
void error (int fd, int err) {
|
||||
if (write (fd, &err, sizeof(err)) != sizeof(err)) {
|
||||
/* Not sure what to do here. I have no one to speak to. */
|
||||
exit(0x80 + err);
|
||||
/* Attempts to send an error code to the parent (which may or may not
|
||||
* work depending on whether the fail pipe exists); then exits with an
|
||||
* error code corresponding to the fail step. */
|
||||
void sendErrorCodeAndExit(int failpipe_fd, int step, int hint, int errno_) {
|
||||
errcode_t errcode;
|
||||
buildErrorCode(&errcode, step, hint, errno_);
|
||||
if (failpipe_fd == -1 || !sendErrorCode(failpipe_fd, errcode)) {
|
||||
/* Write error code to stdout, in the hope someone reads this. */
|
||||
printf("jspawnhelper fail: " ERRCODE_FORMAT "\n", ERRCODE_FORMAT_ARGS(errcode));
|
||||
}
|
||||
exit (1);
|
||||
exit(exitCodeFromErrorCode(errcode));
|
||||
}
|
||||
|
||||
void shutItDown() {
|
||||
fprintf(stdout, "jspawnhelper version %s\n", VERSION_STRING);
|
||||
fprintf(stdout, "This command is not for general use and should ");
|
||||
fprintf(stdout, "only be run as the result of a call to\n");
|
||||
fprintf(stdout, "ProcessBuilder.start() or Runtime.exec() in a java ");
|
||||
fprintf(stdout, "application\n");
|
||||
fflush(stdout);
|
||||
_exit(1);
|
||||
}
|
||||
static const char* usageErrorText =
|
||||
"jspawnhelper version " VERSION_STRING "\n"
|
||||
"This command is not for general use and should "
|
||||
"only be run as the result of a call to\n"
|
||||
"ProcessBuilder.start() or Runtime.exec() in a java "
|
||||
"application\n";
|
||||
|
||||
/*
|
||||
* read the following off the pipefd
|
||||
@ -84,22 +83,31 @@ void initChildStuff (int fdin, int fdout, ChildStuff *c) {
|
||||
int bufsize, offset=0;
|
||||
int magic;
|
||||
int res;
|
||||
const int step = ESTEP_JSPAWN_RCV_CHILDSTUFF_COMM_FAIL;
|
||||
int substep = 0;
|
||||
|
||||
res = readFully (fdin, &magic, sizeof(magic));
|
||||
if (res != 4 || magic != magicNumber()) {
|
||||
error (fdout, ERR_PIPE);
|
||||
if (res != 4) {
|
||||
sendErrorCodeAndExit(fdout, step, substep, errno);
|
||||
}
|
||||
|
||||
substep ++;
|
||||
if (magic != magicNumber()) {
|
||||
sendErrorCodeAndExit(fdout, step, substep, errno);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
jtregSimulateCrash(0, 5);
|
||||
#endif
|
||||
|
||||
substep ++;
|
||||
if (readFully (fdin, c, sizeof(*c)) != sizeof(*c)) {
|
||||
error (fdout, ERR_PIPE);
|
||||
sendErrorCodeAndExit(fdout, step, substep, errno);
|
||||
}
|
||||
|
||||
substep ++;
|
||||
if (readFully (fdin, &sp, sizeof(sp)) != sizeof(sp)) {
|
||||
error (fdout, ERR_PIPE);
|
||||
sendErrorCodeAndExit(fdout, step, substep, errno);
|
||||
}
|
||||
|
||||
bufsize = sp.argvBytes + sp.envvBytes +
|
||||
@ -107,8 +115,9 @@ void initChildStuff (int fdin, int fdout, ChildStuff *c) {
|
||||
|
||||
ALLOC(buf, bufsize);
|
||||
|
||||
substep++;
|
||||
if (readFully (fdin, buf, bufsize) != bufsize) {
|
||||
error (fdout, ERR_PIPE);
|
||||
sendErrorCodeAndExit(fdout, step, substep, errno);
|
||||
}
|
||||
|
||||
/* Initialize argv[] */
|
||||
@ -150,25 +159,29 @@ int main(int argc, char *argv[]) {
|
||||
#endif
|
||||
|
||||
if (argc != 3) {
|
||||
fprintf(stdout, "Incorrect number of arguments: %d\n", argc);
|
||||
shutItDown();
|
||||
printf("Incorrect number of arguments: %d\n", argc);
|
||||
puts(usageErrorText);
|
||||
sendErrorCodeAndExit(-1, ESTEP_JSPAWN_ARG_ERROR, 0, 0);
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], VERSION_STRING) != 0) {
|
||||
fprintf(stdout, "Incorrect Java version: %s\n", argv[1]);
|
||||
shutItDown();
|
||||
printf("Incorrect Java version: %s\n", argv[1]);
|
||||
puts(usageErrorText);
|
||||
sendErrorCodeAndExit(-1, ESTEP_JSPAWN_VERSION_ERROR, 0, 0);
|
||||
}
|
||||
|
||||
r = sscanf (argv[2], "%d:%d:%d", &fdinr, &fdinw, &fdout);
|
||||
if (r == 3 && fcntl(fdinr, F_GETFD) != -1 && fcntl(fdinw, F_GETFD) != -1) {
|
||||
fstat(fdinr, &buf);
|
||||
if (!S_ISFIFO(buf.st_mode)) {
|
||||
fprintf(stdout, "Incorrect input pipe\n");
|
||||
shutItDown();
|
||||
printf("Incorrect input pipe\n");
|
||||
puts(usageErrorText);
|
||||
sendErrorCodeAndExit(-1, ESTEP_JSPAWN_NOT_A_PIPE, fdinr, errno);
|
||||
}
|
||||
} else {
|
||||
fprintf(stdout, "Incorrect FD array data: %s\n", argv[2]);
|
||||
shutItDown();
|
||||
printf("Incorrect FD array data: %s\n", argv[2]);
|
||||
puts(usageErrorText);
|
||||
sendErrorCodeAndExit(-1, ESTEP_JSPAWN_NOT_A_PIPE, fdinr, errno);
|
||||
}
|
||||
|
||||
// Close the writing end of the pipe we use for reading from the parent.
|
||||
|
||||
@ -47,6 +47,7 @@
|
||||
#include <spawn.h>
|
||||
|
||||
#include "childproc.h"
|
||||
#include "childproc_errorcodes.h"
|
||||
|
||||
/*
|
||||
*
|
||||
@ -678,7 +679,6 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
|
||||
jintArray std_fds,
|
||||
jboolean redirectErrorStream)
|
||||
{
|
||||
int errnum;
|
||||
int resultPid = -1;
|
||||
int in[2], out[2], err[2], fail[2], childenv[2];
|
||||
jint *fds = NULL;
|
||||
@ -782,9 +782,11 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
|
||||
}
|
||||
close(fail[1]); fail[1] = -1; /* See: WhyCantJohnnyExec (childproc.c) */
|
||||
|
||||
errcode_t errcode;
|
||||
|
||||
/* If we expect the child to ping aliveness, wait for it. */
|
||||
if (c->sendAlivePing) {
|
||||
switch(readFully(fail[0], &errnum, sizeof(errnum))) {
|
||||
switch(readFully(fail[0], &errcode, sizeof(errcode))) {
|
||||
case 0: /* First exec failed; */
|
||||
{
|
||||
int tmpStatus = 0;
|
||||
@ -792,13 +794,15 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
|
||||
throwExitCause(env, p, tmpStatus, c->mode);
|
||||
goto Catch;
|
||||
}
|
||||
case sizeof(errnum):
|
||||
if (errnum != CHILD_IS_ALIVE) {
|
||||
/* This can happen if the spawn helper encounters an error
|
||||
* before or during the handshake with the parent. */
|
||||
throwInternalIOException(env, 0,
|
||||
"Bad code from spawn helper (Failed to exec spawn helper)",
|
||||
c->mode);
|
||||
case sizeof(errcode):
|
||||
if (errcode.step != ESTEP_CHILD_ALIVE) {
|
||||
/* This can happen if the child process encounters an error
|
||||
* before or during initial handshake with the parent. */
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg),
|
||||
"Bad early code from spawn helper " ERRCODE_FORMAT " (Failed to exec spawn helper)",
|
||||
ERRCODE_FORMAT_ARGS(errcode));
|
||||
throwInternalIOException(env, 0, msg, c->mode);
|
||||
goto Catch;
|
||||
}
|
||||
break;
|
||||
@ -808,11 +812,29 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
|
||||
}
|
||||
}
|
||||
|
||||
switch (readFully(fail[0], &errnum, sizeof(errnum))) {
|
||||
switch (readFully(fail[0], &errcode, sizeof(errcode))) {
|
||||
case 0: break; /* Exec succeeded */
|
||||
case sizeof(errnum):
|
||||
case sizeof(errcode):
|
||||
/* Always reap first! */
|
||||
waitpid(resultPid, NULL, 0);
|
||||
throwIOException(env, errnum, "Exec failed");
|
||||
/* Most of these errors are implementation errors and should result in an internal IOE, but
|
||||
* a few can be caused by bad user input and need to be communicated to the end user. */
|
||||
switch(errcode.step) {
|
||||
case ESTEP_CHDIR_FAIL:
|
||||
throwIOException(env, errcode.errno_, "Failed to access working directory");
|
||||
break;
|
||||
case ESTEP_EXEC_FAIL:
|
||||
throwIOException(env, errcode.errno_, "Exec failed");
|
||||
break;
|
||||
default: {
|
||||
/* Probably implementation error */
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg),
|
||||
"Bad code from spawn helper " ERRCODE_FORMAT " (Failed to exec spawn helper)",
|
||||
ERRCODE_FORMAT_ARGS(errcode));
|
||||
throwInternalIOException(env, 0, msg, c->mode);
|
||||
}
|
||||
};
|
||||
goto Catch;
|
||||
default:
|
||||
throwInternalIOException(env, errno, "Read failed", c->mode);
|
||||
|
||||
@ -34,16 +34,27 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include "childproc.h"
|
||||
#include "childproc_errorcodes.h"
|
||||
#include "jni_util.h"
|
||||
|
||||
const char * const *parentPathv;
|
||||
|
||||
static int
|
||||
restartableDup2(int fd_from, int fd_to)
|
||||
/* All functions taking an errcode_t* as output behave the same: upon error, they populate
|
||||
* errcode_t::hint and errcode_t::errno, but leave errcode_t::step as ESTEP_UNKNOWN since
|
||||
* this information will be provided by the outer caller */
|
||||
|
||||
static bool
|
||||
restartableDup2(int fd_from, int fd_to, errcode_t* errcode)
|
||||
{
|
||||
int err;
|
||||
RESTARTABLE(dup2(fd_from, fd_to), err);
|
||||
return err;
|
||||
if (err == -1) {
|
||||
/* use fd_to (the destination descriptor) as hint: it is a bit more telling
|
||||
* than fd_from in our case */
|
||||
buildErrorCode(errcode, ESTEP_UNKNOWN, fd_to, errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
@ -52,6 +63,16 @@ closeSafely(int fd)
|
||||
return (fd == -1) ? 0 : close(fd);
|
||||
}
|
||||
|
||||
/* Like closeSafely, but sets errcode (hint = fd, errno) on error and returns false */
|
||||
static bool
|
||||
closeSafely2(int fd, errcode_t* errcode) {
|
||||
if (closeSafely(fd) == -1) {
|
||||
buildErrorCode(errcode, ESTEP_UNKNOWN, fd, errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
markCloseOnExec(int fd)
|
||||
{
|
||||
@ -128,15 +149,19 @@ markDescriptorsCloseOnExec(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
moveDescriptor(int fd_from, int fd_to)
|
||||
static bool
|
||||
moveDescriptor(int fd_from, int fd_to, errcode_t* errcode)
|
||||
{
|
||||
if (fd_from != fd_to) {
|
||||
if ((restartableDup2(fd_from, fd_to) == -1) ||
|
||||
(close(fd_from) == -1))
|
||||
return -1;
|
||||
if (!restartableDup2(fd_from, fd_to, errcode)) {
|
||||
return false;
|
||||
}
|
||||
if (close(fd_from) == -1) {
|
||||
buildErrorCode(errcode, ESTEP_UNKNOWN, fd_from, errno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
@ -367,15 +392,16 @@ int
|
||||
childProcess(void *arg)
|
||||
{
|
||||
const ChildStuff* p = (const ChildStuff*) arg;
|
||||
int fail_pipe_fd = p->fail[1];
|
||||
|
||||
if (p->sendAlivePing) {
|
||||
/* Child shall signal aliveness to parent at the very first
|
||||
* moment. */
|
||||
int code = CHILD_IS_ALIVE;
|
||||
if (writeFully(fail_pipe_fd, &code, sizeof(code)) != sizeof(code)) {
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
int fail_pipe_fd = p->fail[1];
|
||||
/* error information for WhyCantJohnnyExec */
|
||||
errcode_t errcode;
|
||||
|
||||
/* Child shall signal aliveness to parent at the very first
|
||||
* moment. */
|
||||
if (p->sendAlivePing && !sendAlivePing(fail_pipe_fd)) {
|
||||
buildErrorCode(&errcode, ESTEP_SENDALIVE_FAIL, fail_pipe_fd, errno);
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
@ -384,34 +410,49 @@ childProcess(void *arg)
|
||||
/* Close the parent sides of the pipes.
|
||||
Closing pipe fds here is redundant, since markDescriptorsCloseOnExec()
|
||||
would do it anyways, but a little paranoia is a good thing. */
|
||||
if ((closeSafely(p->in[1]) == -1) ||
|
||||
(closeSafely(p->out[0]) == -1) ||
|
||||
(closeSafely(p->err[0]) == -1) ||
|
||||
(closeSafely(p->childenv[0]) == -1) ||
|
||||
(closeSafely(p->childenv[1]) == -1) ||
|
||||
(closeSafely(p->fail[0]) == -1))
|
||||
if (!closeSafely2(p->in[1], &errcode) ||
|
||||
!closeSafely2(p->out[0], &errcode) ||
|
||||
!closeSafely2(p->err[0], &errcode) ||
|
||||
!closeSafely2(p->childenv[0], &errcode) ||
|
||||
!closeSafely2(p->childenv[1], &errcode) ||
|
||||
!closeSafely2(p->fail[0], &errcode))
|
||||
{
|
||||
errcode.step = ESTEP_PIPECLOSE_FAIL;
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
|
||||
/* Give the child sides of the pipes the right fileno's. */
|
||||
/* Note: it is possible for in[0] == 0 */
|
||||
if ((moveDescriptor(p->in[0] != -1 ? p->in[0] : p->fds[0],
|
||||
STDIN_FILENO) == -1) ||
|
||||
(moveDescriptor(p->out[1]!= -1 ? p->out[1] : p->fds[1],
|
||||
STDOUT_FILENO) == -1))
|
||||
if (!moveDescriptor(p->in[0] != -1 ? p->in[0] : p->fds[0],
|
||||
STDIN_FILENO, &errcode)) {
|
||||
errcode.step = ESTEP_DUP2_STDIN_FAIL;
|
||||
goto WhyCantJohnnyExec;
|
||||
|
||||
if (p->redirectErrorStream) {
|
||||
if ((closeSafely(p->err[1]) == -1) ||
|
||||
(restartableDup2(STDOUT_FILENO, STDERR_FILENO) == -1))
|
||||
goto WhyCantJohnnyExec;
|
||||
} else {
|
||||
if (moveDescriptor(p->err[1] != -1 ? p->err[1] : p->fds[2],
|
||||
STDERR_FILENO) == -1)
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
|
||||
if (moveDescriptor(fail_pipe_fd, FAIL_FILENO) == -1)
|
||||
if (!moveDescriptor(p->out[1] != -1 ? p->out[1] : p->fds[1],
|
||||
STDOUT_FILENO, &errcode)) {
|
||||
errcode.step = ESTEP_DUP2_STDOUT_FAIL;
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
|
||||
if (p->redirectErrorStream) {
|
||||
if (!closeSafely2(p->err[1], &errcode) ||
|
||||
!restartableDup2(STDOUT_FILENO, STDERR_FILENO, &errcode)) {
|
||||
errcode.step = ESTEP_DUP2_STDERR_REDIRECT_FAIL;
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
} else {
|
||||
if (!moveDescriptor(p->err[1] != -1 ? p->err[1] : p->fds[2],
|
||||
STDERR_FILENO, &errcode)) {
|
||||
errcode.step = ESTEP_DUP2_STDERR_REDIRECT_FAIL;
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
}
|
||||
|
||||
if (!moveDescriptor(fail_pipe_fd, FAIL_FILENO, &errcode)) {
|
||||
errcode.step = ESTEP_DUP2_FAILPIPE_FAIL;
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
|
||||
/* We moved the fail pipe fd */
|
||||
fail_pipe_fd = FAIL_FILENO;
|
||||
@ -424,14 +465,19 @@ childProcess(void *arg)
|
||||
if (markDescriptorsCloseOnExec() == -1) { /* failed, close the old way */
|
||||
int max_fd = (int)sysconf(_SC_OPEN_MAX);
|
||||
int fd;
|
||||
for (fd = STDERR_FILENO + 1; fd < max_fd; fd++)
|
||||
if (markCloseOnExec(fd) == -1 && errno != EBADF)
|
||||
for (fd = STDERR_FILENO + 1; fd < max_fd; fd++) {
|
||||
if (markCloseOnExec(fd) == -1 && errno != EBADF) {
|
||||
buildErrorCode(&errcode, ESTEP_CLOEXEC_FAIL, fd, errno);
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* change to the new working directory */
|
||||
if (p->pdir != NULL && chdir(p->pdir) < 0)
|
||||
if (p->pdir != NULL && chdir(p->pdir) < 0) {
|
||||
buildErrorCode(&errcode, ESTEP_CHDIR_FAIL, 0, errno);
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
|
||||
// Reset any mask signals from parent, but not in VFORK mode
|
||||
if (p->mode != MODE_VFORK) {
|
||||
@ -442,28 +488,32 @@ childProcess(void *arg)
|
||||
|
||||
// Children should be started with default signal disposition for SIGPIPE
|
||||
if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
|
||||
buildErrorCode(&errcode, ESTEP_SET_SIGPIPE, 0, errno);
|
||||
goto WhyCantJohnnyExec;
|
||||
}
|
||||
|
||||
JDK_execvpe(p->mode, p->argv[0], p->argv, p->envv);
|
||||
|
||||
/* Still here. Hmm. */
|
||||
buildErrorCode(&errcode, ESTEP_EXEC_FAIL, 0, errno);
|
||||
|
||||
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.
|
||||
* send the errorcode 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;
|
||||
writeFully(fail_pipe_fd, &errnum, sizeof(errnum));
|
||||
if (!sendErrorCode(fail_pipe_fd, errcode)) {
|
||||
printf("childproc fail: " ERRCODE_FORMAT "\n", ERRCODE_FORMAT_ARGS(errcode));
|
||||
}
|
||||
int exitcode = exitCodeFromErrorCode(errcode);
|
||||
close(fail_pipe_fd);
|
||||
_exit(-1);
|
||||
_exit(exitcode);
|
||||
return 0; /* Suppress warning "no return value from function" */
|
||||
}
|
||||
|
||||
|
||||
@ -107,13 +107,6 @@ typedef struct _SpawnInfo {
|
||||
int parentPathvBytes; /* total number of bytes in parentPathv array */
|
||||
} SpawnInfo;
|
||||
|
||||
/* If ChildStuff.sendAlivePing is true, child shall signal aliveness to
|
||||
* the parent the moment it gains consciousness, before any subsequent
|
||||
* pre-exec errors could happen.
|
||||
* This code must fit into an int and not be a valid errno value on any of
|
||||
* our platforms. */
|
||||
#define CHILD_IS_ALIVE 65535
|
||||
|
||||
/**
|
||||
* The cached and split version of the JDK's effective PATH.
|
||||
* (We don't support putenv("PATH=...") in native code)
|
||||
|
||||
63
src/java.base/unix/native/libjava/childproc_errorcodes.c
Normal file
63
src/java.base/unix/native/libjava/childproc_errorcodes.c
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include "childproc.h"
|
||||
#include "childproc_errorcodes.h"
|
||||
|
||||
void buildErrorCode(errcode_t* errcode, int step, int hint, int errno_) {
|
||||
errcode_t e;
|
||||
|
||||
assert(step < (1 << 8));
|
||||
e.step = step;
|
||||
|
||||
assert(errno_ < (1 << 8));
|
||||
e.errno_ = errno_;
|
||||
|
||||
const int maxhint = (1 << 16);
|
||||
e.hint = hint < maxhint ? hint : maxhint;
|
||||
|
||||
(*errcode) = e;
|
||||
}
|
||||
|
||||
int exitCodeFromErrorCode(errcode_t errcode) {
|
||||
/* We use the fail step number as exit code, but avoid 0 and 1
|
||||
* and try to avoid the [128..256) range since that one is used by
|
||||
* shells to codify abnormal kills by signal. */
|
||||
return 0x10 + errcode.step;
|
||||
}
|
||||
|
||||
bool sendErrorCode(int fd, errcode_t errcode) {
|
||||
return writeFully(fd, &errcode, sizeof(errcode)) == sizeof(errcode);
|
||||
}
|
||||
|
||||
bool sendAlivePing(int fd) {
|
||||
errcode_t errcode;
|
||||
buildErrorCode(&errcode, ESTEP_CHILD_ALIVE, getpid(), 0);
|
||||
return sendErrorCode(fd, errcode);
|
||||
}
|
||||
117
src/java.base/unix/native/libjava/childproc_errorcodes.h
Normal file
117
src/java.base/unix/native/libjava/childproc_errorcodes.h
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#ifndef CHILDPROC_ERRORCODES_H
|
||||
#define CHILDPROC_ERRORCODES_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct errcode_t_ {
|
||||
unsigned step : 8;
|
||||
unsigned hint : 16;
|
||||
unsigned errno_ : 8;
|
||||
} errcode_t;
|
||||
|
||||
/* Helper macros for printing an errcode_t */
|
||||
#define ERRCODE_FORMAT "(%u-%u-%u)"
|
||||
#define ERRCODE_FORMAT_ARGS(errcode) errcode.step, errcode.hint, errcode.errno_
|
||||
|
||||
|
||||
/* Builds up an error code.
|
||||
* Note:
|
||||
* - hint will be capped at 2^16
|
||||
* - both step and errno_ must fit into 8 bits. */
|
||||
void buildErrorCode(errcode_t* errcode, int step, int hint, int errno_);
|
||||
|
||||
/* Sends an error code down a pipe. Returns true if sent successfully. */
|
||||
bool sendErrorCode(int fd, errcode_t errcode);
|
||||
|
||||
/* Build an exit code for an errcode (used as child process exit code
|
||||
* in addition to the errcode being sent to parent). */
|
||||
int exitCodeFromErrorCode(errcode_t errcode);
|
||||
|
||||
/* Sends alive ping down a pipe. Returns true if sent successfully. */
|
||||
bool sendAlivePing(int fd);
|
||||
|
||||
#define ESTEP_UNKNOWN 0
|
||||
|
||||
/* not an error code, but an "I am alive" ping from the child.
|
||||
* hint is child pid, errno is 0. */
|
||||
#define ESTEP_CHILD_ALIVE 255
|
||||
|
||||
/* JspawnHelper */
|
||||
#define ESTEP_JSPAWN_ARG_ERROR 1
|
||||
#define ESTEP_JSPAWN_VERSION_ERROR 2
|
||||
|
||||
/* Checking file descriptor setup
|
||||
* hint is the (16-bit-capped) fd number */
|
||||
#define ESTEP_JSPAWN_INVALID_FD 3
|
||||
#define ESTEP_JSPAWN_NOT_A_PIPE 4
|
||||
|
||||
/* Allocation fail in jspawnhelper.
|
||||
* hint is the (16-bit-capped) fail size */
|
||||
#define ESTEP_JSPAWN_ALLOC_FAILED 5
|
||||
|
||||
/* Receiving Childstuff from parent, communication error.
|
||||
* hint is the substep. */
|
||||
#define ESTEP_JSPAWN_RCV_CHILDSTUFF_COMM_FAIL 6
|
||||
|
||||
/* Expand if needed ... */
|
||||
|
||||
/* childproc() */
|
||||
|
||||
/* Failed to send aliveness ping
|
||||
* hint is the (16-bit-capped) fd. */
|
||||
#define ESTEP_SENDALIVE_FAIL 10
|
||||
|
||||
/* Failed to close a pipe in fork mode
|
||||
* hint is the (16-bit-capped) fd. */
|
||||
#define ESTEP_PIPECLOSE_FAIL 11
|
||||
|
||||
/* Failed to dup2 a file descriptor in fork mode.
|
||||
* hint is the (16-bit-capped) fd_to (!) */
|
||||
#define ESTEP_DUP2_STDIN_FAIL 13
|
||||
#define ESTEP_DUP2_STDOUT_FAIL 14
|
||||
#define ESTEP_DUP2_STDERR_REDIRECT_FAIL 15
|
||||
#define ESTEP_DUP2_STDERR_FAIL 16
|
||||
#define ESTEP_DUP2_FAILPIPE_FAIL 17
|
||||
|
||||
/* Failed to mark a file descriptor as CLOEXEC
|
||||
* hint is the (16-bit-capped) fd */
|
||||
#define ESTEP_CLOEXEC_FAIL 18
|
||||
|
||||
/* Failed to chdir into the target working directory */
|
||||
#define ESTEP_CHDIR_FAIL 19
|
||||
|
||||
/* Failed to change signal disposition for SIGPIPE to default */
|
||||
#define ESTEP_SET_SIGPIPE 20
|
||||
|
||||
/* Expand if needed ... */
|
||||
|
||||
/* All modes: exec() failed */
|
||||
#define ESTEP_EXEC_FAIL 30
|
||||
|
||||
#endif /* CHILDPROC_MD_H */
|
||||
62
test/jdk/java/lang/ProcessBuilder/InvalidWorkDir.java
Normal file
62
test/jdk/java/lang/ProcessBuilder/InvalidWorkDir.java
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test id=FORK
|
||||
* @bug 8379967
|
||||
* @summary Check that passing an invalid work dir yields a corresponding IOE text.
|
||||
* @requires (os.family != "windows")
|
||||
* @requires vm.flagless
|
||||
* @library /test/lib
|
||||
* @run main/othervm -Xmx64m -Djdk.lang.Process.launchMechanism=FORK InvalidWorkDir
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test id=POSIX_SPAWN
|
||||
* @bug 8379967
|
||||
* @summary Check that passing an invalid work dir yields a corresponding IOE text.
|
||||
* @requires (os.family != "windows")
|
||||
* @requires vm.flagless
|
||||
* @library /test/lib
|
||||
* @run main/othervm -Xmx64m -Djdk.lang.Process.launchMechanism=FORK InvalidWorkDir
|
||||
*/
|
||||
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class InvalidWorkDir {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ProcessBuilder bld = new ProcessBuilder("ls").directory(new File("./doesnotexist"));
|
||||
try(Process p = bld.start()) {
|
||||
throw new RuntimeException("IOE expected");
|
||||
} catch (IOException e) {
|
||||
if (!e.getMessage().matches(".*Failed to access working directory.*No such file or directory.*")) {
|
||||
throw new RuntimeException(String.format("got IOE but with different text (%s)", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -22,11 +22,19 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8325567 8325621
|
||||
* @test id=badargs
|
||||
* @bug 8325567 8325621 8379967
|
||||
* @requires (os.family == "linux") | (os.family == "aix") | (os.family == "mac")
|
||||
* @library /test/lib
|
||||
* @run driver JspawnhelperWarnings
|
||||
* @run driver JspawnhelperWarnings badargs
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=badversion
|
||||
* @bug 8325567 8325621 8379967
|
||||
* @requires (os.family == "linux") | (os.family == "aix") | (os.family == "mac")
|
||||
* @library /test/lib
|
||||
* @run driver JspawnhelperWarnings badversion
|
||||
*/
|
||||
|
||||
import java.nio.file.Paths;
|
||||
@ -36,6 +44,13 @@ import jdk.test.lib.process.ProcessTools;
|
||||
|
||||
public class JspawnhelperWarnings {
|
||||
|
||||
// See childproc_errorcodes.h
|
||||
static final int ESTEP_JSPAWN_ARG_ERROR = 1;
|
||||
static final int ESTEP_JSPAWN_VERSION_ERROR = 2;
|
||||
|
||||
// See exitCodeFromErrorCode() in childproc_errorcodes.c
|
||||
static final int EXITCODE_OFFSET = 0x10;
|
||||
|
||||
private static void tryWithNArgs(int nArgs) throws Exception {
|
||||
System.out.println("Running jspawnhelper with " + nArgs + " args");
|
||||
String[] args = new String[nArgs + 1];
|
||||
@ -43,7 +58,8 @@ public class JspawnhelperWarnings {
|
||||
args[0] = Paths.get(System.getProperty("java.home"), "lib", "jspawnhelper").toString();
|
||||
Process p = ProcessTools.startProcess("jspawnhelper", new ProcessBuilder(args));
|
||||
OutputAnalyzer oa = new OutputAnalyzer(p);
|
||||
oa.shouldHaveExitValue(1);
|
||||
oa.shouldHaveExitValue(EXITCODE_OFFSET + ESTEP_JSPAWN_ARG_ERROR);
|
||||
oa.shouldContain("jspawnhelper fail: (1-0-0)");
|
||||
oa.shouldContain("This command is not for general use");
|
||||
if (nArgs != 2) {
|
||||
oa.shouldContain("Incorrect number of arguments");
|
||||
@ -59,16 +75,28 @@ public class JspawnhelperWarnings {
|
||||
args[2] = "1:1:1";
|
||||
Process p = ProcessTools.startProcess("jspawnhelper", new ProcessBuilder(args));
|
||||
OutputAnalyzer oa = new OutputAnalyzer(p);
|
||||
oa.shouldHaveExitValue(1);
|
||||
oa.shouldHaveExitValue(EXITCODE_OFFSET + ESTEP_JSPAWN_VERSION_ERROR);
|
||||
oa.shouldContain("jspawnhelper fail: (2-0-0)");
|
||||
oa.shouldContain("This command is not for general use");
|
||||
oa.shouldContain("Incorrect Java version: wrongVersion");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
for (int nArgs = 0; nArgs < 10; nArgs++) {
|
||||
tryWithNArgs(nArgs);
|
||||
if (args.length != 1) {
|
||||
throw new RuntimeException("test argument error");
|
||||
}
|
||||
switch (args[0]) {
|
||||
case "badargs" -> {
|
||||
for (int nArgs = 0; nArgs < 10; nArgs++) {
|
||||
if (nArgs != 2) {
|
||||
tryWithNArgs(nArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
case "badversion" -> {
|
||||
testVersion();
|
||||
}
|
||||
default -> throw new RuntimeException("test argument error");
|
||||
}
|
||||
|
||||
testVersion();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user