6a134e40d8546fe522d9c5671177d0a4efbca705
[idea/community.git] / platform / util / src / com / intellij / execution / process / UnixProcessManager.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.execution.process;
17
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;
28
29 import java.io.BufferedReader;
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.InputStreamReader;
33 import java.util.*;
34
35 /**
36  * @author traff
37  */
38 public class UnixProcessManager {
39   private static final Logger LOG = Logger.getInstance(UnixProcessManager.class);
40
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;
45
46   private static CLib C_LIB;
47
48   static {
49     try {
50       if (!Platform.isWindows()) {
51         C_LIB = ((CLib)Native.loadLibrary("c", CLib.class));
52       }
53     }
54     catch (Throwable e) {
55       Logger log = Logger.getInstance(UnixProcessManager.class);
56       log.warn("Can't load c library", e);
57       C_LIB = null;
58     }
59   }
60
61   private UnixProcessManager() { }
62
63   public static int getProcessPid(@NotNull Process process) {
64     try {
65       Integer pid = ReflectionUtil.getField(process.getClass(), process, int.class, "pid");
66       return ObjectUtils.assertNotNull(pid);
67     }
68     catch (Exception e) {
69       throw new IllegalStateException("Cannot get PID from instance of " + process.getClass()
70                                       + ", OS: " + SystemInfo.OS_NAME, e);
71     }
72   }
73
74   public static void sendSignal(int pid, int signal) {
75     checkCLib();
76     C_LIB.kill(pid, signal);
77   }
78
79   private static void checkCLib() {
80     if (C_LIB == null) {
81       throw new IllegalStateException("Couldn't load c library, OS: " + SystemInfo.OS_NAME
82                                       + ", isUnix: " + SystemInfo.isUnix);
83     }
84   }
85
86   public static boolean sendSigIntToProcessTree(Process process) {
87     return sendSignalToProcessTree(process, SIGINT);
88   }
89
90   public static boolean sendSigKillToProcessTree(Process process) {
91     return sendSignalToProcessTree(process, SIGKILL);
92   }
93
94   /**
95    * Sends signal to every child process of a tree root process
96    *
97    * @param process tree root process
98    */
99   public static boolean sendSignalToProcessTree(@NotNull Process process, int signal) {
100     try {
101       checkCLib();
102
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);
107       }
108
109       final Ref<Integer> foundPid = new Ref<Integer>();
110       final ProcessInfo processInfo = new ProcessInfo();
111       final List<Integer> childrenPids = new ArrayList<Integer>();
112
113       findChildProcesses(our_pid, process_pid, foundPid, processInfo, childrenPids);
114
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);
119         result = true;
120       }
121       else {
122         for (Integer pid : childrenPids) {
123           processInfo.killProcTree(pid, signal, UNIX_KILLER);
124         }
125         result = !childrenPids.isEmpty(); //we've tried to kill at least one process
126       }
127       if (LOG.isDebugEnabled()) {
128         LOG.debug("Done sending signal " + signal + "; found: " + foundPid.get()
129                   + ", children: " + childrenPids + ", result: " + result);
130       }
131
132       return result;
133     }
134     catch (Exception e) {
135       //If we fail somehow just return false
136       LOG.warn("Error killing the process", e);
137       return false;
138     }
139   }
140
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>() {
147       @Override
148       public boolean process(String s) {
149         StringTokenizer st = new StringTokenizer(s, " ");
150
151         int parent_pid = Integer.parseInt(st.nextToken());
152         int pid = Integer.parseInt(st.nextToken());
153
154         processInfo.register(pid, parent_pid);
155
156         if (parent_pid == process_pid) {
157           childrenPids.add(pid);
158         }
159
160         if (pid == our_pid) {
161           ourPidFound.set(true);
162         }
163         else if (pid == process_pid) {
164           if (parent_pid == our_pid) {
165             foundPid.set(pid);
166           }
167           else {
168             throw new IllegalStateException("Process (pid=" + process_pid + ") is not our child(our pid = " + our_pid + ")");
169           }
170         }
171         return false;
172       }
173     });
174     if (!ourPidFound.get()) {
175       throw new IllegalStateException("IDE pid is not found in ps list(" + our_pid + ")");
176     }
177   }
178
179   public static void processPSOutput(String[] cmd, Processor<String> processor) {
180     processCommandOutput(cmd, processor, true, true);
181   }
182
183   public static void processCommandOutput(String[] cmd, Processor<String> processor, boolean skipFirstLine, boolean throwOnError) {
184     try {
185       Process p = Runtime.getRuntime().exec(cmd);
186       processCommandOutput(p, processor, skipFirstLine, throwOnError);
187     }
188     catch (IOException e) {
189       throw new IllegalStateException(e);
190     }
191   }
192
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()));
195     try {
196       BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
197       try {
198         if (skipFirstLine) {
199           stdOutput.readLine(); //ps output header
200         }
201         String s;
202         while ((s = stdOutput.readLine()) != null) {
203           processor.process(s);
204         }
205
206         StringBuilder errorStr = new StringBuilder();
207         while ((s = stdError.readLine()) != null) {
208           if (s.contains("environment variables being ignored")) {  // PY-8160
209             continue;
210           }
211           errorStr.append(s).append("\n");
212         }
213         if (throwOnError && errorStr.length() > 0) {
214           throw new IOException("Error reading ps output:" + errorStr.toString());
215         }
216       }
217       finally {
218         stdError.close();
219       }
220     }
221     finally {
222       stdOutput.close();
223     }
224   }
225
226   public static String[] getPSCmd(boolean commandLineOnly) {
227     return getPSCmd(commandLineOnly, false);
228   }
229
230   public static String[] getPSCmd(boolean commandLineOnly, boolean isShortenCommand) {
231     String psCommand = "/bin/ps";
232     if (!new File(psCommand).isFile()) {
233       psCommand = "ps";
234     }
235     if (SystemInfo.isLinux) {
236       return new String[]{psCommand, "-e", "--format", commandLineOnly ? "%a" : "%P%p%a"};
237     }
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};
241     }
242     else {
243       throw new IllegalStateException(System.getProperty("os.name") + " is not supported.");
244     }
245   }
246
247   private interface CLib extends Library {
248     int getpid();
249     int kill(int pid, int signal);
250   }
251
252   public static class ProcessInfo {
253     private Map<Integer, List<Integer>> BY_PARENT = new TreeMap<Integer, List<Integer>>(); // pid -> list of children pids
254
255     public void register(Integer pid, Integer parentPid) {
256       List<Integer> children = BY_PARENT.get(parentPid);
257       if (children == null) children = new LinkedList<Integer>();
258       children.add(pid);
259       BY_PARENT.put(parentPid, children);
260     }
261
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);
266       }
267       if (LOG.isDebugEnabled()) {
268         LOG.debug("Sending signal " + signal + " to PID " + pid);
269       }
270       killer.kill(pid, signal);
271     }
272   }
273
274   public interface ProcessKiller {
275     void kill(int pid, int signal);
276   }
277
278   private static final ProcessKiller UNIX_KILLER = new ProcessKiller() {
279     @Override
280     public void kill(int pid, int signal) {
281       sendSignal(pid, signal);
282     }
283   };
284 }