[Safer Java Processes PIC]

Executing Java/OS Processes More Safely

 

I've been working on Java applications that need to call OS commands (e.g. notepad, nircmd), and communicate with the Windows registry, various shell scripts, and DLLs. I decided to implement the OS connections with Java's ProcessBuilder class, and found some good advice and code online (see below for details). However, current solutions didn't quite match my needs, and so I wrote SaferExec.

Using SaferExec

Create an instance of the SaferExec class, then call its exec() method with an OS command as a string argument. For example:

  SaferExec se = new SaferExec();

  String result = se.exec("dir /W *.java");    // get a list of java files using dir

  se.exec("notepad readme.txt");            // call notepad.exe with a text file
            // don't bother looking at the return result of notepad

Note that the above call to dir does not need "cmd /c" in front of it. SaferExec automatically calls shell commands via a shell, which partly fixes one of the most common headaches with Java's Process class. However, it's not a perfect solution since the command might still fail due to incorrect privileges, missing environment settings, differing shell versions, and probably many other reasons :) I've included a few tips in the next section on how to avoid such problems.

By the way, SaferExec should work across multiple OSes, but I've only tested it on Windows XP, Windows 7, and Linux.

exec() tokenizes its input argument, which helps the Java Process understand what needs to be invoked. You can bypass the tokenization step by passing tokens directly to exec() as multiple input arguments:

  String result = se.exec("ping", "-n", "1000", "localhost");
                      // exec() accepts any number of string arguments

SaferExec assumes that time-consuming commands, like the above ping example, have become blocked. It'll automatically interrupt them after a 5 second wait, and any data will be returned. SaferExec's wait time can be changed in the constructor:

  SaferExec se = new SaferExec(60);    // 60 seconds wait
  String result = se.exec("ping", "-n", "1000", "localhost");

There's also an execV() method, which works just like exec(), except that it appends the Java Process' exit value to the end of the result string. If the command worked correctly, then the exit value will be 0.

 

Programming Tips

SaferExec doesn't utilize a Java Process stream to send data to the OS command, since it almost never works. Instead any data must be supplied as command arguments. However, if you really do need to pass a lot of information to the command, then the most reliable way is to have the Java application write into a temporary file, and then call the command, supplying the filename as an input argument. For example:

File tempFile = File.createTempFile("info", ".txt");
tempFile.deleteOnExit();
  // Write to temp file...
se.exec("foo info.txt");   // call the foo command

If you need to combine a series of OS commands, do not try to do it using a clever mix of pipes and/or redirection in the command string. It might work, but probably won't. A better strategy is to combine the commands inside a shell script (e.g. inside a Windows batch file), and call that script from the Java side. One advantage is that you can test the script separately from Java. Another is that the script file acts as an interface, hiding the scripting implementation, making it easier to change in the future. A script also allows you to use variables and control flow operations (e.g. if, for) which are trickier from the command line.

 

Some Background

There are all sorts of problems involved with creating OS processes inside Java. Prior to J2SE 1.5, a programmer had to grapple with Runtime.exec(), as described by Michael Daconta in "When Runtime.exec() won't". Since v1.5., some of the issues have been fixed with the introduction of ProcessBuilder, but plenty remain, as detailed by Kyle Cartmell in "Five Common java.lang.Process Pitfalls" and Alvin Alexander in "Java exec - execute system processes with Java ProcessBuilder and Process" (Parts 1-3).

I 'borrowed' some of their ideas, and added a few of my own to come up with the SaferExec class. SaferExec tokenizes the OS command string before trying to execute it, merges its output and error streams to simplify IO concerns, and interrupts blocked commands using two techniques. The output from the process is read byte-by-byte, again to make IO easier, and care is taken to free up resources at the end of the command's execution.

Probably the most comprehensive solution for executing external processes from Java is Jakarta Commons Exec. The website includes a tutorial, FAQ, and source and binary downloads. Commons Exec exposes a lot more of the underlying Java Process to the user, including the IO streams and a timer interface. This allows greater flexibility in how it can be used, at the expense of more complex coding.

 

Improving SaferExec

If some kind soul makes improvements to SaferExec, please contact me. I'll make changes to this version of SaferExec, and mention your contribution on this page, and in the code.

 

Download


Dr. Andrew Davison
E-mail: ad@coe.psu.ac.th
Back to my home page