1 package org.apache.maven.surefire.booter;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.nio.charset.Charset;
25 import java.util.Queue;
26 import java.util.Scanner;
27 import java.util.StringTokenizer;
28 import java.util.concurrent.ConcurrentLinkedQueue;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 import static java.lang.Long.parseLong;
33 import static java.util.concurrent.TimeUnit.DAYS;
34 import static java.util.concurrent.TimeUnit.HOURS;
35 import static java.util.concurrent.TimeUnit.MINUTES;
36 import static java.util.regex.Pattern.compile;
37 import static org.apache.commons.io.IOUtils.closeQuietly;
38 import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX;
39 import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
40 import static org.apache.maven.surefire.booter.ProcessInfo.ERR_PROCESS_INFO;
41 import static org.apache.maven.surefire.booter.ProcessInfo.INVALID_PROCESS_INFO;
42
43
44
45
46
47
48
49 final class PpidChecker
50 {
51 private static final String WMIC_CREATION_DATE = "CreationDate";
52
53 private final Queue<Process> destroyableCommands = new ConcurrentLinkedQueue<Process>();
54
55
56
57
58 static final Pattern UNIX_CMD_OUT_PATTERN = compile( "^(((\\d+)-)?(\\d{2}):)?(\\d{2}):(\\d{2})$" );
59
60 private final long pluginPid;
61
62 private volatile ProcessInfo pluginProcessInfo;
63 private volatile boolean stopped;
64
65 PpidChecker( long pluginPid )
66 {
67 this.pluginPid = pluginPid;
68 }
69
70 boolean canUse()
71 {
72 return pluginProcessInfo == null
73 ? IS_OS_WINDOWS || IS_OS_UNIX
74 : pluginProcessInfo.isValid() && !pluginProcessInfo.isError();
75 }
76
77
78
79
80
81
82
83
84 @SuppressWarnings( "unchecked" )
85 boolean isProcessAlive()
86 {
87 if ( !canUse() )
88 {
89 throw new IllegalStateException( "irrelevant to call isProcessAlive()" );
90 }
91
92 if ( IS_OS_WINDOWS )
93 {
94 ProcessInfo previousPluginProcessInfo = pluginProcessInfo;
95 pluginProcessInfo = windows();
96 if ( isStopped() || pluginProcessInfo.isError() )
97 {
98 throw new IllegalStateException( "error to read process" );
99 }
100
101 return pluginProcessInfo.isValid()
102 && ( previousPluginProcessInfo == null
103 || pluginProcessInfo.isTimeEqualTo( previousPluginProcessInfo ) );
104 }
105 else if ( IS_OS_UNIX )
106 {
107 ProcessInfo previousPluginProcessInfo = pluginProcessInfo;
108 pluginProcessInfo = unix();
109 if ( isStopped() || pluginProcessInfo.isError() )
110 {
111 throw new IllegalStateException( "error to read process" );
112 }
113
114 return pluginProcessInfo.isValid()
115 && ( previousPluginProcessInfo == null
116 || pluginProcessInfo.isTimeEqualTo( previousPluginProcessInfo )
117 || pluginProcessInfo.isTimeAfter( previousPluginProcessInfo ) );
118 }
119
120 throw new IllegalStateException();
121 }
122
123
124
125
126
127
128
129
130 ProcessInfo unix()
131 {
132 ProcessInfoConsumer reader = new ProcessInfoConsumer( Charset.defaultCharset().name() )
133 {
134 @Override
135 ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo )
136 {
137 if ( !previousProcessInfo.isValid() )
138 {
139 Matcher matcher = UNIX_CMD_OUT_PATTERN.matcher( line );
140 if ( matcher.matches() )
141 {
142 long pidUptime = fromDays( matcher )
143 + fromHours( matcher )
144 + fromMinutes( matcher )
145 + fromSeconds( matcher );
146 return ProcessInfo.unixProcessInfo( pluginPid, pidUptime );
147 }
148 }
149 return previousProcessInfo;
150 }
151 };
152
153 return reader.execute( "/bin/sh", "-c", unixPathToPS() + " -o etime= -p " + pluginPid );
154 }
155
156 ProcessInfo windows()
157 {
158 ProcessInfoConsumer reader = new ProcessInfoConsumer( "US-ASCII" )
159 {
160 private boolean hasHeader;
161
162 @Override
163 ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo )
164 {
165 if ( !previousProcessInfo.isValid() )
166 {
167 StringTokenizer args = new StringTokenizer( line );
168 if ( args.countTokens() == 1 )
169 {
170 if ( hasHeader )
171 {
172 String startTimestamp = args.nextToken();
173 return ProcessInfo.windowsProcessInfo( pluginPid, startTimestamp );
174 }
175 else
176 {
177 hasHeader = WMIC_CREATION_DATE.equals( args.nextToken() );
178 }
179 }
180 }
181 return previousProcessInfo;
182 }
183 };
184 String pid = String.valueOf( pluginPid );
185 return reader.execute( "CMD", "/A", "/X", "/C",
186 "wmic process where (ProcessId=" + pid + ") get " + WMIC_CREATION_DATE
187 );
188 }
189
190 void destroyActiveCommands()
191 {
192 stopped = true;
193 for ( Process p = destroyableCommands.poll(); p != null; p = destroyableCommands.poll() )
194 {
195 p.destroy();
196 }
197 }
198
199 private boolean isStopped()
200 {
201 return stopped;
202 }
203
204 static String unixPathToPS()
205 {
206 return new File( "/usr/bin/ps" ).canExecute() ? "/usr/bin/ps" : "/bin/ps";
207 }
208
209 static long fromDays( Matcher matcher )
210 {
211 String s = matcher.group( 3 );
212 return s == null ? 0L : DAYS.toSeconds( parseLong( s ) );
213 }
214
215 static long fromHours( Matcher matcher )
216 {
217 String s = matcher.group( 4 );
218 return s == null ? 0L : HOURS.toSeconds( parseLong( s ) );
219 }
220
221 static long fromMinutes( Matcher matcher )
222 {
223 String s = matcher.group( 5 );
224 return s == null ? 0L : MINUTES.toSeconds( parseLong( s ) );
225 }
226
227 static long fromSeconds( Matcher matcher )
228 {
229 String s = matcher.group( 6 );
230 return s == null ? 0L : parseLong( s );
231 }
232
233 private static void checkValid( Scanner scanner )
234 throws IOException
235 {
236 IOException exception = scanner.ioException();
237 if ( exception != null )
238 {
239 throw exception;
240 }
241 }
242
243
244
245
246
247
248
249
250 private abstract class ProcessInfoConsumer
251 {
252 private final String charset;
253
254 ProcessInfoConsumer( String charset )
255 {
256 this.charset = charset;
257 }
258
259 abstract ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo );
260
261 ProcessInfo execute( String... command )
262 {
263 ProcessBuilder processBuilder = new ProcessBuilder( command );
264 processBuilder.redirectErrorStream( true );
265 Process process = null;
266 ProcessInfo processInfo = INVALID_PROCESS_INFO;
267 try
268 {
269 process = processBuilder.start();
270 destroyableCommands.add( process );
271 Scanner scanner = new Scanner( process.getInputStream(), charset );
272 while ( scanner.hasNextLine() )
273 {
274 String line = scanner.nextLine().trim();
275 processInfo = consumeLine( line, processInfo );
276 }
277 checkValid( scanner );
278 int exitCode = process.waitFor();
279 return exitCode == 0 ? processInfo : INVALID_PROCESS_INFO;
280 }
281 catch ( IOException e )
282 {
283 DumpErrorSingleton.getSingleton().dumpException( e );
284 return ERR_PROCESS_INFO;
285 }
286 catch ( InterruptedException e )
287 {
288 DumpErrorSingleton.getSingleton().dumpException( e );
289 return ERR_PROCESS_INFO;
290 }
291 finally
292 {
293 if ( process != null )
294 {
295 destroyableCommands.remove( process );
296 process.destroy();
297 closeQuietly( process.getInputStream() );
298 closeQuietly( process.getErrorStream() );
299 closeQuietly( process.getOutputStream() );
300 }
301 }
302 }
303 }
304
305 }