/******************************************************************************* * Copyright (c) 2002, 2015 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.natives.CNativePlugin; import org.eclipse.cdt.internal.core.natives.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(), !isWinPTY); } /** * 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; CNativePlugin.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); } } }