8307990: jspawnhelper must close its writing side of a pipe before reading from it
Reviewed-by: stuefe, rriggs
This commit is contained in:
parent
4460429d7a
commit
39f6d807db
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -23,6 +23,7 @@
|
|||||||
* questions.
|
* questions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
@ -83,11 +84,15 @@ void initChildStuff (int fdin, int fdout, ChildStuff *c) {
|
|||||||
error (fdout, ERR_PIPE);
|
error (fdout, ERR_PIPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readFully (fdin, c, sizeof(*c)) == -1) {
|
#ifdef DEBUG
|
||||||
|
jtregSimulateCrash(0, 5);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (readFully (fdin, c, sizeof(*c)) != sizeof(*c)) {
|
||||||
error (fdout, ERR_PIPE);
|
error (fdout, ERR_PIPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readFully (fdin, &sp, sizeof(sp)) == -1) {
|
if (readFully (fdin, &sp, sizeof(sp)) != sizeof(sp)) {
|
||||||
error (fdout, ERR_PIPE);
|
error (fdout, ERR_PIPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +101,7 @@ void initChildStuff (int fdin, int fdout, ChildStuff *c) {
|
|||||||
|
|
||||||
ALLOC(buf, bufsize);
|
ALLOC(buf, bufsize);
|
||||||
|
|
||||||
if (readFully (fdin, buf, bufsize) == -1) {
|
if (readFully (fdin, buf, bufsize) != bufsize) {
|
||||||
error (fdout, ERR_PIPE);
|
error (fdout, ERR_PIPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,12 +137,15 @@ int main(int argc, char *argv[]) {
|
|||||||
ChildStuff c;
|
ChildStuff c;
|
||||||
struct stat buf;
|
struct stat buf;
|
||||||
/* argv[0] contains the fd number to read all the child info */
|
/* argv[0] contains the fd number to read all the child info */
|
||||||
int r, fdin, fdout;
|
int r, fdinr, fdinw, fdout;
|
||||||
sigset_t unblock_signals;
|
sigset_t unblock_signals;
|
||||||
|
|
||||||
r = sscanf (argv[argc-1], "%d:%d", &fdin, &fdout);
|
#ifdef DEBUG
|
||||||
if (r == 2 && fcntl(fdin, F_GETFD) != -1) {
|
jtregSimulateCrash(0, 4);
|
||||||
fstat(fdin, &buf);
|
#endif
|
||||||
|
r = sscanf (argv[argc-1], "%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))
|
if (!S_ISFIFO(buf.st_mode))
|
||||||
shutItDown();
|
shutItDown();
|
||||||
} else {
|
} else {
|
||||||
@ -148,7 +156,18 @@ int main(int argc, char *argv[]) {
|
|||||||
sigemptyset(&unblock_signals);
|
sigemptyset(&unblock_signals);
|
||||||
sigprocmask(SIG_SETMASK, &unblock_signals, NULL);
|
sigprocmask(SIG_SETMASK, &unblock_signals, NULL);
|
||||||
|
|
||||||
initChildStuff (fdin, fdout, &c);
|
// Close the writing end of the pipe we use for reading from the parent.
|
||||||
|
// We have to do this before we start reading from the parent to avoid
|
||||||
|
// blocking in the case the parent exits before we finished reading from it.
|
||||||
|
close(fdinw); // Deliberately ignore errors (see https://lwn.net/Articles/576478/).
|
||||||
|
initChildStuff (fdinr, fdout, &c);
|
||||||
|
// Now set the file descriptor for the pipe's writing end to -1
|
||||||
|
// for the case that somebody tries to close it again.
|
||||||
|
assert(c.childenv[1] == fdinw);
|
||||||
|
c.childenv[1] = -1;
|
||||||
|
// The file descriptor for reporting errors back to our parent we got on the command
|
||||||
|
// line should be the same like the one in the ChildStuff struct we've just read.
|
||||||
|
assert(c.fail[1] == fdout);
|
||||||
|
|
||||||
childProcess (&c);
|
childProcess (&c);
|
||||||
return 0; /* NOT REACHED */
|
return 0; /* NOT REACHED */
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1995, 2022, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1995, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -487,14 +487,14 @@ static pid_t
|
|||||||
spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath) {
|
spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath) {
|
||||||
pid_t resultPid;
|
pid_t resultPid;
|
||||||
int i, offset, rval, bufsize, magic;
|
int i, offset, rval, bufsize, magic;
|
||||||
char *buf, buf1[(2 * 11) + 2]; // "%d:%d\0"
|
char *buf, buf1[(3 * 11) + 3]; // "%d:%d:%d\0"
|
||||||
char *hlpargs[2];
|
char *hlpargs[2];
|
||||||
SpawnInfo sp;
|
SpawnInfo sp;
|
||||||
|
|
||||||
/* need to tell helper which fd is for receiving the childstuff
|
/* need to tell helper which fd is for receiving the childstuff
|
||||||
* and which fd to send response back on
|
* and which fd to send response back on
|
||||||
*/
|
*/
|
||||||
snprintf(buf1, sizeof(buf1), "%d:%d", c->childenv[0], c->fail[1]);
|
snprintf(buf1, sizeof(buf1), "%d:%d:%d", c->childenv[0], c->childenv[1], c->fail[1]);
|
||||||
/* put the fd string as argument to the helper cmd */
|
/* put the fd string as argument to the helper cmd */
|
||||||
hlpargs[0] = buf1;
|
hlpargs[0] = buf1;
|
||||||
hlpargs[1] = 0;
|
hlpargs[1] = 0;
|
||||||
@ -527,7 +527,7 @@ spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath)
|
|||||||
if (c->fds[i] != -1) {
|
if (c->fds[i] != -1) {
|
||||||
int flags = fcntl(c->fds[i], F_GETFD);
|
int flags = fcntl(c->fds[i], F_GETFD);
|
||||||
if (flags & FD_CLOEXEC) {
|
if (flags & FD_CLOEXEC) {
|
||||||
fcntl(c->fds[i], F_SETFD, flags & (~1));
|
fcntl(c->fds[i], F_SETFD, flags & (~FD_CLOEXEC));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -538,6 +538,10 @@ spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
jtregSimulateCrash(resultPid, 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* now the lengths are known, copy the data */
|
/* now the lengths are known, copy the data */
|
||||||
buf = NEW(char, bufsize);
|
buf = NEW(char, bufsize);
|
||||||
if (buf == 0) {
|
if (buf == 0) {
|
||||||
@ -553,11 +557,24 @@ spawnChild(JNIEnv *env, jobject process, ChildStuff *c, const char *helperpath)
|
|||||||
magic = magicNumber();
|
magic = magicNumber();
|
||||||
|
|
||||||
/* write the two structs and the data buffer */
|
/* write the two structs and the data buffer */
|
||||||
write(c->childenv[1], (char *)&magic, sizeof(magic)); // magic number first
|
if (writeFully(c->childenv[1], (char *)&magic, sizeof(magic)) != sizeof(magic)) { // magic number first
|
||||||
write(c->childenv[1], (char *)c, sizeof(*c));
|
return -1;
|
||||||
write(c->childenv[1], (char *)&sp, sizeof(sp));
|
}
|
||||||
write(c->childenv[1], buf, bufsize);
|
#ifdef DEBUG
|
||||||
|
jtregSimulateCrash(resultPid, 2);
|
||||||
|
#endif
|
||||||
|
if (writeFully(c->childenv[1], (char *)c, sizeof(*c)) != sizeof(*c) ||
|
||||||
|
writeFully(c->childenv[1], (char *)&sp, sizeof(sp)) != sizeof(sp) ||
|
||||||
|
writeFully(c->childenv[1], buf, bufsize) != bufsize) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* We're done. Let jspwanhelper know he can't expect any more data from us. */
|
||||||
|
close(c->childenv[1]);
|
||||||
|
c->childenv[1] = -1;
|
||||||
free(buf);
|
free(buf);
|
||||||
|
#ifdef DEBUG
|
||||||
|
jtregSimulateCrash(resultPid, 3);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* In this mode an external main() in invoked which calls back into
|
/* In this mode an external main() in invoked which calls back into
|
||||||
* childProcess() in this file, rather than directly
|
* childProcess() in this file, rather than directly
|
||||||
@ -610,6 +627,8 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
|
|||||||
|
|
||||||
in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1;
|
in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1;
|
||||||
childenv[0] = childenv[1] = -1;
|
childenv[0] = childenv[1] = -1;
|
||||||
|
// Reset errno to protect against bogus error messages
|
||||||
|
errno = 0;
|
||||||
|
|
||||||
if ((c = NEW(ChildStuff, 1)) == NULL) return -1;
|
if ((c = NEW(ChildStuff, 1)) == NULL) return -1;
|
||||||
c->argv = NULL;
|
c->argv = NULL;
|
||||||
@ -708,11 +727,9 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
|
|||||||
goto Catch;
|
goto Catch;
|
||||||
}
|
}
|
||||||
case sizeof(errnum):
|
case sizeof(errnum):
|
||||||
assert(errnum == CHILD_IS_ALIVE);
|
|
||||||
if (errnum != CHILD_IS_ALIVE) {
|
if (errnum != CHILD_IS_ALIVE) {
|
||||||
/* Should never happen since the first thing the spawn
|
/* This can happen if the spawn helper encounters an error
|
||||||
* helper should do is to send an alive ping to the parent,
|
* before or during the handshake with the parent. */
|
||||||
* before doing any subsequent work. */
|
|
||||||
throwIOException(env, 0, "Bad code from spawn helper "
|
throwIOException(env, 0, "Bad code from spawn helper "
|
||||||
"(Failed to exec spawn helper)");
|
"(Failed to exec spawn helper)");
|
||||||
goto Catch;
|
goto Catch;
|
||||||
@ -748,8 +765,12 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
|
|||||||
/* Always clean up fail and childEnv descriptors */
|
/* Always clean up fail and childEnv descriptors */
|
||||||
closeSafely(fail[0]);
|
closeSafely(fail[0]);
|
||||||
closeSafely(fail[1]);
|
closeSafely(fail[1]);
|
||||||
closeSafely(childenv[0]);
|
/* We use 'c->childenv' here rather than 'childenv' because 'spawnChild()' might have
|
||||||
closeSafely(childenv[1]);
|
* already closed 'c->childenv[1]' and signaled this by setting 'c->childenv[1]' to '-1'.
|
||||||
|
* Otherwise 'c->childenv' and 'childenv' are the same because we just copied 'childenv'
|
||||||
|
* to 'c->childenv' (with 'copyPipe()') before calling 'startChild()'. */
|
||||||
|
closeSafely(c->childenv[0]);
|
||||||
|
closeSafely(c->childenv[1]);
|
||||||
|
|
||||||
releaseBytes(env, helperpath, phelperpath);
|
releaseBytes(env, helperpath, phelperpath);
|
||||||
releaseBytes(env, prog, pprog);
|
releaseBytes(env, prog, pprog);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -36,14 +36,6 @@
|
|||||||
|
|
||||||
const char * const *parentPathv;
|
const char * const *parentPathv;
|
||||||
|
|
||||||
ssize_t
|
|
||||||
restartableWrite(int fd, const void *buf, size_t count)
|
|
||||||
{
|
|
||||||
ssize_t result;
|
|
||||||
RESTARTABLE(write(fd, buf, count), result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
restartableDup2(int fd_from, int fd_to)
|
restartableDup2(int fd_from, int fd_to)
|
||||||
{
|
{
|
||||||
@ -163,6 +155,46 @@ readFully(int fd, void *buf, size_t nbyte)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Writes nbyte bytes from buf into file descriptor fd,
|
||||||
|
* The write operation is retried in case of EINTR or partial writes.
|
||||||
|
*
|
||||||
|
* Returns number of bytes written (normally nbyte).
|
||||||
|
* In case of write errors, returns -1 and sets errno.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
writeFully(int fd, const void *buf, size_t nbyte)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
|
/* This code is only used in debug builds for testing truncated writes
|
||||||
|
* during the handshake with the spawn helper for MODE_POSIX_SPAWN.
|
||||||
|
* See: test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java
|
||||||
|
*/
|
||||||
|
const char* env = getenv("JTREG_JSPAWNHELPER_PROTOCOL_TEST");
|
||||||
|
if (env != NULL && atoi(env) == 99 && nbyte == sizeof(ChildStuff)) {
|
||||||
|
printf("posix_spawn: truncating write of ChildStuff struct\n");
|
||||||
|
fflush(stdout);
|
||||||
|
nbyte = nbyte / 2;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ssize_t remaining = nbyte;
|
||||||
|
for (;;) {
|
||||||
|
ssize_t n = write(fd, buf, remaining);
|
||||||
|
if (n > 0) {
|
||||||
|
remaining -= n;
|
||||||
|
if (remaining <= 0)
|
||||||
|
return nbyte;
|
||||||
|
/* We were interrupted in the middle of writing the bytes.
|
||||||
|
* Unlikely, but possible. */
|
||||||
|
buf = (void *) (((char *)buf) + n);
|
||||||
|
} else if (n == -1 && errno == EINTR) {
|
||||||
|
/* Retry */
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
initVectorFromBlock(const char**vector, const char* block, int count)
|
initVectorFromBlock(const char**vector, const char* block, int count)
|
||||||
{
|
{
|
||||||
@ -210,7 +242,7 @@ execve_with_shell_fallback(int mode, const char *file,
|
|||||||
const char *argv[],
|
const char *argv[],
|
||||||
const char *const envp[])
|
const char *const envp[])
|
||||||
{
|
{
|
||||||
if (mode == MODE_CLONE || mode == MODE_VFORK) {
|
if (mode == MODE_VFORK) {
|
||||||
/* shared address space; be very careful. */
|
/* shared address space; be very careful. */
|
||||||
execve(file, (char **) argv, (char **) envp);
|
execve(file, (char **) argv, (char **) envp);
|
||||||
if (errno == ENOEXEC)
|
if (errno == ENOEXEC)
|
||||||
@ -321,9 +353,14 @@ childProcess(void *arg)
|
|||||||
/* Child shall signal aliveness to parent at the very first
|
/* Child shall signal aliveness to parent at the very first
|
||||||
* moment. */
|
* moment. */
|
||||||
int code = CHILD_IS_ALIVE;
|
int code = CHILD_IS_ALIVE;
|
||||||
restartableWrite(fail_pipe_fd, &code, sizeof(code));
|
if (writeFully(fail_pipe_fd, &code, sizeof(code)) != sizeof(code)) {
|
||||||
|
goto WhyCantJohnnyExec;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
jtregSimulateCrash(0, 6);
|
||||||
|
#endif
|
||||||
/* Close the parent sides of the pipes.
|
/* Close the parent sides of the pipes.
|
||||||
Closing pipe fds here is redundant, since closeDescriptors()
|
Closing pipe fds here is redundant, since closeDescriptors()
|
||||||
would do it anyways, but a little paranoia is a good thing. */
|
would do it anyways, but a little paranoia is a good thing. */
|
||||||
@ -390,9 +427,26 @@ childProcess(void *arg)
|
|||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
int errnum = errno;
|
int errnum = errno;
|
||||||
restartableWrite(fail_pipe_fd, &errnum, sizeof(errnum));
|
writeFully(fail_pipe_fd, &errnum, sizeof(errnum));
|
||||||
}
|
}
|
||||||
close(fail_pipe_fd);
|
close(fail_pipe_fd);
|
||||||
_exit(-1);
|
_exit(-1);
|
||||||
return 0; /* Suppress warning "no return value from function" */
|
return 0; /* Suppress warning "no return value from function" */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
/* This method is only used in debug builds for testing MODE_POSIX_SPAWN
|
||||||
|
* in the light of abnormal program termination of either the parent JVM
|
||||||
|
* or the newly created jspawnhelper child process during the execution of
|
||||||
|
* Java_java_lang_ProcessImpl_forkAndExec().
|
||||||
|
* See: test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java
|
||||||
|
*/
|
||||||
|
void jtregSimulateCrash(pid_t child, int stage) {
|
||||||
|
const char* env = getenv("JTREG_JSPAWNHELPER_PROTOCOL_TEST");
|
||||||
|
if (env != NULL && atoi(env) == stage) {
|
||||||
|
printf("posix_spawn:%d\n", child);
|
||||||
|
fflush(stdout);
|
||||||
|
_exit(stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -85,7 +85,6 @@ extern char **environ;
|
|||||||
#define MODE_FORK 1
|
#define MODE_FORK 1
|
||||||
#define MODE_POSIX_SPAWN 2
|
#define MODE_POSIX_SPAWN 2
|
||||||
#define MODE_VFORK 3
|
#define MODE_VFORK 3
|
||||||
#define MODE_CLONE 4
|
|
||||||
|
|
||||||
typedef struct _ChildStuff
|
typedef struct _ChildStuff
|
||||||
{
|
{
|
||||||
@ -128,7 +127,7 @@ typedef struct _SpawnInfo {
|
|||||||
*/
|
*/
|
||||||
extern const char * const *parentPathv;
|
extern const char * const *parentPathv;
|
||||||
|
|
||||||
ssize_t restartableWrite(int fd, const void *buf, size_t count);
|
ssize_t writeFully(int fd, const void *buf, size_t count);
|
||||||
int restartableDup2(int fd_from, int fd_to);
|
int restartableDup2(int fd_from, int fd_to);
|
||||||
int closeSafely(int fd);
|
int closeSafely(int fd);
|
||||||
int isAsciiDigit(char c);
|
int isAsciiDigit(char c);
|
||||||
@ -149,4 +148,14 @@ void JDK_execvpe(int mode, const char *file,
|
|||||||
const char *const envp[]);
|
const char *const envp[]);
|
||||||
int childProcess(void *arg);
|
int childProcess(void *arg);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
/* This method is only used in debug builds for testing MODE_POSIX_SPAWN
|
||||||
|
* in the light of abnormal program termination of either the parent JVM
|
||||||
|
* or the newly created jspawnhelper child process during the execution of
|
||||||
|
* Java_java_lang_ProcessImpl_forkAndExec().
|
||||||
|
* See: test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java
|
||||||
|
*/
|
||||||
|
void jtregSimulateCrash(pid_t child, int stage);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
237
test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java
Normal file
237
test/jdk/java/lang/ProcessBuilder/JspawnhelperProtocol.java
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* Copyright Amazon.com Inc. 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
|
||||||
|
* @bug 8307990
|
||||||
|
* @requires (os.family == "linux") | (os.family == "aix")
|
||||||
|
* @requires vm.debug
|
||||||
|
* @library /test/lib
|
||||||
|
* @run main/othervm/timeout=300 JspawnhelperProtocol
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import jdk.test.lib.process.ProcessTools;
|
||||||
|
|
||||||
|
public class JspawnhelperProtocol {
|
||||||
|
// Timout in seconds
|
||||||
|
private static final int TIMEOUT = 60;
|
||||||
|
// Base error code to communicate various error states from the parent process to the top-level test
|
||||||
|
private static final int ERROR = 10;
|
||||||
|
private static final String[] CMD = { "pwd" };
|
||||||
|
private static final String ENV_KEY = "JTREG_JSPAWNHELPER_PROTOCOL_TEST";
|
||||||
|
|
||||||
|
private static void parentCode(String arg) throws IOException, InterruptedException {
|
||||||
|
System.out.println("Recursively executing 'JspawnhelperProtocol " + arg + "'");
|
||||||
|
Process p = null;
|
||||||
|
try {
|
||||||
|
p = Runtime.getRuntime().exec(CMD);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(System.out);
|
||||||
|
System.exit(ERROR);
|
||||||
|
}
|
||||||
|
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
|
||||||
|
System.out.println("Child process timed out");
|
||||||
|
System.exit(ERROR + 1);
|
||||||
|
}
|
||||||
|
if (p.exitValue() == 0) {
|
||||||
|
String pwd = p.inputReader().readLine();
|
||||||
|
String realPwd = Path.of("").toAbsolutePath().toString();
|
||||||
|
if (!realPwd.equals(pwd)) {
|
||||||
|
System.out.println("Child process returned '" + pwd + "' (expected '" + realPwd + "')");
|
||||||
|
System.exit(ERROR + 2);
|
||||||
|
}
|
||||||
|
System.out.println(" Successfully executed '" + CMD[0] + "'");
|
||||||
|
System.exit(0);
|
||||||
|
} else {
|
||||||
|
System.out.println(" Failed to executed '" + CMD[0] + "' (exitValue=" + p.exitValue() + ")");
|
||||||
|
System.exit(ERROR + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void normalExec() throws Exception {
|
||||||
|
ProcessBuilder pb;
|
||||||
|
pb = ProcessTools.createJavaProcessBuilder("-Djdk.lang.Process.launchMechanism=posix_spawn",
|
||||||
|
"JspawnhelperProtocol",
|
||||||
|
"normalExec");
|
||||||
|
pb.inheritIO();
|
||||||
|
Process p = pb.start();
|
||||||
|
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
|
||||||
|
throw new Exception("Parent process timed out");
|
||||||
|
}
|
||||||
|
if (p.exitValue() != 0) {
|
||||||
|
throw new Exception("Parent process exited with " + p.exitValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void simulateCrashInChild(int stage) throws Exception {
|
||||||
|
ProcessBuilder pb;
|
||||||
|
pb = ProcessTools.createJavaProcessBuilder("-Djdk.lang.Process.launchMechanism=posix_spawn",
|
||||||
|
"JspawnhelperProtocol",
|
||||||
|
"simulateCrashInChild" + stage);
|
||||||
|
pb.environment().put(ENV_KEY, Integer.toString(stage));
|
||||||
|
Process p = pb.start();
|
||||||
|
|
||||||
|
boolean foundCrashInfo = false;
|
||||||
|
try (BufferedReader br = p.inputReader()) {
|
||||||
|
String line = br.readLine();
|
||||||
|
while (line != null) {
|
||||||
|
System.out.println(line);
|
||||||
|
if (line.equals("posix_spawn:0")) {
|
||||||
|
foundCrashInfo = true;
|
||||||
|
}
|
||||||
|
line = br.readLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!foundCrashInfo) {
|
||||||
|
throw new Exception("Wrong output from child process");
|
||||||
|
}
|
||||||
|
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
|
||||||
|
throw new Exception("Parent process timed out");
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = p.exitValue();
|
||||||
|
if (ret == 0) {
|
||||||
|
throw new Exception("Expected error during child execution");
|
||||||
|
}
|
||||||
|
System.out.println("Parent exit code: " + ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void simulateCrashInParent(int stage) throws Exception {
|
||||||
|
ProcessBuilder pb;
|
||||||
|
pb = ProcessTools.createJavaProcessBuilder("-Djdk.lang.Process.launchMechanism=posix_spawn",
|
||||||
|
"JspawnhelperProtocol",
|
||||||
|
"simulateCrashInParent" + stage);
|
||||||
|
pb.environment().put(ENV_KEY, Integer.toString(stage));
|
||||||
|
Process p = pb.start();
|
||||||
|
|
||||||
|
String line = null;
|
||||||
|
try (BufferedReader br = p.inputReader()) {
|
||||||
|
line = br.readLine();
|
||||||
|
while (line != null && !line.startsWith("posix_spawn:")) {
|
||||||
|
System.out.println(line);
|
||||||
|
line = br.readLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (line == null) {
|
||||||
|
throw new Exception("Wrong output from parent process");
|
||||||
|
}
|
||||||
|
System.out.println(line);
|
||||||
|
long childPid = Integer.parseInt(line.substring(line.indexOf(':') + 1));
|
||||||
|
|
||||||
|
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
|
||||||
|
throw new Exception("Parent process timed out");
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<ProcessHandle> oph = ProcessHandle.of(childPid);
|
||||||
|
if (!oph.isEmpty()) {
|
||||||
|
ProcessHandle ph = oph.get();
|
||||||
|
try {
|
||||||
|
// Give jspawnhelper a chance to exit gracefully
|
||||||
|
ph.onExit().get(TIMEOUT, TimeUnit.SECONDS);
|
||||||
|
} catch (TimeoutException te) {
|
||||||
|
Optional<String> cmd = ph.info().command();
|
||||||
|
if (cmd.isPresent() && cmd.get().endsWith("jspawnhelper")) {
|
||||||
|
throw new Exception("jspawnhelper still alive after parent Java process terminated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int ret = p.exitValue();
|
||||||
|
if (ret != stage) {
|
||||||
|
throw new Exception("Expected exit code " + stage + " but got " + ret);
|
||||||
|
}
|
||||||
|
System.out.println("Parent exit code: " + ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void simulateTruncatedWriteInParent(int stage) throws Exception {
|
||||||
|
ProcessBuilder pb;
|
||||||
|
pb = ProcessTools.createJavaProcessBuilder("-Djdk.lang.Process.launchMechanism=posix_spawn",
|
||||||
|
"JspawnhelperProtocol",
|
||||||
|
"simulateTruncatedWriteInParent" + stage);
|
||||||
|
pb.environment().put(ENV_KEY, Integer.toString(stage));
|
||||||
|
Process p = pb.start();
|
||||||
|
|
||||||
|
BufferedReader br = p.inputReader();
|
||||||
|
String line = br.readLine();
|
||||||
|
while (line != null && !line.startsWith("posix_spawn:")) {
|
||||||
|
System.out.println(line);
|
||||||
|
line = br.readLine();
|
||||||
|
}
|
||||||
|
if (line == null) {
|
||||||
|
throw new Exception("Wrong output from parent process");
|
||||||
|
}
|
||||||
|
System.out.println(line);
|
||||||
|
|
||||||
|
if (!p.waitFor(TIMEOUT, TimeUnit.SECONDS)) {
|
||||||
|
throw new Exception("Parent process timed out");
|
||||||
|
}
|
||||||
|
line = br.readLine();
|
||||||
|
while (line != null) {
|
||||||
|
System.out.println(line);
|
||||||
|
line = br.readLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = p.exitValue();
|
||||||
|
if (ret != ERROR) {
|
||||||
|
throw new Exception("Expected exit code " + ERROR + " but got " + ret);
|
||||||
|
}
|
||||||
|
System.out.println("Parent exit code: " + ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
// This test works as follows:
|
||||||
|
// - jtreg executes the test class `JspawnhelperProtocol` without arguments.
|
||||||
|
// This is the initial "grandparent" process.
|
||||||
|
// - For each sub-test (i.e. `normalExec()`, `simulateCrashInParent()` and
|
||||||
|
// `simulateCrashInChild()`), a new sub-process (called the "parent") will be
|
||||||
|
// forked which executes `JspawnhelperProtocol` recursively with a corresponding
|
||||||
|
// command line argument.
|
||||||
|
// - The forked `JspawnhelperProtocol` process (i.e. the "parent") runs
|
||||||
|
// `JspawnhelperProtocol::parentCode()` which forks off yet another sub-process
|
||||||
|
// (called the "child").
|
||||||
|
// - The sub-tests in the "grandparent" check that various abnormal program
|
||||||
|
// terminations in the "parent" or the "child" process are handled gracefully and
|
||||||
|
// don't lead to deadlocks or zombie processes.
|
||||||
|
if (args.length > 0) {
|
||||||
|
// Entry point for recursive execution in the "parent" process
|
||||||
|
parentCode(args[0]);
|
||||||
|
} else {
|
||||||
|
// Main test entry for execution from jtreg
|
||||||
|
normalExec();
|
||||||
|
simulateCrashInParent(1);
|
||||||
|
simulateCrashInParent(2);
|
||||||
|
simulateCrashInParent(3);
|
||||||
|
simulateCrashInChild(4);
|
||||||
|
simulateCrashInChild(5);
|
||||||
|
simulateCrashInChild(6);
|
||||||
|
simulateTruncatedWriteInParent(99);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user