提交 d120bfcc 编写于 作者: M martin

6850720: (process) Use clone(CLONE_VM), not fork, on Linux to avoid swap exhaustion

Summary: Use clone(CLONE_VM) on Linux; Reluctantly implement execvpe.
Reviewed-by: michaelm
上级 351f5108
...@@ -49,6 +49,18 @@ ...@@ -49,6 +49,18 @@
#include <fcntl.h> #include <fcntl.h>
#include <limits.h> #include <limits.h>
#ifndef USE_CLONE
#ifdef __linux__
#define USE_CLONE 1
#else
#define USE_CLONE 0
#endif
#endif
#if USE_CLONE
#include <sched.h>
#endif
#ifndef STDIN_FILENO #ifndef STDIN_FILENO
#define STDIN_FILENO 0 #define STDIN_FILENO 0
#endif #endif
...@@ -376,70 +388,61 @@ debugPrint(char *format, ...) ...@@ -376,70 +388,61 @@ debugPrint(char *format, ...)
} }
#endif /* DEBUG_PROCESS */ #endif /* DEBUG_PROCESS */
/* Version of execvpe when child's PATH differs from parent's */ /**
static int * Exec FILE as a traditional Bourne shell script (i.e. one without #!).
execvp_usingParentPath(const char *file, const char *const argv[]) * If we could do it over again, we would probably not support such an ancient
* misfeature, but compatibility wins over sanity. The original support for
* this was imported accidentally from execvp().
*/
static void
execve_as_traditional_shell_script(const char *file,
const char *argv[],
const char *const envp[])
{ {
char expanded_file[PATH_MAX]; /* Use the extra word of space provided for us in argv by caller. */
int filelen = strlen(file); const char *argv0 = argv[0];
int sticky_errno = 0; const char *const *end = argv;
const char * const * dirs; while (*end != NULL)
/* Search parent's PATH */ ++end;
for (dirs = parentPathv; *dirs; dirs++) { memmove(argv+2, argv+1, (end-argv) * sizeof (*end));
const char * dir = *dirs; argv[0] = "/bin/sh";
int dirlen = strlen(dir); argv[1] = file;
if (filelen + dirlen + 1 >= PATH_MAX) { execve(argv[0], (char **) argv, (char **) envp);
/* Resist the urge to remove this limit; /* Can't even exec /bin/sh? Big trouble, but let's soldier on... */
* calling malloc after fork is unsafe. */ memmove(argv+1, argv+2, (end-argv) * sizeof (*end));
errno = ENAMETOOLONG; argv[0] = argv0;
continue; }
}
strcpy(expanded_file, dir); /**
strcpy(expanded_file + dirlen, file); * Like execve(2), except that in case of ENOEXEC, FILE is assumed to
execvp(expanded_file, (char **) argv); * be a shell script and the system default shell is invoked to run it.
/* There are 3 responses to various classes of errno: */
* return immediately, continue (especially for ENOENT), static void
* or continue with "sticky" errno. execve_with_shell_fallback(const char *file,
* const char *argv[],
* From exec(3): const char *const envp[])
* {
* If permission is denied for a file (the attempted #if USE_CLONE
* execve returned EACCES), these functions will continue execve(file, (char **) argv, (char **) envp);
* searching the rest of the search path. If no other if (errno == ENOEXEC)
* file is found, however, they will return with the execve_as_traditional_shell_script(file, argv, envp);
* global variable errno set to EACCES. #else
*/ /* Our address space is unshared, so can mutate environ. */
switch (errno) { extern char **environ;
case EACCES: environ = (char **) envp;
sticky_errno = errno; execvp(file, (char **) argv);
/* FALLTHRU */
case ENOENT:
case ENOTDIR:
#ifdef ELOOP
case ELOOP:
#endif
#ifdef ESTALE
case ESTALE:
#endif
#ifdef ENODEV
case ENODEV:
#endif
#ifdef ETIMEDOUT
case ETIMEDOUT:
#endif #endif
break; /* Try other directories in PATH */
default:
return -1;
}
}
if (sticky_errno != 0)
errno = sticky_errno;
return -1;
} }
/* execvpe should have been included in the Unix standards. */ /**
static int * execvpe should have been included in the Unix standards.
execvpe(const char *file, const char *const argv[], const char *const envp[]) * execvpe is identical to execvp, except that the child environment is
* specified via the 3rd argument instead of being inherited from environ.
*/
static void
execvpe(const char *file,
const char *argv[],
const char *const envp[])
{ {
/* This is one of the rare times it's more portable to declare an /* This is one of the rare times it's more portable to declare an
* external symbol explicitly, rather than via a system header. * external symbol explicitly, rather than via a system header.
...@@ -454,28 +457,73 @@ execvpe(const char *file, const char *const argv[], const char *const envp[]) ...@@ -454,28 +457,73 @@ execvpe(const char *file, const char *const argv[], const char *const envp[])
*/ */
extern char **environ; extern char **environ;
if (envp != NULL) if (envp == NULL || (char **) envp == environ) {
environ = (char **) envp; execvp(file, (char **) argv);
return;
if (/* Parent and child environment the same? Use child PATH. */ }
(envp == NULL)
/* http://www.opengroup.org/onlinepubs/009695399/functions/exec.html
* "If the file argument contains a slash character, it is used as
* the pathname for this file. Otherwise, the path prefix for this
* file is obtained by a search of the directories passed in the
* PATH environment variable" */
|| (strchr(file, '/') != NULL)
/* Parent and child PATH the same? Use child PATH. */
|| (strcmp(parentPath, effectivePath()) == 0)
/* We want ENOENT, not EACCES, for zero-length program names. */ if (*file == '\0') {
|| (*file == '\0')) errno = ENOENT;
return;
}
return execvp(file, (char **) argv); if (strchr(file, '/') != NULL) {
else execve_with_shell_fallback(file, argv, envp);
return execvp_usingParentPath(file, argv); } else {
/* We must search PATH (parent's, not child's) */
char expanded_file[PATH_MAX];
int filelen = strlen(file);
int sticky_errno = 0;
const char * const * dirs;
for (dirs = parentPathv; *dirs; dirs++) {
const char * dir = *dirs;
int dirlen = strlen(dir);
if (filelen + dirlen + 1 >= PATH_MAX) {
errno = ENAMETOOLONG;
continue;
}
memcpy(expanded_file, dir, dirlen);
memcpy(expanded_file + dirlen, file, filelen);
expanded_file[dirlen + filelen] = '\0';
execve_with_shell_fallback(expanded_file, argv, envp);
/* There are 3 responses to various classes of errno:
* return immediately, continue (especially for ENOENT),
* or continue with "sticky" errno.
*
* From exec(3):
*
* If permission is denied for a file (the attempted
* execve returned EACCES), these functions will continue
* searching the rest of the search path. If no other
* file is found, however, they will return with the
* global variable errno set to EACCES.
*/
switch (errno) {
case EACCES:
sticky_errno = errno;
/* FALLTHRU */
case ENOENT:
case ENOTDIR:
#ifdef ELOOP
case ELOOP:
#endif
#ifdef ESTALE
case ESTALE:
#endif
#ifdef ENODEV
case ENODEV:
#endif
#ifdef ETIMEDOUT
case ETIMEDOUT:
#endif
break; /* Try other directories in PATH */
default:
return;
}
}
if (sticky_errno != 0)
errno = sticky_errno;
}
} }
static void static void
...@@ -516,10 +564,95 @@ readFully(int fd, void *buf, size_t nbyte) ...@@ -516,10 +564,95 @@ readFully(int fd, void *buf, size_t nbyte)
} }
} }
#ifndef __solaris__ typedef struct _ChildStuff
#undef fork1 {
#define fork1() fork() int in[2];
#endif int out[2];
int err[2];
int fail[2];
int fds[3];
const char **argv;
const char **envv;
const char *pdir;
jboolean redirectErrorStream;
} ChildStuff;
static void
copyPipe(int from[2], int to[2])
{
to[0] = from[0];
to[1] = from[1];
}
/**
* Child process after a successful fork() or clone().
* This function must not return, and must be prepared for either all
* of its address space to be shared with its parent, or to be a copy.
* It must not modify global variables such as "environ".
*/
static int
childProcess(void *arg)
{
const ChildStuff* p = (const ChildStuff*) arg;
/* Close the parent sides of the pipes.
Closing pipe fds here is redundant, since closeDescriptors()
would do it anyways, but a little paranoia is a good thing. */
closeSafely(p->in[1]);
closeSafely(p->out[0]);
closeSafely(p->err[0]);
closeSafely(p->fail[0]);
/* Give the child sides of the pipes the right fileno's. */
/* Note: it is possible for in[0] == 0 */
moveDescriptor(p->in[0] != -1 ? p->in[0] : p->fds[0], STDIN_FILENO);
moveDescriptor(p->out[1]!= -1 ? p->out[1] : p->fds[1], STDOUT_FILENO);
if (p->redirectErrorStream) {
closeSafely(p->err[1]);
dup2(STDOUT_FILENO, STDERR_FILENO);
} else {
moveDescriptor(p->err[1] != -1 ? p->err[1] : p->fds[2], STDERR_FILENO);
}
moveDescriptor(p->fail[1], FAIL_FILENO);
/* close everything */
if (closeDescriptors() == 0) { /* failed, close the old way */
int max_fd = (int)sysconf(_SC_OPEN_MAX);
int i;
for (i = FAIL_FILENO + 1; i < max_fd; i++)
close(i);
}
/* change to the new working directory */
if (p->pdir != NULL && chdir(p->pdir) < 0)
goto WhyCantJohnnyExec;
if (fcntl(FAIL_FILENO, F_SETFD, FD_CLOEXEC) == -1)
goto WhyCantJohnnyExec;
execvpe(p->argv[0], p->argv, p->envv);
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.
* 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;
write(FAIL_FILENO, &errnum, sizeof(errnum));
}
close(FAIL_FILENO);
_exit(-1);
return 0; /* Suppress warning "no return value from function" */
}
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
...@@ -533,34 +666,43 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, ...@@ -533,34 +666,43 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
{ {
int errnum; int errnum;
int resultPid = -1; int resultPid = -1;
#if USE_CLONE
void *clone_stack = NULL;
#endif
int in[2], out[2], err[2], fail[2]; int in[2], out[2], err[2], fail[2];
const char **argv = NULL;
const char **envv = NULL;
const char *pprog = getBytes(env, prog);
const char *pargBlock = getBytes(env, argBlock);
const char *penvBlock = getBytes(env, envBlock);
const char *pdir = getBytes(env, dir);
jint *fds = NULL; jint *fds = NULL;
const char *pprog = NULL;
const char *pargBlock = NULL;
const char *penvBlock = NULL;
ChildStuff *c;
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;
assert(prog != NULL && argBlock != NULL); if ((c = NEW(ChildStuff, 1)) == NULL) return -1;
if (pprog == NULL) goto Catch; c->argv = NULL;
if (pargBlock == NULL) goto Catch; c->envv = NULL;
if (envBlock != NULL && penvBlock == NULL) goto Catch; c->pdir = NULL;
if (dir != NULL && pdir == NULL) goto Catch;
/* Convert pprog + pargBlock into a char ** argv */ /* Convert prog + argBlock into a char ** argv.
if ((argv = NEW(const char *, argc + 2)) == NULL) * Add one word room for expansion of argv for use by
goto Catch; * execve_as_traditional_shell_script.
argv[0] = pprog; */
initVectorFromBlock(argv+1, pargBlock, argc); assert(prog != NULL && argBlock != NULL);
if ((pprog = getBytes(env, prog)) == NULL) goto Catch;
if ((pargBlock = getBytes(env, argBlock)) == NULL) goto Catch;
if ((c->argv = NEW(const char *, argc + 3)) == NULL) goto Catch;
c->argv[0] = pprog;
initVectorFromBlock(c->argv+1, pargBlock, argc);
if (envBlock != NULL) { if (envBlock != NULL) {
/* Convert penvBlock into a char ** envv */ /* Convert envBlock into a char ** envv */
if ((envv = NEW(const char *, envc + 1)) == NULL) if ((penvBlock = getBytes(env, envBlock)) == NULL) goto Catch;
goto Catch; if ((c->envv = NEW(const char *, envc + 1)) == NULL) goto Catch;
initVectorFromBlock(envv, penvBlock, envc); initVectorFromBlock(c->envv, penvBlock, envc);
}
if (dir != NULL) {
if ((c->pdir = getBytes(env, dir)) == NULL) goto Catch;
} }
assert(std_fds != NULL); assert(std_fds != NULL);
...@@ -574,72 +716,45 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, ...@@ -574,72 +716,45 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
throwIOException(env, errno, "Bad file descriptor"); throwIOException(env, errno, "Bad file descriptor");
goto Catch; goto Catch;
} }
c->fds[0] = fds[0];
c->fds[1] = fds[1];
c->fds[2] = fds[2];
copyPipe(in, c->in);
copyPipe(out, c->out);
copyPipe(err, c->err);
copyPipe(fail, c->fail);
c->redirectErrorStream = redirectErrorStream;
{
#if USE_CLONE
/* See clone(2).
* Instead of worrying about which direction the stack grows, just
* allocate twice as much and start the stack in the middle. */
const int stack_size = 64 * 1024;
if ((clone_stack = NEW(char, 2 * stack_size)) == NULL) goto Catch;
resultPid = clone(childProcess, clone_stack + stack_size,
/* CLONE_VFORK | // works, but unnecessary */
CLONE_VM | SIGCHLD, c);
#else
/* From fork(2): In Solaris 10, a call to fork() is identical
* to a call to fork1(); only the calling thread is replicated
* in the child process. This is the POSIX-specified behavior
* for fork(). */
resultPid = fork();
if (resultPid == 0) {
childProcess(c);
assert(0); /* childProcess must not return */
}
#endif
}
resultPid = fork1();
if (resultPid < 0) { if (resultPid < 0) {
throwIOException(env, errno, "Fork failed"); throwIOException(env, errno, "Fork failed");
goto Catch; goto Catch;
} }
if (resultPid == 0) {
/* Child process */
/* Close the parent sides of the pipes.
Closing pipe fds here is redundant, since closeDescriptors()
would do it anyways, but a little paranoia is a good thing. */
closeSafely(in[1]);
closeSafely(out[0]);
closeSafely(err[0]);
closeSafely(fail[0]);
/* Give the child sides of the pipes the right fileno's. */
/* Note: it is possible for in[0] == 0 */
moveDescriptor(in[0] != -1 ? in[0] : fds[0], STDIN_FILENO);
moveDescriptor(out[1]!= -1 ? out[1] : fds[1], STDOUT_FILENO);
if (redirectErrorStream) {
closeSafely(err[1]);
dup2(STDOUT_FILENO, STDERR_FILENO);
} else {
moveDescriptor(err[1] != -1 ? err[1] : fds[2], STDERR_FILENO);
}
moveDescriptor(fail[1], FAIL_FILENO);
/* close everything */
if (closeDescriptors() == 0) { /* failed, close the old way */
int max_fd = (int)sysconf(_SC_OPEN_MAX);
int i;
for (i = FAIL_FILENO + 1; i < max_fd; i++)
close(i);
}
/* change to the new working directory */
if (pdir != NULL && chdir(pdir) < 0)
goto WhyCantJohnnyExec;
if (fcntl(FAIL_FILENO, F_SETFD, FD_CLOEXEC) == -1)
goto WhyCantJohnnyExec;
execvpe(argv[0], argv, envv);
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.
* 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.
*/
errnum = errno;
write(FAIL_FILENO, &errnum, sizeof(errnum));
close(FAIL_FILENO);
_exit(-1);
}
/* parent process */ /* parent process */
close(fail[1]); fail[1] = -1; /* See: WhyCantJohnnyExec */ close(fail[1]); fail[1] = -1; /* See: WhyCantJohnnyExec */
...@@ -660,6 +775,10 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, ...@@ -660,6 +775,10 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
fds[2] = (err[0] != -1) ? err[0] : -1; fds[2] = (err[0] != -1) ? err[0] : -1;
Finally: Finally:
#if USE_CLONE
free(clone_stack);
#endif
/* Always clean up the child's side of the pipes */ /* Always clean up the child's side of the pipes */
closeSafely(in [0]); closeSafely(in [0]);
closeSafely(out[1]); closeSafely(out[1]);
...@@ -669,13 +788,14 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env, ...@@ -669,13 +788,14 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
closeSafely(fail[0]); closeSafely(fail[0]);
closeSafely(fail[1]); closeSafely(fail[1]);
free(argv);
free(envv);
releaseBytes(env, prog, pprog); releaseBytes(env, prog, pprog);
releaseBytes(env, argBlock, pargBlock); releaseBytes(env, argBlock, pargBlock);
releaseBytes(env, envBlock, penvBlock); releaseBytes(env, envBlock, penvBlock);
releaseBytes(env, dir, pdir); releaseBytes(env, dir, c->pdir);
free(c->argv);
free(c->envv);
free(c);
if (fds != NULL) if (fds != NULL)
(*env)->ReleaseIntArrayElements(env, std_fds, fds, 0); (*env)->ReleaseIntArrayElements(env, std_fds, fds, 0);
......
...@@ -257,6 +257,18 @@ public class Basic { ...@@ -257,6 +257,18 @@ public class Basic {
s.write(bytes); // Might hang! s.write(bytes); // Might hang!
} }
static void checkPermissionDenied(ProcessBuilder pb) {
try {
pb.start();
fail("Expected IOException not thrown");
} catch (IOException e) {
String m = e.getMessage();
if (EnglishUnix.is() &&
! matches(m, "Permission denied"))
unexpected(e);
} catch (Throwable t) { unexpected(t); }
}
public static class JavaChild { public static class JavaChild {
public static void main(String args[]) throws Throwable { public static void main(String args[]) throws Throwable {
String action = args[0]; String action = args[0];
...@@ -317,12 +329,10 @@ public class Basic { ...@@ -317,12 +329,10 @@ public class Basic {
for (final ProcessBuilder pb : for (final ProcessBuilder pb :
new ProcessBuilder[] {pb1, pb2}) { new ProcessBuilder[] {pb1, pb2}) {
pb.command("true"); pb.command("true");
r = run(pb.start()); equal(run(pb).exitValue(), True.exitValue());
equal(r.exitValue(), True.exitValue());
pb.command("false"); pb.command("false");
r = run(pb.start()); equal(run(pb).exitValue(), False.exitValue());
equal(r.exitValue(), False.exitValue());
} }
if (failed != 0) throw new Error("null PATH"); if (failed != 0) throw new Error("null PATH");
...@@ -367,31 +377,82 @@ public class Basic { ...@@ -367,31 +377,82 @@ public class Basic {
// Can't execute a directory -- permission denied // Can't execute a directory -- permission denied
// Report EACCES errno // Report EACCES errno
new File("dir1/prog").mkdirs(); new File("dir1/prog").mkdirs();
try { checkPermissionDenied(pb);
pb.start();
fail("Expected IOException not thrown");
} catch (IOException e) {
String m = e.getMessage();
if (EnglishUnix.is() &&
! matches(m, "Permission denied"))
unexpected(e);
} catch (Throwable t) { unexpected(t); }
// continue searching if EACCES // continue searching if EACCES
copy("/bin/true", "dir2/prog"); copy("/bin/true", "dir2/prog");
equal(run(pb.start()).exitValue(), True.exitValue()); equal(run(pb).exitValue(), True.exitValue());
new File("dir1/prog").delete(); new File("dir1/prog").delete();
new File("dir2/prog").delete(); new File("dir2/prog").delete();
new File("dir2/prog").mkdirs(); new File("dir2/prog").mkdirs();
copy("/bin/true", "dir1/prog"); copy("/bin/true", "dir1/prog");
equal(run(pb.start()).exitValue(), True.exitValue()); equal(run(pb).exitValue(), True.exitValue());
// Check empty PATH component means current directory // Check empty PATH component means current directory.
//
// While we're here, let's test different kinds of
// Unix executables, and PATH vs explicit searching.
new File("dir1/prog").delete(); new File("dir1/prog").delete();
new File("dir2/prog").delete(); new File("dir2/prog").delete();
copy("/bin/true", "./prog"); for (String[] command :
equal(run(pb.start()).exitValue(), True.exitValue()); new String[][] {
new String[] {"./prog"},
cmd}) {
pb.command(command);
File prog = new File("./prog");
// "Normal" binaries
copy("/bin/true", "./prog");
equal(run(pb).exitValue(),
True.exitValue());
copy("/bin/false", "./prog");
equal(run(pb).exitValue(),
False.exitValue());
prog.delete();
// Interpreter scripts with #!
setFileContents(prog, "#!/bin/true\n");
prog.setExecutable(true);
equal(run(pb).exitValue(),
True.exitValue());
prog.delete();
setFileContents(prog, "#!/bin/false\n");
prog.setExecutable(true);
equal(run(pb).exitValue(),
False.exitValue());
// Traditional shell scripts without #!
setFileContents(prog, "exec /bin/true\n");
prog.setExecutable(true);
equal(run(pb).exitValue(),
True.exitValue());
prog.delete();
setFileContents(prog, "exec /bin/false\n");
prog.setExecutable(true);
equal(run(pb).exitValue(),
False.exitValue());
prog.delete();
}
// Test Unix interpreter scripts
File dir1Prog = new File("dir1/prog");
dir1Prog.delete();
pb.command(new String[] {"prog", "world"});
setFileContents(dir1Prog, "#!/bin/echo hello\n");
checkPermissionDenied(pb);
dir1Prog.setExecutable(true);
equal(run(pb).out(), "hello dir1/prog world\n");
equal(run(pb).exitValue(), True.exitValue());
dir1Prog.delete();
pb.command(cmd);
// Test traditional shell scripts without #!
setFileContents(dir1Prog, "/bin/echo \"$@\"\n");
pb.command(new String[] {"prog", "hello", "world"});
checkPermissionDenied(pb);
dir1Prog.setExecutable(true);
equal(run(pb).out(), "hello world\n");
equal(run(pb).exitValue(), True.exitValue());
dir1Prog.delete();
pb.command(cmd);
// If prog found on both parent and child's PATH, // If prog found on both parent and child's PATH,
// parent's is used. // parent's is used.
...@@ -402,10 +463,10 @@ public class Basic { ...@@ -402,10 +463,10 @@ public class Basic {
copy("/bin/true", "dir1/prog"); copy("/bin/true", "dir1/prog");
copy("/bin/false", "dir3/prog"); copy("/bin/false", "dir3/prog");
pb.environment().put("PATH","dir3"); pb.environment().put("PATH","dir3");
equal(run(pb.start()).exitValue(), True.exitValue()); equal(run(pb).exitValue(), True.exitValue());
copy("/bin/true", "dir3/prog"); copy("/bin/true", "dir3/prog");
copy("/bin/false", "dir1/prog"); copy("/bin/false", "dir1/prog");
equal(run(pb.start()).exitValue(), False.exitValue()); equal(run(pb).exitValue(), False.exitValue());
} finally { } finally {
// cleanup // cleanup
...@@ -1503,21 +1564,19 @@ public class Basic { ...@@ -1503,21 +1564,19 @@ public class Basic {
childArgs.add("OutErr"); childArgs.add("OutErr");
ProcessBuilder pb = new ProcessBuilder(childArgs); ProcessBuilder pb = new ProcessBuilder(childArgs);
{ {
ProcessResults r = run(pb.start()); ProcessResults r = run(pb);
equal(r.out(), "outout"); equal(r.out(), "outout");
equal(r.err(), "errerr"); equal(r.err(), "errerr");
} }
{ {
pb.redirectErrorStream(true); pb.redirectErrorStream(true);
ProcessResults r = run(pb.start()); ProcessResults r = run(pb);
equal(r.out(), "outerrouterr"); equal(r.out(), "outerrouterr");
equal(r.err(), ""); equal(r.err(), "");
} }
} catch (Throwable t) { unexpected(t); } } catch (Throwable t) { unexpected(t); }
if (! Windows.is() && if (Unix.is()) {
new File("/bin/true").exists() &&
new File("/bin/false").exists()) {
//---------------------------------------------------------------- //----------------------------------------------------------------
// We can find true and false when PATH is null // We can find true and false when PATH is null
//---------------------------------------------------------------- //----------------------------------------------------------------
...@@ -1526,7 +1585,7 @@ public class Basic { ...@@ -1526,7 +1585,7 @@ public class Basic {
childArgs.add("null PATH"); childArgs.add("null PATH");
ProcessBuilder pb = new ProcessBuilder(childArgs); ProcessBuilder pb = new ProcessBuilder(childArgs);
pb.environment().remove("PATH"); pb.environment().remove("PATH");
ProcessResults r = run(pb.start()); ProcessResults r = run(pb);
equal(r.out(), ""); equal(r.out(), "");
equal(r.err(), ""); equal(r.err(), "");
equal(r.exitValue(), 0); equal(r.exitValue(), 0);
...@@ -1540,7 +1599,7 @@ public class Basic { ...@@ -1540,7 +1599,7 @@ public class Basic {
childArgs.add("PATH search algorithm"); childArgs.add("PATH search algorithm");
ProcessBuilder pb = new ProcessBuilder(childArgs); ProcessBuilder pb = new ProcessBuilder(childArgs);
pb.environment().put("PATH", "dir1:dir2:"); pb.environment().put("PATH", "dir1:dir2:");
ProcessResults r = run(pb.start()); ProcessResults r = run(pb);
equal(r.out(), ""); equal(r.out(), "");
equal(r.err(), ""); equal(r.err(), "");
equal(r.exitValue(), True.exitValue()); equal(r.exitValue(), True.exitValue());
......
/*
* Copyright 2009 Google Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
import java.util.*;
import java.io.*;
/**
* A manual test that demonstrates the ability to start a subprocess
* on Linux without getting ENOMEM. Run this test like:
*
* java -Xmx7000m BigFork
*
* providing a -Xmx flag suitable for your operating environment.
* Here's the bad old behavior:
*
* ==> java -Xmx7000m -esa -ea BigFork
* -------
* CommitLimit: 6214700 kB
* Committed_AS: 2484452 kB
* -------
* size=4.6GB
* -------
* CommitLimit: 6214700 kB
* Committed_AS: 7219680 kB
* -------
* Exception in thread "main" java.io.IOException: Cannot run program "/bin/true": java.io.IOException: error=12, Cannot allocate memory
* at java.lang.ProcessBuilder.start(ProcessBuilder.java:1018)
* at BigFork.main(BigFork.java:79)
* Caused by: java.io.IOException: java.io.IOException: error=12, Cannot allocate memory
* at java.lang.UNIXProcess.<init>(UNIXProcess.java:190)
* at java.lang.ProcessImpl.start(ProcessImpl.java:128)
* at java.lang.ProcessBuilder.start(ProcessBuilder.java:1010)
* ... 1 more
*/
public class BigFork {
static final Random rnd = new Random();
static void touchPages(byte[] chunk) {
final int pageSize = 4096;
for (int i = 0; i < chunk.length; i+= pageSize) {
chunk[i] = (byte) rnd.nextInt();
}
}
static void showCommittedMemory() throws IOException {
BufferedReader r =
new BufferedReader(
new InputStreamReader(
new FileInputStream("/proc/meminfo")));
System.out.println("-------");
String line;
while ((line = r.readLine()) != null) {
if (line.startsWith("Commit")) {
System.out.printf("%s%n", line);
}
}
System.out.println("-------");
}
public static void main(String[] args) throws Throwable {
showCommittedMemory();
final int chunkSize = 1024 * 1024 * 100;
List<byte[]> chunks = new ArrayList<byte[]>(100);
try {
for (;;) {
byte[] chunk = new byte[chunkSize];
touchPages(chunk);
chunks.add(chunk);
}
} catch (OutOfMemoryError e) {
chunks.set(0, null); // Free up one chunk
System.gc();
int size = chunks.size();
System.out.printf("size=%.2gGB%n", (double)size/10);
showCommittedMemory();
// Can we fork/exec in our current bloated state?
Process p = new ProcessBuilder("/bin/true").start();
p.waitFor();
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册