Changeset 31646
- Timestamp:
- 2017-05-04T20:32:17+12:00 (7 years ago)
- Location:
- main/trunk/gli
- Files:
-
- 2 added
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/gli/src/org/greenstone/gatherer/file/FileAssociationManager.java
r31637 r31646 117 117 String [] browsers = new String [] {"mozilla", "netscape", "firefox"}; 118 118 for (int i=0; i<browsers.length; i++) { 119 if ( isAvailable(browsers[i])) {119 if (SafeProcess.isAvailable(browsers[i])) { 120 120 command = browsers[i]+ " %1"; 121 121 break; … … 351 351 } 352 352 353 354 protected boolean isAvailable(String program) {355 try {356 // `which bla` does nothing, prompt is returned357 // `which grep` returns a line of output with the path to grep358 SafeProcess prcs = new SafeProcess("which " + program);359 prcs.runProcess();360 String output = prcs.getStdOutput();361 if(output.equals("")) {362 return false;363 }364 //System.err.println("*** 'which " + program + "' returned: " + output);365 return true;366 } catch (Exception exc) {367 return false;368 }369 }370 353 } -
main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java
r31643 r31646 11 11 import java.io.OutputStreamWriter; 12 12 import java.util.Arrays; 13 import java.util.Scanner; 14 15 import com.sun.jna.*; 16 import com.sun.jna.platform.win32.Kernel32; 17 import com.sun.jna.platform.win32.WinNT; 18 19 import java.lang.reflect.Field; 20 //import java.lang.reflect.Method; 13 21 14 22 import org.apache.log4j.*; … … 253 261 254 262 if( this.forciblyTerminateProcess ) { 255 process.destroy(); // see runProcess() below 256 //killAnyPerl(); 263 destroyProcess(process); // see runProcess() below 257 264 } 258 265 process = null; … … 299 306 if(procErrHandler == null) { 300 307 errorGobbler // ReaderFromProcessOutputStream 301 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);308 = new SafeProcess.InputStreamGobbler(process.getErrorStream(), this.splitStdErrorNewLines); 302 309 } else { 303 310 errorGobbler … … 308 315 if(procOutHandler == null) { 309 316 outputGobbler 310 = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);317 = new SafeProcess.InputStreamGobbler(process.getInputStream(), this.splitStdOutputNewLines); 311 318 } else { 312 319 outputGobbler … … 345 352 if( this.forciblyTerminateProcess ) { 346 353 log("*** Going to call process.destroy 2"); 347 process.destroy(); 348 //killAnyPerl(); 354 destroyProcess(process); 349 355 log("*** Have called process.destroy 2"); 350 356 } … … 434 440 if( this.forciblyTerminateProcess ) { 435 441 log("*** Going to call process.destroy 1"); 436 process.destroy(); 437 //killAnyPerl(); 442 destroyProcess(process); 438 443 log("*** Have called process.destroy 1"); 439 444 } … … 454 459 // only on Windows do we need to still kill perl despite process.destroy() 455 460 try{ 456 Runtime.getRuntime().exec("taskkill /f /im perl.exe"); 461 //Runtime.getRuntime().exec("taskkill /f /im perl.exe"); 462 SafeProcess proc = new SafeProcess("taskkill /f /im perl.exe"); 463 // /f Specifies to forcefully terminate the process(es). 464 // /t Terminates the specified process and any child processes which were started by it. 465 // /im is the image name (the name of the program), see Image Name column in Win Task Manager. 466 int exitValue = proc.runBasicProcess(); // don't care about IOstreams 457 467 } catch(Exception e) { 458 468 log("@@@ Exception attempting to stop perl " + e.getMessage(), e); 459 469 } 460 470 } 471 472 // http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program 473 // http://www.golesny.de/p/code/javagetpid 474 // Need to use JNA (Java Native Access), http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id 475 // Got 4.1.0 from https://mvnrepository.com/artifact/net.java.dev.jna/jna/4.1.0 and https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform/4.1.0 476 // (see also https://mvnrepository.com/artifact/net.java.dev.jna/jna), since github's GettingStarted doc talks about downloading jna.jar but never provides it. 477 // See https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md 478 public static long getProcessID(Process p) 479 { 480 long pid = -1; 481 try 482 { 483 //for windows 484 if (p.getClass().getName().equals("java.lang.Win32Process") || 485 p.getClass().getName().equals("java.lang.ProcessImpl")) 486 { 487 Field f = p.getClass().getDeclaredField("handle"); 488 f.setAccessible(true); 489 long handl = f.getLong(p); 490 Kernel32 kernel = Kernel32.INSTANCE; 491 WinNT.HANDLE hand = new WinNT.HANDLE(); 492 hand.setPointer(Pointer.createConstant(handl)); 493 pid = kernel.GetProcessId(hand); 494 f.setAccessible(false); 495 } 496 //for unix based operating systems 497 else if (p.getClass().getName().equals("java.lang.UNIXProcess")) 498 { 499 Field f = p.getClass().getDeclaredField("pid"); 500 f.setAccessible(true); 501 pid = f.getLong(p); 502 f.setAccessible(false); 503 } 504 } 505 catch(Exception ex) 506 { 507 log("SafeProcess.getProcessID(): Exception when attempting to get process ID for process " + ex.getMessage(), ex); 508 pid = -1; 509 } 510 return pid; 511 } 512 513 // UNUSED - only kills toplevel process, same as p.destroy(). 514 // Call destroyProcess(p) instead to kill any childprocesses launched 515 static void killProcessIfOnWindows(Process p) { 516 if(!Utility.isWindows()) return; 517 518 // only on Windows do we need to still kill perl despite process.destroy() 519 // http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program 520 // http://www.golesny.de/p/code/javagetpid 521 long processID = getProcessID(p); 522 523 SafeProcess.killWinProcessWithID(processID); 524 } 525 526 527 // Eventually move to using https://github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/master/src/main/java/de/flapdoodle/embed/process/runtime/Processes.java ? 528 // Works with Apache license, http://www.apache.org/licenses/LICENSE-2.0 529 530 531 // stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java 532 // (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?) 533 // stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process 534 // Searching for: "forcibly terminate external process launched by Java on Windows" 535 static void killWinProcessWithID(long processID) { 536 537 // check if we have taskkill or tskill 538 String cmd = null; 539 if (SafeProcess.isAvailable("wmic")) { 540 // https://isc.sans.edu/diary/Windows+Command-Line+Kung+Fu+with+WMIC/1229 541 cmd = "wmic process " + processID + " delete"; // like "kill -9" on Windows 542 } else if(SafeProcess.isAvailable("taskkill")) { 543 cmd = "taskkill /f /t /PID " + processID; 544 } else { //if(SafeProcess.isAvailable("tskill")) { can't check availability since "which tskill" doesn't ever succeed 545 cmd = "tskill " + processID; // https://ss64.com/nt/tskill.html 546 } 547 cmd = "tskill " + processID; 548 549 try{ 550 // Despite the /T flag, the following still doesn't kill subprocesses. Taskkill is also not available 551 // universally. Try alternates tskill and pskill, though they too don't kill further processes launched by p. 552 log("Attempting to taskkil process with ID " + processID); 553 //Runtime.getRuntime().exec("taskkill /f /t /PID " + processID); 554 SafeProcess proc = new SafeProcess(cmd); 555 // /f Specifies to forcefully terminate the process(es). 556 // /t Terminates the specified process and any child processes which were started by it. 557 int exitValue = proc.runProcess(); // no IOstreams for Taskkill, but for "wmic process pid delete" there is output that needs flushing 558 559 } catch(Exception e) { 560 log("@@@ Exception attempting to stop perl " + e.getMessage(), e); 561 } 562 } 563 564 // On linux and mac, p.destroy() suffices to kill processes launched by p as well. 565 // On Windows we need to do more work, since processes launched by p remain around executing until termination otherwise. 566 // e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until termination. 567 static void destroyProcess(Process p) { 568 //killProcessIfOnWindows(p); // doesn't work: only terminates first level process launched, same as p.destroy() 569 //p.destroy(); 570 //killAnyPerl(); // kills any running perl, even those not launched by GS 571 572 if(!Utility.isWindows()) { 573 p.destroy(); 574 return; 575 } 576 577 578 579 // We don't want to kill all perl running processes. Just the ones whose command we sort of know and can see in TaskManager 580 // under the "Command Line" column of the Processes tab. 581 // This is possible with wmic, available since xp. First check wmic is really present on this Windows machine 582 // https://superuser.com/questions/52159/kill-a-process-with-a-specific-command-line-from-command-line 583 if(!SafeProcess.isAvailable("wmic")) { 584 log("wmic used to kill subprocesses is not available. Unable to terminate subprocesses..."); 585 log("Kill them manually from the TaskManager or they will proceed to run to termination"); 586 587 // At least we can get rid of the top level process we launched 588 p.destroy(); 589 return; 590 } 591 592 593 // get the process id of the process we launched 594 long processID = SafeProcess.getProcessID(p); 595 596 597 // What we want to be able to do on Windows is to kill a process by its command line as seen in Task Manager for that process. 598 // full-import.pl launches import.pl, full-build will launch buildcol.pl and activate.pl. 599 // These tend to have the collection name still at the end, so we can terminate a process knowing the collection name, since 600 // we can use wildcards in our wmic termination command. 601 // However, lucene_passes.pl is launched by buildcol.pl and doesn't have the collection name in its launching cmd line. 602 603 // Works beatufilly to kill any CommandLine (as seen in TaskManager) that matches 604 //C:>wmic PATH win32_process Where "CommandLine like '%-site%localsite%%lucene-jdbm-demo%'" Call Terminate 605 // https://codeslammer.wordpress.com/2009/02/21/wmic-wildcard-search-using-like-and/ 606 // "WMIC Wildcard Search using like and %" 607 // All subsequent perl processes launched by Greenstone are launched with bin\windows\perl\bin\perl. So this works well: 608 // wmic PATH win32_process Where "CommandLine like '%bin%windows%perl%bin%perl%'" Call Terminate 609 // However do we want to terminate all perl processes launched by Greenstone, or only those that we can infer were spawned by 610 // the SafeProcess that we've been running? 611 612 /* 613 // Works in terminal, but when run through GLI, GLI is terminated too... 614 try{ 615 616 log("Attempting to use Windows to terminate sub processes"); 617 Runtime.getRuntime().exec("wmic PATH win32_process Where \"CommandLine like '%bin%windows%perl%bin%perl%'\" Call Terminate"); 618 } catch(Exception e) { 619 log("@@@ Exception attempting to stop perl " + e.getMessage(), e); 620 } 621 */ 622 623 // https://superuser.com/questions/851692/track-which-program-launches-a-certain-process 624 // Can get parent process id and process id, and then kill those with child process id 625 // http://stackoverflow.com/questions/7486717/finding-parent-process-id-on-windows 626 // Can get the pids of all childprocesses launched by parent process with parent pid. 627 // And vice versa: 628 // e.g. if you know the parent pid and want to know all the pids of the child processes spawned: 629 // wmic process where (parentprocessid=596) get processid 630 // if you know a child process id and want to know the parent's id: 631 // wmic process where (processid=180) get parentprocessid 632 633 String[] child_pids = null; 634 635 636 log("Attempting to use Windows to terminate sub processes"); 637 SafeProcess proc = new SafeProcess("wmic process where (parentprocessid="+processID+") get processid"); 638 proc.setSplitStdOutputNewLines(true); // since this is windows, splits lines by \r\n 639 // Output looks like: 640 // ProcessId 641 // 6040 642 // 180 643 // 4948 644 // 1084 645 // 6384 646 // If no children, then STDERR output starts with the following, possibly succeeded by empty lines: 647 // No Instance(s) Available. 648 int exitValue = proc.runProcess(); // exitValue (%ERRORLEVEL%) is 0 either way. 649 log("@@@@ Return value from proc: " + exitValue); 650 String stdOutput = proc.getStdOutput(); 651 // http://stackoverflow.com/questions/691184/scanner-vs-stringtokenizer-vs-string-split 652 653 log("@@@@ Got output:\n" + stdOutput); 654 if(stdOutput.indexOf("No Instance(s) Available.") == -1) { 655 //int start = stdOutput.indexOf("\n"); // skip first line, which is the column name "ProcessId" 656 //stdOutput = stdOutput.substring(start+1); 657 //child_pids = stdOutput.split("/(\\r?\\n/)+"); // get rid of newline, so we should only have a list of Long numbers 658 659 // Now we can get rid of the top level process 660 p.destroy(); 661 // find all childprocesses for that pid and terminate them too: 662 Scanner sc = new Scanner(stdOutput); 663 while (sc.hasNext()) { 664 if(!sc.hasNextLong()) { 665 sc.next(); // discard the current token since it's not a Long 666 } else { 667 long child_pid = sc.nextLong(); 668 log("Attempting to terminate child pid: " + child_pid); 669 SafeProcess.killWinProcessWithID(child_pid); 670 } 671 } 672 sc.close(); 673 } else { 674 // No child process. Only the top level process needs to be got rid of: 675 p.destroy(); 676 677 } 678 679 /* 680 681 // Now we can get rid of the top level process 682 p.destroy(); 683 684 685 // Then kill each subprocess. 686 687 if(child_pids != null) { 688 for(int i = 0; i < child_pids.length; i++) { 689 log("Attempting to terminate child pid: |" + child_pids[i] + "|"); 690 SafeProcess.killWinProcessWithID(Long.parseLong(child_pids[i].trim())); 691 } 692 } 693 */ 694 695 } 696 697 698 // Run `which` on a program to find out if it is available. which.exe is included in winbin. 699 // On Windows, can use where or which. GLI's file/FileAssociationManager.java used which, so we stick to the same. 700 // where is not part of winbin. where is a system command on windows, but only since 2003, https://ss64.com/nt/where.html 701 // There is no `where` on Linux/Mac, must use which for them. 702 // On windows, "which tskill" fails but "which" succeeds on taskkill|wmic|browser names. 703 public static boolean isAvailable(String program) { 704 try { 705 // On linux `which bla` does nothing, prompt is returned; on Windows, it prints "which: no bla in" 706 // `which grep` returns a line of output with the path to grep. On windows too, the location of the program is printed 707 SafeProcess prcs = new SafeProcess("which " + program); 708 prcs.runProcess(); 709 String output = prcs.getStdOutput(); 710 if(output.equals("")) { 711 return false; 712 } 713 //System.err.println("*** 'which " + program + "' returned: " + output); 714 return true; 715 } catch (Exception exc) { 716 return false; 717 } 718 } 719 720 // Google Java external process destroy kill subprocesses 721 // https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/ 461 722 462 723 //******************** Inner class and interface definitions ********************// … … 544 805 545 806 public InputStreamGobbler(InputStream is, boolean split_newlines) 546 { 807 { 547 808 this(); // sets thread name 548 809 this.is = is; 549 810 this.split_newlines = split_newlines; 811 550 812 } 551 813
Note:
See TracChangeset
for help on using the changeset viewer.