Changeset 31646

Show
Ignore:
Timestamp:
04.05.2017 20:32:17 (2 years ago)
Author:
ak19
Message:

1. Moved isAvailable(program) into SafeProcess?.java from FileAssociationManager?.java. 2. SafeProcess?.java now successfully terminates any child processes launched on Windows too because of the addition of new methods. But the real solution may need to be in Perl, especially if using mobile windows OS where the system tools currently being used may not be present. 4. Adding JNA jar files, jna.jar and jna-platform.jar of version 4.1.0 (2013), to gli's lib. These are needed to get the PID of a running process on Windows. 5. Need to tidy up code, need to check linux is not affected by platform specific import statements used by Windows, need to make the process termination recursive (not just children, but all descendants need to be killed on destroyProcess(p). Rather than wmic delete or taskkill or tskill, can use the linked Java code Processes.java which is dependent on JNA.

Location:
main/trunk/gli
Files:
2 added
2 modified

Legend:

Unmodified
Added
Removed
  • main/trunk/gli/src/org/greenstone/gatherer/file/FileAssociationManager.java

    r31637 r31646  
    117117        String [] browsers = new String [] {"mozilla", "netscape", "firefox"}; 
    118118        for (int i=0; i<browsers.length; i++) { 
    119         if (isAvailable(browsers[i])) { 
     119        if (SafeProcess.isAvailable(browsers[i])) { 
    120120            command = browsers[i]+ " %1"; 
    121121            break; 
     
    351351    } 
    352352 
    353  
    354     protected boolean isAvailable(String program) { 
    355     try { 
    356         // `which bla` does nothing, prompt is returned 
    357         // `which grep` returns a line of output with the path to grep 
    358         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     } 
    370353} 
  • main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java

    r31643 r31646  
    1111import java.io.OutputStreamWriter; 
    1212import java.util.Arrays; 
     13import java.util.Scanner; 
     14 
     15import com.sun.jna.*; 
     16import com.sun.jna.platform.win32.Kernel32; 
     17import com.sun.jna.platform.win32.WinNT; 
     18 
     19import java.lang.reflect.Field; 
     20//import java.lang.reflect.Method; 
    1321 
    1422import org.apache.log4j.*; 
     
    253261 
    254262        if( this.forciblyTerminateProcess ) { 
    255         process.destroy(); // see runProcess() below 
    256         //killAnyPerl(); 
     263        destroyProcess(process); // see runProcess() below       
    257264        } 
    258265        process = null; 
     
    299306        if(procErrHandler == null) { 
    300307        errorGobbler // ReaderFromProcessOutputStream 
    301             = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines); 
     308            = new SafeProcess.InputStreamGobbler(process.getErrorStream(), this.splitStdErrorNewLines); 
    302309        } else { 
    303310        errorGobbler 
     
    308315        if(procOutHandler == null) { 
    309316        outputGobbler 
    310             = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines); 
     317            = new SafeProcess.InputStreamGobbler(process.getInputStream(), this.splitStdOutputNewLines); 
    311318        } else { 
    312319        outputGobbler 
     
    345352        if( this.forciblyTerminateProcess ) { 
    346353        log("*** Going to call process.destroy 2"); 
    347         process.destroy(); 
    348         //killAnyPerl(); 
     354        destroyProcess(process);                 
    349355        log("*** Have called process.destroy 2"); 
    350356        } 
     
    434440        if( this.forciblyTerminateProcess ) { 
    435441        log("*** Going to call process.destroy 1"); 
    436         process.destroy(); 
    437         //killAnyPerl(); 
     442        destroyProcess(process);     
    438443        log("*** Have called process.destroy 1"); 
    439444        } 
     
    454459    // only on Windows do we need to still kill perl despite process.destroy() 
    455460    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 
    457467    } catch(Exception e) { 
    458468        log("@@@ Exception attempting to stop perl " + e.getMessage(), e);       
    459469    } 
    460470} 
     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 
     478public 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 
     515static 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"  
     535static 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. 
     567static 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. 
     703public 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/   
    461722     
    462723//******************** Inner class and interface definitions ********************// 
     
    544805     
    545806    public InputStreamGobbler(InputStream is, boolean split_newlines) 
    546     { 
     807    {    
    547808    this(); // sets thread name 
    548809    this.is = is; 
    549810    this.split_newlines = split_newlines; 
     811     
    550812    } 
    551813