diff: add gaps in content titles
[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.Processor;
22 import com.intellij.util.ReflectionUtil;
23 import com.sun.jna.Library;
24 import com.sun.jna.Native;
25 import com.sun.jna.Platform;
26
27 import java.io.BufferedReader;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.InputStreamReader;
31 import java.util.*;
32
33 /**
34  * @author traff
35  */
36 public class UnixProcessManager {
37   private static final Logger LOG = Logger.getInstance(UnixProcessManager.class);
38
39   public static final int SIGINT = 2;
40   public static final int SIGKILL = 9;
41   public static final int SIGTERM = 15;
42   public static final int SIGCONT = 19;
43
44   private static CLib C_LIB;
45
46   static {
47     try {
48       if (!Platform.isWindows()) {
49         C_LIB = ((CLib)Native.loadLibrary("c", CLib.class));
50       }
51     }
52     catch (Throwable e) {
53       Logger log = Logger.getInstance(UnixProcessManager.class);
54       log.warn("Can't load c library", e);
55       C_LIB = null;
56     }
57   }
58
59   private UnixProcessManager() { }
60
61   public static int getProcessPid(Process process) {
62     try {
63       return ReflectionUtil.getField(process.getClass(), process, int.class, "pid");
64     }
65     catch (Exception e) {
66       throw new IllegalStateException("system is not unix", e);
67     }
68   }
69
70   public static void sendSignal(int pid, int signal) {
71     checkCLib();
72     C_LIB.kill(pid, signal);
73   }
74
75   private static void checkCLib() {
76     if (C_LIB == null) {
77       throw new IllegalStateException("System is not unix(couldn't load c library)");
78     }
79   }
80
81   public static boolean sendSigIntToProcessTree(Process process) {
82     return sendSignalToProcessTree(process, SIGINT);
83   }
84
85   public static boolean sendSigKillToProcessTree(Process process) {
86     return sendSignalToProcessTree(process, SIGKILL);
87   }
88
89   /**
90    * Sends signal to every child process of a tree root process
91    *
92    * @param process tree root process
93    */
94   public static boolean sendSignalToProcessTree(Process process, int signal) {
95     try {
96       checkCLib();
97
98       final int our_pid = C_LIB.getpid();
99       final int process_pid = getProcessPid(process);
100
101       final Ref<Integer> foundPid = new Ref<Integer>();
102       final ProcessInfo processInfo = new ProcessInfo();
103       final List<Integer> childrenPids = new ArrayList<Integer>();
104
105       findChildProcesses(our_pid, process_pid, foundPid, processInfo, childrenPids);
106
107       // result is true if signal was sent to at least one process
108       final boolean result;
109       if (!foundPid.isNull()) {
110         processInfo.killProcTree(foundPid.get(), signal, UNIX_KILLER);
111         result = true;
112       }
113       else {
114         for (Integer pid : childrenPids) {
115           processInfo.killProcTree(pid, signal, UNIX_KILLER);
116         }
117         result = !childrenPids.isEmpty(); //we've tried to kill at least one process
118       }
119
120       return result;
121     }
122     catch (Exception e) {
123       //If we fail somehow just return false
124       LOG.warn("Error killing the process", e);
125       return false;
126     }
127   }
128
129   private static void findChildProcesses(final int our_pid,
130                                          final int process_pid,
131                                          final Ref<Integer> foundPid,
132                                          final ProcessInfo processInfo, final List<Integer> childrenPids) {
133     final Ref<Boolean> ourPidFound = Ref.create(false);
134     processPSOutput(getPSCmd(false), new Processor<String>() {
135       @Override
136       public boolean process(String s) {
137         StringTokenizer st = new StringTokenizer(s, " ");
138
139         int parent_pid = Integer.parseInt(st.nextToken());
140         int pid = Integer.parseInt(st.nextToken());
141
142         processInfo.register(pid, parent_pid);
143
144         if (parent_pid == process_pid) {
145           childrenPids.add(pid);
146         }
147
148         if (pid == our_pid) {
149           ourPidFound.set(true);
150         }
151         else if (pid == process_pid) {
152           if (parent_pid == our_pid) {
153             foundPid.set(pid);
154           }
155           else {
156             throw new IllegalStateException("Process (pid=" + process_pid + ") is not our child(our pid = " + our_pid + ")");
157           }
158         }
159         return false;
160       }
161     });
162     if (!ourPidFound.get()) {
163       throw new IllegalStateException("IDE pid is not found in ps list(" + our_pid + ")");
164     }
165   }
166
167   public static void processPSOutput(String[] cmd, Processor<String> processor) {
168     processCommandOutput(cmd, processor, true, true);
169   }
170
171   public static void processCommandOutput(String[] cmd, Processor<String> processor, boolean skipFirstLine, boolean throwOnError) {
172     try {
173       Process p = Runtime.getRuntime().exec(cmd);
174       processCommandOutput(p, processor, skipFirstLine, throwOnError);
175     }
176     catch (IOException e) {
177       throw new IllegalStateException(e);
178     }
179   }
180
181   private static void processCommandOutput(Process process, Processor<String> processor, boolean skipFirstLine, boolean throwOnError) throws IOException {
182     BufferedReader stdOutput = new BufferedReader(new InputStreamReader(process.getInputStream()));
183     try {
184       BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
185       try {
186         if (skipFirstLine) {
187           stdOutput.readLine(); //ps output header
188         }
189         String s;
190         while ((s = stdOutput.readLine()) != null) {
191           processor.process(s);
192         }
193
194         StringBuilder errorStr = new StringBuilder();
195         while ((s = stdError.readLine()) != null) {
196           if (s.contains("environment variables being ignored")) {  // PY-8160
197             continue;
198           }
199           errorStr.append(s).append("\n");
200         }
201         if (throwOnError && errorStr.length() > 0) {
202           throw new IOException("Error reading ps output:" + errorStr.toString());
203         }
204       }
205       finally {
206         stdError.close();
207       }
208     }
209     finally {
210       stdOutput.close();
211     }
212   }
213
214   public static String[] getPSCmd(boolean commandLineOnly) {
215     return getPSCmd(commandLineOnly, false);
216   }
217
218   public static String[] getPSCmd(boolean commandLineOnly, boolean isShortenCommand) {
219     String psCommand = "/bin/ps";
220     if (!new File(psCommand).isFile()) {
221       psCommand = "ps";
222     }
223     if (SystemInfo.isLinux) {
224       return new String[]{psCommand, "-e", "--format", commandLineOnly ? "%a" : "%P%p%a"};
225     }
226     else if (SystemInfo.isMac || SystemInfo.isFreeBSD) {
227       final String command = isShortenCommand ? "comm" : "command";
228       return new String[]{psCommand, "-ax", "-o", commandLineOnly ? command : "ppid,pid," + command};
229     }
230     else {
231       throw new IllegalStateException(System.getProperty("os.name") + " is not supported.");
232     }
233   }
234
235   private interface CLib extends Library {
236     int getpid();
237     int kill(int pid, int signal);
238   }
239
240   public static class ProcessInfo {
241     private Map<Integer, List<Integer>> BY_PARENT = new TreeMap<Integer, List<Integer>>(); // pid -> list of children pids
242
243     public void register(Integer pid, Integer parentPid) {
244       List<Integer> children = BY_PARENT.get(parentPid);
245       if (children == null) children = new LinkedList<Integer>();
246       children.add(pid);
247       BY_PARENT.put(parentPid, children);
248     }
249
250     public void killProcTree(int pid, int signal, ProcessKiller killer) {
251       List<Integer> children = BY_PARENT.get(pid);
252       if (children != null) {
253         for (int child : children) killProcTree(child, signal, killer);
254       }
255       killer.kill(pid, signal);
256     }
257   }
258
259   public interface ProcessKiller {
260     void kill(int pid, int signal);
261   }
262
263   private static final ProcessKiller UNIX_KILLER = new ProcessKiller() {
264     @Override
265     public void kill(int pid, int signal) {
266       sendSignal(pid, signal);
267     }
268   };
269 }