source: main/trunk/greenstone3/src/java/org/greenstone/util/SafeProcess.java@ 31665

Last change on this file since 31665 was 31665, checked in by ak19, 7 years ago

GS3 source code now updated to use SafeProcess instead of Process (calling Runtime.exec() directly). The use of SafeProcess in RunTarget and BrowserLauncher has been tested on Linux. GDBMWrapper, MapRetrieve and admin/guiext's Command.java are not tested. For GDBMWrapper, because it's in a bit of code that works with txtgz databases. MapRetrive.java and Command.java are untested because I don't know how to test them.

File size: 43.8 KB
Line 
1package org.greenstone.util;
2
3import java.io.BufferedReader;
4import java.io.BufferedWriter;
5import java.io.Closeable;
6import java.io.File;
7import java.io.InputStream;
8import java.io.InputStreamReader;
9import java.io.IOException;
10import java.io.OutputStream;
11import java.io.OutputStreamWriter;
12import java.util.Arrays;
13import java.util.Scanner;
14import java.util.Stack;
15
16import com.sun.jna.*;
17import com.sun.jna.platform.win32.Kernel32;
18import com.sun.jna.platform.win32.WinNT;
19
20import java.lang.reflect.Field;
21//import java.lang.reflect.Method;
22
23import org.apache.log4j.*;
24
25//import org.greenstone.gatherer.DebugStream;
26
27// Use this class to run a Java Process. It follows the good and safe practices at
28// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
29// to avoid blocking problems that can arise from a Process' input and output streams.
30
31// On Windows, Perl could launch processes as proper ProcessTrees: http://search.cpan.org/~gsar/libwin32-0.191/
32// Then killing the root process will kill child processes naturally.
33
34public class SafeProcess {
35 public static int DEBUG = 1;
36
37 public static final int STDERR = 0;
38 public static final int STDOUT = 1;
39 public static final int STDIN = 2;
40 // can't make this variable final and init in a static block, because it needs to use other SafeProcess static methods which rely on this in turn:
41 public static String WIN_KILL_CMD;
42
43
44 // charset for reading process stderr and stdout streams
45 //public static final String UTF8 = "UTF-8";
46
47 static Logger logger = Logger.getLogger(org.greenstone.util.SafeProcess.class.getName());
48
49 // input to SafeProcess and initialising it
50 private String command = null;
51 private String[] command_args = null;
52 private String[] envp = null;
53 private File dir = null;
54 private String inputStr = null;
55 private Process process = null;
56 private boolean forciblyTerminateProcess = false;
57
58 // output from running SafeProcess.runProcess()
59 private String outputStr = "";
60 private String errorStr = "";
61 private int exitValue = -1;
62 //private String charset = null;
63
64 // allow callers to process exceptions of the main process thread if they want
65 private ExceptionHandler exceptionHandler = null;
66
67 // whether std/err output should be split at new lines
68 private boolean splitStdOutputNewLines = false;
69 private boolean splitStdErrorNewLines = false;
70
71 // call one of these constructors
72
73 // cmd args version
74 public SafeProcess(String[] cmd_args)
75 {
76 command_args = cmd_args;
77 }
78
79 // cmd string version
80 public SafeProcess(String cmdStr)
81 {
82 command = cmdStr;
83 }
84
85 // cmd args with env version, launchDir can be null.
86 public SafeProcess(String[] cmd_args, String[] envparams, File launchDir)
87 {
88 command_args = cmd_args;
89 envp = envparams;
90 dir = launchDir;
91 }
92
93 // The important methods:
94 // to get the output from std err and std out streams after the process has been run
95 public String getStdOutput() { return outputStr; }
96 public String getStdError() { return errorStr; }
97 public int getExitValue() { return exitValue; }
98
99 //public void setStreamCharSet(String charset) { this.charset = charset; }
100
101 // set any string to send as input to the process spawned by SafeProcess
102 public void setInputString(String sendStr) {
103 inputStr = sendStr;
104 }
105
106 // register a SafeProcess ExceptionHandler whose gotException() method will
107 // get called for each exception encountered
108 public void setExceptionHandler(ExceptionHandler exception_handler) {
109 exceptionHandler = exception_handler;
110 }
111
112 // set if you want the std output or err output to have \n at each newline read from the stream
113 public void setSplitStdOutputNewLines(boolean split) {
114 splitStdOutputNewLines = split;
115 }
116 public void setSplitStdErrorNewLines(boolean split) {
117 splitStdErrorNewLines = split;
118 }
119
120 // In future, think of changing the method doRuntimeExec() over to using ProcessBuilder
121 // instead of Runtime.exec(). ProcessBuilder seems to have been introduced from Java 5.
122 // https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html
123 // https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/
124 // which suggests using Apache Common Exec to launch processes and says what will be forthcoming in Java 9
125
126 private Process doRuntimeExec() throws IOException {
127 Process prcs = null;
128 Runtime rt = Runtime.getRuntime();
129
130 if(this.command != null) {
131 log("SafeProcess running: " + command);
132 prcs = rt.exec(this.command);
133 }
134 else { // at least command_args must be set now
135
136 // http://stackoverflow.com/questions/5283444/convert-array-of-strings-into-a-string-in-java
137 //log("SafeProcess running:" + Arrays.toString(command_args));
138 StringBuffer cmdDisplay = new StringBuffer();
139 for(int i = 0; i < command_args.length; i++) {
140 cmdDisplay.append(" ").append(command_args[i]);
141 }
142 log("SafeProcess running: [" + cmdDisplay + "]");
143 cmdDisplay = null; // let the GC have it
144
145
146 if(this.envp == null) {
147 prcs = rt.exec(this.command_args);
148 } else { // launch process using cmd str with env params
149
150 if(this.dir == null) {
151 //log("\twith: " + Arrays.toString(this.envp));
152 prcs = rt.exec(this.command_args, this.envp);
153 } else {
154 //log("\tfrom directory: " + this.dir);
155 //log("\twith: " + Arrays.toString(this.envp));
156 prcs = rt.exec(this.command_args, this.envp, this.dir);
157 }
158 }
159 }
160
161 return prcs;
162 }
163
164 // Copied from gli's gui/FormatConversionDialog.java
165 private int waitForWithStreams(SafeProcess.OutputStreamGobbler inputGobbler,
166 SafeProcess.InputStreamGobbler outputGobbler,
167 SafeProcess.InputStreamGobbler errorGobbler)
168 throws IOException, InterruptedException
169 {
170 // kick off the stream gobblers
171 inputGobbler.start();
172 errorGobbler.start();
173 outputGobbler.start();
174
175 // any error???
176 try {
177 this.exitValue = process.waitFor(); // can throw an InterruptedException if process did not terminate
178 } catch(InterruptedException ie) {
179 log("*** Process interrupted (InterruptedException). Expected to be a Cancel operation.");
180 // don't print stacktrace: an interrupt here is not an error, it's expected to be a cancel action
181 if(exceptionHandler != null) {
182 exceptionHandler.gotException(ie);
183 }
184
185 // propagate interrupts to worker threads here
186 // unless the interrupt emanated from any of them in any join(),
187 // which will be caught by caller's catch on InterruptedException.
188 // Only if the thread that SafeProcess runs in was interrupted
189 // should we propagate the interrupt to the worker threads.
190 // http://stackoverflow.com/questions/2126997/who-is-calling-the-java-thread-interrupt-method-if-im-not
191 // "I know that in JCiP it is mentioned that you should never interrupt threads you do not own"
192 // But SafeProcess owns the worker threads, so it has every right to interrupt them
193 // Also read http://stackoverflow.com/questions/13623445/future-cancel-method-is-not-working?noredirect=1&lq=1
194
195 // http://stackoverflow.com/questions/3976344/handling-interruptedexception-in-java
196 // http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
197 // "Only code that implements a thread's interruption policy may swallow an interruption request. General-purpose task and library code should never swallow interruption requests."
198 // Does that mean that since this code implements this thread's interruption policy, it's ok
199 // to swallow the interrupt this time and not let it propagate by commenting out the next line?
200 //Thread.currentThread().interrupt(); // re-interrupt the thread
201
202 inputGobbler.interrupt();
203 errorGobbler.interrupt();
204 outputGobbler.interrupt();
205
206 // Since we have been cancelled (InterruptedException), or on any Exception, we need
207 // to forcibly terminate process eventually after the finally code first waits for each worker thread
208 // to die off. Don't set process=null until after we've forcibly terminated it if needs be.
209 this.forciblyTerminateProcess = true;
210
211 // even after the interrupts, we want to proceed to calling join() on all the worker threads
212 // in order to wait for each of them to die before attempting to destroy the process if it
213 // still hasn't terminated after all that.
214 } finally {
215
216 //log("Process exitValue: " + exitValue);
217
218 // From the comments of
219 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
220 // To avoid running into nondeterministic failures to get the process output
221 // if there's no waiting for the threads, call join() on each Thread (StreamGobbler) object.
222 // From Thread API: join() "Waits for this thread (the thread join() is invoked on) to die."
223
224 // Wait for each of the threads to die, before attempting to destroy the process
225 // Any of these can throw InterruptedExceptions too
226 // and will be processed by the calling function's catch on InterruptedException.
227 // However, no one besides us will interrupting these threads I think...
228 // and we won't be throwing the InterruptedException from within the threads...
229 // So if any streamgobbler.join() call throws an InterruptedException, that would be unexpected
230
231 outputGobbler.join();
232 errorGobbler.join();
233 inputGobbler.join();
234
235
236 // set the variables that the code which created a SafeProcess object may want to inspect
237 this.outputStr = outputGobbler.getOutput();
238 this.errorStr = errorGobbler.getOutput();
239 }
240
241 // Don't return from finally, it's considered an abrupt completion and exceptions are lost, see
242 // http://stackoverflow.com/questions/18205493/can-we-use-return-in-finally-block
243 return this.exitValue;
244 }
245
246
247 public synchronized boolean processRunning() {
248 if(process == null) return false;
249 return SafeProcess.processRunning(this.process);
250 }
251
252 // Run a very basic process: with no reading from or writing to the Process' iostreams,
253 // this just execs the process and waits for it to return.
254 // Don't call this method but the zero-argument runProcess() instead if your process will
255 // output stuff to its stderr and stdout streams but you don't need to monitory these.
256 // Because, as per a comment in GLI's GS3ServerThread.java,
257 // in Java 6, it wil block if you don't handle a process' streams when the process is
258 // outputting something. (Java 7+ won't block if you don't bother to handle the output streams)
259 public int runBasicProcess() {
260 try {
261 this.forciblyTerminateProcess = true;
262
263 // 1. create the process
264 process = doRuntimeExec();
265 // 2. basic waitFor the process to finish
266 this.exitValue = process.waitFor();
267
268 this.forciblyTerminateProcess = false;
269 } catch(IOException ioe) {
270
271 if(exceptionHandler != null) {
272 exceptionHandler.gotException(ioe);
273 } else {
274 log("IOException: " + ioe.getMessage(), ioe);
275 }
276 } catch(InterruptedException ie) {
277
278 if(exceptionHandler != null) {
279 exceptionHandler.gotException(ie);
280 } else { // Unexpected InterruptedException, so printstacktrace
281 log("Process InterruptedException: " + ie.getMessage(), ie);
282 }
283
284 Thread.currentThread().interrupt();
285 } finally {
286
287 if( this.forciblyTerminateProcess ) {
288 destroyProcess(process); // see runProcess() below
289 }
290 process = null;
291 this.forciblyTerminateProcess = false; // reset
292 }
293 return this.exitValue;
294 }
295
296 // Runs a process with default stream processing. Returns the exitValue
297 public int runProcess() {
298 return runProcess(null, null, null); // use default processing of all 3 of the process' iostreams
299 }
300
301 // Run a process with custom stream processing (any custom handlers passed in that are null
302 // will use the default stream processing).
303 // Returns the exitValue from running the Process
304 public int runProcess(CustomProcessHandler procInHandler,
305 CustomProcessHandler procOutHandler,
306 CustomProcessHandler procErrHandler)
307 {
308 SafeProcess.OutputStreamGobbler inputGobbler = null;
309 SafeProcess.InputStreamGobbler errorGobbler = null;
310 SafeProcess.InputStreamGobbler outputGobbler = null;
311
312 try {
313 this.forciblyTerminateProcess = false;
314
315 // 1. get the Process object
316 process = doRuntimeExec();
317
318
319 // 2. create the streamgobblers and set any specified handlers on them
320
321 // PROC INPUT STREAM
322 if(procInHandler == null) {
323 // send inputStr to process. The following constructor can handle inputStr being null
324 inputGobbler = // WriterToProcessInputStream
325 new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
326 } else { // user will do custom handling of process' InputStream
327 inputGobbler = new SafeProcess.OutputStreamGobbler(process.getOutputStream(), procInHandler);
328 }
329
330 // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
331 if(procErrHandler == null) {
332 errorGobbler // ReaderFromProcessOutputStream
333 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), this.splitStdErrorNewLines);
334 } else {
335 errorGobbler
336 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), procErrHandler);
337 }
338
339 // PROC OUT STREAM to monitor for the expected std output line(s)
340 if(procOutHandler == null) {
341 outputGobbler
342 = new SafeProcess.InputStreamGobbler(process.getInputStream(), this.splitStdOutputNewLines);
343 } else {
344 outputGobbler
345 = new SafeProcess.InputStreamGobbler(process.getInputStream(), procOutHandler);
346 }
347
348
349 // 3. kick off the stream gobblers
350 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
351
352 } catch(IOException ioe) {
353 this.forciblyTerminateProcess = true;
354
355 if(exceptionHandler != null) {
356 exceptionHandler.gotException(ioe);
357 } else {
358 log("IOexception: " + ioe.getMessage(), ioe);
359 }
360 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so print stack trace
361 this.forciblyTerminateProcess = true;
362
363 if(exceptionHandler != null) {
364 exceptionHandler.gotException(ie);
365 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
366 } else {
367 log("*** Unexpected InterruptException when waiting for process stream gobblers to die:" + ie.getMessage(), ie);
368 }
369
370 // see comments in other runProcess()
371 Thread.currentThread().interrupt();
372
373 } finally {
374 //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command;
375 //log("*** In finally of SafeProcess.runProcess(3 params): " + cmd);
376
377 if( this.forciblyTerminateProcess ) {
378 log("*** Going to call process.destroy 2");
379 destroyProcess(process);
380 log("*** Have called process.destroy 2");
381 }
382 process = null;
383 this.forciblyTerminateProcess = false; // reset
384 }
385
386 return this.exitValue;
387 }
388
389 public int runProcess(LineByLineHandler outLineByLineHandler, LineByLineHandler errLineByLineHandler)
390 {
391 SafeProcess.OutputStreamGobbler inputGobbler = null;
392 SafeProcess.InputStreamGobbler errorGobbler = null;
393 SafeProcess.InputStreamGobbler outputGobbler = null;
394
395 try {
396 this.forciblyTerminateProcess = false;
397
398 // 1. get the Process object
399 process = doRuntimeExec();
400
401
402 // 2. create the streamgobblers and set any specified handlers on them
403
404 // PROC INPUT STREAM
405 // send inputStr to process. The following constructor can handle inputStr being null
406 inputGobbler = // WriterToProcessInputStream
407 new SafeProcess.OutputStreamGobbler(process.getOutputStream(), this.inputStr);
408
409 // PROC ERR STREAM to monitor for any error messages or expected output in the process' stderr
410 errorGobbler // ReaderFromProcessOutputStream
411 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);
412 // PROC OUT STREAM to monitor for the expected std output line(s)
413 outputGobbler
414 = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);
415
416
417 // 3. register line by line handlers, if any were set, for the process stderr and stdout streams
418 if(outLineByLineHandler != null) {
419 outputGobbler.setLineByLineHandler(outLineByLineHandler);
420 }
421 if(errLineByLineHandler != null) {
422 errorGobbler.setLineByLineHandler(errLineByLineHandler);
423 }
424
425
426 // 4. kick off the stream gobblers
427 this.exitValue = waitForWithStreams(inputGobbler, outputGobbler, errorGobbler);
428
429 } catch(IOException ioe) {
430 this.forciblyTerminateProcess = true;
431
432 if(exceptionHandler != null) {
433 exceptionHandler.gotException(ioe);
434 } else {
435 log("IOexception: " + ioe.getMessage(), ioe);
436 }
437 } catch(InterruptedException ie) { // caused during any of the gobblers.join() calls, this is unexpected so log it
438 this.forciblyTerminateProcess = true;
439
440 if(exceptionHandler != null) {
441 exceptionHandler.gotException(ie);
442 log("@@@@ Unexpected InterruptedException when waiting for process stream gobblers to die");
443 } else {
444 log("*** Unexpected InterruptException when waiting for process stream gobblers to die: " + ie.getMessage(), ie);
445 }
446 // We're not causing any interruptions that may occur when trying to stop the worker threads
447 // So resort to default behaviour in this catch?
448 // "On catching InterruptedException, re-interrupt the thread."
449 // This is just how InterruptedExceptions tend to be handled
450 // See also http://stackoverflow.com/questions/4906799/why-invoke-thread-currentthread-interrupt-when-catch-any-interruptexception
451 // and https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/
452 Thread.currentThread().interrupt(); // re-interrupt the thread - which thread? Infinite loop?
453
454 } finally {
455
456 // Moved into here from GS2PerlConstructor and GShell.runLocal() which said
457 // "I need to somehow kill the child process. Unfortunately Thread.stop() and Process.destroy() both fail to do this. But now, thankx to the magic of Michaels 'close the stream suggestion', it works fine (no it doesn't!)"
458 // http://steveliles.github.io/invoking_processes_from_java.html
459 // http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
460 // http://mark.koli.ch/leaky-pipes-remember-to-close-your-streams-when-using-javas-runtimegetruntimeexec
461
462 //String cmd = (this.command == null) ? Arrays.toString(this.command_args) : this.command;
463 //log("*** In finally of SafeProcess.runProcess(2 params): " + cmd);
464
465 if( this.forciblyTerminateProcess ) {
466 log("*** Going to call process.destroy 1");
467 destroyProcess(process);
468 log("*** Have called process.destroy 1");
469 }
470 process = null;
471 this.forciblyTerminateProcess = false; //reset
472 }
473
474 return this.exitValue;
475 }
476
477/*
478
479 On Windows, p.destroy() terminates process p that Java launched,
480 but does not terminate any processes that p may have launched. Presumably since they didn't form a proper process tree.
481 https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/e3cb7532-87f6-4ae3-9d80-a3afc8b9d437/how-to-kill-a-process-tree-in-cc-on-windows-platform?forum=vclanguage
482 https://msdn.microsoft.com/en-us/library/windows/desktop/ms684161(v=vs.85).aspx
483
484 Searching for: "forcibly terminate external process launched by Java on Windows"
485 Not possible: stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java
486 But can use taskkill or tskill or wmic commands to terminate a process by processID
487 stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process
488 Taskkill command can kill by Image Name, such as all running perl, e.g. taskkill /f /im perl.exe
489 But what if we kill perl instances not launched by GS?
490 /f Specifies to forcefully terminate the process(es). We need this flag switched on to kill childprocesses.
491 /t Terminates the specified process and any child processes which were started by it.
492 /t didn't work to terminate subprocesses. Maybe since the process wasn't launched as
493 a properly constructed processtree.
494 /im is the image name (the name of the program), see Image Name column in Win Task Manager.
495
496 We don't want to kill all perl running processes.
497 Another option is to use wmic, available since Windows XP, to kill a process based on its command
498 which we sort of know (SafeProcess.command) and which can be seen in TaskManager under the
499 "Command Line" column of the Processes tab.
500 https://superuser.com/questions/52159/kill-a-process-with-a-specific-command-line-from-command-line
501 The following works kill any Command Line that matches -site localsite lucene-jdbm-demo
502 C:>wmic PATH win32_process Where "CommandLine like '%-site%localsite%%lucene-jdbm-demo%'" Call Terminate
503 "WMIC Wildcard Search using 'like' and %"
504 https://codeslammer.wordpress.com/2009/02/21/wmic-wildcard-search-using-like-and/
505 However, we're not even guaranteed that every perl command GS launches will contain the collection name
506 Nor do we want to kill all perl processes that GS launches with bin\windows\perl\bin\perl, though this works:
507 wmic PATH win32_process Where "CommandLine like '%bin%windows%perl%bin%perl%'" Call Terminate
508 The above could kill GS perl processes we don't intend to terminate, as they're not spawned by the particular
509 Process we're trying to terminate from the root down.
510
511 Solution: We can use taskkill or the longstanding tskill or wmic to kill a process by ID. Since we can
512 kill an external process that SafeProcess launched OK, and only have trouble killing any child processes
513 it launched, we need to know the pids of the child processes.
514
515 We can use Windows' wmic to discover the childpids of a process whose id we know.
516 And we can use JNA to get the process ID of the external process that SafeProcess launched.
517
518 To find the processID of the process launched by SafeProcess,
519 need to use Java Native Access (JNA) jars, available jna.jar and jna-platform.jar.
520 http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
521 http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id
522 http://www.golesny.de/p/code/javagetpid
523 https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md
524 We're using JNA v 4.1.0, https://mvnrepository.com/artifact/net.java.dev.jna/jna
525
526 WMIC can show us a list of parent process id and process id of running processes, and then we can
527 kill those child processes with a specific process id.
528 https://superuser.com/questions/851692/track-which-program-launches-a-certain-process
529 http://stackoverflow.com/questions/7486717/finding-parent-process-id-on-windows
530 WMIC can get us the pids of all childprocesses launched by parent process denoted by parent pid.
531 And vice versa:
532 if you know the parent pid and want to know all the pids of the child processes spawned:
533 wmic process where (parentprocessid=596) get processid
534 if you know a child process id and want to know the parent's id:
535 wmic process where (processid=180) get parentprocessid
536
537 The above is the current solution.
538
539 Eventually, instead of running a windows command to kill the process ourselves, consider changing over to use
540 https://github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/master/src/main/java/de/flapdoodle/embed/process/runtime/Processes.java
541 (works with Apache license, http://www.apache.org/licenses/LICENSE-2.0)
542 This is a Java class that uses JNA to terminate processes. It also has the getProcessID() method.
543
544 Linux ps equivalent on Windows is "tasklist", see
545 http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
546
547*/
548
549// http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
550// Uses Java Native Access, JNA
551public static long getProcessID(Process p)
552{
553 long pid = -1;
554 try {
555 //for windows
556 if (p.getClass().getName().equals("java.lang.Win32Process") ||
557 p.getClass().getName().equals("java.lang.ProcessImpl"))
558 {
559 Field f = p.getClass().getDeclaredField("handle");
560 f.setAccessible(true);
561 long handl = f.getLong(p);
562 Kernel32 kernel = Kernel32.INSTANCE;
563 WinNT.HANDLE hand = new WinNT.HANDLE();
564 hand.setPointer(Pointer.createConstant(handl));
565 pid = kernel.GetProcessId(hand);
566 f.setAccessible(false);
567 }
568 //for unix based operating systems
569 else if (p.getClass().getName().equals("java.lang.UNIXProcess"))
570 {
571 Field f = p.getClass().getDeclaredField("pid");
572 f.setAccessible(true);
573 pid = f.getLong(p);
574 f.setAccessible(false);
575 }
576
577 } catch(Exception ex) {
578 log("SafeProcess.getProcessID(): Exception when attempting to get process ID for process " + ex.getMessage(), ex);
579 pid = -1;
580 }
581 return pid;
582}
583
584
585// stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java
586// (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?)
587// stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process
588// Searching for: "forcibly terminate external process launched by Java on Windows"
589static void killWinProcessWithID(long processID) {
590
591 String cmd = SafeProcess.getWinProcessKillCmd(processID);
592 if (cmd == null) return;
593
594 try {
595 log("\tAttempting to terminate Win subprocess with pid: " + processID);
596 SafeProcess proc = new SafeProcess(cmd);
597 int exitValue = proc.runProcess(); // no IOstreams for Taskkill, but for "wmic process pid delete"
598 // there is output that needs flushing, so don't use runBasicProcess()
599
600 } catch(Exception e) {
601 log("@@@ Exception attempting to stop perl " + e.getMessage(), e);
602 }
603}
604
605
606// On linux and mac, p.destroy() suffices to kill processes launched by p as well.
607// On Windows we need to do more work, since otherwise processes launched by p remain around executing until they naturally terminate.
608// e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until it naturally terminates.
609static void destroyProcess(Process p) {
610 // If it isn't windows, process.destroy() terminates any child processes too
611 if(!Misc.isWindows()) {
612 p.destroy();
613 return;
614 }
615
616 if(!SafeProcess.isAvailable("wmic")) {
617 log("wmic, used to kill subprocesses, is not available. Unable to terminate subprocesses...");
618 log("Kill them manually from the TaskManager or they will proceed to run to termination");
619
620 // At least we can get rid of the top level process we launched
621 p.destroy();
622 return;
623 }
624
625 // get the process id of the process we launched,
626 // so we can use it to find the pids of any subprocesses it launched in order to terminate those too.
627
628 long processID = SafeProcess.getProcessID(p);
629 log("Attempting to terminate sub processes of Windows process with pid " + processID);
630 terminateSubProcessesRecursively(processID, p);
631
632}
633
634// Helper function. Only for Windows.
635// Counterintuitively, we're be killing all parent processess and then all child procs and all their descendants
636// as soon as we discover any further process each (sub)process has launched. The parent processes are killed
637// first in each case for 2 reasons:
638// 1. on Windows, killing the parent process leaves the child running as an orphan anyway, so killing the
639// parent is an independent action, the child process is not dependent on the parent;
640// 2. Killing a parent process prevents it from launching further processes while we're killing off each child process
641private static void terminateSubProcessesRecursively(long parent_pid, Process p) {
642
643 // Use Windows wmic to find the pids of any sub processes launched by the process denoted by parent_pid
644 SafeProcess proc = new SafeProcess("wmic process where (parentprocessid="+parent_pid+") get processid");
645 proc.setSplitStdOutputNewLines(true); // since this is windows, splits lines by \r\n
646 int exitValue = proc.runProcess(); // exitValue (%ERRORLEVEL%) is 0 either way.
647 //log("@@@@ Return value from proc: " + exitValue);
648
649 // need output from both stdout and stderr: stderr will say there are no pids, stdout will contain pids
650 String stdOutput = proc.getStdOutput();
651 String stdErrOutput = proc.getStdError();
652
653
654 // Now we know the pids of the immediate subprocesses, we can get rid of the parent process
655 // We know the children remain running: since the whole problem on Windows is that these
656 // child processes remain running as orphans after the parent is forcibly terminated.
657 if(p != null) { // we're the top level process, terminate the java way
658 p.destroy();
659 } else { // terminate windows way
660 SafeProcess.killWinProcessWithID(parent_pid); // get rid off current pid
661 }
662
663 // parse the output to get the sub processes' pids
664 // Output looks like:
665 // ProcessId
666 // 6040
667 // 180
668 // 4948
669 // 1084
670 // 6384
671 // If no children, then STDERR output starts with the following, possibly succeeded by empty lines:
672 // No Instance(s) Available.
673
674 // base step of the recursion
675 if(stdErrOutput.indexOf("No Instance(s) Available.") != -1) {
676 //log("@@@@ Got output on stderr: " + stdErrOutput);
677 // No further child processes. And we already terminated the parent process, so we're done
678 return;
679 } else {
680 //log("@@@@ Got output on stdout:\n" + stdOutput);
681
682 // http://stackoverflow.com/questions/691184/scanner-vs-stringtokenizer-vs-string-split
683
684 // find all childprocesses for that pid and terminate them too:
685 Stack<Long> subprocs = new Stack<Long>();
686 Scanner sc = new Scanner(stdOutput);
687 while (sc.hasNext()) {
688 if(!sc.hasNextLong()) {
689 sc.next(); // discard the current token since it's not a Long
690 } else {
691 long child_pid = sc.nextLong();
692 subprocs.push(new Long(child_pid));
693 }
694 }
695 sc.close();
696
697 // recursion step if subprocs is not empty (but if it is empty, then it's another base step)
698 if(!subprocs.empty()) {
699 long child_pid = subprocs.pop().longValue();
700 terminateSubProcessesRecursively(child_pid, null);
701 }
702 }
703}
704
705// This method should only be called on a Windows OS
706private static String getWinProcessKillCmd(Long processID) {
707 // check if we first need to init WIN_KILL_CMD. We do this only once, but can't do it in a static codeblock
708
709 if(WIN_KILL_CMD == null) {
710 if(SafeProcess.isAvailable("wmic")) {
711 // https://isc.sans.edu/diary/Windows+Command-Line+Kung+Fu+with+WMIC/1229
712 WIN_KILL_CMD = "wmic process _PROCID_ delete"; // like "kill -9" on Windows
713 }
714 else if(SafeProcess.isAvailable("taskkill")) { // check if we have taskkill or else use the longstanding tskill
715
716 WIN_KILL_CMD = "taskkill /f /t /PID _PROCID_"; // need to forcefully /f terminate the process
717 // /t "Terminates the specified process and any child processes which were started by it."
718 // But despite the /T flag, the above doesn't kill subprocesses.
719 }
720 else { //if(SafeProcess.isAvailable("tskill")) { can't check availability since "which tskill" doesn't ever succeed
721 WIN_KILL_CMD = "tskill _PROCID_"; // https://ss64.com/nt/tskill.html
722 }
723 }
724
725 if(WIN_KILL_CMD == null) { // can happen if none of the above cmds were available
726 return null;
727 }
728 return WIN_KILL_CMD.replace( "_PROCID_", Long.toString(processID) );
729}
730
731
732// Run `which` on a program to find out if it is available. which.exe is included in winbin.
733// On Windows, can use where or which. GLI's file/FileAssociationManager.java used which, so we stick to the same.
734// where is not part of winbin. where is a system command on windows, but only since 2003, https://ss64.com/nt/where.html
735// There is no `where` on Linux/Mac, must use which for them.
736// On windows, "which tskill" fails but "which" succeeds on taskkill|wmic|browser names.
737public static boolean isAvailable(String program) {
738 try {
739 // On linux `which bla` does nothing, prompt is returned; on Windows, it prints "which: no bla in"
740 // `which grep` returns a line of output with the path to grep. On windows too, the location of the program is printed
741 SafeProcess prcs = new SafeProcess("which " + program);
742 prcs.runProcess();
743 String output = prcs.getStdOutput().trim();
744 ///System.err.println("*** 'which " + program + "' returned: |" + output + "|");
745 if(output.equals("")) {
746 return false;
747 } else if(output.indexOf("no "+program) !=-1) { // from GS3's org.greenstone.util.BrowserLauncher.java's isAvailable(program)
748 log("@@@ SafeProcess.isAvailable(): " + program + "is not available");
749 return false;
750 }
751 //System.err.println("*** 'which " + program + "' returned: " + output);
752 return true;
753 } catch (Exception exc) {
754 return false;
755 }
756}
757
758// Google Java external process destroy kill subprocesses
759// https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/
760
761//******************** Inner class and interface definitions ********************//
762// Static inner classes can be instantiated without having to instantiate an object of the outer class first
763
764// Can have public static interfaces too,
765// see http://stackoverflow.com/questions/71625/why-would-a-static-nested-interface-be-used-in-java
766// Implementors need to take care that the implementations are thread safe
767// http://stackoverflow.com/questions/14520814/why-synchronized-method-is-not-included-in-interface
768public static interface ExceptionHandler {
769
770 // when implementing ExceptionHandler.gotException(), if it manipulates anything that's
771 // not threadsafe, declare gotException() as a synchronized method to ensure thread safety
772 public void gotException(Exception e); // can't declare as synchronized in interface method declaration
773}
774
775// Write your own run() body for any StreamGobbler. You need to create an instance of a class
776// extending CustomProcessHandler for EACH IOSTREAM of the process that you want to handle.
777// Do not create a single CustomProcessHandler instance and reuse it for all three streams,
778// i.e. don't call SafeProcess' runProcess(x, x, x); It should be runProcess(x, y, z).
779// Make sure your implementation is threadsafe if you're sharing immutable objects between the threaded streams
780// example implementation is in the GS2PerlConstructor.SynchronizedProcessHandler class.
781// CustomProcessHandler is made an abstract class instead of an interface to force classes that want
782// to use a CustomProcessHandler to create a separate class that extends CustomProcessHandler, rather than
783// that the classes that wish to use it "implementing" the CustomProcessHandler interface itself: the
784// CustomProcessHandler.run() method may then be called in the major thread from which the Process is being
785// executed, rather than from the individual threads that deal with each iostream of the Process.
786public static abstract class CustomProcessHandler {
787
788 protected final int source;
789
790 protected CustomProcessHandler(int src) {
791 this.source = src; // STDERR or STDOUT or STDIN
792 }
793
794 public String getThreadNamePrefix() {
795 return SafeProcess.streamToString(this.source);
796 }
797
798 public abstract void run(Closeable stream); //InputStream or OutputStream
799}
800
801// When using the default stream processing to read from a process' stdout or stderr stream, you can
802// create a class extending LineByLineHandler for the process' err stream and one for its output stream
803// to do something on a line by line basis, such as sending the line to a log
804public static abstract class LineByLineHandler {
805 protected final int source;
806
807 protected LineByLineHandler(int src) {
808 this.source = src; // STDERR or STDOUT
809 }
810
811 public String getThreadNamePrefix() {
812 return SafeProcess.streamToString(this.source);
813 }
814
815 public abstract void gotLine(String line); // first non-null line
816 public abstract void gotException(Exception e); // for when an exception occurs instead of getting a line
817}
818
819
820//**************** StreamGobbler Inner class definitions (stream gobblers copied from GLI) **********//
821
822// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
823// This class is used in FormatConversionDialog to properly read from the stdout and stderr
824// streams of a Process, Process.getInputStream() and Process.getErrorSream()
825public static class InputStreamGobbler extends Thread
826{
827 private InputStream is = null;
828 private StringBuffer outputstr = new StringBuffer();
829 private boolean split_newlines = false;
830 private CustomProcessHandler customHandler = null;
831 private LineByLineHandler lineByLineHandler = null;
832
833 protected InputStreamGobbler() {
834 super("InputStreamGobbler");
835 }
836
837 public InputStreamGobbler(InputStream is)
838 {
839 this(); // sets thread name
840 this.is = is;
841 this.split_newlines = false;
842 }
843
844 public InputStreamGobbler(InputStream is, boolean split_newlines)
845 {
846 this(); // sets thread name
847 this.is = is;
848 this.split_newlines = split_newlines;
849
850 }
851
852 public InputStreamGobbler(InputStream is, CustomProcessHandler customHandler)
853 {
854 this(); // thread name
855 this.is = is;
856 this.customHandler = customHandler;
857 this.adjustThreadName(customHandler.getThreadNamePrefix());
858 }
859
860
861 private void adjustThreadName(String prefix) {
862 this.setName(prefix + this.getName());
863 }
864
865 public void setLineByLineHandler(LineByLineHandler lblHandler) {
866 this.lineByLineHandler = lblHandler;
867 this.adjustThreadName(lblHandler.getThreadNamePrefix());
868 }
869
870 // default run() behaviour
871 public void runDefault()
872 {
873 BufferedReader br = null;
874 try {
875 br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
876 String line=null;
877 while ( !this.isInterrupted() && (line = br.readLine()) != null ) {
878
879 //log("@@@ GOT LINE: " + line);
880 outputstr.append(line);
881 if(split_newlines) {
882 outputstr.append(Misc.NEWLINE); // "\n" is system dependent (Win must be "\r\n")
883 }
884
885 if(lineByLineHandler != null) { // let handler deal with newlines
886 lineByLineHandler.gotLine(line);
887 }
888 }
889
890 } catch (IOException ioe) {
891 if(lineByLineHandler != null) {
892 lineByLineHandler.gotException(ioe);
893 } else {
894 log("Exception when reading process stream with " + this.getName() + ": ", ioe);
895 }
896 } finally {
897 if(this.isInterrupted()) {
898 log("@@@ Successfully interrupted " + this.getName() + ".");
899 }
900 SafeProcess.closeResource(br);
901 }
902 }
903
904 public void runCustom() {
905 this.customHandler.run(is);
906 }
907
908 public void run() {
909 if(this.customHandler == null) {
910 runDefault();
911 } else {
912 runCustom();
913 }
914 }
915
916 public String getOutput() {
917 return outputstr.toString(); // implicit toString() call anyway. //return outputstr;
918 }
919} // end static inner class InnerStreamGobbler
920
921
922// http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html?page=2
923// This class is used in FormatConversionDialog to properly write to the inputstream of a Process
924// Process.getOutputStream()
925public static class OutputStreamGobbler extends Thread
926{
927 private OutputStream os = null;
928 private String inputstr = "";
929 private CustomProcessHandler customHandler = null;
930
931 protected OutputStreamGobbler() {
932 super("stdinOutputStreamGobbler"); // thread name
933 }
934
935 public OutputStreamGobbler(OutputStream os) {
936 this(); // set thread name
937 this.os = os;
938 }
939
940 public OutputStreamGobbler(OutputStream os, String inputstr)
941 {
942 this(); // set thread name
943 this.os = os;
944 this.inputstr = inputstr;
945 }
946
947 public OutputStreamGobbler(OutputStream os, CustomProcessHandler customHandler) {
948 this(); // set thread name
949 this.os = os;
950 this.customHandler = customHandler;
951 }
952
953 // default run() behaviour
954 public void runDefault() {
955
956 if (inputstr == null) {
957 return;
958 }
959
960 // also quit if the process was interrupted before we could send anything to its stdin
961 if(this.isInterrupted()) {
962 log(this.getName() + " thread was interrupted.");
963 return;
964 }
965
966 BufferedWriter osw = null;
967 try {
968 osw = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
969 //System.out.println("@@@ SENDING LINE: " + inputstr);
970 osw.write(inputstr, 0, inputstr.length());
971 osw.newLine();//osw.write("\n");
972 osw.flush();
973
974 // Don't explicitly send EOF when using StreamGobblers as below,
975 // as the EOF char is echoed to output.
976 // Flushing the write handle and/or closing the resource seems
977 // to already send EOF silently.
978
979 /*if(Misc.isWindows()) {
980 osw.write("\032"); // octal for Ctrl-Z, EOF on Windows
981 } else { // EOF on Linux/Mac is Ctrl-D
982 osw.write("\004"); // octal for Ctrl-D, see http://www.unix-manuals.com/refs/misc/ascii-table.html
983 }
984 osw.flush();
985 */
986 } catch (IOException ioe) {
987 log("Exception writing to SafeProcess' inputstream: ", ioe);
988 } finally {
989 SafeProcess.closeResource(osw);
990 }
991 }
992
993 // call the user's custom handler for the run() method
994 public void runCustom() {
995 this.customHandler.run(os);
996 }
997
998 public void run()
999 {
1000 if(this.customHandler == null) {
1001 runDefault();
1002 } else {
1003 runCustom();
1004 }
1005 }
1006} // end static inner class OutputStreamGobbler
1007
1008//**************** Static methods **************//
1009
1010
1011 // logger and DebugStream print commands are synchronized, therefore thread safe.
1012 public static void log(String msg) {
1013 if(DEBUG == 0) return;
1014 logger.info(msg);
1015
1016 System.err.println(msg);
1017
1018 //DebugStream.println(msg);
1019 }
1020
1021 public static void log(String msg, Exception e) { // Print stack trace on the exception
1022 if(DEBUG == 0) return;
1023 logger.error(msg, e);
1024
1025 System.err.println(msg);
1026 e.printStackTrace();
1027
1028 //DebugStream.println(msg);
1029 //DebugStream.printStackTrace(e);
1030 }
1031
1032 public static void log(Exception e) {
1033 if(DEBUG == 0) return;
1034 logger.error(e);
1035
1036 e.printStackTrace();
1037
1038 //DebugStream.printStackTrace(e);
1039 }
1040
1041 public static void log(String msg, Exception e, boolean printStackTrace) {
1042 if(printStackTrace) {
1043 log(msg, e);
1044 } else {
1045 log(msg);
1046 }
1047 }
1048
1049 public static String streamToString(int src) {
1050 String stream;
1051 switch(src) {
1052 case STDERR:
1053 stream = "stderr";
1054 break;
1055 case STDOUT:
1056 stream = "stdout";
1057 break;
1058 default:
1059 stream = "stdin";
1060 }
1061 return stream;
1062 }
1063
1064//**************** Useful static methods. Copied from GLI's Utility.java ******************
1065 // For safely closing streams/handles/resources.
1066 // For examples of use look in the Input- and OutputStreamGobbler classes.
1067 // http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
1068 // http://stackoverflow.com/questions/481446/throws-exception-in-finally-blocks
1069 public static boolean closeResource(Closeable resourceHandle) {
1070 boolean success = false;
1071 try {
1072 if(resourceHandle != null) {
1073 resourceHandle.close();
1074 resourceHandle = null;
1075 success = true;
1076 }
1077 } catch(Exception e) {
1078 log("Exception closing resource: " + e.getMessage(), e);
1079 resourceHandle = null;
1080 success = false;
1081 } finally {
1082 return success;
1083 }
1084 }
1085
1086 public static boolean closeProcess(Process prcs) {
1087 boolean success = true;
1088 if( prcs != null ) {
1089 success = success && closeResource(prcs.getErrorStream());
1090 success = success && closeResource(prcs.getOutputStream());
1091 success = success && closeResource(prcs.getInputStream());
1092 prcs.destroy();
1093 }
1094 return success;
1095 }
1096
1097// Moved from GShell.java
1098 /** Determine if the given process is still executing. It does this by attempting to throw an exception - not the most efficient way, but the only one as far as I know
1099 * @param process the Process to test
1100 * @return true if it is still executing, false otherwise
1101 */
1102 static public boolean processRunning(Process process) {
1103 boolean process_running = false;
1104
1105 try {
1106 process.exitValue(); // This will throw an exception if the process hasn't ended yet.
1107 }
1108 catch(IllegalThreadStateException itse) {
1109 process_running = true;
1110 }
1111 catch(Exception exception) {
1112 log(exception); // DebugStream.printStackTrace(exception);
1113 }
1114 return process_running;
1115 }
1116
1117} // end class SafeProcess
Note: See TracBrowser for help on using the repository browser.