Path: blob/master/src/java.base/unix/classes/java/lang/ProcessImpl.java
41134 views
/*1* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.lang;2627import java.lang.ProcessBuilder.Redirect;28import java.io.BufferedInputStream;29import java.io.BufferedOutputStream;30import java.io.ByteArrayInputStream;31import java.io.FileDescriptor;32import java.io.FileInputStream;33import java.io.FileOutputStream;34import java.io.IOException;35import java.io.InputStream;36import java.io.OutputStream;37import java.util.Arrays;38import java.util.EnumSet;39import java.util.Locale;40import java.util.Set;41import java.util.concurrent.CompletableFuture;42import java.util.concurrent.TimeUnit;43import java.security.AccessController;44import java.security.PrivilegedAction;45import java.security.PrivilegedActionException;46import java.security.PrivilegedExceptionAction;47import java.util.Properties;48import jdk.internal.access.JavaIOFileDescriptorAccess;49import jdk.internal.access.SharedSecrets;50import jdk.internal.util.StaticProperty;51import sun.security.action.GetPropertyAction;5253/**54* java.lang.Process subclass in the UNIX environment.55*56* @author Mario Wolczko and Ross Knippel.57* @author Konstantin Kladko (ported to Linux and Bsd)58* @author Martin Buchholz59* @author Volker Simonis (ported to AIX)60* @since 1.561*/62final class ProcessImpl extends Process {63private static final JavaIOFileDescriptorAccess fdAccess64= SharedSecrets.getJavaIOFileDescriptorAccess();6566// Linux platforms support a normal (non-forcible) kill signal.67static final boolean SUPPORTS_NORMAL_TERMINATION = true;6869private final int pid;70private final ProcessHandleImpl processHandle;71private int exitcode;72private boolean hasExited;7374private /* final */ OutputStream stdin;75private /* final */ InputStream stdout;76private /* final */ InputStream stderr;7778private static enum LaunchMechanism {79// order IS important!80FORK,81POSIX_SPAWN,82VFORK83}8485private static enum Platform {8687LINUX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.VFORK, LaunchMechanism.FORK),8889BSD(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK),9091AIX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK);9293final LaunchMechanism defaultLaunchMechanism;94final Set<LaunchMechanism> validLaunchMechanisms;9596Platform(LaunchMechanism ... launchMechanisms) {97this.defaultLaunchMechanism = launchMechanisms[0];98this.validLaunchMechanisms =99EnumSet.copyOf(Arrays.asList(launchMechanisms));100}101102@SuppressWarnings("removal")103LaunchMechanism launchMechanism() {104return AccessController.doPrivileged(105(PrivilegedAction<LaunchMechanism>) () -> {106String s = System.getProperty(107"jdk.lang.Process.launchMechanism");108LaunchMechanism lm;109if (s == null) {110lm = defaultLaunchMechanism;111s = lm.name().toLowerCase(Locale.ENGLISH);112} else {113try {114lm = LaunchMechanism.valueOf(115s.toUpperCase(Locale.ENGLISH));116} catch (IllegalArgumentException e) {117lm = null;118}119}120if (lm == null || !validLaunchMechanisms.contains(lm)) {121throw new Error(122s + " is not a supported " +123"process launch mechanism on this platform."124);125}126return lm;127}128);129}130131static Platform get() {132String osName = GetPropertyAction.privilegedGetProperty("os.name");133134if (osName.equals("Linux")) { return LINUX; }135if (osName.contains("OS X")) { return BSD; }136if (osName.equals("AIX")) { return AIX; }137138throw new Error(osName + " is not a supported OS platform.");139}140}141142private static final Platform platform = Platform.get();143private static final LaunchMechanism launchMechanism = platform.launchMechanism();144private static final byte[] helperpath = toCString(StaticProperty.javaHome() + "/lib/jspawnhelper");145146private static byte[] toCString(String s) {147if (s == null)148return null;149byte[] bytes = s.getBytes();150byte[] result = new byte[bytes.length + 1];151System.arraycopy(bytes, 0,152result, 0,153bytes.length);154result[result.length-1] = (byte)0;155return result;156}157158// Only for use by ProcessBuilder.start()159static Process start(String[] cmdarray,160java.util.Map<String,String> environment,161String dir,162ProcessBuilder.Redirect[] redirects,163boolean redirectErrorStream)164throws IOException165{166assert cmdarray != null && cmdarray.length > 0;167168// Convert arguments to a contiguous block; it's easier to do169// memory management in Java than in C.170byte[][] args = new byte[cmdarray.length-1][];171int size = args.length; // For added NUL bytes172for (int i = 0; i < args.length; i++) {173args[i] = cmdarray[i+1].getBytes();174size += args[i].length;175}176byte[] argBlock = new byte[size];177int i = 0;178for (byte[] arg : args) {179System.arraycopy(arg, 0, argBlock, i, arg.length);180i += arg.length + 1;181// No need to write NUL bytes explicitly182}183184int[] envc = new int[1];185byte[] envBlock = ProcessEnvironment.toEnvironmentBlock(environment, envc);186187int[] std_fds;188189FileInputStream f0 = null;190FileOutputStream f1 = null;191FileOutputStream f2 = null;192193try {194boolean forceNullOutputStream = false;195if (redirects == null) {196std_fds = new int[] { -1, -1, -1 };197} else {198std_fds = new int[3];199200if (redirects[0] == Redirect.PIPE) {201std_fds[0] = -1;202} else if (redirects[0] == Redirect.INHERIT) {203std_fds[0] = 0;204} else if (redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) {205std_fds[0] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd());206} else {207f0 = new FileInputStream(redirects[0].file());208std_fds[0] = fdAccess.get(f0.getFD());209}210211if (redirects[1] == Redirect.PIPE) {212std_fds[1] = -1;213} else if (redirects[1] == Redirect.INHERIT) {214std_fds[1] = 1;215} else if (redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) {216std_fds[1] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd());217// Force getInputStream to return a null stream,218// the fd is directly assigned to the next process.219forceNullOutputStream = true;220} else {221f1 = new FileOutputStream(redirects[1].file(),222redirects[1].append());223std_fds[1] = fdAccess.get(f1.getFD());224}225226if (redirects[2] == Redirect.PIPE) {227std_fds[2] = -1;228} else if (redirects[2] == Redirect.INHERIT) {229std_fds[2] = 2;230} else if (redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) {231std_fds[2] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd());232} else {233f2 = new FileOutputStream(redirects[2].file(),234redirects[2].append());235std_fds[2] = fdAccess.get(f2.getFD());236}237}238239Process p = new ProcessImpl240(toCString(cmdarray[0]),241argBlock, args.length,242envBlock, envc[0],243toCString(dir),244std_fds,245forceNullOutputStream,246redirectErrorStream);247if (redirects != null) {248// Copy the fd's if they are to be redirected to another process249if (std_fds[0] >= 0 &&250redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) {251fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd(), std_fds[0]);252}253if (std_fds[1] >= 0 &&254redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) {255fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd(), std_fds[1]);256}257if (std_fds[2] >= 0 &&258redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) {259fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd(), std_fds[2]);260}261}262return p;263} finally {264// In theory, close() can throw IOException265// (although it is rather unlikely to happen here)266try { if (f0 != null) f0.close(); }267finally {268try { if (f1 != null) f1.close(); }269finally { if (f2 != null) f2.close(); }270}271}272}273274275/**276* Creates a process. Depending on the {@code mode} flag, this is done by277* one of the following mechanisms:278* <pre>279* 1 - fork(2) and exec(2)280* 2 - posix_spawn(3P)281* 3 - vfork(2) and exec(2)282* </pre>283* @param fds an array of three file descriptors.284* Indexes 0, 1, and 2 correspond to standard input,285* standard output and standard error, respectively. On286* input, a value of -1 means to create a pipe to connect287* child and parent processes. On output, a value which288* is not -1 is the parent pipe fd corresponding to the289* pipe which has been created. An element of this array290* is -1 on input if and only if it is <em>not</em> -1 on291* output.292* @return the pid of the subprocess293*/294private native int forkAndExec(int mode, byte[] helperpath,295byte[] prog,296byte[] argBlock, int argc,297byte[] envBlock, int envc,298byte[] dir,299int[] fds,300boolean redirectErrorStream)301throws IOException;302303@SuppressWarnings("removal")304private ProcessImpl(final byte[] prog,305final byte[] argBlock, final int argc,306final byte[] envBlock, final int envc,307final byte[] dir,308final int[] fds,309final boolean forceNullOutputStream,310final boolean redirectErrorStream)311throws IOException {312313pid = forkAndExec(launchMechanism.ordinal() + 1,314helperpath,315prog,316argBlock, argc,317envBlock, envc,318dir,319fds,320redirectErrorStream);321processHandle = ProcessHandleImpl.getInternal(pid);322323try {324AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {325initStreams(fds, forceNullOutputStream);326return null;327});328} catch (PrivilegedActionException ex) {329throw (IOException) ex.getCause();330}331}332333static FileDescriptor newFileDescriptor(int fd) {334FileDescriptor fileDescriptor = new FileDescriptor();335fdAccess.set(fileDescriptor, fd);336return fileDescriptor;337}338339/**340* Initialize the streams from the file descriptors.341* @param fds array of stdin, stdout, stderr fds342* @param forceNullOutputStream true if the stdout is being directed to343* a subsequent process. The stdout stream should be a null output stream .344* @throws IOException345*/346void initStreams(int[] fds, boolean forceNullOutputStream) throws IOException {347switch (platform) {348case LINUX:349case BSD:350stdin = (fds[0] == -1) ?351ProcessBuilder.NullOutputStream.INSTANCE :352new ProcessPipeOutputStream(fds[0]);353354stdout = (fds[1] == -1 || forceNullOutputStream) ?355ProcessBuilder.NullInputStream.INSTANCE :356new ProcessPipeInputStream(fds[1]);357358stderr = (fds[2] == -1) ?359ProcessBuilder.NullInputStream.INSTANCE :360new ProcessPipeInputStream(fds[2]);361362ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {363synchronized (this) {364this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();365this.hasExited = true;366this.notifyAll();367}368369if (stdout instanceof ProcessPipeInputStream)370((ProcessPipeInputStream) stdout).processExited();371372if (stderr instanceof ProcessPipeInputStream)373((ProcessPipeInputStream) stderr).processExited();374375if (stdin instanceof ProcessPipeOutputStream)376((ProcessPipeOutputStream) stdin).processExited();377378return null;379});380break;381382case AIX:383stdin = (fds[0] == -1) ?384ProcessBuilder.NullOutputStream.INSTANCE :385new ProcessPipeOutputStream(fds[0]);386387stdout = (fds[1] == -1 || forceNullOutputStream) ?388ProcessBuilder.NullInputStream.INSTANCE :389new DeferredCloseProcessPipeInputStream(fds[1]);390391stderr = (fds[2] == -1) ?392ProcessBuilder.NullInputStream.INSTANCE :393new DeferredCloseProcessPipeInputStream(fds[2]);394395ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {396synchronized (this) {397this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();398this.hasExited = true;399this.notifyAll();400}401402if (stdout instanceof DeferredCloseProcessPipeInputStream)403((DeferredCloseProcessPipeInputStream) stdout).processExited();404405if (stderr instanceof DeferredCloseProcessPipeInputStream)406((DeferredCloseProcessPipeInputStream) stderr).processExited();407408if (stdin instanceof ProcessPipeOutputStream)409((ProcessPipeOutputStream) stdin).processExited();410411return null;412});413break;414415default: throw new AssertionError("Unsupported platform: " + platform);416}417}418419public OutputStream getOutputStream() {420return stdin;421}422423public InputStream getInputStream() {424return stdout;425}426427public InputStream getErrorStream() {428return stderr;429}430431public synchronized int waitFor() throws InterruptedException {432while (!hasExited) {433wait();434}435return exitcode;436}437438@Override439public synchronized boolean waitFor(long timeout, TimeUnit unit)440throws InterruptedException441{442long remainingNanos = unit.toNanos(timeout); // throw NPE before other conditions443if (hasExited) return true;444if (timeout <= 0) return false;445446long deadline = System.nanoTime() + remainingNanos;447do {448TimeUnit.NANOSECONDS.timedWait(this, remainingNanos);449if (hasExited) {450return true;451}452remainingNanos = deadline - System.nanoTime();453} while (remainingNanos > 0);454return hasExited;455}456457public synchronized int exitValue() {458if (!hasExited) {459throw new IllegalThreadStateException("process hasn't exited");460}461return exitcode;462}463464private void destroy(boolean force) {465switch (platform) {466case LINUX:467case BSD:468case AIX:469// There is a risk that pid will be recycled, causing us to470// kill the wrong process! So we only terminate processes471// that appear to still be running. Even with this check,472// there is an unavoidable race condition here, but the window473// is very small, and OSes try hard to not recycle pids too474// soon, so this is quite safe.475synchronized (this) {476if (!hasExited)477processHandle.destroyProcess(force);478}479try { stdin.close(); } catch (IOException ignored) {}480try { stdout.close(); } catch (IOException ignored) {}481try { stderr.close(); } catch (IOException ignored) {}482break;483484default: throw new AssertionError("Unsupported platform: " + platform);485}486}487488@Override489public CompletableFuture<Process> onExit() {490return ProcessHandleImpl.completion(pid, false)491.handleAsync((unusedExitStatus, unusedThrowable) -> {492boolean interrupted = false;493while (true) {494// Ensure that the concurrent task setting the exit status has completed495try {496waitFor();497break;498} catch (InterruptedException ie) {499interrupted = true;500}501}502if (interrupted) {503Thread.currentThread().interrupt();504}505return this;506});507}508509@Override510public ProcessHandle toHandle() {511@SuppressWarnings("removal")512SecurityManager sm = System.getSecurityManager();513if (sm != null) {514sm.checkPermission(new RuntimePermission("manageProcess"));515}516return processHandle;517}518519@Override520public boolean supportsNormalTermination() {521return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;522}523524@Override525public void destroy() {526destroy(false);527}528529@Override530public Process destroyForcibly() {531destroy(true);532return this;533}534535@Override536public long pid() {537return pid;538}539540@Override541public synchronized boolean isAlive() {542return !hasExited;543}544545/**546* The {@code toString} method returns a string consisting of547* the native process ID of the process and the exit value of the process.548*549* @return a string representation of the object.550*/551@Override552public String toString() {553return new StringBuilder("Process[pid=").append(pid)554.append(", exitValue=").append(hasExited ? exitcode : "\"not exited\"")555.append("]").toString();556}557558private static native void init();559560static {561init();562}563564/**565* A buffered input stream for a subprocess pipe file descriptor566* that allows the underlying file descriptor to be reclaimed when567* the process exits, via the processExited hook.568*569* This is tricky because we do not want the user-level InputStream to be570* closed until the user invokes close(), and we need to continue to be571* able to read any buffered data lingering in the OS pipe buffer.572*/573private static class ProcessPipeInputStream extends BufferedInputStream {574private final Object closeLock = new Object();575576ProcessPipeInputStream(int fd) {577super(new PipeInputStream(newFileDescriptor(fd)));578}579private static byte[] drainInputStream(InputStream in)580throws IOException {581int n = 0;582int j;583byte[] a = null;584while ((j = in.available()) > 0) {585a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);586n += in.read(a, n, j);587}588return (a == null || n == a.length) ? a : Arrays.copyOf(a, n);589}590591/** Called by the process reaper thread when the process exits. */592synchronized void processExited() {593synchronized (closeLock) {594try {595InputStream in = this.in;596// this stream is closed if and only if: in == null597if (in != null) {598byte[] stragglers = drainInputStream(in);599in.close();600this.in = (stragglers == null) ?601ProcessBuilder.NullInputStream.INSTANCE :602new ByteArrayInputStream(stragglers);603}604} catch (IOException ignored) {}605}606}607608@Override609public void close() throws IOException {610// BufferedInputStream#close() is not synchronized unlike most other611// methods. Synchronizing helps avoid race with processExited().612synchronized (closeLock) {613super.close();614}615}616}617618/**619* A buffered output stream for a subprocess pipe file descriptor620* that allows the underlying file descriptor to be reclaimed when621* the process exits, via the processExited hook.622*/623private static class ProcessPipeOutputStream extends BufferedOutputStream {624ProcessPipeOutputStream(int fd) {625super(new FileOutputStream(newFileDescriptor(fd)));626}627628/** Called by the process reaper thread when the process exits. */629synchronized void processExited() {630OutputStream out = this.out;631if (out != null) {632try {633out.close();634} catch (IOException ignored) {635// We know of no reason to get an IOException, but if636// we do, there's nothing else to do but carry on.637}638this.out = ProcessBuilder.NullOutputStream.INSTANCE;639}640}641}642643// A FileInputStream that supports the deferment of the actual close644// operation until the last pending I/O operation on the stream has645// finished. This is required on Solaris because we must close the stdin646// and stdout streams in the destroy method in order to reclaim the647// underlying file descriptors. Doing so, however, causes any thread648// currently blocked in a read on one of those streams to receive an649// IOException("Bad file number"), which is incompatible with historical650// behavior. By deferring the close we allow any pending reads to see -1651// (EOF) as they did before.652//653private static class DeferredCloseInputStream extends PipeInputStream {654DeferredCloseInputStream(FileDescriptor fd) {655super(fd);656}657658private Object lock = new Object(); // For the following fields659private boolean closePending = false;660private int useCount = 0;661private InputStream streamToClose;662663private void raise() {664synchronized (lock) {665useCount++;666}667}668669private void lower() throws IOException {670synchronized (lock) {671useCount--;672if (useCount == 0 && closePending) {673streamToClose.close();674}675}676}677678// stc is the actual stream to be closed; it might be this object, or679// it might be an upstream object for which this object is downstream.680//681private void closeDeferred(InputStream stc) throws IOException {682synchronized (lock) {683if (useCount == 0) {684stc.close();685} else {686closePending = true;687streamToClose = stc;688}689}690}691692public void close() throws IOException {693synchronized (lock) {694useCount = 0;695closePending = false;696}697super.close();698}699700public int read() throws IOException {701raise();702try {703return super.read();704} finally {705lower();706}707}708709public int read(byte[] b) throws IOException {710raise();711try {712return super.read(b);713} finally {714lower();715}716}717718public int read(byte[] b, int off, int len) throws IOException {719raise();720try {721return super.read(b, off, len);722} finally {723lower();724}725}726727public long skip(long n) throws IOException {728raise();729try {730return super.skip(n);731} finally {732lower();733}734}735736public int available() throws IOException {737raise();738try {739return super.available();740} finally {741lower();742}743}744}745746/**747* A buffered input stream for a subprocess pipe file descriptor748* that allows the underlying file descriptor to be reclaimed when749* the process exits, via the processExited hook.750*751* This is tricky because we do not want the user-level InputStream to be752* closed until the user invokes close(), and we need to continue to be753* able to read any buffered data lingering in the OS pipe buffer.754*755* On AIX this is especially tricky, because the 'close()' system call756* will block if another thread is at the same time blocked in a file757* operation (e.g. 'read()') on the same file descriptor. We therefore758* combine 'ProcessPipeInputStream' approach used on Linux and Bsd759* with the DeferredCloseInputStream approach used on Solaris. This means760* that every potentially blocking operation on the file descriptor761* increments a counter before it is executed and decrements it once it762* finishes. The 'close()' operation will only be executed if there are763* no pending operations. Otherwise it is deferred after the last pending764* operation has finished.765*766*/767private static class DeferredCloseProcessPipeInputStream768extends BufferedInputStream {769770private final Object closeLock = new Object();771private int useCount = 0;772private boolean closePending = false;773774DeferredCloseProcessPipeInputStream(int fd) {775super(new PipeInputStream(newFileDescriptor(fd)));776}777778private InputStream drainInputStream(InputStream in)779throws IOException {780int n = 0;781int j;782byte[] a = null;783synchronized (closeLock) {784if (buf == null) // asynchronous close()?785return null; // discard786j = in.available();787}788while (j > 0) {789a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);790synchronized (closeLock) {791if (buf == null) // asynchronous close()?792return null; // discard793n += in.read(a, n, j);794j = in.available();795}796}797return (a == null) ?798ProcessBuilder.NullInputStream.INSTANCE :799new ByteArrayInputStream(n == a.length ? a : Arrays.copyOf(a, n));800}801802/** Called by the process reaper thread when the process exits. */803synchronized void processExited() {804try {805InputStream in = this.in;806if (in != null) {807InputStream stragglers = drainInputStream(in);808in.close();809this.in = stragglers;810}811} catch (IOException ignored) { }812}813814private void raise() {815synchronized (closeLock) {816useCount++;817}818}819820private void lower() throws IOException {821synchronized (closeLock) {822useCount--;823if (useCount == 0 && closePending) {824closePending = false;825super.close();826}827}828}829830@Override831public int read() throws IOException {832raise();833try {834return super.read();835} finally {836lower();837}838}839840@Override841public int read(byte[] b) throws IOException {842raise();843try {844return super.read(b);845} finally {846lower();847}848}849850@Override851public int read(byte[] b, int off, int len) throws IOException {852raise();853try {854return super.read(b, off, len);855} finally {856lower();857}858}859860@Override861public long skip(long n) throws IOException {862raise();863try {864return super.skip(n);865} finally {866lower();867}868}869870@Override871public int available() throws IOException {872raise();873try {874return super.available();875} finally {876lower();877}878}879880@Override881public void close() throws IOException {882// BufferedInputStream#close() is not synchronized unlike most other883// methods. Synchronizing helps avoid racing with drainInputStream().884synchronized (closeLock) {885if (useCount == 0) {886super.close();887}888else {889closePending = true;890}891}892}893}894}895896897