JDK-4039120 : Pipes don't work with child processes under certain circumstances
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 1.1
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_nt
  • CPU: x86
  • Submitted: 1997-03-14
  • Updated: 1998-04-15
  • Resolved: 1998-04-15
Related Reports
Duplicate :  
Duplicate :  
Description
Name: mc57594			Date: 03/14/97


There are two related problems here. The first is that pipes to
and from child processes don't work. The second is ( I believe)
related. The first is described in comments in the code.
CommandStreamEditor.doPipe is the method to look at.

The second problem is that when a command shell is launched as a
child, and used to launch another child
( something like cmd /c tar --help ), the command processor opens
a fullscreen window and sends stdout to the window. The window closes
and nothing comes back to the parent of the command shell. After
doing some MSVC ( ewww ) research, I thought
this may be because the command shell is launched with options 
indicating that handles should be non-inheritable.

You can see this by doing something like

dir | java CommandStreamEditor

or

java CommandStreamEditor someparam

then type in the name of an executable on the path that writes to stdout.

I've set it up so that if there's nothing in the lower window,
nothing will be sent to the child process. That seems to be
part of the problem - see the comments.

Anyways, here's the source:

=======================================
import java.awt.*;
import java.io.*;

/**
This class reads from stdin, displays what's there in 
an editable window. When the window is closed, the contents
are sent to stdout.
*/
class BasicStreamEditor extends Frame
{
	private TextArea editWindow;
	protected String args[];

	public BasicStreamEditor ( String someArgs[] )
	{
		super();
		args = someArgs;
		setTitle ( getClass().getName() );
		createLayout();
	}

	protected void createLayout()
	{
		// make the area where input is displayed
		// and which can be edited for the output
		editWindow = new TextArea();
		editWindow.setForeground ( SystemColor.textText );
		editWindow.setBackground ( SystemColor.text );
		editWindow.setFont ( new Font ( "Dauphin", Font.PLAIN, 20 ) );
		add ( "Center", editWindow );
	}

	public boolean handleEvent(Event event)
	{
		if (event.id == Event.WINDOW_DESTROY)
		{
			// send the contents of the edit area to stdout
			System.out.print ( getContents() );
			System.exit(0);
		}
		return super.handleEvent(event);
    }

	public void setContents ( Reader anInput )
		throws IOException
	{
		BufferedReader input = new BufferedReader ( anInput );

		StringBuffer buffer = new StringBuffer ( "" );
		String next;

		// can't use input.ready() or input.available here
		while ( ( next = input.readLine() ) != null )
		{
			buffer.append ( input.readLine() );
			buffer.append ( "\n" );
		}
		editWindow.setText ( buffer.toString() );
	}

	// convenience method for InputStream, rather than Reader.
	// Silly dichotomy that... ;-)
	public void setContents ( InputStream anInput )
	{
		try
		{
			setContents ( new BufferedReader ( new InputStreamReader ( anInput ) ) );
		}
		catch ( IOException exception )
		{
			editWindow.setText ( "Error\n" );
		}
	}

	public String getContents()
	{
		return editWindow.getText();
	}

	public void startEditor()
	{
		if ( args.length == 0 )
		{
			try
			{
				setContents ( System.in );
			}
			catch ( Exception exception )
			{
				System.err.println ( exception.getMessage() );
				exception.printStackTrace();
				System.exit(0);
			}
		}

		pack();
		show();
		// put the cursor in the edit area
		editWindow.requestFocus();
	}

	// if there are no arguments then stdin is read. Otherwise
	// not. This is mainly to allow for debugging.
	public static void main ( String args[] )
		throws IOException
	{
		BasicStreamEditor instance = new BasicStreamEditor( args );
		instance.startEditor();
	}
}
=======================================
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;

class CommandStreamEditor
	extends BasicStreamEditor
	implements ActionListener
{
	private TextField commandLine;
	private Process child;

	public CommandStreamEditor ( String someArgs[] )
	{
		super ( someArgs );
	}

	protected void createLayout()
	{
		super.createLayout();

		// make the text field into which commands can be typed
		commandLine = new TextField();
		commandLine.addActionListener ( this );
		commandLine.setForeground ( SystemColor.textText );
		commandLine.setBackground ( SystemColor.text );
		commandLine.setFont ( new Font ( "Lucida Console", Font.PLAIN, 10 ) );
		add ( "North", commandLine );
	}

	public void actionPerformed ( ActionEvent event)
	{
		// catch the enter pressed
		doPipe();
	}

	// make a child process. Send the contents of the edit window
	// to the stdin of the process, and retrieve the stdout of the
	// process. Replace the contents of the edit window with the
	// contents of stdout from the process.
	protected void doPipe()
	{
		try
		{
			// get the string that allows child processes and
			// shell commands to be executed
/*
			Properties localProperties = new Properties();
			localProperties.load ( new FileInputStream ( "properties" ) );
			// run the specified child command
			child = Runtime.getRuntime().exec (
				localProperties.getProperty( "shellExecChild" ) + " " + commandLine.getText()
			);
*/
			// Does this exec allow children of child to inherit
			// child's file handles ( stdin stdout and stderr) ?
			// It seems not because a command shell ( cmd.exe or 4nt.exe )			// trying to execute its own child
			// process seems to always open its own window. The std out from that child
			// process always appears ( briefly ) in the window,
			// but never gets back to here.
			// This bug also affects the CgiServlet in Java Server.
			child = Runtime.getRuntime().exec ( commandLine.getText() );

			if ( getContents().length() != 0 )
			{
				try
				{
					// send the contents of the TextArea to the child command
					System.err.println ( "make toChild" );
					OutputStreamWriter toChild = new OutputStreamWriter ( child.getOutputStream() );
					System.err.println ( "send contents" );
					toChild.write ( getContents() );

					// make sure the child knows where the pipe ends
					System.err.println ( "Flushing..." );
					// it stops and waits forever here if the child process
					// is ignoring stdin
					// But if the child process is killed from somewhere else
					// this thread keeps going...
					toChild.flush();
					System.err.println ( "closing..." );
					toChild.close();
					System.err.println ( "closed" );
				}
				catch ( IOException e )
				{
					System.err.println ( "Error sending contents" );
				}
			}

			// get the output from the child command
			System.err.println ( "getting input..." );

			// if a process does accept input from stdin,
			// then we wait here forever.
			// But: if no attempt is made to send input to the
			// child, this line works OK.
			// If the child process is killed from somewhere else
			// most of its ouput is picked up.
			setContents ( child.getInputStream() );
			System.err.println ( "got input" );

			// make sure gc happens
			child = null;
		}
		catch ( IOException exception )
		{
			exception.printStackTrace();
		}
	}

	// override this to get focus to the right component
	public void startEditor()
	{
		super.startEditor();
		commandLine.requestFocus();
	}

	static public void main ( String args[] )
	{
		CommandStreamEditor instance = new CommandStreamEditor ( args );

		instance.startEditor();
	}
}

company - Semiosix , email - ###@###.###
======================================================================

Comments
WORK AROUND Name: mc57594 Date: 03/14/97 ======================================================================
11-06-2004

EVALUATION This bug has been fixed along with other bugs related to Runtime.exec(). However, the Runtime.exec() is not, and will not be fully supported, because the external process is definite platform specific. There is no way to get platform independent behavior. The second case mentioned in the description is caused by that the user does not understand the semantics used by the Runtime.exec(). Please see bug 4079419, 4064912 and javadoc in the Runtime.java ###@###.### 1998-04-14
14-04-1998