/* * Copyright (c) 2014, 2015, 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. */ package java.lang; import java.security.PrivilegedAction; import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import java.util.stream.Stream; import sun.misc.InnocuousThread; import static java.security.AccessController.doPrivileged; /** * ProcessHandleImpl is the implementation of ProcessHandle. * * @see Process * @since 9 */ final class ProcessHandleImpl implements ProcessHandle { /** * Cache the ProcessHandle of this process. */ private static final ProcessHandleImpl current; /** * Map of pids to ExitCompletions. */ private static final ConcurrentMap completions = new ConcurrentHashMap<>(); static { initNative(); long pid = getCurrentPid0(); current = new ProcessHandleImpl(pid, isAlive0(pid)); } private static native void initNative(); /** * The thread pool of "process reaper" daemon threads. */ private static final Executor processReaperExecutor = doPrivileged((PrivilegedAction) () -> { ThreadGroup tg = Thread.currentThread().getThreadGroup(); while (tg.getParent() != null) tg = tg.getParent(); ThreadGroup systemThreadGroup = tg; ThreadFactory threadFactory = grimReaper -> { // Our thread stack requirement is quite modest. Thread t = new Thread(systemThreadGroup, grimReaper, "process reaper", 32768); t.setDaemon(true); // A small attempt (probably futile) to avoid priority inversion t.setPriority(Thread.MAX_PRIORITY); return t; }; return Executors.newCachedThreadPool(threadFactory); }); private static class ExitCompletion extends CompletableFuture { final boolean isReaping; ExitCompletion(boolean isReaping) { this.isReaping = isReaping; } } /** * Returns a CompletableFuture that completes with process exit status when * the process completes. * * @param shouldReap true if the exit value should be reaped */ static CompletableFuture completion(long pid, boolean shouldReap) { // check canonicalizing cache 1st ExitCompletion completion = completions.get(pid); // re-try until we get a completion that shouldReap => isReaping while (completion == null || (shouldReap && !completion.isReaping)) { ExitCompletion newCompletion = new ExitCompletion(shouldReap); if (completion == null) { completion = completions.putIfAbsent(pid, newCompletion); } else { completion = completions.replace(pid, completion, newCompletion) ? null : completions.get(pid); } if (completion == null) { // newCompletion has just been installed successfully completion = newCompletion; // spawn a thread to wait for and deliver the exit value processReaperExecutor.execute(() -> { int exitValue = waitForProcessExit0(pid, shouldReap); newCompletion.complete(exitValue); // remove from cache afterwards completions.remove(pid, newCompletion); }); } } return completion; } @Override public CompletableFuture onExit() { if (this.equals(current)) { throw new IllegalStateException("onExit for current process not allowed"); } return ProcessHandleImpl.completion(getPid(), false) .handleAsync((exitStatus, unusedThrowable) -> this); } /** * Wait for the process to exit, return the value. * Conditionally reap the value if requested * @param pid the processId * @param reapvalue if true, the value is retrieved, * else return the value and leave the process waitable * * @return the value or -1 if an error occurs */ private static native int waitForProcessExit0(long pid, boolean reapvalue); /** * The pid of this ProcessHandle. */ private final long pid; /** * The start time of this process. * If STARTTIME_ANY, the start time of the process is not available from the os. * If greater than zero, the start time of the process. */ private final long startTime; /* The start time should match any value. * Typically, this is because the OS can not supply it. * The process is known to exist but not the exact start time. */ private final long STARTTIME_ANY = 0L; /* The start time of a Process that does not exist. */ private final long STARTTIME_PROCESS_UNKNOWN = -1; /** * Private constructor. Instances are created by the {@code get(long)} factory. * @param pid the pid for this instance */ private ProcessHandleImpl(long pid, long startTime) { this.pid = pid; this.startTime = startTime; } /** * Returns a ProcessHandle for an existing native process. * * @param pid the native process identifier * @return The ProcessHandle for the pid if the process is alive; * or {@code null} if the process ID does not exist in the native system. * @throws SecurityException if RuntimePermission("manageProcess") is not granted */ static Optional get(long pid) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("manageProcess")); } long start = isAlive0(pid); return (start >= 0) ? Optional.of(new ProcessHandleImpl(pid, start)) : Optional.empty(); } /** * Returns a ProcessHandle for an existing native process known to be alive. * The startTime of the process is retrieved and stored in the ProcessHandle. * It does not perform a security check since it is called from ProcessImpl. * @param pid of the known to exist process * @return a ProcessHandle corresponding to an existing Process instance */ static ProcessHandleImpl getInternal(long pid) { return new ProcessHandleImpl(pid, isAlive0(pid)); } /** * Returns the native process ID. * A {@code long} is used to be able to fit the system specific binary values * for the process. * * @return the native process ID */ @Override public long getPid() { return pid; } /** * Returns the ProcessHandle for the current native process. * * @return The ProcessHandle for the OS process. * @throws SecurityException if RuntimePermission("manageProcess") is not granted */ public static ProcessHandleImpl current() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("manageProcess")); } return current; } /** * Return the pid of the current process. * * @return the pid of the current process */ private static native long getCurrentPid0(); /** * Returns a ProcessHandle for the parent process. * * @return a ProcessHandle of the parent process; {@code null} is returned * if the child process does not have a parent * @throws SecurityException if permission is not granted by the * security policy */ public Optional parent() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("manageProcess")); } long ppid = parent0(pid, startTime); if (ppid <= 0) { return Optional.empty(); } return get(ppid); } /** * Returns the parent of the native pid argument. * * @param pid the process id * @param startTime the startTime of the process * @return the parent of the native pid; if any, otherwise -1 */ private static native long parent0(long pid, long startTime); /** * Returns the number of pids filled in to the array. * @param pid if {@code pid} equals zero, then all known processes are returned; * otherwise only direct child process pids are returned * @param pids an allocated long array to receive the pids * @param ppids an allocated long array to receive the parent pids; may be null * @param starttimes an allocated long array to receive the child start times; may be null * @return if greater than or equals to zero is the number of pids in the array; * if greater than the length of the arrays, the arrays are too small */ private static native int getProcessPids0(long pid, long[] pids, long[] ppids, long[] starttimes); /** * Destroy the process for this ProcessHandle. * The native code checks the start time before sending the termination request. * * @param force {@code true} if the process should be terminated forcibly; * else {@code false} for a normal termination */ boolean destroyProcess(boolean force) { if (this.equals(current)) { throw new IllegalStateException("destroy of current process not allowed"); } return destroy0(pid, startTime, force); } /** * Signal the process to terminate. * The process is signaled only if its start time matches the known start time. * * @param pid process id to kill * @param startTime the start time of the process * @param forcibly true to forcibly terminate (SIGKILL vs SIGTERM) * @return true if the process was signaled without error; false otherwise */ private static native boolean destroy0(long pid, long startTime, boolean forcibly); @Override public boolean destroy() { return destroyProcess(false); } @Override public boolean destroyForcibly() { return destroyProcess(true); } @Override public boolean supportsNormalTermination() { return ProcessImpl.SUPPORTS_NORMAL_TERMINATION; } /** * Tests whether the process represented by this {@code ProcessHandle} is alive. * * @return {@code true} if the process represented by this * {@code ProcessHandle} object has not yet terminated. * @since 9 */ @Override public boolean isAlive() { long start = isAlive0(pid); return (start >= 0 && (start == startTime || start == 0 || startTime == 0)); } /** * Returns the process start time depending on whether the pid is alive. * This must not reap the exitValue. * * @param pid the pid to check * @return the start time in milliseconds since 1970, * 0 if the start time cannot be determined, * -1 if the pid does not exist. */ private static native long isAlive0(long pid); @Override public Stream children() { // The native OS code selects based on matching the requested parent pid. // If the original parent exits, the pid may have been re-used for // this newer process. // Processes started by the original parent (now dead) will all have // start times less than the start of this newer parent. // Processes started by this newer parent will have start times equal // or after this parent. return children(pid).filter(ph -> startTime <= ((ProcessHandleImpl)ph).startTime); } /** * Returns a Stream of the children of a process or all processes. * * @param pid the pid of the process for which to find the children; * 0 for all processes * @return a stream of ProcessHandles */ static Stream children(long pid) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("manageProcess")); } int size = 100; long[] childpids = null; long[] starttimes = null; while (childpids == null || size > childpids.length) { childpids = new long[size]; starttimes = new long[size]; size = getProcessPids0(pid, childpids, null, starttimes); } final long[] cpids = childpids; final long[] stimes = starttimes; return IntStream.range(0, size).mapToObj(i -> new ProcessHandleImpl(cpids[i], stimes[i])); } @Override public Stream descendants() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("manageProcess")); } int size = 100; long[] pids = null; long[] ppids = null; long[] starttimes = null; while (pids == null || size > pids.length) { pids = new long[size]; ppids = new long[size]; starttimes = new long[size]; size = getProcessPids0(0, pids, ppids, starttimes); } int next = 0; // index of next process to check int count = -1; // count of subprocesses scanned long ppid = pid; // start looking for this parent long ppStart = 0; // Find the start time of the parent for (int i = 0; i < size; i++) { if (pids[i] == ppid) { ppStart = starttimes[i]; break; } } do { // Scan from next to size looking for ppid with child start time // the same or later than the parent. // If found, exchange it with index next for (int i = next; i < size; i++) { if (ppids[i] == ppid && ppStart <= starttimes[i]) { swap(pids, i, next); swap(ppids, i, next); swap(starttimes, i, next); next++; } } ppid = pids[++count]; // pick up the next pid to scan for ppStart = starttimes[count]; // and its start time } while (count < next); final long[] cpids = pids; final long[] stimes = starttimes; return IntStream.range(0, count).mapToObj(i -> new ProcessHandleImpl(cpids[i], stimes[i])); } // Swap two elements in an array private static void swap(long[] array, int x, int y) { long v = array[x]; array[x] = array[y]; array[y] = v; } @Override public ProcessHandle.Info info() { return ProcessHandleImpl.Info.info(pid, startTime); } @Override public int compareTo(ProcessHandle other) { return Long.compare(pid, ((ProcessHandleImpl) other).pid); } @Override public String toString() { return Long.toString(pid); } @Override public int hashCode() { return Long.hashCode(pid); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof ProcessHandleImpl) { ProcessHandleImpl other = (ProcessHandleImpl) obj; return (pid == other.pid) && (startTime == other.startTime || startTime == 0 || other.startTime == 0); } return false; } /** * Implementation of ProcessHandle.Info. * Information snapshot about a process. * The attributes of a process vary by operating system and are not available * in all implementations. Additionally, information about other processes * is limited by the operating system privileges of the process making the request. * If a value is not available, either a {@code null} or {@code -1} is stored. * The accessor methods return {@code null} if the value is not available. */ static class Info implements ProcessHandle.Info { static { initIDs(); } /** * Initialization of JNI fieldIDs. */ private static native void initIDs(); /** * Fill in this Info instance with information about the native process. * If values are not available the native code does not modify the field. * @param pid of the native process */ private native void info0(long pid); String command; String commandLine; String[] arguments; long startTime; long totalTime; String user; Info() { command = null; commandLine = null; arguments = null; startTime = -1L; totalTime = -1L; user = null; } /** * Returns the Info object with the fields from the process. * Whatever fields are provided by native are returned. * If the startTime of the process does not match the provided * startTime then an empty Info is returned. * * @param pid the native process identifier * @param startTime the startTime of the process being queried * @return ProcessHandle.Info non-null; individual fields may be null * or -1 if not available. */ public static ProcessHandle.Info info(long pid, long startTime) { Info info = new Info(); info.info0(pid); if (startTime != info.startTime) { info.command = null; info.arguments = null; info.startTime = -1L; info.totalTime = -1L; info.user = null; } return info; } @Override public Optional command() { return Optional.ofNullable(command); } @Override public Optional commandLine() { if (command != null && arguments != null) { return Optional.of(command + " " + String.join(" ", arguments)); } else { return Optional.ofNullable(commandLine); } } @Override public Optional arguments() { return Optional.ofNullable(arguments); } @Override public Optional startInstant() { return (startTime > 0) ? Optional.of(Instant.ofEpochMilli(startTime)) : Optional.empty(); } @Override public Optional totalCpuDuration() { return (totalTime != -1) ? Optional.of(Duration.ofNanos(totalTime)) : Optional.empty(); } @Override public Optional user() { return Optional.ofNullable(user); } @Override public String toString() { StringBuilder sb = new StringBuilder(60); sb.append('['); if (user != null) { sb.append("user: "); sb.append(user()); } if (command != null) { if (sb.length() != 0) sb.append(", "); sb.append("cmd: "); sb.append(command); } if (arguments != null && arguments.length > 0) { if (sb.length() != 0) sb.append(", "); sb.append("args: "); sb.append(Arrays.toString(arguments)); } if (commandLine != null) { if (sb.length() != 0) sb.append(", "); sb.append("cmdLine: "); sb.append(commandLine); } if (startTime > 0) { if (sb.length() != 0) sb.append(", "); sb.append("startTime: "); sb.append(startInstant()); } if (totalTime != -1) { if (sb.length() != 0) sb.append(", "); sb.append("totalTime: "); sb.append(totalCpuDuration().toString()); } sb.append(']'); return sb.toString(); } } }