diff options
Diffstat (limited to 'core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils')
9 files changed, 1484 insertions, 0 deletions
diff --git a/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/WindowsRegistry.java b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/WindowsRegistry.java new file mode 100644 index 00000000000..cc216e2095b --- /dev/null +++ b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/WindowsRegistry.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 QNX Software Systems + * 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 + *******************************************************************************/ +/** + * + */ +package org.eclipse.cdt.utils; + +import org.eclipse.core.runtime.Platform; + +/** + * @author DSchaefer + * @noextend This class is not intended to be subclassed by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class WindowsRegistry { + + private static boolean failed = false; + private static WindowsRegistry registry; + + private WindowsRegistry() { + } + + public static WindowsRegistry getRegistry() { + if (registry == null && !failed) { + if (Platform.getOS().equals(Platform.OS_WIN32)) { + try { + System.loadLibrary("winreg"); //$NON-NLS-1$ + registry = new WindowsRegistry(); + } catch (UnsatisfiedLinkError e) { + failed = true; + return null; + } + } else + failed = true; + } + + return registry; + } + + /** + * Gets the registry value for the subkey of HKEY_LOCAL_MACHINE with the + * given name. If problems occur, like the name is not found, null is returned. + * + * @param subkey subkey of HKEY_LOCAL_MACHINE + * @param name name of the registry value + * @return registry value or null if not found + */ + public native String getLocalMachineValue(String subkey, String name); + + /** + * Given a subkey of HKEY_LOCAL_MACHINE, and an index (starting from 0) + * to the key's array of values, return the name of the indexed value. + * The return value is null on any error or when the index is invalid. + * The value name can be used in the above getLocalMachineValue() to retrieve + * the value data. + * @param subkey subkey of HKEY_LOCAL_MACHINE + * @param index index to the subkey's array of values, starting from 0. + * @return name of registry value or null if not found + */ + public native String getLocalMachineValueName(String subkey, int index); + + /** + * Given a subkey of HKEY_LOCAL_MACHINE, and an index (starting from 0) + * to the key's array of sub keys, return the name of the indexed key. + * The return value is null on any error or when the index is invalid. + * The key name can be used in the above getLocalMachineValueName() + * to retrieve value names. + * @param subkey subkey of HKEY_CURRENT_USER + * @param index index to the subkey's array of values, starting from 0. + * @return name of registry value or null if not found + */ + public native String getLocalMachineKeyName(String subkey, int index); + + /** + * Gets the registry value for the subkey of HKEY_CURRENT_USER with the + * given name. If problems occur, like the name is not found, null is returned. + * + * @param subkey subkey of HKEY_CURRENT_USER + * @param name name of the registry value + * @return registry value or null if not found + */ + public native String getCurrentUserValue(String subkey, String name); + + /** + * Given a subkey of HKEY_CURRENT_USER, and an index (starting from 0) + * to the key's array of values, return the name of the indexed value. + * The return value is null on any error or when the index is invalid. + * The value name can be used in the above getCurrentUserValue() to retrieve + * the value data. + * @param subkey subkey of HKEY_CURRENT_USER + * @param index index to the subkey's array of values, starting from 0. + * @return name of registry value or null if not found + */ + public native String getCurrentUserValueName(String subkey, int index); + + /** + * Given a subkey of HKEY_CURRENT_USER, and an index (starting from 0) + * to the key's array of sub keys, return the name of the indexed key. + * The return value is null on any error or when the index is invalid. + * The key name can be used in the above getCurrentUserValueName() + * to retrieve value names. + * @param subkey subkey of HKEY_CURRENT_USER + * @param index index to the subkey's array of values, starting from 0. + * @return name of registry value or null if not found + */ + public native String getCurrentUserKeyName(String subkey, int index); + +} diff --git a/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/pty/PTY.java b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/pty/PTY.java new file mode 100644 index 00000000000..f61fc8d29a2 --- /dev/null +++ b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/pty/PTY.java @@ -0,0 +1,275 @@ +/******************************************************************************* + * Copyright (c) 2002, 2014 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, Inc. - bug 248071 + * Martin Oberhuber (Wind River) - [303083] Split out the Spawner + *******************************************************************************/ +package org.eclipse.cdt.utils.pty; + +import java.io.IOException; + +import org.eclipse.cdt.internal.core.spawner.CSpawnerPlugin; +import org.eclipse.cdt.internal.core.spawner.Messages; +import org.eclipse.cdt.utils.spawner.Spawner; +import org.eclipse.core.runtime.Platform; + +/** + * PTY - pseudo terminal support. + */ +public class PTY { + + /** + * The pty modes. + * @since 5.6 + */ + public enum Mode { + /** This mode is for use with an Eclipse console. */ + CONSOLE, + /** This mode is for use with a terminal emulator. */ + TERMINAL + } + + final boolean console; + final String slave; + final PTYInputStream in; + final PTYOutputStream out; + + /** + * NOTE: Field is accessed by the native layer. Do not refactor! + */ + int master; + + private static boolean hasPTY; + private static boolean isWinPTY; + private static boolean isConsoleModeSupported; + private static boolean setTerminalSizeErrorAlreadyLogged; + + /** + * The master fd is used on two streams. We need to wrap the fd + * so that when stream.close() is called the other stream is disabled. + */ + public class MasterFD { + + public int getFD() { + return master; + } + + void setFD(int fd) { + master = fd; + } + } + + /** + * @return whether PTY support for console mode is available on this platform + */ + public static boolean isSupported() { + return isSupported(Mode.CONSOLE); + } + + /** + * @return whether PTY support for given mode is available on this platform + * @since 5.6 + */ + public static boolean isSupported(Mode mode ) { + return hasPTY && (isConsoleModeSupported || mode == Mode.TERMINAL); + } + + + /** + * Create PTY for use with Eclipse console. + * Identical to {@link PTY#PTY(boolean) PTY(Mode.CONSOLE)}. + */ + public PTY() throws IOException { + this(Mode.CONSOLE); + } + + /** + * Create PTY for given mode. + * + * <p> + * The provided mode indicates whether the pseudo terminal is used with the interactive + * Eclipse console or a terminal emulation: + * <ul> + * <li><code>CONSOLE</code> - the terminal is configured with no echo and stderr is + * redirected to a pipe instead of the PTY. This mode is not supported on windows</li> + * <li><code>TERMINAL</code> - the terminal is configured with echo and stderr is + * connected to the PTY. This mode is best suited for use with a proper terminal emulation. + * Note that this mode might not be supported on all platforms. + * Known platforms which support this mode are: + * <code>linux-x86</code>, <code>linux-x86_64</code>, <code>solaris-sparc</code>, <code>macosx</code>. + * </li> + * </ul> + * </p> + * @param mode the desired mode of operation + * @throws IOException if the PTY could not be created + * @since 5.6 + */ + public PTY(Mode mode) throws IOException { + this(mode == Mode.CONSOLE); + } + + /** + * Create pseudo terminal. + * + * <p> + * The provided flag indicates whether the pseudo terminal is used with the interactive + * Eclipse console: + * <ul> + * <li>If <code>true</code> the terminal is configured with no echo and stderr is + * redirected to a pipe instead of the PTY. This mode is not supported on windows</li> + * <li>If <code>false</code> the terminal is configured with echo and stderr is + * connected to the PTY. This mode is best suited for use with a proper terminal emulation. + * Note that this mode might not be supported on all platforms. + * Known platforms which support this mode are: + * <code>linux-x86</code>, <code>linux-x86_64</code>, <code>solaris-sparc</code>, <code>macosx</code>. + * </li> + * </ul> + * </p> + * + * @param console whether terminal is used with Eclipse console + * @throws IOException if the PTY could not be created + * @deprecated Use {@link #PTY(Mode)} instead + * @since 5.2 + */ + @Deprecated + public PTY(boolean console) throws IOException { + this.console = console; + if (console && !isConsoleModeSupported) { + throw new IOException(Messages.Util_exception_cannotCreatePty); + } + slave= hasPTY ? openMaster(console) : null; + + if (slave == null) { + throw new IOException(Messages.Util_exception_cannotCreatePty); + } + + in = new PTYInputStream(new MasterFD()); + out = new PTYOutputStream(new MasterFD()); + } + + /** + * Test whether the slave name can be used as a tty device by external processes (e.g. gdb). + * If the slave name is not valid an IOException is thrown. + * @throws IOException if the slave name is not valid + * @since 5.6 + */ + public void validateSlaveName() throws IOException { + // on windows the slave name is just an internal identifier + // and does not represent a real device + if (isWinPTY) + throw new IOException("Slave name is not valid"); //$NON-NLS-1$ + } + + public String getSlaveName() { + return slave; + } + + public MasterFD getMasterFD() { + return new MasterFD(); + } + + /** + * @return whether this pseudo terminal is for use with the Eclipse console. + * + * @since 5.2 + */ + public final boolean isConsole() { + return console; + } + + public PTYOutputStream getOutputStream() { + return out; + } + + public PTYInputStream getInputStream() { + return in; + } + + /** + * Change terminal window size to given width and height. + * <p> + * This should only be used when the pseudo terminal is configured + * for use with a terminal emulation, i.e. when {@link #isConsole()} + * returns <code>false</code>. + * </p> + * <p> + * <strong>Note:</strong> This method may not be supported on all platforms. + * Known platforms which support this method are: + * <code>linux-x86</code>, <code>linux-x86_64</code>, <code>solaris-sparc</code>, <code>macosx</code>. + * </p> + * + * @since 5.2 + */ + public final void setTerminalSize(int width, int height) { + try { + change_window_size(master, width, height); + } catch (UnsatisfiedLinkError ule) { + if (!setTerminalSizeErrorAlreadyLogged) { + setTerminalSizeErrorAlreadyLogged = true; + CSpawnerPlugin.log(Messages.Util_exception_cannotSetTerminalSize, ule); + } + } + } + + /** + * @noreference This method is not intended to be referenced by clients. + * @since 5.6 + */ + public int exec_pty(Spawner spawner, String[] cmdarray, String[] envp, String dir, int[] chan) throws IOException { + if (isWinPTY) { + return exec2(cmdarray, envp, dir, chan, slave, master, console); + } else { + return spawner.exec2(cmdarray, envp, dir, chan, slave, master, console); + } + } + + /** + * @noreference This method is not intended to be referenced by clients. + * @since 5.6 + */ + public int waitFor(Spawner spawner, int pid) { + if (isWinPTY) { + return waitFor(master, pid); + } else { + return spawner.waitFor(pid); + } + } + + native String openMaster(boolean console); + + native int change_window_size(int fdm, int width, int height); + + /** + * Native method when executing with a terminal emulation (winpty only). + */ + native int exec2(String[] cmdarray, String[] envp, String dir, int[] chan, String slaveName, int masterFD, boolean console) throws IOException; + + /** + * Native method to wait for process to terminate (winpty only). + */ + native int waitFor(int masterFD, int processID); + + static { + try { + System.loadLibrary("pty"); //$NON-NLS-1$ + hasPTY = true; + isWinPTY = Platform.OS_WIN32.equals(Platform.getOS()); + // on windows console mode is not supported except for experimental use + // to enable it, set system property org.eclipse.cdt.core.winpty_console_mode=true + isConsoleModeSupported = !isWinPTY || Boolean.getBoolean("org.eclipse.cdt.core.winpty_console_mode"); //$NON-NLS-1$ + } catch (SecurityException e) { + // Comment out it worries the users too much + //CCorePlugin.log(e); + } catch (UnsatisfiedLinkError e) { + // Comment out it worries the users too much + //CCorePlugin.log(e); + } + } + +} diff --git a/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/pty/PTYInputStream.java b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/pty/PTYInputStream.java new file mode 100644 index 00000000000..c2506a006d2 --- /dev/null +++ b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/pty/PTYInputStream.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2000, 2014 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 286162 + * Martin Oberhuber (Wind River) - [303083] Split out the Spawner + *******************************************************************************/ +package org.eclipse.cdt.utils.pty; + + +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.cdt.internal.core.spawner.Messages; +import org.eclipse.cdt.utils.pty.PTY.MasterFD; + +class PTYInputStream extends InputStream { + + MasterFD master; + + /** + * From a Unix valid file descriptor set a Reader. + * @param fd file descriptor. + */ + public PTYInputStream(MasterFD fd) { + master = fd; + } + + /** + * Implementation of read for the InputStream. + * + * @exception IOException on error. + */ + @Override + public int read() throws IOException { + byte b[] = new byte[1]; + if (1 != read(b, 0, 1)) + return -1; + return b[0]; + } + + /** + * @see InputStream#read(byte[], int, int) + */ + @Override + public int read(byte[] buf, int off, int len) throws IOException { + if (buf == null) { + throw new NullPointerException(); + } else if ((off < 0) || (off > buf.length) + || (len < 0) || ((off + len) > buf.length) + || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + byte[] tmpBuf = new byte[len]; + + len = read0(master.getFD(), tmpBuf, len); + if (len <= 0) + return -1; + + System.arraycopy(tmpBuf, 0, buf, off, len); + return len; + } + + /** + * Close the Reader + * @exception IOException on error. + */ + @Override + public void close() throws IOException { + if (master.getFD() == -1) + return; + close0(master.getFD()); + // ignore error on close - see bug 286162 +// if (status == -1) +// throw new IOException(Messages.Util_exception_closeError); + master.setFD(-1); + } + + @Override + protected void finalize() throws IOException { + close(); + } + + private native int read0(int fd, byte[] buf, int len) throws IOException; + private native int close0(int fd) throws IOException; + + static { + System.loadLibrary("pty"); //$NON-NLS-1$ + } + +} diff --git a/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/pty/PTYOutputStream.java b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/pty/PTYOutputStream.java new file mode 100644 index 00000000000..f6096e5a443 --- /dev/null +++ b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/pty/PTYOutputStream.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.cdt.utils.pty; + + +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.cdt.utils.pty.PTY.MasterFD; + +public class PTYOutputStream extends OutputStream { + + MasterFD master; + + /** + * From a Unix valid file descriptor set a Reader. + * @param fd file descriptor. + */ + public PTYOutputStream(MasterFD fd) { + master = fd; + } + + /** + * @see OutputStream#write(byte[], int, int) + */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if ( + (off < 0) + || (off > b.length) + || (len < 0) + || ((off + len) > b.length) + || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + byte[] tmpBuf = new byte[len]; + System.arraycopy(b, off, tmpBuf, off, len); + write0(master.getFD(), tmpBuf, len); + } + /** + * Implementation of read for the InputStream. + * + * @exception IOException on error. + */ + @Override + public void write(int b) throws IOException { + byte[] buf = new byte[1]; + buf[0] = (byte) b; + write(buf, 0, 1); + } + + /** + * Close the Reader + * @exception IOException on error. + */ + @Override + public void close() throws IOException { + if (master.getFD() == -1) + return; + int status = close0(master.getFD()); + if (status == -1) + throw new IOException("close error"); //$NON-NLS-1$ + master.setFD(-1); + } + + @Override + protected void finalize() throws IOException { + close(); + } + + private native int write0(int fd, byte[] b, int len) throws IOException; + private native int close0(int fd) throws IOException; + + static { + System.loadLibrary("pty"); //$NON-NLS-1$ + } + +} diff --git a/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/EnvironmentReader.java b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/EnvironmentReader.java new file mode 100644 index 00000000000..39c94d806e9 --- /dev/null +++ b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/EnvironmentReader.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2000, 2010 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 + *******************************************************************************/ +package org.eclipse.cdt.utils.spawner; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Properties; + +import org.eclipse.core.runtime.Platform; + +/** + * This class provides environment variables supplied as {@link Properties} class. + * + * @noextend This class is not intended to be subclassed by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class EnvironmentReader { + private static Properties envVars = null; + private static Properties envVarsNormalized = null; + private static ArrayList<String> rawVars = null; + + private static synchronized void init() { + if (envVars==null) { + envVars = new Properties(); + // on Windows environment variable names are case-insensitive + if (Platform.getOS().equals(Platform.OS_WIN32)) { + envVarsNormalized = new Properties(); + } else { + envVarsNormalized = envVars; + } + rawVars = new ArrayList<String>(); + Map<String, String> envMap = System.getenv(); + for (String var : envMap.keySet()) { + String value = envMap.get(var); + envVars.setProperty(var, value); + if (envVarsNormalized!=envVars) { + envVarsNormalized.setProperty(var.toUpperCase(), value); + } + rawVars.add(var + "=" + value); //$NON-NLS-1$ + } + rawVars.trimToSize(); + } + } + + /** + * @return list of environment variables. + */ + public static Properties getEnvVars() { + init(); + return (Properties) envVars.clone(); + } + + /** + * @param key - name of environment variable (without $ sign). + * @return value of environment variable. + */ + public static String getEnvVar(String key) { + init(); + return envVarsNormalized.getProperty(key); + } + + /** + * @deprecated since CDT 6.1. {@link #getEnvVars()} provides all the data. + */ + @Deprecated + public static String[] getRawEnvVars() { + init(); + return rawVars.toArray(new String[rawVars.size()]); + } +} diff --git a/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/ProcessFactory.java b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/ProcessFactory.java new file mode 100644 index 00000000000..66c0a176cc8 --- /dev/null +++ b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/ProcessFactory.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2000, 2014 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 + * Martin Oberhuber (Wind River) - [303083] Split out the Spawner + *******************************************************************************/ +package org.eclipse.cdt.utils.spawner; + + +import java.io.File; +import java.io.IOException; + +import org.eclipse.cdt.internal.core.spawner.CSpawnerPlugin; +import org.eclipse.cdt.internal.core.spawner.Messages; +import org.eclipse.cdt.utils.pty.PTY; + +/** + * @noextend This class is not intended to be subclassed by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class ProcessFactory { + + static private ProcessFactory instance; + private boolean hasSpawner; + private Runtime runtime; + + private ProcessFactory() { + hasSpawner = false; + String OS = System.getProperty("os.name").toLowerCase(); //$NON-NLS-1$ + runtime = Runtime.getRuntime(); + try { + // Spawner does not work for Windows 98 fallback + if (OS != null && OS.equals("windows 98")) { //$NON-NLS-1$ + hasSpawner = false; + } else { + System.loadLibrary("spawner"); //$NON-NLS-1$ + hasSpawner = true; + } + } catch (SecurityException e) { + e.printStackTrace(); + } catch (UnsatisfiedLinkError e) { + CSpawnerPlugin.log(e.getMessage()); + } + } + + public static ProcessFactory getFactory() { + if (instance == null) + instance = new ProcessFactory(); + return instance; + } + + public Process exec(String cmd) throws IOException { + if (hasSpawner) + return new Spawner(cmd); + return runtime.exec(cmd); + } + + public Process exec(String[] cmdarray) throws IOException { + if (hasSpawner) + return new Spawner(cmdarray); + return runtime.exec(cmdarray); + } + + public Process exec(String[] cmdarray, String[] envp) throws IOException { + if (hasSpawner) + return new Spawner(cmdarray, envp); + return runtime.exec(cmdarray, envp); + } + + public Process exec(String cmd, String[] envp) throws IOException { + if (hasSpawner) + return new Spawner(cmd, envp); + return runtime.exec(cmd, envp); + } + + public Process exec(String cmd, String[] envp, File dir) + throws IOException { + if (hasSpawner) + return new Spawner(cmd, envp, dir); + return runtime.exec(cmd, envp, dir); + } + + public Process exec(String cmdarray[], String[] envp, File dir) + throws IOException { + if (hasSpawner) + return new Spawner(cmdarray, envp, dir); + return runtime.exec(cmdarray, envp, dir); + } + + public Process exec(String cmdarray[], String[] envp, File dir, PTY pty) + throws IOException { + if (hasSpawner) + return new Spawner(cmdarray, envp, dir, pty); + throw new UnsupportedOperationException(Messages.Util_exception_cannotCreatePty); + } +} diff --git a/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/Spawner.java b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/Spawner.java new file mode 100644 index 00000000000..e4e69a8ebff --- /dev/null +++ b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/Spawner.java @@ -0,0 +1,517 @@ +/******************************************************************************* + * Copyright (c) 2000, 2014 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 + * Martin Oberhuber (Wind River) - [303083] Split out the Spawner + *******************************************************************************/ +package org.eclipse.cdt.utils.spawner; + + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.StringTokenizer; + +import org.eclipse.cdt.internal.core.spawner.CSpawnerPlugin; +import org.eclipse.cdt.internal.core.spawner.Messages; +import org.eclipse.cdt.utils.pty.PTY; +import org.eclipse.core.runtime.Platform; +import org.eclipse.osgi.util.NLS; + +public class Spawner extends Process { + + 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 = { -1, -1, -1 }; + 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, final 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]; + + Reaper reaper = new Reaper(cmdarray, envp, dirpath) { + @Override + int execute(String[] cmd, String[] env, String dir, int[] channels) throws IOException { + return pty.exec_pty(Spawner.this, cmd, env, dir, channels); + } + + @Override + protected int waitFor(int pid) { + return pty.waitFor(Spawner.this, pid); + } + }; + 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$ + } + } + + /** + * 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. + * @noreference This method is not intended to be referenced by clients. + */ + public 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. + * @noreference This method is not intended to be referenced by clients. + */ + public native int waitFor(int processID); + + static { + try { + System.loadLibrary("spawner"); //$NON-NLS-1$ + } catch (SecurityException e) { + CSpawnerPlugin.log(e); + } catch (UnsatisfiedLinkError e) { + CSpawnerPlugin.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); + } + + int waitFor(int pid) { + return Spawner.this.waitFor(pid); + } + + @Override + public void run() { + int _pid; + try { + _pid = execute(fCmdarray, fEnvp, fDirpath, fChannels); + } catch (Exception e) { + _pid = -1; + fException= e; + } + + // Tell spawner that the process started. + synchronized (Spawner.this) { + pid = _pid; + 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(Messages.Util_error_cannotRun, fCmdarray[0], reason); + } + } +} diff --git a/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/SpawnerInputStream.java b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/SpawnerInputStream.java new file mode 100644 index 00000000000..dfdbe060e8e --- /dev/null +++ b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/SpawnerInputStream.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2000, 2014 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 + * Martin Oberhuber (Wind River) - [303083] Split out the Spawner + *******************************************************************************/ +package org.eclipse.cdt.utils.spawner; + + +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.cdt.internal.core.spawner.Messages; + +class SpawnerInputStream extends InputStream { + private int fd; + + /** + * From a Unix valid file descriptor set a Reader. + * @param fd file descriptor. + */ + public SpawnerInputStream(int fd) { + this.fd = fd; + } + + /** + * Implementation of read for the InputStream. + * + * @exception IOException on error. + */ + @Override + public int read() throws IOException { + byte b[] = new byte[1]; + if (1 != read(b, 0, 1)) + return -1; + return b[0]; + } + + /** + * @see InputStream#read(byte[], int, int) + */ + @Override + public int read(byte[] buf, int off, int len) throws IOException { + if (fd == -1) { + return -1; + } + if (buf == null) { + throw new NullPointerException(); + } else if ( + (off < 0) + || (off > buf.length) + || (len < 0) + || ((off + len) > buf.length) + || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + byte[] tmpBuf = off > 0 ? new byte[len] : buf; + + len = read0(fd, tmpBuf, len); + if (len <= 0) + return -1; + + if (tmpBuf != buf) { + System.arraycopy(tmpBuf, 0, buf, off, len); + } + return len; + } + + /** + * Close the Reader + * @exception IOException on error. + */ + @Override + public void close() throws IOException { + if (fd == -1) + return; + int status = close0(fd); + if (status == -1) + throw new IOException(Messages.Util_exception_closeError); + fd = -1; + } + + @Override + public int available() throws IOException { + if (fd == -1) { + return 0; + } + try { + return available0(fd); + } + catch (UnsatisfiedLinkError e) { + // for those platforms that do not implement available0 + return super.available(); + } + } + + @Override + protected void finalize() throws IOException { + close(); + } + + private native int read0(int fileDesc, byte[] buf, int len) throws IOException; + private native int close0(int fileDesc) throws IOException; + private native int available0(int fileDesc) throws IOException; + + static { + System.loadLibrary("spawner"); //$NON-NLS-1$ + } + + +} diff --git a/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/SpawnerOutputStream.java b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/SpawnerOutputStream.java new file mode 100644 index 00000000000..e3c8014f6c8 --- /dev/null +++ b/core/org.eclipse.cdt.core.native/utils/org/eclipse/cdt/utils/spawner/SpawnerOutputStream.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.cdt.utils.spawner; + + +import java.io.IOException; +import java.io.OutputStream; + +/** + * @noextend This class is not intended to be subclassed by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public class SpawnerOutputStream extends OutputStream { + private int fd; + + /** + * From a Unix valid file descriptor set a Reader. + * @param fd file descriptor. + */ + public SpawnerOutputStream(int fd) { + this.fd = fd; + } + + /** + * @see OutputStream#write(byte[], int, int) + */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if ( + (off < 0) + || (off > b.length) + || (len < 0) + || ((off + len) > b.length) + || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + byte[] tmpBuf = new byte[len]; + System.arraycopy(b, off, tmpBuf, off, len); + write0(fd, tmpBuf, len); + } + /** + * Implementation of read for the InputStream. + * + * @exception IOException on error. + */ + @Override + public void write(int b) throws IOException { + byte[] buf = new byte[1]; + buf[0] = (byte) b; + write(buf, 0, 1); + } + + /** + * Close the Reader + * @exception IOException on error. + */ + @Override + public void close() throws IOException { + if (fd == -1) + return; + int status = close0(fd); + if (status == -1) + throw new IOException("close error"); //$NON-NLS-1$ + fd = -1; + } + + @Override + protected void finalize() throws IOException { + close(); + } + + private native int write0(int fd, byte[] b, int len) throws IOException; + private native int close0(int fd); + + static { + System.loadLibrary("spawner"); //$NON-NLS-1$ + } + +} |