1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.execution.process;
4 import com.intellij.jna.JnaLoader;
5 import com.intellij.openapi.diagnostic.Logger;
6 import com.intellij.openapi.util.Ref;
7 import com.intellij.openapi.util.SystemInfo;
8 import com.intellij.openapi.util.SystemInfoRt;
9 import com.intellij.util.Processor;
10 import com.intellij.util.ReflectionUtil;
11 import com.sun.jna.Library;
12 import com.sun.jna.Native;
13 import org.jetbrains.annotations.NotNull;
15 import java.io.BufferedReader;
17 import java.io.IOException;
18 import java.io.InputStreamReader;
19 import java.lang.invoke.MethodHandle;
20 import java.lang.invoke.MethodHandles;
21 import java.lang.invoke.MethodType;
22 import java.nio.charset.StandardCharsets;
26 * Use {@link com.intellij.execution.process.OSProcessUtil} wherever possible.
28 public final class UnixProcessManager {
29 private static final Logger LOG = Logger.getInstance(UnixProcessManager.class);
31 private static final MethodHandle signalStringToIntConverter;
34 Class<?> signalClass = Class.forName("sun.misc.Signal");
35 MethodHandle signalConstructor = MethodHandles.publicLookup().findConstructor(signalClass, MethodType.methodType(void.class, String.class));
36 MethodHandle getNumber = MethodHandles.publicLookup().findVirtual(signalClass, "getNumber", MethodType.methodType(int.class));
37 signalStringToIntConverter = MethodHandles.filterReturnValue(signalConstructor, getNumber);
40 throw new RuntimeException(e);
44 // https://en.wikipedia.org/wiki/Signal_(IPC)#POSIX_signals
45 public static final int SIGINT = 2;
46 public static final int SIGABRT = 6;
47 public static final int SIGKILL = 9;
48 public static final int SIGTERM = 15;
49 public static final int SIGPIPE = getSignalNumber("PIPE");
51 private UnixProcessManager() { }
53 public static int getProcessId(@NotNull Process process) {
55 if (SystemInfoRt.IS_AT_LEAST_JAVA9 && "java.lang.ProcessImpl".equals(process.getClass().getName())) {
56 //noinspection JavaReflectionMemberAccess
57 return ((Long)Process.class.getMethod("pid").invoke(process)).intValue();
60 return Objects.requireNonNull(ReflectionUtil.getField(process.getClass(), process, int.class, "pid"));
63 throw new IllegalStateException("Failed to get PID from an instance of " + process.getClass() + ", OS: " + SystemInfo.OS_NAME, t);
67 public static int getCurrentProcessId() {
68 return Java8Helper.C_LIB != null ? Java8Helper.C_LIB.getpid() : 0;
72 * @param signalName without the 'SIG' prefix ('INT', not 'SIGINT')
73 * @return -1 for unknown signal
75 public static int getSignalNumber(@NotNull String signalName) {
77 return (int)signalStringToIntConverter.invokeExact(signalName);
79 catch (IllegalArgumentException e) {
83 throw new RuntimeException(t);
87 public static int sendSignal(int pid, @NotNull String signalName) {
88 final int signalNumber = getSignalNumber(signalName);
89 return (signalNumber == -1) ? -1 : sendSignal(pid, signalNumber);
92 public static int sendSignal(int pid, int signal) {
94 return Java8Helper.C_LIB.kill(pid, signal);
97 private static void checkCLib() {
98 if (Java8Helper.C_LIB == null) {
99 throw new IllegalStateException("Couldn't load c library, OS: " + SystemInfo.OS_NAME + ", isUnix: " + SystemInfo.isUnix);
103 public static boolean sendSigIntToProcessTree(@NotNull Process process) {
104 return sendSignalToProcessTree(process, SIGINT);
107 public static boolean sendSigKillToProcessTree(@NotNull Process process) {
108 return sendSignalToProcessTree(process, SIGKILL);
111 public static boolean sendSignalToProcessTree(@NotNull Process process, int signal) {
113 return sendSignalToProcessTree(getProcessId(process), signal);
115 catch (Exception e) {
116 LOG.warn("Error killing the process", e);
121 public static boolean sendSignalToProcessTree(int processId, int signal) {
124 int ourPid = Java8Helper.C_LIB.getpid();
125 return sendSignalToProcessTree(processId, signal, ourPid);
128 public static boolean sendSignalToProcessTree(int processId, int signal, int ourPid) {
129 if (LOG.isDebugEnabled()) {
130 LOG.debug("Sending signal " + signal + " to process tree with root PID " + processId);
133 final Ref<Integer> foundPid = new Ref<>();
134 final ProcessInfo processInfo = new ProcessInfo();
135 final List<Integer> childrenPids = new ArrayList<>();
137 findChildProcesses(ourPid, processId, foundPid, processInfo, childrenPids);
139 // result is true if signal was sent to at least one process
140 final boolean result;
141 if (!foundPid.isNull()) {
142 processInfo.killProcTree(foundPid.get(), signal);
146 for (Integer pid : childrenPids) {
147 processInfo.killProcTree(pid, signal);
149 result = !childrenPids.isEmpty(); //we've tried to kill at least one process
151 if (LOG.isDebugEnabled()) {
152 LOG.debug("Done sending signal " + signal + "; found: " + foundPid.get() + ", children: " + childrenPids + ", result: " + result);
158 private static void findChildProcesses(final int our_pid,
159 final int process_pid,
160 final Ref<? super Integer> foundPid,
161 final ProcessInfo processInfo,
162 final List<? super Integer> childrenPids) {
163 final Ref<Boolean> ourPidFound = Ref.create(false);
164 processPSOutput(getPSCmd(false), s -> {
165 StringTokenizer st = new StringTokenizer(s, " ");
167 int parent_pid = Integer.parseInt(st.nextToken());
168 int pid = Integer.parseInt(st.nextToken());
170 processInfo.register(pid, parent_pid);
172 if (parent_pid == process_pid) {
173 childrenPids.add(pid);
176 if (pid == our_pid) {
177 ourPidFound.set(true);
179 else if (pid == process_pid) {
180 if (parent_pid == our_pid || our_pid == -1) {
184 throw new IllegalStateException("Process (pid=" + process_pid + ") is not our child(our pid = " + our_pid + ")");
189 if (our_pid != -1 && !ourPidFound.get()) {
190 throw new IllegalStateException("IDE pid is not found in ps list(" + our_pid + ")");
194 public static void processPSOutput(String[] cmd, Processor<? super String> processor) {
195 processCommandOutput(cmd, processor, true, true);
198 public static void processCommandOutput(String[] cmd, Processor<? super String> processor, boolean skipFirstLine, boolean throwOnError) {
200 Process p = Runtime.getRuntime().exec(cmd);
201 processCommandOutput(p, processor, skipFirstLine, throwOnError);
203 catch (IOException e) {
204 throw new IllegalStateException(e);
208 private static void processCommandOutput(Process process, Processor<? super String> processor, boolean skipFirstLine, boolean throwOnError) throws IOException {
209 try (BufferedReader stdOutput = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
210 try (BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) {
212 stdOutput.readLine(); //ps output header
215 while ((s = stdOutput.readLine()) != null) {
216 processor.process(s);
219 StringBuilder errorStr = new StringBuilder();
220 while ((s = stdError.readLine()) != null) {
221 if (s.contains("environment variables being ignored")) { // PY-8160
224 errorStr.append(s).append("\n");
226 if (throwOnError && errorStr.length() > 0) {
227 throw new IOException("Error reading ps output:" + errorStr);
233 public static String[] getPSCmd(boolean commandLineOnly) {
234 return getPSCmd(commandLineOnly, false);
237 public static String[] getPSCmd(boolean commandLineOnly, boolean isShortenCommand) {
238 String psCommand = "/bin/ps";
239 if (!new File(psCommand).isFile()) {
242 if (SystemInfo.isLinux) {
243 return new String[]{psCommand, "-e", "--format", commandLineOnly ? "%a" : "%P%p%a"};
245 else if (SystemInfo.isMac || SystemInfo.isFreeBSD) {
246 final String command = isShortenCommand ? "comm" : "command";
247 return new String[]{psCommand, "-ax", "-o", commandLineOnly ? command : "ppid,pid," + command};
250 throw new IllegalStateException(System.getProperty("os.name") + " is not supported.");
254 private static class ProcessInfo {
255 private final Map<Integer, List<Integer>> BY_PARENT = new TreeMap<>(); // pid -> list of children pids
257 public void register(Integer pid, Integer parentPid) {
258 List<Integer> children = BY_PARENT.get(parentPid);
259 if (children == null) BY_PARENT.put(parentPid, children = new LinkedList<>());
263 public void killProcTree(int pid, int signal) {
264 List<Integer> children = BY_PARENT.get(pid);
265 if (children != null) {
266 for (int child : children) {
267 killProcTree(child, signal);
270 if (LOG.isDebugEnabled()) {
271 LOG.debug("Sending signal " + signal + " to PID " + pid);
273 sendSignal(pid, signal);
278 final class Java8Helper {
279 interface CLib extends Library {
282 int kill(int pid, int signal);
285 static final CLib C_LIB;
290 if (SystemInfoRt.isUnix && JnaLoader.isLoaded()) {
291 lib = Native.load("c", CLib.class);
294 catch (Throwable t) {
295 Logger.getInstance(UnixProcessManager.class).warn("Can't load standard library", t);