blob: acdf49c83232b2be561d83c0f10131a91e56cf74 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2011 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - Initial API and implementation
* Wind River Systems - bug 248071, bug 286162
*******************************************************************************/
package org.eclipse.cdt.utils.spawner;
import java.io.*;
import java.util.StringTokenizer;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.utils.pty.PTY;
import org.eclipse.core.runtime.Platform;
import org.eclipse.osgi.util.NLS;
public class Spawner extends Process {
static final String LIBRARY_NAME = "gspawner"; //$NON-NLS-1$
public int NOOP = 0;
public int HUP = 1;
public int KILL = 9;
public int TERM = 15;
/**
* On Windows, what this does is far from easy to explain.
* Some of the logic is in the JNI code, some in the spawner.exe code.
*
* <ul>
* <li>If the process this is being raised against was launched by us (the Spawner)
* <ul>
* <li>If the process is a cygwin program (has the cygwin1.dll loaded), then issue a 'kill -SIGINT'. If
* the 'kill' utility isn't available, send the process a CTRL-C
* <li>If the process is <i>not</i> a cygwin program, send the process a CTRL-C
* </ul>
* <li>If the process this is being raised against was <i>not</i> launched by us, use
* DebugBreakProcess to interrupt it (sending a CTRL-C is easy only if we share a console
* with the target process)
* </ul>
*
* On non-Windows, raising this just raises a POSIX SIGINT
*
*/
public int INT = 2;
/**
* A fabricated signal number for use on Windows only. Tells the starter program to send a CTRL-C
* regardless of whether the process is a Cygwin one or not.
*
* @since 5.2
*/
public int CTRLC = 1000; // arbitrary high number to avoid collision
int pid = 0;
int status;
final int[] fChannels = new int[3];
boolean isDone;
OutputStream out;
InputStream in;
InputStream err;
private PTY fPty;
public Spawner(String command, boolean bNoRedirect) throws IOException {
StringTokenizer tokenizer = new StringTokenizer(command);
String[] cmdarray = new String[tokenizer.countTokens()];
for (int n = 0; tokenizer.hasMoreTokens(); n++) {
cmdarray[n] = tokenizer.nextToken();
}
if (bNoRedirect) {
exec_detached(cmdarray, new String[0], "."); //$NON-NLS-1$
}
else {
exec(cmdarray, new String[0], "."); //$NON-NLS-1$
}
}
/**
* Executes the specified command and arguments in a separate process with the
* specified environment and working directory.
**/
protected Spawner(String[] cmdarray, String[] envp, File dir) throws IOException {
String dirpath = "."; //$NON-NLS-1$
if (dir != null) {
dirpath = dir.getAbsolutePath();
}
exec(cmdarray, envp, dirpath);
}
protected Spawner(String[] cmdarray, String[] envp, File dir, PTY pty) throws IOException {
String dirpath = "."; //$NON-NLS-1$
if (dir != null) {
dirpath = dir.getAbsolutePath();
}
fPty = pty;
exec_pty(cmdarray, envp, dirpath, pty);
}
/**
* Executes the specified string command in a separate process.
**/
protected Spawner(String command) throws IOException {
this(command, null);
}
/**
* Executes the specified command and arguments in a separate process.
**/
protected Spawner(String[] cmdarray) throws IOException {
this(cmdarray, null);
}
/**
* Executes the specified command and arguments in a separate process with the
* specified environment.
**/
protected Spawner(String[] cmdarray, String[] envp) throws IOException {
this(cmdarray, envp, null);
}
/**
* Executes the specified string command in a separate process with the specified
* environment.
**/
protected Spawner(String cmd, String[] envp) throws IOException {
this(cmd, envp, null);
}
/**
* Executes the specified string command in a separate process with the specified
* environment and working directory.
**/
protected Spawner(String command, String[] envp, File dir) throws IOException {
StringTokenizer tokenizer = new StringTokenizer(command);
String[] cmdarray = new String[tokenizer.countTokens()];
for (int n = 0; tokenizer.hasMoreTokens(); n++) {
cmdarray[n] = tokenizer.nextToken();
}
String dirpath = "."; //$NON-NLS-1$
if (dir != null) {
dirpath = dir.getAbsolutePath();
}
exec(cmdarray, envp, dirpath);
}
@Override
protected void finalize() throws Throwable {
closeUnusedStreams();
}
/**
* See java.lang.Process#getInputStream ();
* The client is responsible for closing the stream explicitly.
**/
@Override
public synchronized InputStream getInputStream() {
if(null == in) {
if (fPty != null) {
in = fPty.getInputStream();
} else {
in = new SpawnerInputStream(fChannels[1]);
}
}
return in;
}
/**
* See java.lang.Process#getOutputStream ();
* The client is responsible for closing the stream explicitly.
**/
@Override
public synchronized OutputStream getOutputStream() {
if(null == out) {
if (fPty != null) {
out = fPty.getOutputStream();
} else {
out = new SpawnerOutputStream(fChannels[0]);
}
}
return out;
}
/**
* See java.lang.Process#getErrorStream ();
* The client is responsible for closing the stream explicitly.
**/
@Override
public synchronized InputStream getErrorStream() {
if(null == err) {
if (fPty != null && !fPty.isConsole()) {
// If PTY is used and it's not in "Console" mode, then stderr is
// redirected to the PTY's output stream. Therefore, return a
// dummy stream for error stream.
err = new InputStream() {
@Override
public int read() throws IOException {
return -1;
}
};
} else {
err = new SpawnerInputStream(fChannels[2]);
}
}
return err;
}
/**
* See java.lang.Process#waitFor ();
**/
@Override
public synchronized int waitFor() throws InterruptedException {
while (!isDone) {
wait();
}
// For situations where the user does not call destroy(),
// we try to kill the streams that were not used here.
// We check for streams that were not created, we create
// them to attach to the pipes, and then we close them
// to release the pipes.
// Streams that were created by the client need to be
// closed by the client itself.
//
// But 345164
closeUnusedStreams();
return status;
}
/**
* See java.lang.Process#exitValue ();
**/
@Override
public synchronized int exitValue() {
if (!isDone) {
throw new IllegalThreadStateException("Process not Terminated"); //$NON-NLS-1$
}
return status;
}
/**
* See java.lang.Process#destroy ();
*
* Clients are responsible for explicitly closing any streams
* that they have requested through
* getErrorStream(), getInputStream() or getOutputStream()
**/
@Override
public synchronized void destroy() {
// Sends the TERM
terminate();
// Close the streams on this side.
//
// We only close the streams that were
// never used by any client.
// So, if the stream was not created yet,
// we create it ourselves and close it
// right away, so as to release the pipe.
// Note that even if the stream was never
// created, the pipe has been allocated in
// native code, so we need to create the
// stream and explicitly close it.
//
// We don't close streams the clients have
// created because we don't know when the
// client will be finished using them.
// It is up to the client to close those
// streams.
//
// But 345164
closeUnusedStreams();
// Grace before using the heavy gone.
if (!isDone) {
try {
wait(1000);
} catch (InterruptedException e) {
}
}
if (!isDone) {
kill();
}
}
/**
* On Windows, interrupt the spawned program by using Cygwin's utility 'kill -SIGINT' if it's a Cgywin
* program, otherwise send it a CTRL-C. If Cygwin's 'kill' command is not available, send a CTRL-C. On
* linux, interrupt it by raising a SIGINT.
*/
public int interrupt() {
return raise(pid, INT);
}
/**
* On Windows, interrupt the spawned program by send it a CTRL-C (even if it's a Cygwin program). On
* linux, interrupt it by raising a SIGINT.
*
* @since 5.2
*/
public int interruptCTRLC() {
if (Platform.getOS().equals(Platform.OS_WIN32)) {
return raise(pid, CTRLC);
}
else {
return interrupt();
}
}
public int hangup() {
return raise(pid, HUP);
}
public int kill() {
return raise(pid, KILL);
}
public int terminate() {
return raise(pid, TERM);
}
public boolean isRunning() {
return (raise(pid, NOOP) == 0);
}
private void exec(String[] cmdarray, String[] envp, String dirpath) throws IOException {
String command = cmdarray[0];
SecurityManager s = System.getSecurityManager();
if (s != null) {
s.checkExec(command);
}
if (envp == null) {
envp = new String[0];
}
Reaper reaper = new Reaper(cmdarray, envp, dirpath);
reaper.setDaemon(true);
reaper.start();
// Wait until the subprocess is started or error.
synchronized (this) {
while (pid == 0) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
// Check for errors.
if (pid == -1) {
throw new IOException(reaper.getErrorMessage());
}
}
private void exec_pty(String[] cmdarray, String[] envp, String dirpath, PTY pty) throws IOException {
String command = cmdarray[0];
SecurityManager s = System.getSecurityManager();
if (s != null) {
s.checkExec(command);
}
if (envp == null) {
envp = new String[0];
}
final String slaveName = pty.getSlaveName();
final int masterFD = pty.getMasterFD().getFD();
final boolean console = pty.isConsole();
//int fdm = pty.get
Reaper reaper = new Reaper(cmdarray, envp, dirpath) {
/* (non-Javadoc)
* @see org.eclipse.cdt.utils.spawner.Spawner.Reaper#execute(java.lang.String[], java.lang.String[], java.lang.String, int[])
*/
@Override
int execute(String[] cmd, String[] env, String dir, int[] channels) throws IOException {
return exec2(cmd, env, dir, channels, slaveName, masterFD, console);
}
};
reaper.setDaemon(true);
reaper.start();
// Wait until the subprocess is started or error.
synchronized (this) {
while (pid == 0) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
// Check for errors.
if (pid == -1) {
throw new IOException("Exec_tty error:" + reaper.getErrorMessage()); //$NON-NLS-1$
}
}
public void exec_detached(String[] cmdarray, String[] envp, String dirpath) throws IOException {
String command = cmdarray[0];
SecurityManager s = System.getSecurityManager();
if (s != null) {
s.checkExec(command);
}
if (envp == null) {
envp = new String[0];
}
pid = exec1(cmdarray, envp, dirpath);
if (pid == -1) {
throw new IOException("Exec error"); //$NON-NLS-1$
}
fChannels[0] = -1;
fChannels[1] = -1;
fChannels[2] = -1;
}
/**
* Close any streams not used by clients.
*/
private synchronized void closeUnusedStreams() {
try {
if(null == err) {
getErrorStream().close();
}
} catch (IOException e) {}
try {
if(null == in) {
getInputStream().close();
}
} catch (IOException e) {}
try {
if(null == out) {
getOutputStream().close();
}
} catch (IOException e) {}
}
/**
* Native method use in normal exec() calls.
*/
native int exec0( String[] cmdarray, String[] envp, String dir, int[] chan) throws IOException;
/**
* Native method use in no redirect meaning to streams will created.
*/
native int exec1( String[] cmdarray, String[] envp, String dir) throws IOException;
/**
* Native method when executing with a terminal emulation.
*/
native int exec2( String[] cmdarray, String[] envp, String dir, int[] chan, String slaveName, int masterFD, boolean console) throws IOException;
/**
* Native method to drop a signal on the process with pid.
*/
public native int raise(int processID, int sig);
/*
* Native method to wait(3) for process to terminate.
*/
native int waitFor(int processID);
static {
try {
System.loadLibrary(LIBRARY_NAME);
} catch (SecurityException e) {
CCorePlugin.log(e);
} catch (UnsatisfiedLinkError e) {
CCorePlugin.log(e);
}
}
// Spawn a thread to handle the forking and waiting
// We do it this way because on linux the SIGCHLD is
// send to the one thread. So do the forking and
// the wait in the same thread.
class Reaper extends Thread {
String[] fCmdarray;
String[] fEnvp;
String fDirpath;
volatile Throwable fException;
public Reaper(String[] array, String[] env, String dir) {
super("Spawner Reaper"); //$NON-NLS-1$
fCmdarray = array;
fEnvp = env;
fDirpath = dir;
fException = null;
}
int execute(String[] cmdarray, String[] envp, String dir, int[] channels) throws IOException {
return exec0(cmdarray, envp, dir, channels);
}
@Override
public void run() {
try {
pid = execute(fCmdarray, fEnvp, fDirpath, fChannels);
} catch (Exception e) {
pid = -1;
fException= e;
}
// Tell spawner that the process started.
synchronized (Spawner.this) {
Spawner.this.notifyAll();
}
if (pid != -1) {
// Sync with spawner and notify when done.
status = waitFor(pid);
synchronized (Spawner.this) {
isDone = true;
Spawner.this.notifyAll();
}
}
}
public String getErrorMessage() {
final String reason= fException != null ? fException.getMessage() : "Unknown reason"; //$NON-NLS-1$
return NLS.bind(CCorePlugin.getResourceString("Util.error.cannotRun"), fCmdarray[0], reason); //$NON-NLS-1$
}
}
}