8379967: (process) Improve ProcessBuilder error reporting

Reviewed-by: mbaesken, rriggs
This commit is contained in:
Thomas Stuefe 2026-03-16 13:31:38 +00:00
parent 318646a6b4
commit 2240ff4179
9 changed files with 455 additions and 106 deletions

View File

@ -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), \
))

View File

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

View File

@ -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);

View File

@ -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" */
}

View File

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

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

View 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 */

View 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()));
}
}
}
}

View File

@ -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();
}
}