2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.execution.process;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.Ref;
20 import com.intellij.openapi.util.SystemInfo;
21 import com.intellij.util.ObjectUtils;
22 import com.intellij.util.Processor;
23 import com.intellij.util.ReflectionUtil;
24 import com.sun.jna.Library;
25 import com.sun.jna.Native;
26 import com.sun.jna.Platform;
27 import org.jetbrains.annotations.NotNull;
29 import java.io.BufferedReader;
31 import java.io.IOException;
32 import java.io.InputStreamReader;
38 public class UnixProcessManager {
39 private static final Logger LOG = Logger.getInstance(UnixProcessManager.class);
41 public static final int SIGINT = 2;
42 public static final int SIGKILL = 9;
43 public static final int SIGTERM = 15;
44 public static final int SIGCONT = 19;
46 private static CLib C_LIB;
50 if (!Platform.isWindows()) {
51 C_LIB = ((CLib)Native.loadLibrary("c", CLib.class));
55 Logger log = Logger.getInstance(UnixProcessManager.class);
56 log.warn("Can't load c library", e);
61 private UnixProcessManager() { }
63 public static int getProcessPid(@NotNull Process process) {
65 Integer pid = ReflectionUtil.getField(process.getClass(), process, int.class, "pid");
66 return ObjectUtils.assertNotNull(pid);
69 throw new IllegalStateException("Cannot get PID from instance of " + process.getClass()
70 + ", OS: " + SystemInfo.OS_NAME, e);
74 public static void sendSignal(int pid, int signal) {
76 C_LIB.kill(pid, signal);
79 private static void checkCLib() {
81 throw new IllegalStateException("Couldn't load c library, OS: " + SystemInfo.OS_NAME
82 + ", isUnix: " + SystemInfo.isUnix);
86 public static boolean sendSigIntToProcessTree(Process process) {
87 return sendSignalToProcessTree(process, SIGINT);
90 public static boolean sendSigKillToProcessTree(Process process) {
91 return sendSignalToProcessTree(process, SIGKILL);
95 * Sends signal to every child process of a tree root process
97 * @param process tree root process
99 public static boolean sendSignalToProcessTree(@NotNull Process process, int signal) {
103 final int our_pid = C_LIB.getpid();
104 final int process_pid = getProcessPid(process);
105 if (LOG.isDebugEnabled()) {
106 LOG.debug("Sending signal " + signal + " to process tree with root PID " + process_pid);
109 final Ref<Integer> foundPid = new Ref<Integer>();
110 final ProcessInfo processInfo = new ProcessInfo();
111 final List<Integer> childrenPids = new ArrayList<Integer>();
113 findChildProcesses(our_pid, process_pid, foundPid, processInfo, childrenPids);
115 // result is true if signal was sent to at least one process
116 final boolean result;
117 if (!foundPid.isNull()) {
118 processInfo.killProcTree(foundPid.get(), signal, UNIX_KILLER);
122 for (Integer pid : childrenPids) {
123 processInfo.killProcTree(pid, signal, UNIX_KILLER);
125 result = !childrenPids.isEmpty(); //we've tried to kill at least one process
127 if (LOG.isDebugEnabled()) {
128 LOG.debug("Done sending signal " + signal + "; found: " + foundPid.get()
129 + ", children: " + childrenPids + ", result: " + result);
134 catch (Exception e) {
135 //If we fail somehow just return false
136 LOG.warn("Error killing the process", e);
141 private static void findChildProcesses(final int our_pid,
142 final int process_pid,
143 final Ref<Integer> foundPid,
144 final ProcessInfo processInfo, final List<Integer> childrenPids) {
145 final Ref<Boolean> ourPidFound = Ref.create(false);
146 processPSOutput(getPSCmd(false), new Processor<String>() {
148 public boolean process(String s) {
149 StringTokenizer st = new StringTokenizer(s, " ");
151 int parent_pid = Integer.parseInt(st.nextToken());
152 int pid = Integer.parseInt(st.nextToken());
154 processInfo.register(pid, parent_pid);
156 if (parent_pid == process_pid) {
157 childrenPids.add(pid);
160 if (pid == our_pid) {
161 ourPidFound.set(true);
163 else if (pid == process_pid) {
164 if (parent_pid == our_pid) {
168 throw new IllegalStateException("Process (pid=" + process_pid + ") is not our child(our pid = " + our_pid + ")");
174 if (!ourPidFound.get()) {
175 throw new IllegalStateException("IDE pid is not found in ps list(" + our_pid + ")");
179 public static void processPSOutput(String[] cmd, Processor<String> processor) {
180 processCommandOutput(cmd, processor, true, true);
183 public static void processCommandOutput(String[] cmd, Processor<String> processor, boolean skipFirstLine, boolean throwOnError) {
185 Process p = Runtime.getRuntime().exec(cmd);
186 processCommandOutput(p, processor, skipFirstLine, throwOnError);
188 catch (IOException e) {
189 throw new IllegalStateException(e);
193 private static void processCommandOutput(Process process, Processor<String> processor, boolean skipFirstLine, boolean throwOnError) throws IOException {
194 BufferedReader stdOutput = new BufferedReader(new InputStreamReader(process.getInputStream()));
196 BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
199 stdOutput.readLine(); //ps output header
202 while ((s = stdOutput.readLine()) != null) {
203 processor.process(s);
206 StringBuilder errorStr = new StringBuilder();
207 while ((s = stdError.readLine()) != null) {
208 if (s.contains("environment variables being ignored")) { // PY-8160
211 errorStr.append(s).append("\n");
213 if (throwOnError && errorStr.length() > 0) {
214 throw new IOException("Error reading ps output:" + errorStr.toString());
226 public static String[] getPSCmd(boolean commandLineOnly) {
227 return getPSCmd(commandLineOnly, false);
230 public static String[] getPSCmd(boolean commandLineOnly, boolean isShortenCommand) {
231 String psCommand = "/bin/ps";
232 if (!new File(psCommand).isFile()) {
235 if (SystemInfo.isLinux) {
236 return new String[]{psCommand, "-e", "--format", commandLineOnly ? "%a" : "%P%p%a"};
238 else if (SystemInfo.isMac || SystemInfo.isFreeBSD) {
239 final String command = isShortenCommand ? "comm" : "command";
240 return new String[]{psCommand, "-ax", "-o", commandLineOnly ? command : "ppid,pid," + command};
243 throw new IllegalStateException(System.getProperty("os.name") + " is not supported.");
247 private interface CLib extends Library {
249 int kill(int pid, int signal);
252 public static class ProcessInfo {
253 private Map<Integer, List<Integer>> BY_PARENT = new TreeMap<Integer, List<Integer>>(); // pid -> list of children pids
255 public void register(Integer pid, Integer parentPid) {
256 List<Integer> children = BY_PARENT.get(parentPid);
257 if (children == null) children = new LinkedList<Integer>();
259 BY_PARENT.put(parentPid, children);
262 public void killProcTree(int pid, int signal, ProcessKiller killer) {
263 List<Integer> children = BY_PARENT.get(pid);
264 if (children != null) {
265 for (int child : children) killProcTree(child, signal, killer);
267 if (LOG.isDebugEnabled()) {
268 LOG.debug("Sending signal " + signal + " to PID " + pid);
270 killer.kill(pid, signal);
274 public interface ProcessKiller {
275 void kill(int pid, int signal);
278 private static final ProcessKiller UNIX_KILLER = new ProcessKiller() {
280 public void kill(int pid, int signal) {
281 sendSignal(pid, signal);